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

如何从带高度图的Three.js平面创建Rapier.js高度场?

从Three.js高度平面生成Rapier.js高度场

核心问题拆解

你遇到的本质问题是Three.js PlaneGeometry顶点的行优先排列,和Rapier高度场要求的列主序数组不匹配,加上如果用了位移贴图(而非直接修改顶点位置),直接读取BufferGeometry的position会得不到真实高度。以下是分步解决方案:


情况1:已修改PlaneGeometry的顶点位置(直接设置高度)

如果你的高度是直接写入BufferGeometry的position属性(比如通过修改顶点y值),按以下步骤处理:

1. 提取原始高度数组(行优先)

Three.js的PlaneGeometry顶点是行优先排列的:先遍历X轴所有顶点(从左到右),再推进Z轴一格,重复遍历X轴。我们先提取每个顶点的y高度:

// 获取平面的分段参数
const { widthSegments: wSeg, heightSegments: hSeg } = planeGeometry.parameters;
const vertexCountX = wSeg + 1; // X方向顶点数
const vertexCountZ = hSeg + 1; // Z方向顶点数

// 提取顶点位置数组
const positions = planeGeometry.attributes.position.array;
const rowMajorHeights = [];

// 遍历每个顶点,取出y值(高度)
for (let i = 0; i < positions.length; i += 3) {
  rowMajorHeights.push(positions[i + 1]);
}

2. 转换为Rapier需要的列主序数组

Rapier高度场要求数组按列主序存储:先遍历Z轴所有顶点(从前到后),再推进X轴一格,重复遍历Z轴。转换逻辑如下:

const columnMajorHeights = new Float32Array(vertexCountX * vertexCountZ);

for (let x = 0; x < vertexCountX; x++) {
  for (let z = 0; z < vertexCountZ; z++) {
    // 行优先索引:Z行 * X方向顶点数 + X列
    const rowIndex = z * vertexCountX + x;
    // 列主序索引:X列 * Z方向顶点数 + Z行
    const colIndex = x * vertexCountZ + z;
    columnMajorHeights[colIndex] = rowMajorHeights[rowIndex];
  }
}

3. 创建Rapier高度场并对齐Three.js平面

根据转换后的数组创建高度场,同时缩放和定位以匹配Three.js平面的尺寸:

// 创建高度场描述符
const heightfieldDesc = Rapier.HeightfieldDesc.new(
  vertexCountX,
  vertexCountZ,
  columnMajorHeights,
  false // 若Three.js与Rapier的Y轴方向相反,设为true翻转高度
);

// 在Rapier世界中创建碰撞体
const heightfield = world.createCollider(heightfieldDesc);

// 缩放高度场以匹配Three.js平面尺寸
const planeWidth = planeGeometry.parameters.width;
const planeDepth = planeGeometry.parameters.height;
heightfield.setScale(new Rapier.Vector3(
  planeWidth / (vertexCountX - 1), // X方向每个网格单元的长度
  1, // 高度缩放(若已在高度值中处理强度,此处设为1)
  planeDepth / (vertexCountZ - 1) // Z方向每个网格单元的长度
));

// 对齐平面的位置和旋转(假设平面已旋转为XZ平面,即rotation.x = -Math.PI/2)
heightfield.setTranslation(new Rapier.Vector3(plane.position.x, plane.position.y, plane.position.z));
heightfield.setRotation(plane.quaternion);

情况2:用位移贴图(DisplacementMap)实现高度

如果你的高度是通过ShaderMaterial的位移贴图实时计算的,BufferGeometry的position并没有被修改,此时需要手动从纹理采样高度:

1. 从位移贴图中采样高度值

const displacementMap = plane.material.displacementMap; // THREE.Texture
const displacementScale = plane.material.displacementScale; // 位移强度
const uvs = planeGeometry.attributes.uv.array;

const rowMajorHeights = [];
const imgData = displacementMap.image.data;
const imgWidth = displacementMap.image.width;
const imgHeight = displacementMap.image.height;

// 遍历每个顶点的UV坐标,采样纹理获取高度
for (let i = 0; i < uvs.length; i += 2) {
  const u = uvs[i];
  const v = 1 - uvs[i + 1]; // Three.js纹理的V轴是翻转的,需修正
  // 计算纹理像素索引(灰度图取R通道)
  const pixelX = Math.floor(u * (imgWidth - 1));
  const pixelY = Math.floor(v * (imgHeight - 1));
  const pixelIndex = (pixelY * imgWidth + pixelX) * 4;
  // 归一化高度值并乘以强度
  const height = (imgData[pixelIndex] / 255) * displacementScale;
  rowMajorHeights.push(height);
}

2. 后续步骤

得到rowMajorHeights后,重复情况1中的步骤2和3,转换为列主序数组并创建Rapier高度场即可。


常见误区说明

  1. 顶点顺序混淆:Three.js PlaneGeometry默认是行优先,Rapier要求列主序,必须转换,否则高度场会出现错位。
  2. 位移贴图的坑:Shader中的位移不会修改BufferGeometry的position,直接读取y值会得到原始平面的0高度。
  3. 坐标对齐:确保Rapier高度场的缩放、位置、旋转与Three.js平面对齐,否则会出现碰撞检测偏移。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 00:07:06