You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何优化THREE.js地形可视化:提升低分辨率高度图的分辨率与平滑度

嘿,针对你遇到的「低分辨率高度图生成太慢,想平滑处理并提升分辨率」的问题,这里有几个实用的方案,结合你的Three.js场景给你具体实现思路:

方案1:CPU端双线性插值(快速上手,无需修改核心逻辑)

这个方案的核心是保留原simulate函数生成低分辨率高度图,然后用双线性插值算法在CPU端生成高分辨率的顶点数据,既不用增加simulate的计算量,又能获得平滑的高分辨率地形。

具体修改步骤:

  1. 先定义高低分辨率的尺寸参数:
// 原低分辨率高度图的尺寸(你的simulate返回11x11数组,对应PlaneGeometry的10分段)
const lowResSize = 11;
// 目标高分辨率分段数(比如100x100,对应101x101个顶点)
const highResSegments = 100;
const highResSize = highResSegments + 1;
  1. 替换原PlaneGeometry为高分辨率版本:
// 原来的PlaneGeometry(80,80,10,10)替换成这个
const planeGeometry = new THREE.PlaneGeometry(80, 80, highResSegments, highResSegments);
  1. 实现双线性插值函数,用来从低分辨率图中计算高分辨率顶点的高度:
// 双线性插值:根据高分辨率坐标(x,y),从低分辨率heightMap中获取平滑后的高度
function bilinearInterpolation(heightMap, x, y) {
    // 把高分辨率坐标映射到低分辨率的范围[0, lowResSize-1]
    const fx = x * (lowResSize - 1) / (highResSize - 1);
    const fy = y * (lowResSize - 1) / (highResSize - 1);
    
    // 获取四个相邻的低分辨率顶点
    const x0 = Math.floor(fx);
    const x1 = Math.min(x0 + 1, lowResSize - 1);
    const y0 = Math.floor(fy);
    const y1 = Math.min(y0 + 1, lowResSize - 1);
    
    // 计算插值权重
    const wx = fx - x0;
    const wy = fy - y0;
    
    // 双线性插值计算最终高度
    const topRow = heightMap[y0][x0] * (1 - wx) + heightMap[y0][x1] * wx;
    const bottomRow = heightMap[y1][x0] * (1 - wx) + heightMap[y1][x1] * wx;
    return topRow * (1 - wy) + bottomRow * wy;
}
  1. 修改drawFrame函数,用插值后的高度更新高分辨率平面的顶点:
function drawFrame() {
    window.requestAnimationFrame(drawFrame);
    // 还是用原simulate生成低分辨率图,计算量不变
    const lowResHeightMap = simulate(T);
    
    // 遍历所有高分辨率顶点,用插值更新z值
    for (let y = 0; y < highResSize; y++) {
        for (let x = 0; x < highResSize; x++) {
            const vertexIndex = y * highResSize + x;
            plane.geometry.vertices[vertexIndex].z = bilinearInterpolation(lowResHeightMap, x, y);
        }
    }
    
    plane.geometry.verticesNeedUpdate = true;
    renderer.render(scene, camera);
    T += 0.01;
}

优点:

  • 完全保留原simulate逻辑,改动极小
  • 插值计算速度快,100x100分辨率下完全流畅
  • 视觉效果平滑,能有效消除低分辨率的锯齿感

方案2:GPU端纹理采样(性能最优,适合动态实时场景)

如果你的地形需要频繁动态更新,或者想要更高的分辨率(比如200x200以上),可以把低分辨率高度图转成纹理,用GPU的纹理插值功能来处理,性能会比CPU插值提升一个量级。

具体修改步骤:

  1. 实现一个函数,把低分辨率高度图转换成Three.js可用的纹理:
// 将低分辨率heightMap转成Canvas纹理
function createHeightMapTexture(heightMap) {
    const size = heightMap.length;
    const canvas = document.createElement('canvas');
    canvas.width = size;
    canvas.height = size;
    const ctx = canvas.getContext('2d');
    const imageData = ctx.createImageData(size, size);
    
    // 把高度值转换成灰度值(0-255)存入图像数据
    for (let y = 0; y < size; y++) {
        for (let x = 0; x < size; x++) {
            const index = (y * size + x) * 4;
            // 假设你的高度范围是-20到20,归一化到0-255(可根据实际调整)
            const grayValue = Math.floor((heightMap[y][x] + 20) / 40 * 255);
            imageData.data[index] = grayValue;
            imageData.data[index + 1] = grayValue;
            imageData.data[index + 2] = grayValue;
            imageData.data[index + 3] = 255; // 透明度
        }
    }
    
    ctx.putImageData(imageData, 0, 0);
    // 创建纹理并开启线性插值,实现平滑采样
    const texture = new THREE.CanvasTexture(canvas);
    texture.minFilter = THREE.LinearFilter;
    texture.magFilter = THREE.LinearFilter;
    return texture;
}
  1. 替换原MeshLambertMaterial为自定义ShaderMaterial,用顶点着色器读取纹理高度:
// 顶点着色器:采样高度图纹理,计算每个顶点的z值
const vertexShader = `
    uniform sampler2D heightMap;
    uniform float heightScale; // 控制高度幅度的缩放因子
    varying vec2 vUv;

    void main() {
        vUv = uv;
        // 采样纹理的灰度值,转换成高度
        float height = texture2D(heightMap, vUv).r * heightScale;
        vec3 pos = position;
        pos.z = height;
        gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
    }
`;

// 片元着色器:这里用简单的纯色,你也可以改成带光照的版本
const fragmentShader = `
    varying vec2 vUv;
    uniform vec3 color;

    void main() {
        gl_FragColor = vec4(color, 1.0);
    }
`;

// 创建高分辨率平面
const highResSegments = 200;
const planeGeometry = new THREE.PlaneGeometry(80, 80, highResSegments, highResSegments);

// 创建ShaderMaterial
const planeMaterial = new THREE.ShaderMaterial({
    uniforms: {
        heightMap: { value: null },
        heightScale: { value: 40 }, // 和之前的归一化范围对应,调整这个值控制高度
        color: { value: new THREE.Color(0x88aadd) }
    },
    vertexShader,
    fragmentShader,
    // wireframe: true // 可以打开看线框效果
});

// 创建平面并添加到场景
const plane = new THREE.Mesh(planeGeometry, planeMaterial);
plane.rotation.x = -0.4 * Math.PI;
scene.add(plane);
  1. 修改drawFrame函数,只需要更新纹理即可:
function drawFrame() {
    window.requestAnimationFrame(drawFrame);
    const lowResHeightMap = simulate(T);
    // 更新纹理,GPU会自动处理插值
    plane.material.uniforms.heightMap.value = createHeightMapTexture(lowResHeightMap);
    renderer.render(scene, camera);
    T += 0.01;
}

优点:

  • 利用GPU并行计算,高分辨率下依然流畅
  • 纹理插值是硬件级别的,性能远高于CPU计算
  • 可以扩展实现更复杂的着色效果(比如地形纹理、光照)

额外优化小技巧
  • 缓存纹理:如果simulate的输出不是每一帧都完全变化,可以缓存上一帧的纹理,只在高度图变化时更新,减少Canvas绘制的开销
  • 用BufferGeometry替代Geometry:BufferGeometry在处理大量顶点时性能更好,建议把PlaneGeometry换成PlaneBufferGeometry
  • Web Worker计算低分辨率图:如果simulate本身还是有点慢,可以把simulate放到Web Worker里运行,避免阻塞主线程的渲染

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

火山引擎 最新活动