如何使用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
相关产品推荐
相关产品推荐

