Skip to content

271. 采样器实战:漫山遍野的植被

Published:

前面我们绘制过林海雪原的场景:

2025-04-04 14.27.26.gif

在山坡上随机位置种树。

计算出随机的 x、y 坐标,然后拿到那个位置的顶点坐标。

学了表面采样器后,我们完全可以用表面采样器来做。

试一下:

npx create-vite hillside-flowers

image.png

进入项目,安装依赖:

pnpm install
pnpm install --save three
pnpm install --save-dev @types/three

先写下基础代码:

改下 src/main.js

import './style.css';
import * as THREE from 'three';
import {
    OrbitControls
} from 'three/addons/controls/OrbitControls.js';
import mesh from './mesh.js';

const scene = new THREE.Scene();

scene.add(mesh);

const light = new THREE.DirectionalLight(0xffffff);
light.position.set(500, 300, 600);
scene.add(light);

const light2 = new THREE.AmbientLight();
scene.add(light2);

const axesHelper = new THREE.AxesHelper(1000);
scene.add(axesHelper);

const width = window.innerWidth;
const height = window.innerHeight;

const camera = new THREE.PerspectiveCamera(60, width / height, 1, 10000);
camera.position.set(0, 500, 500);
camera.lookAt(0, 0, 0);

const renderer = new THREE.WebGLRenderer({
  antialias: true
});
renderer.setSize(width, height)

function render() {
    renderer.render(scene, camera);
    requestAnimationFrame(render);
}

render();

document.body.append(renderer.domElement);

const controls = new OrbitControls(camera, renderer.domElement);

创建 Scene、Light、Camera、Renderer

改下 style.css

body {
    margin: 0;
}

然后创建 mesh.js

import * as THREE from 'three';
import { SimplexNoise } from 'three/examples/jsm/Addons.js';

const simplex = new SimplexNoise();
const geometry = new THREE.PlaneGeometry(3000, 3000, 100, 100);

const positions = geometry.attributes.position;

for (let i = 0 ; i < positions.count; i ++) {
    const x = positions.getX(i);
    const y = positions.getY(i);

    const z = simplex.noise(x / 800, y / 800) * 50;

    positions.setZ(i, z);
}

const material = new THREE.MeshLambertMaterial({
    color: new THREE.Color('white'),
    wireframe: true
});

const mountainside = new THREE.Mesh(geometry, material);
mountainside.rotateX(- Math.PI / 2);

export default mountainside;

创建一个平面几何体,用噪音算法修改顶点的 z 坐标,生成一个山坡。

跑一下:

npm run dev

image.png

2025-10-26 18.46.08.gif

让坡度更陡峭一点:

image.png

2025-10-26 18.52.43.gif

通过多重噪音添加更多细节:

image.png

// 多重噪音层叠
// 第一层:大尺度地形 (山脉轮廓)
const noise1 = simplex.noise(x / 1200, y / 1200) * 150;

// 第二层:中等尺度地形 (丘陵)
const noise2 = simplex.noise(x / 600, y / 600) * 80;

// 第三层:小尺度地形 (细节)
const noise3 = simplex.noise(x / 300, y / 300) * 40;

// 第四层:微细节 (岩石纹理)
const noise4 = simplex.noise(x / 150, y / 150) * 20;

// 第五层:非常细微的细节
const noise5 = simplex.noise(x / 75, y / 75) * 10;

// 组合所有噪音层
const z = noise1 + noise2 + noise3 + noise4 + noise5;

用 5 层噪音可以做到非常细致的地形。

2025-10-26 18.58.20.gif

然后加一下颜色:

image.png

我们对顶点的 z 排下序,算出整体的高度差。

然后准备 5 个颜色来渐变。

image.png

根据 z 的百分比分别设置不同的颜色渐变。

设置到 geometry.attributes.color

// 计算高度范围用于颜色映射
const heightArr = [];
for (let i = 0; i < positions.count; i++) {
    heightArr.push(positions.getZ(i));
}
heightArr.sort();

const minHeight = heightArr[0];
const maxHeight = heightArr[heightArr.length - 1];
const height = maxHeight - minHeight;

// 设置颜色渐变 - 土黄色系山地
const colorsArr = [];
const color1 = new THREE.Color('#d2b48c');  // 低处 - 浅土黄 (较浅的土黄色)
const color2 = new THREE.Color('#cd853f');  // 中低处 - 土黄色 (经典土黄色)
const color3 = new THREE.Color('#b8860b');  // 中高处 - 暗金色 (深土黄色)
const color4 = new THREE.Color('#8b7355');  // 高处 - 深棕色 (最深土黄色)

for (let i = 0; i < positions.count; i++) {
    const percent = (positions.getZ(i) - minHeight) / height;
    let c;
    
    if (percent < 0.33) {
        // 低处:深绿到浅绿
        const localPercent = percent / 0.33;
        c = color1.clone().lerp(color2, localPercent);
    } else if (percent < 0.66) {
        // 中低处:浅绿到土黄
        const localPercent = (percent - 0.33) / 0.33;
        c = color2.clone().lerp(color3, localPercent);
    } else {
        // 中高处:土黄到灰色
        const localPercent = (percent - 0.66) / 0.34;
        c = color3.clone().lerp(color4, localPercent);
    }
    
    colorsArr.push(c.r, c.g, c.b); 
}

const colors = new Float32Array(colorsArr);
geometry.attributes.color = new THREE.BufferAttribute(colors, 3);

const material = new THREE.MeshLambertMaterial({
    vertexColors: true
});

看下效果:

2025-10-26 19.14.08.gif

这样看起来就有山坡的感觉了。

案例代码上传了小册仓库

总结

这节我们把山画了一下,相比之前,现在做的更细致了。

用了 5 层噪音来实现更真实的山坡效果。

用了 5 种颜色渐变来添加颜色。

这样的山坡会更加的真实。

下节加上植被。

评论