繁体   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