You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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),将局部顶点转换为世界坐标后,通过两种逻辑判断面是否在视锥内:

  1. 面的任意顶点在视锥内 → 面可见
  2. 所有顶点都不在视锥内,但面所在平面与视锥相交 → 面部分可见

示例代码:

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 08:40:27