Three.js 检测对象何时被部分和完全遮挡
Three.js detect when object is partially and fully occluded
我正在尝试检测 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;
}
}
但是它不考虑对象的尺寸(宽度、高度、深度)。
没有被遮挡(因为物体中心不在后面)
被遮挡(因为物体中心在后面)
查看工作演示:
https://jsfiddle.net/kmturley/nb9f5gho/57/
目前想我可以计算对象框的大小,并为框的每个角投射光线。但这可能还是有点太简单了:
var box = new THREE.Box3().setFromObject(object);
var size = box.getSize();
我想找到一种更强大的方法,可以给出 partially occluded
和 fully occluded
布尔值,甚至 percentage occluded
?
Search Stack Overflow 和 Three.js 示例 "GPU picking." 这个概念可以分为三个基本步骤:
- 将每个形状的 material 更改为独特的平面 (
MeshBasicMaterial
) 颜色。
- 使用独特的 materials 渲染场景。
- 读取渲染帧的像素以收集颜色信息。
您的场景允许您注意一些事项。
- 只给你正在测试的形状一个独特的颜色——其他的都可以是黑色。
- 您不需要渲染整个场景来测试一个形状。您可以调整视口以仅渲染相关形状周围的区域。
- 因为你只给你的测试部分一个颜色,其余的数据应该是零,使得找到匹配你的独特颜色的像素更容易。
现在您已经有了像素数据,您可以确定以下内容:
- 如果没有像素匹配独特的颜色,则形状被完全遮挡。
- 如果某些像素匹配独特的颜色,则形状至少部分可见。
第二个项目符号表示形状 "at least partially" 可见。这是因为您无法使用当前拥有的信息测试完整的可见性。
我会做的(其他人可能有更好的解决方案)是渲染同一个视口第二次,但仅使测试形状可见,这相当于零件完全可见。掌握了这些信息后,将像素与第一次渲染进行比较。如果两者具有相同数量(可能在公差范围内)的唯一颜色像素,那么您可以说该部分完全 visible/not 被遮挡。
我根据 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, etc
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/
使用这种方法需要注意的一点是,您的拾取场景需要与主场景的变化保持同步。因此,如果您的对象移动 position/rotation 等,它们也需要在拾取场景中更新。在我的例子中,相机在移动,而不是物体在移动,所以它不需要更新。
对于WebGL2我们会有更好的解决方案:
https://tsherif.github.io/webgl2examples/occlusion.html
但这还不是所有浏览器都支持:
我正在尝试检测 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;
}
}
但是它不考虑对象的尺寸(宽度、高度、深度)。
没有被遮挡(因为物体中心不在后面)
被遮挡(因为物体中心在后面)
查看工作演示:
https://jsfiddle.net/kmturley/nb9f5gho/57/
目前想我可以计算对象框的大小,并为框的每个角投射光线。但这可能还是有点太简单了:
var box = new THREE.Box3().setFromObject(object);
var size = box.getSize();
我想找到一种更强大的方法,可以给出 partially occluded
和 fully occluded
布尔值,甚至 percentage occluded
?
Search Stack Overflow 和 Three.js 示例 "GPU picking." 这个概念可以分为三个基本步骤:
- 将每个形状的 material 更改为独特的平面 (
MeshBasicMaterial
) 颜色。 - 使用独特的 materials 渲染场景。
- 读取渲染帧的像素以收集颜色信息。
您的场景允许您注意一些事项。
- 只给你正在测试的形状一个独特的颜色——其他的都可以是黑色。
- 您不需要渲染整个场景来测试一个形状。您可以调整视口以仅渲染相关形状周围的区域。
- 因为你只给你的测试部分一个颜色,其余的数据应该是零,使得找到匹配你的独特颜色的像素更容易。
现在您已经有了像素数据,您可以确定以下内容:
- 如果没有像素匹配独特的颜色,则形状被完全遮挡。
- 如果某些像素匹配独特的颜色,则形状至少部分可见。
第二个项目符号表示形状 "at least partially" 可见。这是因为您无法使用当前拥有的信息测试完整的可见性。
我会做的(其他人可能有更好的解决方案)是渲染同一个视口第二次,但仅使测试形状可见,这相当于零件完全可见。掌握了这些信息后,将像素与第一次渲染进行比较。如果两者具有相同数量(可能在公差范围内)的唯一颜色像素,那么您可以说该部分完全 visible/not 被遮挡。
我根据 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, etc
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/
使用这种方法需要注意的一点是,您的拾取场景需要与主场景的变化保持同步。因此,如果您的对象移动 position/rotation 等,它们也需要在拾取场景中更新。在我的例子中,相机在移动,而不是物体在移动,所以它不需要更新。
对于WebGL2我们会有更好的解决方案:
https://tsherif.github.io/webgl2examples/occlusion.html
但这还不是所有浏览器都支持: