Three.js:如何判断空间点是否可见或被遮挡?
解决Three.js中标注被遮挡时的隐藏问题
你现在用的视锥体检测只能判断点是否在相机的可视范围内,完全没法处理被其他网格遮挡的情况——这就是为什么只有大幅旋转时标注才会消失的原因。要解决遮挡检测,咱们得用**射线投射(Raycaster)**来实现精准的遮挡判断。
首先先修正你现有代码里的一个小错误:你写的逻辑搞反了,frustum.containsPoint(vector)返回true说明点在视野里,这时候应该显示标注(opacity:1),反之才隐藏。
接下来是完整的解决方案:
核心思路
从相机位置向目标空间点发射一条射线,检查这条射线在到达目标点之前是否和场景中的其他物体相交。如果有相交,说明目标点被遮挡,隐藏标注;否则显示标注。
完整代码实现
const vector = new THREE.Vector3(x, y, z); this.aCamera.updateMatrixWorld(true); // 1. 先做视锥体检测,快速过滤掉视野外的点 const frustum = new THREE.Frustum(); frustum.setFromMatrix(new THREE.Matrix4().multiplyMatrices( this.aCamera.projectionMatrix, this.aCamera.matrixWorldInverse )); if (!frustum.containsPoint(vector)) { // 点不在视野里,直接隐藏标注 anAnnotation.css({opacity: 0}); return; } // 2. 视野内的点,再做遮挡检测 const raycaster = new THREE.Raycaster(); // 设置射线起点为相机位置,方向指向目标点 raycaster.set(this.aCamera.position, vector.clone().sub(this.aCamera.position).normalize()); // 计算相机到目标点的距离,作为射线检测的最大距离(避免检测到更远的物体) const distanceToPoint = this.aCamera.position.distanceTo(vector); raycaster.far = distanceToPoint; // 检测场景中的物体(替换成你实际的场景物体数组,第二个true表示递归检测子物体) const intersects = raycaster.intersectObjects(scene.children, true); // 判断是否被遮挡:如果有相交结果,且第一个相交的物体距离小于目标点距离,说明被遮挡 let isOccluded = false; if (intersects.length > 0) { // 注意:如果目标点本身在某个网格上,需要排除该网格(根据你的需求调整) // 比如可以给目标点所在网格加个标记,然后在这里判断 isOccluded = intersects[0].distance < distanceToPoint - 0.01; // 加个小偏移避免精度问题 } // 根据遮挡状态设置标注显示 anAnnotation.css({opacity: isOccluded ? 0 : 1});
关键细节说明
- 视锥体检测前置:先做视锥体检测可以快速过滤掉视野外的点,减少不必要的射线检测,提升性能。
- 射线距离限制:把射线的
far设置为相机到目标点的距离,这样只会检测目标点之前的物体,不会误判更远的物体。 - 精度偏移:判断距离时减去一个很小的值(比如0.01),避免因为浮点精度问题,把目标点所在的网格当成遮挡物。
- 排除自身网格:如果你的目标点是附着在某个网格上的,记得在检测时排除这个网格,否则会误判为被自身遮挡。你可以给该网格添加一个自定义属性(比如
isAnnotationHost: true),然后在遍历相交结果时跳过它。
性能优化建议
如果你的场景里有大量标注,频繁的射线检测可能会影响性能,可以试试这些优化:
- 只在相机移动、旋转或者物体动画结束时执行检测,而不是每帧都检测。
- 对标注进行批量处理,或者使用WebGL的遮挡查询(Occlusion Query)(Three.js的
THREE.OcclusionQuery类),这种方式更适合批量遮挡检测,但实现起来稍复杂。
内容的提问来源于stack exchange,提问作者Dino
相关产品推荐
相关产品推荐

