Three.js检测物体部分和完全遮挡

3

我将尝试检测Three.js中的一个对象是否被部分或完全覆盖(隐藏在)另一个对象后面。

目前我的简单解决方案是向对象中心投射一条射线:

function getScreenPos(object) {
  var pos = object.position.clone();
  camera.updateMatrixWorld();
  pos.project(camera);
  return new THREE.Vector2(pos.x, pos.y);
}

function isOccluded(object) {
  raycaster.setFromCamera(getScreenPos(object), camera);
  var intersects = raycaster.intersectObjects(scene.children);
  if (intersects[0] && intersects[0].object === object) {
    return false;
  } else {
    return true;
  }
}

然而,它并没有考虑到物体的尺寸(宽度、高度、深度)。

未被遮挡(因为物体中心不在后面) Object not occluded

被遮挡(因为物体中心在后面) Object occluded

查看工作演示:

https://jsfiddle.net/kmturley/nb9f5gho/57/

目前的想法是计算物体包围盒大小,并为盒子的每个角投射光线。但这可能仍然过于简单:

var box = new THREE.Box3().setFromObject(object);
var size = box.getSize();

我希望找到一种更为稳健的方法,可以提供部分遮挡和完全遮挡的布尔值,甚至可以提供遮挡百分比?
2个回答

2

在 Stack Overflow 和 Three.js 的示例中搜索“GPU picking”。这个概念可以分解为三个基本步骤:

  1. 将每个形状的材质更改为唯一的平坦(MeshBasicMaterial)颜色。
  2. 使用唯一的材质渲染场景。
  3. 读取渲染帧的像素以收集颜色信息。

您的情况允许一些注意事项。

  1. 只给您正在测试的形状一个唯一的颜色,其他所有东西都可以是黑色。
  2. 您不需要渲染整个场景来测试一个形状。您可以调整视口,只渲染与所需形状周围区域相邻的区域。
  3. 由于您只给测试部分赋予了颜色,其余数据应该是零,这样可以更容易地找到与您唯一颜色匹配的像素。

现在您有了像素数据,您可以确定以下内容:

  • 如果没有像素匹配唯一颜色,则形状完全被遮挡。
  • 如果有一些像素匹配唯一颜色,则形状至少部分可见。

第二个要点指出形状“至少部分可见”。这是因为根据您目前拥有的信息无法测试完全可见性。

我会这样做(也许有更好的解决方案),就是再次渲染相同的视口,但只显示测试形状,这相当于部分完全可见。有了这些信息,将像素与第一次渲染进行比较。如果两者具有相同数量(或许在一个公差范围内)的唯一颜色像素,则可以说该部分完全可见/未被遮挡。


太棒了,我不知道有“GPU picking”这种方法。看起来这里有一个示例可以做一些工作:https://threejs.org/examples/#webgl_interactive_cubes_gpu - Kim T
并发现了这个WebGL2示例:https://tsherif.github.io/webgl2examples/occlusion.html。但是并不是所有浏览器都支持,可以参考https://www.caniuse.com/#search=webgl。 - Kim T

2
我成功地根据TheJim01的答案,为WebGL1创建了一个可工作的版本!
首先创建第二个更简单的场景用于计算:
pickingScene = new THREE.Scene();
pickingTextureOcclusion = new THREE.WebGLRenderTarget(window.innerWidth / 2, window.innerHeight / 2);
pickingMaterial = new THREE.MeshBasicMaterial({ vertexColors: THREE.VertexColors });
pickingScene.add(new THREE.Mesh(BufferGeometryUtils.mergeBufferGeometries([
  createBuffer(geometry, mesh),
  createBuffer(geometry2, mesh2)
]), pickingMaterial));

重新创建您的对象为缓冲几何体(性能更快):
function createBuffer(geometry, mesh) {
  var buffer = new THREE.SphereBufferGeometry(geometry.parameters.radius, geometry.parameters.widthSegments, geometry.parameters.heightSegments);
  quaternion.setFromEuler(mesh.rotation);
  matrix.compose(mesh.position, quaternion, mesh.scale);
  buffer.applyMatrix4(matrix);
  applyVertexColors(buffer, color.setHex(mesh.name));
  return buffer;
}

根据mesh.name添加颜色,例如id 1、2、3等

function applyVertexColors(geometry, color) {
  var position = geometry.attributes.position;
  var colors = [];
  for (var i = 0; i < position.count; i ++) {
    colors.push(color.r, color.g, color.b);
  }
  geometry.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3));
}

然后在渲染循环期间,检查第二个场景中的纹理,并将像素数据与网格名称匹配:

function isOccludedBuffer(object) {
  renderer.setRenderTarget(pickingTextureOcclusion);
  renderer.render(pickingScene, camera);
  var pixelBuffer = new Uint8Array(window.innerWidth * window.innerHeight);
  renderer.readRenderTargetPixels(pickingTextureOcclusion, 0, 0, window.innerWidth / 2, window.innerHeight / 2, pixelBuffer);
  renderer.setRenderTarget(null);
  return !pixelBuffer.includes(object.name);
}

您可以在此处查看WebGL1的演示:https://jsfiddle.net/kmturley/nb9f5gho/62/ 需要注意的一点是,使用这种方法时,您的拾取场景需要与主场景中的更改保持同步。因此,如果您的对象移动位置/旋转等,则它们也需要在拾取场景中进行更新。在我的示例中,相机在移动,而不是对象,因此不需要更新。
对于WebGL2,我们将有一个更好的解决方案:https://tsherif.github.io/webgl2examples/occlusion.html 但并非所有浏览器都支持此功能:https://www.caniuse.com/#search=webgl

感谢您抽出时间放置这两个演示!它们真的很有帮助。 - Felipe
没问题!我会在某个时间点写一篇博客文章 :) - Kim T

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接