簡體   English   中英

iOS WebGL紋理渲染中的缺陷

[英]Flaw in iOS WebGL texture rendering

這個使用three.js庫進行WebGL紋理渲染的簡單測試:

 // Canvas dimensions canvasW = Math.floor(0.9*window.innerWidth); canvasH = Math.floor(0.75*canvasW); cAR = canvasW / canvasH; canvasWrapper = document.getElementById('canvasWrapper'); canvasWrapper.style.width=canvasW+'px'; canvasWrapper.style.height=canvasH+'px'; // Renderer renderer = new THREE.WebGLRenderer({antialias: true}); renderer.setPixelRatio(window.devicePixelRatio); console.log("Renderer pixel ratio = "+window.devicePixelRatio); renderer.setSize(canvasW, canvasH); canvas = renderer.domElement; canvasWrapper.appendChild(canvas); // Set up camera cameraDist = 24; camera = new THREE.PerspectiveCamera(25, cAR, 0.01, 1000); cameraAngle = 0; camera.position.x = cameraDist*Math.sin(cameraAngle); camera.position.y = 0.3*cameraDist; camera.position.z = cameraDist*Math.cos(cameraAngle); camera.lookAt(new THREE.Vector3(0,0,0)); // Set up scene, consisting of texture-tiled ground scene = new THREE.Scene(); groundWidth = 1000; groundMaterial = null; groundGeom = new THREE.PlaneGeometry(groundWidth,groundWidth); groundGeom.rotateX(-Math.PI/2); groundMesh = new THREE.Mesh(groundGeom, groundMaterial || new THREE.MeshBasicMaterial()); scene.add(groundMesh); //window.requestAnimationFrame(draw); // Insert texture once it has loaded function setGroundTexture(texture) { groundTexture = texture; groundTexture.wrapS = THREE.RepeatWrapping; groundTexture.wrapT = THREE.RepeatWrapping; groundTexture.repeat.set(groundWidth, groundWidth); groundTexture.anisotropy = renderer.getMaxAnisotropy(); console.log("Texture anisotropy = "+groundTexture.anisotropy); groundMaterial = new THREE.MeshBasicMaterial({map: groundTexture}); if (groundMesh) { groundMesh.material = groundMaterial; window.requestAnimationFrame(draw); }; } // Start texture loading //new THREE.TextureLoader().load("Texture.png", setGroundTexture, function (xhr) {}, function (xhr) {}); setGroundTexture(makeTexture()); // Render a frame function draw() { renderer.render(scene, camera); } // ------- function makeTexture() { var ctx = document.createElement("canvas").getContext("2d"); ctx.canvas.width = 256; ctx.canvas.height = 256; ctx.fillStyle = "rgb(238, 238, 238)"; ctx.fillRect(0, 0, 256, 256); ctx.fillStyle = "rgb(208, 208, 208)"; ctx.fillRect(0, 0, 128, 128); ctx.fillRect(128, 128, 128, 128); for (var y = 0; y < 2; ++y) { for (var x = 0; x < 2; ++x) { ctx.save(); ctx.translate(x * 128 + 64, y * 128 + 64); ctx.lineWidth = 3; ctx.beginPath(); var radius = 50; ctx.moveTo(radius, 0); for (var i = 0; i <= 6; ++i) { var a = i / 3 * Math.PI; ctx.lineTo(Math.cos(a) * radius, Math.sin(a) * radius); } ctx.stroke(); ctx.restore(); } } var tex = new THREE.Texture(ctx.canvas); tex.needsUpdate = true; return tex; } 
 canvas, #canvasWrapper {margin-left: auto; margin-right: auto;} 
 <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r78/three.js"></script> <div id="canvasWrapper"></div> 

在我試過的桌面瀏覽器上完美呈現,但在iPad上渲染時非常模糊,如頁面下方的屏幕截圖所示。

桌面

在此輸入圖像描述

iPad的

在此輸入圖像描述

