【问题标题】:Piecing together meshes in ThreeJS causes visible seam在 ThreeJS 中拼接网格会导致可见的接缝
【发布时间】:2020-06-03 01:39:07
【问题描述】:

我正在尝试将球体与单个切片拼凑在一起。基本上,我有多个 SphereGeoemtery 切片,它们形成一个球体并用于投影全景图。切片用于延迟加载非常大的全景图。

在这些切片上使用默认的纹理环绕模式 (THREE.ClampToEdgeWrapping),从远处看全景图看起来不错,但如果放大,则可以非常清楚地看到网格的边缘正在拉伸,从而导致可见的接缝。这是有道理的,因为它拉伸了边缘的最后一个像素..

我也尝试将包装模式更改为 THREE.RepeatWrapping,但是,接缝变得完全可见:

所以我的问题是,这里拼接网格的最佳方法是什么?或者这只是不可避免的?

【问题讨论】:

  • 可能有很多,但很难说。你能举一个活生生的例子吗?此外,可能与此有关:github.com/mrdoob/three.js/issues/18565 尽管该主题似乎与反射和法线有关。
  • 你为什么希望重复包装来解决这个问题?

标签: three.js 3d


【解决方案1】:

在我的脑海中,你必须让每个纹理在每个方向上都包含一个边框行和边框列,这是它的邻居的重复,然后适当地调整 UV 坐标

例如,如果大图像是 8 像素宽和 6 像素高

ABCDEFGH
IJKLMNOP
QRSTUVWX
YZ123456
789abcde
fghijklm

你想把它分成 4 个部分(每个 4、3) 那么你需要这4个部分

ABCDE DEFGH
IJKLM LMNOP
QRSTU TUVWX
YZ123 23456

QRSTU TUVWX
YZ123 23456
789ab abcde
fghij ijklm

也是为了方便重复边缘所以

AABCDE DEFGHH
AABCDE DEFGHH
IIJKLM LMNOPP
QQRSTU TUVWXX
YYZ123 234566

QQRSTU TUVWXX
YYZ123 234566
7789ab abcdee
ffghij ijklmm
ffghij ijklmm

重复边缘是因为我假设您要分割成超过 2x2 的大小,所以从技术上讲,如果您要将 50 像素宽的东西分成 5 个部分,您可以做 11、12、12、12、11 的部分在宽度上。只有 11 个像素而不是 12 个像素的边缘需要不同的 UV 调整。但是,通过重复边,我们可以使它们全部为 12、12、12、12、12,所以一切都是一致的。

测试,左边是正常的分裂,显示接缝。右边是固定的,没有接缝。

body {
  margin: 0;
}
#c {
  width: 100vw;
  height: 100vh;
  display: block;
}
<canvas id="c"></canvas>
<script type="module">
import * as THREE from 'https://threejsfundamentals.org/threejs/resources/threejs/r115/build/three.module.js';

