Three.js:查找相机视锥体内可见的Mesh面
找出相机视锥体内可见的Mesh面
核心思路
利用相机的**视锥体(Frustum)**逐个判断Mesh的面是否处于视锥范围内,无需考虑遮挡——只要面的部分或全部在视锥内,即可判定为可见。
步骤1:获取相机的视锥体对象
先从相机的投影矩阵和视图矩阵计算出当前视锥体的边界:
const frustum = new THREE.Frustum(); const viewProjMatrix = new THREE.Matrix4(); // 更新相机世界矩阵,确保计算准确 camera.updateMatrixWorld(); // 计算视图投影矩阵:投影矩阵 × 视图矩阵(矩阵世界逆) viewProjMatrix.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse ); // 从视图投影矩阵生成视锥体 frustum.setFromProjectionMatrix(viewProjMatrix);
步骤2:遍历Mesh的面进行视锥判断
处理Mesh的顶点数据(支持索引/非索引BufferGeometry),将局部顶点转换为世界坐标后,通过两种逻辑判断面是否在视锥内:
- 面的任意顶点在视锥内 → 面可见
- 所有顶点都不在视锥内,但面所在平面与视锥相交 → 面部分可见
示例代码:
const visibleFaceIndices = []; const geometry = mesh.geometry; const positionAttr = geometry.attributes.position; const indexAttr = geometry.index; // 工具函数:获取指定面的三个世界坐标顶点 const getFaceWorldVertices = (faceIdx) => { const vertices = []; const tempVec = new THREE.Vector3(); let vertexIndices; // 适配索引/非索引几何体 if (indexAttr) { vertexIndices = [ indexAttr.array[faceIdx * 3], indexAttr.array[faceIdx * 3 + 1], indexAttr.array[faceIdx * 3 + 2] ]; } else { vertexIndices = [faceIdx * 3, faceIdx * 3 + 1, faceIdx * 3 + 2]; } // 转换局部顶点到世界坐标 for (const idx of vertexIndices) { tempVec.fromBufferAttribute(positionAttr, idx); mesh.localToWorld(tempVec); vertices.push(tempVec.clone()); } return vertices; }; // 遍历所有面 const totalFaces = indexAttr ? indexAttr.array.length / 3 : positionAttr.array.length / 3; for (let i = 0; i < totalFaces; i++) { const vertices = getFaceWorldVertices(i); let isVisible = false; // 检查是否有顶点在视锥内 for (const v of vertices) { if (frustum.containsPoint(v)) { isVisible = true; break; } } // 顶点都不在的话,检查面平面是否与视锥相交 if (!isVisible) { const facePlane = new THREE.Plane().setFromCoplanarPoints(...vertices); if (frustum.intersectsPlane(facePlane)) { isVisible = true; } } if (isVisible) { visibleFaceIndices.push(i); } } // visibleFaceIndices 即为视锥体内的面索引集合
性能优化技巧
- 先判断Mesh包围盒:如果Mesh的世界包围盒完全在视锥外,直接跳过所有面;如果完全在视锥内,所有面都可见,无需逐个判断:
mesh.updateMatrixWorld(); const boundingBox = new THREE.Box3().setFromObject(mesh); if (frustum.intersectsBox(boundingBox)) { // 执行上面的面遍历逻辑 } else { // 所有面都不在视锥内 }
内容的提问来源于stack exchange,提问作者lviggiani
相关产品推荐
相关产品推荐