在這兩種情況下,紋理都呈現為16的各向異性(渲染器支持的最大值)。 用於紋理的圖像具有256×256的尺寸(2的冪,這是重復紋理所必需的),並且使其變大或變小並不能解決問題。

質地:

在此輸入圖像描述

我將渲染器的像素比率設置為與瀏覽器窗口匹配,這意味着桌面系統為1,iPad為視網膜顯示器為2。 這種方法通常為渲染的其他方面提供最佳結果,並且在任何情況下,在iPad上將像素比率設置為1而不是2,不會改善紋理的外觀。

所以我的問題是:這是iOS WebGL中的一個我不得不忍受的錯誤,還是我可以在我自己的代碼中調整以在iOS設備上獲得更好的結果?

編輯:這個three.js 演示頁面在iPad上的渲染效果要比桌面瀏覽器低得多,演示的源代碼使用與我自己的代碼相同的通用方法,這表明無論我缺少什么技巧,都不是簡單而明顯。

我無法完全解釋問題的根源,但我發現一個解決方案,表明原因是數值精度的某種降低,我想在GPU中,每個iPad圖形都不會出現這種情況卡。

解決方法涉及將地面的平面幾何體(最初只是一個正方形(三個可能分為兩個三角形))分割成多個正方形的網格。 據推測,這會改變對象上的(u,v)坐標和紋理坐標與GPU中浮點精度限制相反的方式。 此外,將地面尺寸從1000減小到200有助於。

令人討厭的是在平面幾何體中具有所有這些額外面的開銷,即使它們在指定形狀時完全是多余的。

在任何情況下,結果在我的桌面瀏覽器上看起來完全相同,但在我的iPad 4上要好得多。

編輯:經過更仔細的測試,我不認為細分THREE.PlaneGeometry會產生任何影響,它只會減少有助於平鋪平面的整體尺寸 事實上,通過使平鋪平面的大小足夠 ,當尺寸為1000時,在我的iMac上可以達到大小為1000的iPad 4上的任何限制,作為代碼片段的第二個版本顯示。 (紋理開始降低大約50,000,但80,000使得失真不容錯過。)顯然,沒有真正的應用程序,你需要用50,000 x 50,000個紋理紋理平鋪表面,但每個方向幾百個,這是iPad 4開始出現問題,並不奢侈。

