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

如何找出被THREE.SpotLight照亮的所有网格面?

实现思路

要找出被THREE.SpotLight照亮的网格面,需同时满足两个核心条件:面处于光源的锥形照射范围内,且面朝向光源(无遮挡场景下)。以下是具体实现方案:

1. 提取网格面数据

针对BufferGeometry,需按顶点索引分组获取每个面的顶点信息:

  • 若几何体有index属性,每3个索引对应一个面的三个顶点;
  • 若无index属性,直接按连续3个顶点为一组处理。

2. 判断面是否在光源锥形范围内

取面的中心作为判断基准点,完成两个维度的校验:

  • 距离校验:计算面中心到光源的距离,若超过spotLight.distance(非0时),则排除;
  • 锥形范围校验:计算光源方向向量与面中心指向光源的向量的点积,若点积小于光源锥形半角的余弦值,说明面不在锥形内,直接排除。

3. 判断面是否朝向光源

通过面法线与光源指向面的向量的点积判断:

  • 若使用平滑着色,取三个顶点法线的平均值作为面法线;若为平面着色,可直接计算面的几何法线;
  • 计算面法线与「光源指向面中心的向量」的点积,若结果大于0,说明面朝向光源,满足光照条件。

代码示例

function getLitFaces(mesh, spotLight) {
  const geometry = mesh.geometry;
  const positions = geometry.attributes.position.array;
  const normals = geometry.attributes.normal.array;
  const index = geometry.index;
  const litFaceIndices = [];

  // 预计算光源的关键参数
  const lightPos = spotLight.position.clone();
  const lightDir = spotLight.target.position.clone().sub(lightPos).normalize();
  const cosHalfAngle = Math.cos(spotLight.angle / 2);
  const maxDistance = spotLight.distance;
  // 处理网格的世界变换矩阵
  const meshMatrix = mesh.matrixWorld;

  // 遍历所有面
  const faceCount = index ? index.count / 3 : positions.length / 9;
  for (let i = 0; i < faceCount; i++) {
    let v1Idx, v2Idx, v3Idx;
    if (index) {
      v1Idx = index.array[i * 3] * 3;
      v2Idx = index.array[i * 3 + 1] * 3;
      v3Idx = index.array[i * 3 + 2] * 3;
    } else {
      v1Idx = i * 9;
      v2Idx = i * 9 + 3;
      v3Idx = i * 9 + 6;
    }

    // 获取并转换顶点到世界空间
    const v1 = new THREE.Vector3(positions[v1Idx], positions[v1Idx + 1], positions[v1Idx + 2]).applyMatrix4(meshMatrix);
    const v2 = new THREE.Vector3(positions[v2Idx], positions[v2Idx + 1], positions[v2Idx + 2]).applyMatrix4(meshMatrix);
    const v3 = new THREE.Vector3(positions[v3Idx], positions[v3Idx + 1], positions[v3Idx + 2]).applyMatrix4(meshMatrix);

    // 计算面中心
    const faceCenter = new THREE.Vector3();
    faceCenter.addVectors(v1, v2).add(v3).divideScalar(3);

    // 校验距离是否超出范围
    const distanceToLight = lightPos.distanceTo(faceCenter);
    if (maxDistance > 0 && distanceToLight > maxDistance) {
      continue;
    }

    // 校验是否在锥形范围内
    const dirToFace = faceCenter.clone().sub(lightPos).normalize();
    if (lightDir.dot(dirToFace) < cosHalfAngle) {
      continue;
    }

    // 计算面法线并校验朝向
    const n1 = new THREE.Vector3(normals[v1Idx], normals[v1Idx + 1], normals[v1Idx + 2]).applyMatrix4(meshMatrix).normalize();
    const n2 = new THREE.Vector3(normals[v2Idx], normals[v2Idx + 1], normals[v2Idx + 2]).applyMatrix4(meshMatrix).normalize();
    const n3 = new THREE.Vector3(normals[v3Idx], normals[v3Idx + 1], normals[v3Idx + 2]).applyMatrix4(meshMatrix).normalize();
    const faceNormal = new THREE.Vector3().addVectors(n1, n2).add(n3).divideScalar(3).normalize();

    const lightToFace = faceCenter.clone().sub(lightPos).normalize();
    if (faceNormal.dot(lightToFace.negate()) <= 0) {
      continue;
    }

    // 记录符合条件的面索引
    litFaceIndices.push(i);
  }

  return litFaceIndices;
}

// 使用示例
const litFaces = getLitFaces(yourMesh, yourSpotLight);
console.log("被照亮的面索引:", litFaces);

注意事项

  • 必须将顶点转换到世界空间计算,因为光源位置默认是世界空间坐标;
  • 若使用平面着色,可直接通过三个顶点计算面的几何法线,无需平均顶点法线,提升计算效率;
  • 代码用面中心作为判断点,若需处理面部分在锥形内的场景,可额外对每个顶点做锥形范围校验。

内容的提问来源于stack exchange,提问作者Nevermore

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 20:01:02