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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 07:02:31