Three.js:如何在3D空间正确绘制顶点定义的2D平面?
解决Three.js中3D多边形平面绘制的三角化问题
看起来你遇到了用Three.js绘制3D多边形平面时的三角化bug——当前的faceToTriangles用的是扇形三角化(以第一个点为中心连接后续点),这种方法只对凸多边形有效。如果你的点数组是凹多边形,或者点的排列顺序不是连续的顺时针/逆时针环绕,就会出现三角形超出图形范围的问题。
下面给你两种靠谱的解决方案,包括修复三角化逻辑,以及不需要转三角的直接绘制方法:
方案1:通用多边形三角化(支持凸/凹多边形)
如果还是想用Mesh来绘制填充平面,建议改用耳切法(Ear Clipping),这是处理任意简单多边形(无交叉边)的标准三角化算法。你可以借助Three.js自带的工具类,这里提供一个基于THREE.ShapeUtils.triangulateShape的适配方案(该方法原本是处理2D图形的,我们需要先把3D点投影到平面再转换回去):
function faceToTriangles(VerticesArray) { // 确保输入是THREE.Vector3数组 if (!VerticesArray.every(v => v instanceof THREE.Vector3)) { throw new Error("VerticesArray must contain THREE.Vector3 instances"); } // 步骤1:计算多边形所在平面的法向量,选择最优投影轴 const v0 = VerticesArray[0]; const v1 = VerticesArray[1]; const v2 = VerticesArray[2]; const edge1 = new THREE.Vector3().subVectors(v1, v0); const edge2 = new THREE.Vector3().subVectors(v2, v0); const normal = new THREE.Vector3().crossVectors(edge1, edge2).normalize(); // 选择法向量分量最小的轴投影,减少变形 let projAxis = 'x'; if (Math.abs(normal.y) < Math.abs(normal.x) && Math.abs(normal.y) < Math.abs(normal.z)) { projAxis = 'y'; } else if (Math.abs(normal.z) < Math.abs(normal.x) && Math.abs(normal.z) < Math.abs(normal.y)) { projAxis = 'z'; } // 步骤2:将3D点投影为2D坐标 const points2D = VerticesArray.map(v => { switch(projAxis) { case 'x': return new THREE.Vector2(v.y, v.z); case 'y': return new THREE.Vector2(v.x, v.z); default: return new THREE.Vector2(v.x, v.y); } }); // 步骤3:用耳切法三角化2D多边形 const triangleIndices = THREE.ShapeUtils.triangulateShape(points2D, []); // 步骤4:将三角化结果映射回3D坐标 const vertices = new Float32Array(triangleIndices.length * 9); let idx = 0; triangleIndices.forEach(tri => { const p0 = VerticesArray[tri[0]]; const p1 = VerticesArray[tri[1]]; const p2 = VerticesArray[tri[2]]; vertices[idx++] = p0.x; vertices[idx++] = p0.y; vertices[idx++] = p0.z; vertices[idx++] = p1.x; vertices[idx++] = p1.y; vertices[idx++] = p1.z; vertices[idx++] = p2.x; vertices[idx++] = p2.y; vertices[idx++] = p2.z; }); return vertices; }
方案2:不转三角,直接绘制多边形(更简单)
如果你的需求只是绘制填充平面+可选轮廓,Three.js在新版本中提供了更直接的方式,完全不需要手动三角化:
方式A:用PolygonGeometry(Three.js r158及以上版本)
新版本新增的PolygonGeometry可以直接接收共面的3D点数组,自动处理三角化:
// 注意:VerticesArray必须是THREE.Vector3数组,且点按顺时针/逆时针顺序排列、共面 const geometry = new THREE.PolygonGeometry(VerticesArray); const material = new THREE.MeshLambertMaterial({ color: 'red', side: THREE.DoubleSide // 双面渲染,避免背面看不到 }); const mesh = new THREE.Mesh(geometry, material); scene.add(mesh);
方式B:同时绘制填充平面和轮廓线
如果需要显示多边形的轮廓,可以结合Mesh和LineLoop:
// 1. 绘制填充平面 const fillGeometry = new THREE.PolygonGeometry(VerticesArray); const fillMaterial = new THREE.MeshLambertMaterial({ color: 'red', side: THREE.DoubleSide }); const fillMesh = new THREE.Mesh(fillGeometry, fillMaterial); // 2. 绘制轮廓线 const lineGeometry = new THREE.BufferGeometry().setFromPoints(VerticesArray); const lineMaterial = new THREE.LineBasicMaterial({ color: '#000', linewidth: 2 }); const lineLoop = new THREE.LineLoop(lineGeometry, lineMaterial); // 3. 把两个对象合并到一个组中 const polygonGroup = new THREE.Group(); polygonGroup.add(fillMesh); polygonGroup.add(lineLoop); scene.add(polygonGroup);
关键注意事项
- 你的3D点必须共面:如果点不在同一个平面上,任何方法都无法生成完美的平面图形,会出现扭曲。
- 点的顺序必须是连续的顺时针或逆时针环绕:如果点的排列顺序混乱,三角化或
PolygonGeometry都会生成错误的图形。 - 如果使用旧版本Three.js没有
PolygonGeometry,可以用第三方三角化库比如earcut(轻量且高效),或者手动实现耳切法。
内容的提问来源于stack exchange,提问作者Przemysław Niemiec
相关产品推荐
相关产品推荐

