iOS WebGL纹理渲染中的缺陷

时间:2016-09-14 08:38:03

标签: javascript ios webgl

使用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上渲染时非常模糊,如页面下方的屏幕截图所示。

桌面

enter image description here

ipad公司

enter image description here

在这两种情况下,渲染纹理的各向异性为16(渲染器支持的最大值)。用于纹理的图像具有256×256的尺寸(2的幂,这是重复纹理所必需的),并且使其更大或更小并不能解决问题。

质地:

enter image description here

我将渲染器的像素比率设置为与浏览器窗口相匹配,这意味着桌面系统为1,iPad为视网膜显示器为2。这种方法通常可以为渲染的其他方面提供最佳效果,并且在任何情况下,在iPad上将像素比率设置为1而不是2,都不会改善纹理的外观。

所以我的问题是:这是iOS WebGL中的一个我不得不忍受的错误,还是我可以在我自己的代码中调整以在iOS设备上获得更好的结果?

编辑:这三个.js demo page在iPad上的呈现效果要比在桌面浏览器上少得多,而且演示源使用与我自己的代码相同的通用方法,这表明无论我缺少什么技巧,都不是简单而明显的。

2 个答案:

答案 0 :(得分:2)

我无法完全解释问题的根源,但是我发现了一种解决方法,表明原因是数值精度的某种降低,我想在GPU中,不会发生每张iPad显卡。

解决方法涉及将地面的平面几何体(最初只是一个正方形(三个.js可能分为两个三角形))分割成多个正方形的网格。据推测,这会改变对象上的(u,v)坐标和纹理坐标与GPU中浮点精度限制相反的方式。此外,将地面尺寸从1000减小到200有助于。

令人讨厌的是在平面几何体中拥有所有这些额外面的开销,即使它们在指定形状时完全是多余的。

在任何情况下,我的桌面浏览器上的结果看起来都完全一样,但在我的iPad 4上效果要好得多。

编辑经过更仔细的测试后,我认为细分THREE.PlaneGeometry并没有任何区别,只是减少了平铺的整体尺寸帮助的飞机。事实上,通过使平铺平面的尺寸足够大,当尺寸为1000时,当我的iMac尺寸为80,000时,可以在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>

答案 1 :(得分:1)

Greg Egan的观察很有意义。如果您不仅要细分平面而是平铺UV坐标,那么它们会重复,而不是使用可能修复它的大数字。

&#13;
&#13;
//	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;
}
&#13;
canvas, #canvasWrapper {margin-left: auto; margin-right: auto;}
&#13;
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r78/three.js"></script>
<div id="canvasWrapper"></div>
&#13;
&#13;
&#13;