关键限制是HTML5不提供地形建模能力,需用WebGL(如Three.js)运行时解析高度图像素生成BufferGeometry;核心是将PNG灰度值0–255映射为Y坐标,须确保无跨域、加载完成、无缩放失真,推荐2ⁿ+1尺寸。

WebGL 场景中用 heightmap 生成地形的关键限制
HTML5 本身不提供地形建模能力,所谓“HTML5 建模”实际依赖 WebGL(通常通过 Three.js 等库)加载高度图(heightmap)并构造顶点网格。浏览器无法直接执行离线建模操作,所有地形必须在运行时由 JavaScript 解析图像像素、生成 BufferGeometry 并赋予材质。
怎么把一张 PNG 高度图转成 Three.js 可用的地形网格
核心是读取灰度图每个像素的亮度值(0–255),映射为顶点 Y 坐标。常见错误是直接用 ImageBitmap 或 canvas.getContext('2d') 读取后忽略图像是否跨域、是否已加载完成、是否被缩放抗锯齿干扰。
- 高度图必须是纯灰度 PNG(无 alpha 通道干扰),推荐尺寸为 2n+1(如 513×513),便于细分和 LOD
- 用
fetch+createImageBitmap加载,避免 canvas drawImage 的自动缩放失真 - 用
getImageData读取像素后,遍历data数组,每 4 字节取data[i](R 通道即灰度)作为高度源 - 生成顶点时注意:X/Z 坐标按网格索引等距铺开,Y =
heightScale * (grayValue / 255),heightScale决定地形起伏幅度
const img = await createImageBitmap(response);
const canvas = new OffscreenCanvas(img.width, img.height);
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0);
const data = ctx.getImageData(0, 0, img.width, img.height).data;
const geometry = new THREE.PlaneGeometry(width, depth, img.width - 1, img.height - 1);
const vertices = geometry.attributes.position.array;
for (let i = 0; i < data.length; i += 4) {
const x = (i / 4) % img.width;
const z = Math.floor((i / 4) / img.width);
const h = (data[i] / 255) * heightScale; // 仅用 R 通道
const idx = (z * (img.width) + x) * 3;
vertices[idx + 1] = h; // Y 分量
}为什么用 THREE.PlaneGeometry 而不是 THREE.BoxGeometry 或手写顶点
BoxGeometry 是封闭立方体,无法表达连续起伏表面;手写完整顶点+索引虽灵活但极易出错(法线、UV、索引顺序)。而 PlaneGeometry 提供规整的 XY 网格拓扑,只需修改 Y 值即可复用其 UV 和法线计算逻辑,且内置 rotateX(Math.PI / 2) 可快速让平面朝上。
- 务必调用
geometry.computeVertexNormals(),否则光照会全黑或异常 - 若需平滑过渡,可在修改顶点后调用
geometry.mergeVertices()(对大图慎用,性能下降明显) - UV 默认从 (0,0) 到 (1,1),若高度图需重复贴图,应手动重写
geometry.attributes.uv.array
移动端和低配设备上高度图地形卡顿的根源
问题不在“画质”,而在顶点数爆炸:513×513 网格产生约 26 万顶点,WebGL 渲染管线需逐顶点计算光照+变换,低端 GPU 显存带宽不足,requestAnimationFrame 掉帧严重。
立即学习“前端免费学习笔记(深入)”;
- 真实项目中应预设 LOD 层级:远距离用 129×129 网格,中距离 257×257,近处才用原图精度
- 禁用
antialias: true(尤其在 iOS Safari),它会让WebGLRenderingContext内部多一倍渲染路径 - 高度图纹理本身建议压缩为
KTX2格式并启用EXT_texture_compression_bptc,减小 GPU 显存占用
真正难的不是“怎么画出来”,而是怎么让同一张 heightmap 在 iPhone SE 和 RTX 4090 上都保持 60fps——这取决于你是否愿意为不同设备动态切换几何精度、是否接受用 WebAssembly 预处理高度图而非 JS 实时解析。











