如何从带高度图的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高度场即可。
常见误区说明
- 顶点顺序混淆:Three.js PlaneGeometry默认是行优先,Rapier要求列主序,必须转换,否则高度场会出现错位。
- 位移贴图的坑:Shader中的位移不会修改BufferGeometry的position,直接读取y值会得到原始平面的0高度。
- 坐标对齐:确保Rapier高度场的缩放、位置、旋转与Three.js平面对齐,否则会出现碰撞检测偏移。
内容的提问来源于stack exchange,提问作者SGTMM
相关产品推荐
相关产品推荐

