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

基于THREE.js地形网格创建CANNON.js凸多面体物理碰撞体问题

THREE.js地形+Cannon.js物理碰撞问题解决

1. 解决updateNormals/updateEdges报错问题

Cannon.js新版本已经移除了shape.updateNormals()和shape.updateEdges()这两个方法,相关逻辑已经整合到ConvexPolyhedron的构造函数里了。直接删掉这两行代码即可,无需手动调用。

2. 解决性能极差的问题

别用凸多面体做地形碰撞! 地形属于凹形结构,凸多面体只能生成包裹整个地形的凸包,不仅碰撞判定不准确,还会因为面数过多直接导致性能崩盘。Cannon.js专门提供了Heightfield(高度场)类,完美适配这种网格地形,性能表现远优于凸多面体。

用Heightfield实现地形碰撞的代码示例

// 假设你的Three.js地形网格是terrainMesh
const widthSegments = terrainMesh.parameters.widthSegments;
const heightSegments = terrainMesh.parameters.heightSegments;
const unitSize = terrainMesh.parameters.width / widthSegments; // 每个网格单元的尺寸

// 从Three.js网格提取高度数据
const heights = [];
for (let z = 0; z <= heightSegments; z++) {
  const row = [];
  for (let x = 0; x <= widthSegments; x++) {
    // 按网格顺序取顶点高度(这里假设地形沿Y轴凸起,可按需调整轴向)
    const vertex = terrainMesh.geometry.vertices[x + z * (widthSegments + 1)];
    row.push(vertex.y);
  }
  heights.push(row);
}

// 创建Cannon高度场碰撞体
const heightfieldShape = new CANNON.Heightfield(heights, { elementSize: unitSize });
const terrainBody = new CANNON.Body({ mass: 0 }); // mass=0表示静态物体
terrainBody.addShape(heightfieldShape);

// 对齐Three.js地形的位置和旋转
terrainBody.position.copy(terrainMesh.position);
terrainBody.quaternion.copy(terrainMesh.quaternion);
world.addBody(terrainBody);

3. 解决法线顺序错误问题

这个提示说明你的面顶点顺序不符合右手定则,导致法线指向了地形内部。如果非要用凸多面体(极度不推荐用于地形),可以手动调整每个面的顶点顺序:

修正顶点顺序的代码

function fixFaceVertexOrder(geometry) {
  geometry.faces.forEach(face => {
    // 计算当前面的法线方向
    const v1 = geometry.vertices[face.a];
    const v2 = geometry.vertices[face.b];
    const v3 = geometry.vertices[face.c];
    const computedNormal = new THREE.Vector3()
      .subVectors(v2, v1)
      .cross(new THREE.Vector3().subVectors(v3, v1))
      .normalize();

    // 如果计算出的法线和原始法线方向相反,交换b、c顶点顺序
    if (computedNormal.dot(face.normal) < 0) {
      [face.b, face.c] = [face.c, face.b];
      face.normal.multiplyScalar(-1);
    }
  });
  geometry.computeFaceNormals();
}

// 处理地形网格的面顺序
fixFaceVertexOrder(terrainMesh.geometry);

// 转换为Cannon凸多面体(仅适合小凸形物体,地形别用)
const cannonVertices = terrainMesh.geometry.vertices.map(v => new CANNON.Vec3(v.x, v.y, v.z));
const cannonFaces = terrainMesh.geometry.faces.map(f => [f.a, f.b, f.c]);
const convexShape = new CANNON.ConvexPolyhedron({ vertices: cannonVertices, faces: cannonFaces });

相机物理碰撞实现

用胶囊体(Capsule)做相机的碰撞体,适配第一人称视角场景:

// 创建相机物理体
const cameraBody = new CANNON.Body({
  mass: 5, // 有质量才会受重力和碰撞影响
  shape: new CANNON.Capsule(0.5, 1.8), // 半径+高度,按需调整
});
cameraBody.position.copy(camera.position);
world.addBody(cameraBody);

// 每一帧同步相机和物理体的位置旋转
function animate() {
  requestAnimationFrame(animate);
  world.step(1/60); // 物理世界步进
  
  // 同步位置旋转
  camera.position.copy(cameraBody.position);
  camera.quaternion.copy(cameraBody.quaternion);
  
  // 你的渲染逻辑...
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 08:31:06