function main() {
  const canvas = document.querySelector('#c');
  const renderer = new THREE.WebGLRenderer({canvas});

  const fov = 75;
  const aspect = 2;  // the canvas default
  const near = 0.1;
  const far = 5;
  const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
  camera.position.z = 1;

  const scene = new THREE.Scene();

  // make our texture using a canvas to test
  const bigImage = document.createElement('canvas');
  {
    const ctx = bigImage.getContext('2d');
    const width = 32;
    const height = 16;
    ctx.canvas.width = width;
    ctx.canvas.height = height;
    const gradient = ctx.createLinearGradient(0, 0, width, height);
    gradient.addColorStop(0, 'red');
    gradient.addColorStop(0.5, 'yellow');
    gradient.addColorStop(1, 'blue');
    ctx.fillStyle = gradient;
    ctx.fillRect(0, 0, width, height);
  }
  
  const forceTextureInitialization = function() {
    const material = new THREE.MeshBasicMaterial();
    const geometry = new THREE.PlaneBufferGeometry();
    const scene = new THREE.Scene();
    scene.add(new THREE.Mesh(geometry, material));
    const camera = new THREE.Camera();

    return function forceTextureInitialization(texture) {
      material.map = texture;
      renderer.render(scene, camera);
    };
  }();  

  // bad
  {
    const ctx = document.createElement('canvas').getContext('2d');
  
    // split the texture into 4 parts across 4 planes
    const across = 2;
    const down = 2;
    const pixelsAcross = bigImage.width / across;
    const pixelsDown = bigImage.height / down;
    ctx.canvas.width = pixelsAcross;
    ctx.canvas.height = pixelsDown;
    
    for (let y = 0; y < down; ++y) {
      for (let x = 0; x < across; ++x) {
        ctx.clearRect(0, 0, pixelsAcross, pixelsDown);
        ctx.drawImage(bigImage, 
                      x * pixelsAcross, (down - 1 - y) * pixelsDown, pixelsAcross, pixelsDown,
                      0, 0, pixelsAcross, pixelsDown);
        const texture = new THREE.CanvasTexture(ctx.canvas);
        // see https://threejsfundamentals.org/threejs/lessons/threejs-canvas-textures.html
        forceTextureInitialization(texture);
        const geometry = new THREE.PlaneBufferGeometry(1 / across, 1 / down);
        const material = new THREE.MeshBasicMaterial({map: texture});
        const plane = new THREE.Mesh(geometry, material);
        scene.add(plane);
        plane.position.set(-1 + x / across, y / down - 0.25, 0);
      }
    }
  }
  
  // good
  {
    const ctx = document.createElement('canvas').getContext('2d');
  
    // split the texture into 4 parts across 4 planes
    const across = 2;
    const down = 2;
    const pixelsAcross = bigImage.width / across;
    const pixelsDown = bigImage.height / down;
    ctx.canvas.width = pixelsAcross + 2;
    ctx.canvas.height = pixelsDown + 2;
    
    // just draw the image at all these offsets.
    // it would be more efficient to draw the edges
    // 1 pixel wide but I'm lazy
    const offsets = [
      [ 0,  0],
      [ 1,  0],
      [ 2,  0],
      [ 0,  1],
      [ 2,  1],
      [ 0,  2],
      [ 1,  2],
      [ 2,  2],
      [ 1,  1],
    ];
    
    for (let y = 0; y < down; ++y) {
      for (let x = 0; x < across; ++x) {
        ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
        let srcX = x * pixelsAcross - 1;
        let srcY = (down - 1 - y) * pixelsDown - 1;
        let dstX = 0;
        let dstY = 0;
        let width = pixelsAcross + 2;
        let height = pixelsDown + 2;
        ctx.drawImage(bigImage, 
                      srcX, srcY, width, height,
                      dstX, dstY, width, height);
        // handle edges
        if (srcX < 0) { 
          // repeat left edge
          ctx.drawImage(bigImage,
                        0, srcY, 1, height,
                        0, dstY, 1, height);
        }
        if (srcY < 0) {
          // repeat top edge
          ctx.drawImage(bigImage,
                        srcX, 0, width, 1,
                        dstX, 0, width, 1);
        }
        if (srcX + width > bigImage.width) {
          // repeat right edge
          ctx.drawImage(bigImage,
                        bigImage.width - 1, srcY, 1, height,
                        ctx.canvas.width - 1, dstY, 1, height);
        }
        if (srcY + height > bigImage.height) {
          // repeat bottom edge
          ctx.drawImage(bigImage,
                        srcX, bigImage.height - 1, width, 1,
                        dstX, ctx.canvas.height - 1, width, 1);
        }
        // TODO: handle corners
        const texture = new THREE.CanvasTexture(ctx.canvas);
        texture.minFilter = THREE.LinearFilter;
        // offset UV coords 1 pixel to skip the edge pixel
        texture.offset.set(1 / ctx.canvas.width, 1 / ctx.canvas.height);
        // only textureSize - 2 of the pixels in the texture
        texture.repeat.set(pixelsAcross / ctx.canvas.width, pixelsDown / ctx.canvas.height);
        // see https://threejsfundamentals.org/threejs/lessons/threejs-canvas-textures.html
        forceTextureInitialization(texture);
        const geometry = new THREE.PlaneBufferGeometry(1 / across, 1 / down);
        const material = new THREE.MeshBasicMaterial({map: texture});
        const plane = new THREE.Mesh(geometry, material);
        scene.add(plane);
        plane.position.set(1 + x / across - 0.5, y / down - 0.25, 0);
      }
    }
  }

  function resizeRendererToDisplaySize(renderer) {
    const canvas = renderer.domElement;
    const width = canvas.clientWidth;
    const height = canvas.clientHeight;
    const needResize = canvas.width !== width || canvas.height !== height;
    if (needResize) {
      renderer.setSize(width, height, false);
    }
    return needResize;
  }

  function render(time) {
    time *= 0.001;

    if (resizeRendererToDisplaySize(renderer)) {
      const canvas = renderer.domElement;
      camera.aspect = canvas.clientWidth / canvas.clientHeight;
      camera.updateProjectionMatrix();
    }

    renderer.render(scene, camera);

    requestAnimationFrame(render);
  }

  requestAnimationFrame(render);
}

main();
</script>

【讨论】:

  • 这太棒了!正是我需要做的......我只需要修改我的瓷砖提取代码以适应。谢谢你把解释写得这么清楚!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-08-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-11-14
  • 1970-01-01
相关资源
最近更新 更多