代碼片段的第一個版本,它修復了iPad 4上的問題:

 // Canvas dimensions canvasW = Math.floor(0.9*window.innerWidth); canvasH = Math.floor(0.75*canvasW); cAR = canvasW / canvasH; canvasWrapper = document.getElementById('canvasWrapper'); canvasWrapper.style.width=canvasW+'px'; canvasWrapper.style.height=canvasH+'px'; // Renderer renderer = new THREE.WebGLRenderer({antialias: true}); renderer.setPixelRatio(window.devicePixelRatio); console.log("Renderer pixel ratio = "+window.devicePixelRatio); renderer.setSize(canvasW, canvasH); canvas = renderer.domElement; canvasWrapper.appendChild(canvas); // Set up camera cameraDist = 24; camera = new THREE.PerspectiveCamera(25, cAR, 0.01, 1000); cameraAngle = 0; camera.position.x = cameraDist*Math.sin(cameraAngle); camera.position.y = 0.3*cameraDist; camera.position.z = cameraDist*Math.cos(cameraAngle); camera.lookAt(new THREE.Vector3(0,0,0)); // Set up scene, consisting of texture-tiled ground scene = new THREE.Scene(); // groundWidth = 1000; // Reduce overall size of ground groundWidth = 200; groundMaterial = null; // groundGeom = new THREE.PlaneGeometry(groundWidth,groundWidth); // Split plane geometry into a grid of smaller squares groundGeom = new THREE.PlaneGeometry(groundWidth,groundWidth,20,20); groundGeom.rotateX(-Math.PI/2); groundMesh = new THREE.Mesh(groundGeom, groundMaterial || new THREE.MeshBasicMaterial()); scene.add(groundMesh); //window.requestAnimationFrame(draw); // Insert texture once it has loaded function setGroundTexture(texture) { groundTexture = texture; groundTexture.wrapS = THREE.RepeatWrapping; groundTexture.wrapT = THREE.RepeatWrapping; groundTexture.repeat.set(groundWidth, groundWidth); groundTexture.anisotropy = renderer.getMaxAnisotropy(); console.log("Texture anisotropy = "+groundTexture.anisotropy); groundMaterial = new THREE.MeshBasicMaterial({map: groundTexture}); if (groundMesh) { groundMesh.material = groundMaterial; window.requestAnimationFrame(draw); }; } // Start texture loading //new THREE.TextureLoader().load("Texture.png", setGroundTexture, function (xhr) {}, function (xhr) {}); setGroundTexture(makeTexture()); // Render a frame function draw() { renderer.render(scene, camera); } // ------- function makeTexture() { var ctx = document.createElement("canvas").getContext("2d"); ctx.canvas.width = 256; ctx.canvas.height = 256; ctx.fillStyle = "rgb(238, 238, 238)"; ctx.fillRect(0, 0, 256, 256); ctx.fillStyle = "rgb(208, 208, 208)"; ctx.fillRect(0, 0, 128, 128); ctx.fillRect(128, 128, 128, 128); for (var y = 0; y < 2; ++y) { for (var x = 0; x < 2; ++x) { ctx.save(); ctx.translate(x * 128 + 64, y * 128 + 64); ctx.lineWidth = 3; ctx.beginPath(); var radius = 50; ctx.moveTo(radius, 0); for (var i = 0; i <= 6; ++i) { var a = i / 3 * Math.PI; ctx.lineTo(Math.cos(a) * radius, Math.sin(a) * radius); } ctx.stroke(); ctx.restore(); } } var tex = new THREE.Texture(ctx.canvas); tex.needsUpdate = true; return tex; } 
 canvas, #canvasWrapper {margin-left: auto; margin-right: auto;} 
 <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r78/three.js"></script> <div id="canvasWrapper"></div> 

第二個版本的代碼片段,它破壞了2007 iMac上的紋理:

 // Canvas dimensions canvasW = Math.floor(0.9*window.innerWidth); canvasH = Math.floor(0.75*canvasW); cAR = canvasW / canvasH; canvasWrapper = document.getElementById('canvasWrapper'); canvasWrapper.style.width=canvasW+'px'; canvasWrapper.style.height=canvasH+'px'; // Renderer renderer = new THREE.WebGLRenderer({antialias: true}); renderer.setPixelRatio(window.devicePixelRatio); console.log("Renderer pixel ratio = "+window.devicePixelRatio); renderer.setSize(canvasW, canvasH); canvas = renderer.domElement; canvasWrapper.appendChild(canvas); // Set up camera cameraDist = 24; camera = new THREE.PerspectiveCamera(25, cAR, 0.01, 1000); cameraAngle = 0; camera.position.x = cameraDist*Math.sin(cameraAngle); camera.position.y = 0.3*cameraDist; camera.position.z = cameraDist*Math.cos(cameraAngle); camera.lookAt(new THREE.Vector3(0,0,0)); // Set up scene, consisting of texture-tiled ground scene = new THREE.Scene(); // groundWidth = 1000; // Increase the size of the plane to trigger the problem groundWidth = 80000; groundMaterial = null; groundGeom = new THREE.PlaneGeometry(groundWidth,groundWidth); groundGeom.rotateX(-Math.PI/2); groundMesh = new THREE.Mesh(groundGeom, groundMaterial || new THREE.MeshBasicMaterial()); scene.add(groundMesh); //window.requestAnimationFrame(draw); // Insert texture once it has loaded function setGroundTexture(texture) { groundTexture = texture; groundTexture.wrapS = THREE.RepeatWrapping; groundTexture.wrapT = THREE.RepeatWrapping; groundTexture.repeat.set(groundWidth, groundWidth); groundTexture.anisotropy = renderer.getMaxAnisotropy(); console.log("Texture anisotropy = "+groundTexture.anisotropy); groundMaterial = new THREE.MeshBasicMaterial({map: groundTexture}); if (groundMesh) { groundMesh.material = groundMaterial; window.requestAnimationFrame(draw); }; } // Start texture loading //new THREE.TextureLoader().load("Texture.png", setGroundTexture, function (xhr) {}, function (xhr) {}); setGroundTexture(makeTexture()); // Render a frame function draw() { renderer.render(scene, camera); } // ------- function makeTexture() { var ctx = document.createElement("canvas").getContext("2d"); ctx.canvas.width = 256; ctx.canvas.height = 256; ctx.fillStyle = "rgb(238, 238, 238)"; ctx.fillRect(0, 0, 256, 256); ctx.fillStyle = "rgb(208, 208, 208)"; ctx.fillRect(0, 0, 128, 128); ctx.fillRect(128, 128, 128, 128); for (var y = 0; y < 2; ++y) { for (var x = 0; x < 2; ++x) { ctx.save(); ctx.translate(x * 128 + 64, y * 128 + 64); ctx.lineWidth = 3; ctx.beginPath(); var radius = 50; ctx.moveTo(radius, 0); for (var i = 0; i <= 6; ++i) { var a = i / 3 * Math.PI; ctx.lineTo(Math.cos(a) * radius, Math.sin(a) * radius); } ctx.stroke(); ctx.restore(); } } var tex = new THREE.Texture(ctx.canvas); tex.needsUpdate = true; return tex; } 
 canvas, #canvasWrapper {margin-left: auto; margin-right: auto;} 
 <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r78/three.js"></script> <div id="canvasWrapper"></div> 

