简体   繁体   English

iOS WebGL纹理渲染中的缺陷

[英]Flaw in iOS WebGL texture rendering

This simple test of WebGL texture rendering using the three.js library: 这个使用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> 

renders perfectly on the desktop browsers I've tried, but is badly blurred when rendered on an iPad, as shown by the screenshot further down the page. 在我试过的桌面浏览器上完美呈现,但在iPad上渲染时非常模糊,如页面下方的屏幕截图所示。

Desktop 桌面

在此输入图像描述

iPad iPad的

在此输入图像描述

In both cases, the texture is rendered with an anisotropy of 16 (the maximum supported by the renderer). 在这两种情况下,纹理都呈现为16的各向异性(渲染器支持的最大值)。 The image used for the texture has dimensions 256 × 256 (a power of 2, which is necessary for repeated textures), and making it larger or smaller doesn't fix the problem. 用于纹理的图像具有256×256的尺寸(2的幂,这是重复纹理所必需的),并且使其变大或变小并不能解决问题。

texture: 质地:

在此输入图像描述

I'm setting the renderer's pixel ratio to match the browser window, which means it is 1 for desktop systems and 2 for the iPad's retina display. 我将渲染器的像素比率设置为与浏览器窗口匹配,这意味着桌面系统为1,iPad为视网膜显示器为2。 This approach generally gives the best results for other aspects of rendering, and in any case setting the pixel ratio to 1 on the iPad, instead of 2, doesn't improve the appearance of the texture. 这种方法通常为渲染的其他方面提供最佳结果,并且在任何情况下,在iPad上将像素比率设置为1而不是2,不会改善纹理的外观。

So my question is: is this a bug in iOS WebGL that I'll just have to live with, or is there something I can tweak in my own code to get better results on iOS devices? 所以我的问题是:这是iOS WebGL中的一个我不得不忍受的错误,还是我可以在我自己的代码中调整以在iOS设备上获得更好的结果?

Edit: This three.js demo page also renders much less clearly on the iPad than on desktop browsers, and the source for the demo uses the same general approach as my own code, which suggests that whatever trick I'm missing, it's not something simple and obvious. 编辑:这个three.js 演示页面在iPad上的渲染效果要比桌面浏览器低得多,演示的源代码使用与我自己的代码相同的通用方法,这表明无论我缺少什么技巧,都不是简单而明显。

I can't fully explain the source of the problem, but I've found a work-around that suggests that the cause is some kind of degradation of numerical precision, I guess in the GPU, that doesn't occur with every iPad graphics card. 我无法完全解释问题的根源,但我发现一个解决方案,表明原因是数值精度的某种降低,我想在GPU中,每个iPad图形都不会出现这种情况卡。

The work-around involves splitting the plane geometry for the ground, which was originally just a single square (which three.js presumably divides into 2 triangles), into a grid of multiple squares. 解决方法涉及将地面的平面几何体(最初只是一个正方形(三个可能分为两个三角形))分割成多个正方形的网格。 Presumably this changes something in the way the (u,v) coordinates on the object and the texture coordinates run up against the limits of floating point precision in the GPU. 据推测,这会改变对象上的(u,v)坐标和纹理坐标与GPU中浮点精度限制相反的方式。 Also, reducing the size of the ground from 1000 to 200 helps. 此外,将地面尺寸从1000减小到200有助于。

The annoying thing is the overhead from having all those extra faces in the plane geometry, even though they're completely redundant in specifying the shape. 令人讨厌的是在平面几何体中具有所有这些额外面的开销,即使它们在指定形状时完全是多余的。

In any case, the result looks exactly the same on my desktop browsers, but vastly better on my iPad 4. 在任何情况下,结果在我的桌面浏览器上看起来完全相同,但在我的iPad 4上要好得多。

Edit: After more careful testing, I don't think subdividing the THREE.PlaneGeometry is making any difference, it's only reducing the overall size of the tiled plane that helps. 编辑:经过更仔细的测试,我不认为细分THREE.PlaneGeometry会产生任何影响,它只会减少有助于平铺平面的整体尺寸 And in fact, by making the size of the tiled plane large enough, whatever limit is being hit on the iPad 4 when the size is just 1000 can be reached on my iMac when the size is 80,000, as the second version of the code snippet shows. 事实上,通过使平铺平面的大小足够 ,当尺寸为1000时,在我的iMac上可以达到大小为1000的iPad 4上的任何限制,作为代码片段的第二个版本显示。 (The texture starts to degrade around 50,000, but 80,000 makes the distortion unmissable.) Obviously there are no real applications where you need to tile a surface with 50,000 x 50,000 copies of a texture, but a few hundred in each direction, which is where the iPad 4 starts to have problems, isn't extravagant. (纹理开始降低大约50,000,但80,000使得失真不容错过。)显然,没有真正的应用程序,你需要用50,000 x 50,000个纹理纹理平铺表面,但每个方向几百个,这是iPad 4开始出现问题,并不奢侈。

First version of the code snippet, which fixes the problem on an 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> 

Second version of the code snippet, which breaks the texture on a 2007 iMac: 第二个版本的代码片段,它破坏了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's observation makes a lot of sense. Greg Egan的观察很有意义。 If you not only subdivide the plane but tile the UV coords so they repeat instead of using large numbers that might fix it. 如果您不仅要细分平面而是平铺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