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

如何使用Three.js创建可接收阴影的粗线条几何体?

实现可接收阴影的道路标线(粗线条)

你的思路完全正确:要让线条能接收阴影,必须用带面的几何体(而非Three.js的Line类),MeshLambertMaterial支持阴影接收,BufferGeometry则是高效生成自定义顶点的最佳选择。下面是具体的实现步骤和代码示例:

核心思路

我们需要把一维的点集转换成二维的带宽度的面——本质上是把每一段直线扩展成一个四边形(由两个三角形组成),这样几何体就有了可以接收阴影的表面,同时所有面保持朝上(法线指向Y轴正方向)。

具体实现代码

1. 生成自定义BufferGeometry的函数

这个函数接收点数组(所有点Y值相同)和线条宽度,返回对应的BufferGeometry:

function createLineGeometry(points, lineWidth) {
  const vertices = [];
  const normals = [];
  const indices = [];
  const halfWidth = lineWidth / 2;

  // 遍历每一对相邻点
  for (let i = 0; i < points.length - 1; i++) {
    const p1 = points[i];
    const p2 = points[i + 1];

    // 计算线段的方向向量
    const dirX = p2.x - p1.x;
    const dirZ = p2.z - p1.z;
    // 计算垂直于线段的方向向量(用于向两侧偏移)
    const perpX = -dirZ;
    const perpZ = dirX;
    // 归一化垂直向量,确保偏移距离一致
    const length = Math.sqrt(perpX * perpX + perpZ * perpZ);
    const normPerpX = perpX / length;
    const normPerpZ = perpZ / length;

    // 生成四个顶点:线段两侧各两个点
    const v0 = {
      x: p1.x + normPerpX * halfWidth,
      y: p1.y,
      z: p1.z + normPerpZ * halfWidth
    };
    const v1 = {
      x: p1.x - normPerpX * halfWidth,
      y: p1.y,
      z: p1.z - normPerpZ * halfWidth
    };
    const v2 = {
      x: p2.x + normPerpX * halfWidth,
      y: p2.y,
      z: p2.z + normPerpZ * halfWidth
    };
    const v3 = {
      x: p2.x - normPerpX * halfWidth,
      y: p2.y,
      z: p2.z - normPerpZ * halfWidth
    };

    // 将顶点加入数组
    vertices.push(v0.x, v0.y, v0.z);
    vertices.push(v1.x, v1.y, v1.z);
    vertices.push(v2.x, v2.y, v2.z);
    vertices.push(v3.x, v3.y, v3.z);

    // 所有面朝上,法线统一为(0,1,0)
    normals.push(0, 1, 0);
    normals.push(0, 1, 0);
    normals.push(0, 1, 0);
    normals.push(0, 1, 0);

    // 生成两个三角形的索引(v0-v1-v2 和 v1-v3-v2)
    const baseIndex = i * 4;
    indices.push(baseIndex, baseIndex + 1, baseIndex + 2);
    indices.push(baseIndex + 1, baseIndex + 3, baseIndex + 2);
  }

  // 创建BufferGeometry
  const geometry = new THREE.BufferGeometry();
  geometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3));
  geometry.setAttribute('normal', new THREE.Float32BufferAttribute(normals, 3));
  geometry.setIndex(indices);

  return geometry;
}

2. 创建Mesh并配置阴影

接下来用这个几何体创建Mesh,同时开启阴影相关设置:

// 示例点集(所有Y值相同,比如Y=0)
const linePoints = [
  { x: -5, y: 0, z: -5 },
  { x: 5, y: 0, z: -5 },
  { x: 5, y: 0, z: 5 },
  { x: -5, y: 0, z: 5 }
];

// 生成几何体
const lineGeometry = createLineGeometry(linePoints, 0.5); // 线条宽度0.5

// 创建材质,开启阴影接收
const lineMaterial = new THREE.MeshLambertMaterial({
  color: 0xffff00, // 黄色标线
  receiveShadow: true
});

// 创建Mesh
const lineMesh = new THREE.Mesh(lineGeometry, lineMaterial);
scene.add(lineMesh);

// 别忘了开启场景阴影和光源的阴影投射
scene.shadowMap.enabled = true;

// 示例光源(比如方向光)
const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
directionalLight.position.set(10, 20, 10);
directionalLight.castShadow = true;
scene.add(directionalLight);

注意事项

  • 转角处理:上面的代码是基础实现,折线的转角处会有缝隙。如果需要更平滑的转角,可以在转角处添加额外的顶点(比如生成圆角或者斜接角),或者使用THREE.ExtrudeGeometry配合路径来生成,但BufferGeometry的方式性能更优。
  • 性能优化:如果点集非常大,BufferGeometry的索引模式已经帮你减少了顶点重复,比非索引模式更高效。
  • 法线:因为所有面都在同一Y平面朝上,手动设置法线为(0,1,0)比调用geometry.computeVertexNormals()更高效,也能避免计算误差。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 04:14:57