Greg Egan的觀察很有意義。 如果您不僅要細分平面而是平鋪UV坐標,那么它們會重復,而不是使用可能修復它的大數字。

 // Canvas dimensions canvasW = Math.floor(0.9*window.innerWidth); canvasH = Math.floor(0.75*canvasW); cAR = canvasW / canvasH; canvasWrapper = document.getElementById('canvasWrapper'); canvasWrapper.style.width=canvasW+'px'; canvasWrapper.style.height=canvasH+'px'; // Renderer renderer = new THREE.WebGLRenderer({antialias: true}); renderer.setPixelRatio(window.devicePixelRatio); console.log("Renderer pixel ratio = "+window.devicePixelRatio); renderer.setSize(canvasW, canvasH); canvas = renderer.domElement; canvasWrapper.appendChild(canvas); // Set up camera cameraDist = 24; camera = new THREE.PerspectiveCamera(25, cAR, 0.01, 1000); cameraAngle = 0; camera.position.x = cameraDist*Math.sin(cameraAngle); camera.position.y = 0.3*cameraDist; camera.position.z = cameraDist*Math.cos(cameraAngle); camera.lookAt(new THREE.Vector3(0,0,0)); // Set up scene, consisting of texture-tiled ground scene = new THREE.Scene(); // groundWidth = 1000; // Reduce overall size of ground groundWidth = 200; groundMaterial = null; // groundGeom = new THREE.PlaneGeometry(groundWidth,groundWidth); // Split plane geometry into a grid of smaller squares //groundGeom = new THREE.PlaneGeometry(groundWidth,groundWidth,20,20); var groundGeom = new THREE.BufferGeometry(); var quads = groundWidth * groundWidth; var positions = new Float32Array( quads * 6 * 3 ); var normals = new Float32Array( quads * 6 * 3 ); var texcoords = new Float32Array( quads * 6 * 2 ); for (var yy = 0; yy < groundWidth; ++yy) { for (var xx = 0; xx < groundWidth; ++xx) { var qoff = (yy * groundWidth + xx) * 6; var poff = qoff * 3; var x = xx - groundWidth / 2; var y = yy - groundWidth / 2; positions[poff + 0] = x; positions[poff + 1] = y; positions[poff + 2] = 0; positions[poff + 3] = x + 1; positions[poff + 4] = y; positions[poff + 5] = 0; positions[poff + 6] = x; positions[poff + 7] = y + 1; positions[poff + 8] = 0; positions[poff + 9] = x; positions[poff + 10] = y + 1; positions[poff + 11] = 0; positions[poff + 12] = x + 1; positions[poff + 13] = y; positions[poff + 14] = 0; positions[poff + 15] = x + 1; positions[poff + 16] = y + 1; positions[poff + 17] = 0; normals[poff + 0] = 0; normals[poff + 1] = 1; normals[poff + 2] = 0; normals[poff + 3] = 0; normals[poff + 4] = 1; normals[poff + 5] = 0; normals[poff + 6] = 0; normals[poff + 7] = 1; normals[poff + 8] = 0; normals[poff + 9] = 0; normals[poff + 10] = 1; normals[poff + 11] = 0; normals[poff + 12] = 0; normals[poff + 13] = 1; normals[poff + 14] = 0; normals[poff + 15] = 0; normals[poff + 16] = 1; normals[poff + 17] = 0; var toff = qoff * 2; texcoords[toff + 0] = 0; texcoords[toff + 1] = 0; texcoords[toff + 2] = 1; texcoords[toff + 3] = 0; texcoords[toff + 4] = 0; texcoords[toff + 5] = 1; texcoords[toff + 6] = 0; texcoords[toff + 7] = 1; texcoords[toff + 8] = 1; texcoords[toff + 9] = 0; texcoords[toff + 10] = 1; texcoords[toff + 11] = 1; } } groundGeom.addAttribute( 'position', new THREE.BufferAttribute( positions, 3 ) ); groundGeom.addAttribute( 'normal', new THREE.BufferAttribute( normals, 3 ) ); groundGeom.addAttribute( 'uv', new THREE.BufferAttribute( texcoords, 2 ) ); groundGeom.computeBoundingSphere(); groundGeom.rotateX(-Math.PI/2); groundMesh = new THREE.Mesh(groundGeom, groundMaterial || new THREE.MeshBasicMaterial()); scene.add(groundMesh); //window.requestAnimationFrame(draw); // Insert texture once it has loaded function setGroundTexture(texture) { groundTexture = texture; groundTexture.wrapS = THREE.RepeatWrapping; groundTexture.wrapT = THREE.RepeatWrapping; groundTexture.repeat.set(1, 1); groundTexture.anisotropy = renderer.getMaxAnisotropy(); console.log("Texture anisotropy = "+groundTexture.anisotropy); groundMaterial = new THREE.MeshBasicMaterial({map: groundTexture}); if (groundMesh) { groundMesh.material = groundMaterial; window.requestAnimationFrame(draw); }; } // Start texture loading //new THREE.TextureLoader().load("Texture.png", setGroundTexture, function (xhr) {}, function (xhr) {}); setGroundTexture(makeTexture()); // Render a frame function draw() { renderer.render(scene, camera); } // ------- function makeTexture() { var ctx = document.createElement("canvas").getContext("2d"); ctx.canvas.width = 256; ctx.canvas.height = 256; ctx.fillStyle = "rgb(238, 238, 238)"; ctx.fillRect(0, 0, 256, 256); ctx.fillStyle = "rgb(208, 208, 208)"; ctx.fillRect(0, 0, 128, 128); ctx.fillRect(128, 128, 128, 128); for (var y = 0; y < 2; ++y) { for (var x = 0; x < 2; ++x) { ctx.save(); ctx.translate(x * 128 + 64, y * 128 + 64); ctx.lineWidth = 3; ctx.beginPath(); var radius = 50; ctx.moveTo(radius, 0); for (var i = 0; i <= 6; ++i) { var a = i / 3 * Math.PI; ctx.lineTo(Math.cos(a) * radius, Math.sin(a) * radius); } ctx.stroke(); ctx.restore(); } } var tex = new THREE.Texture(ctx.canvas); tex.needsUpdate = true; return tex; } 
 canvas, #canvasWrapper {margin-left: auto; margin-right: auto;} 
 <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r78/three.js"></script> <div id="canvasWrapper"></div> 

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM