Skip to content

164. 几何体粒子和噪声

Published:

前面我们渲染粒子都是默认的 Sprite,而 Sprite 有个特点是永远正对摄像头,所以只能看到一个面。

但有时候我们需要让粒子翻转到背面。

比如漫天飞舞的树叶,它会做 360 度的旋转。

这种情况用 Sprite 渲染粒子就不行了。

能不能用网格模型 Mesh 来作为粒子呢?

可以的,three.quarks 支持 mesh 模式。

而且漫天飞舞的树叶除了翻转外,位置也会做随机的变化,这需要噪声,three.quarks 也做了内置的支持。

我们来试一下:

npx create-vite three-quarks-mesh-noise

image.png

进入项目,安装依赖:

npm install
npm install --save three
npm 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 helper = new THREE.AxesHelper(1000);
// scene.add(helper);

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

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

const renderer = new THREE.WebGLRenderer();
renderer.setSize(width, height)

const clock = new THREE.Clock();
function render() {
    const delta = clock.getDelta();
    renderer.render(scene, camera);
    requestAnimationFrame(render);
}
render();

document.body.append(renderer.domElement);

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

创建 Scene、Camera、Renderer。

相机在 0,0,20 的位置,正对 XY 平面。

改下 style.css

body {
  margin: 0;
}

安装下粒子效果库:

npm install --save three.quarks

在 mesh.js 初始化下 three.quarks

import * as THREE from "three";
import { ApplySequences, BatchedParticleRenderer,ConeEmitter, ConstantValue, DonutEmitter, GridEmitter, IntervalValue, ParticleSystem, RandomColor, RectangleEmitter, TextureSequencer, Vector3, Vector4 } from "three.quarks";

const group = new THREE.Group();

const batchRenderer = new BatchedParticleRenderer();
group.add(batchRenderer);

const loader = new THREE.TextureLoader();
const texture = loader.load('./point.png');

const particles = new ParticleSystem({
    duration: 20,
    looping: true,
    startLife: new ConstantValue(20),
    startSpeed: new IntervalValue(100, 200),
    startSize: new IntervalValue(10, 20),
    startColor: new RandomColor(
        new Vector4(1, 1, 1, 1),
        new Vector4(1, 0.7, 0, 1)
    ),
    emissionOverTime: new IntervalValue(1000, 2000),
    shape: new GridEmitter({
        width: 500, 
        height: 500, 
        column: 10, 
        row: 10
    }),
    material: new THREE.MeshBasicMaterial({
        map: texture,
        transparent: true
    })
});

group.add(particles.emitter);

batchRenderer.addSystem(particles);

export {
    batchRenderer
}

export default group;

用网格粒子发射器 GridEmitter。

10 * 10 的发射粒子。

point.png 从这里下载:

https://github.com/QuarkGluonPlasma/threejs-course-code/blob/main/all-shape-three-quarks/public/point.png

image.png

image.png

在 main.js 引入:

import { batchRenderer } from './mesh.js';
const clock = new THREE.Clock();
function render() {
    const delta = clock.getDelta();
    renderer.render(scene, camera);
    requestAnimationFrame(render);

    if(batchRenderer) {
      batchRenderer.update(delta);
    }
}
render();

跑一下,看下效果:

npm run dev

image.png

2025-06-05 09.24.00.gif

换张图片:

leaf.png

image.png

size 改大,发射的数量改小:

image.png

看下效果:

2025-06-05 09.30.19.gif

因为是 Sprite,所以无论怎么旋转,树叶都是正对屏幕的。

我们换成 mesh 试一下:

image.png

image.png

指定 renderMode 是渲染网格模型,用的 geometry 是 PlaneGeometry,开启双面渲染

const geometry = new THREE.PlaneGeometry(10, 10);
instancingGeometry: geometry,
startSize: new IntervalValue(5, 10),
renderMode: RenderMode.Mesh,
material: new THREE.MeshBasicMaterial({
    map: texture,
    transparent: true,
    side: THREE.DoubleSide
})

2025-06-05 09.36.03.gif

现在就不再是永远正对摄像头了,因为现在渲染的是网格模型。

如何让粒子在生命周期内旋转呢?

显然是要做行为控制:

image.png

image.png

最开始指定一个随机旋转角度。

然后在生命周期中,绕某个轴来做随机角度的旋转。

startRotation: new IntervalValue(Math.PI / 6, Math.PI / 3),
particles.addBehavior(
    new Rotation3DOverLife(
        new AxisAngleGenerator(
            new THREE.Vector3(0, 1, 1).normalize(),
            new IntervalValue(Math.PI / 10, Math.PI)
        )
    )
)

看下效果:

2025-06-05 09.46.25.gif

现在就有随风飞舞的树叶的感觉了。

现在运动位置太规律了,加点噪声:

image.png

particles.addBehavior(
    new Noise(
        new ConstantValue(0.5),
        new IntervalValue(50, 100)
    )
)

第一个参数是改变频率,第二个参数是改变力度。

2025-06-05 09.51.07.gif

加大一些,效果更明显:

image.png

2025-06-05 09.52.22.gif

然后调解下参数:

网格宽高调大:

image.png

width: 1000, 
height: 1000, 

让 emitter 旋转一下,从上往下落:

image.png

particles.emitter.rotateX(Math.PI / 2);

然后调下相机角度:

image.png

camera.position.set(0, -500, 200);
camera.lookAt(0, -500, 0);

去掉 OrbitControls,不然 lookAt 会被重置:

image.png

这里我还顺便改了下颜色值是红和黄之间随机:

image.png

startColor: new RandomColor(
    new Vector4(1, 0, 0, 1),
    new Vector4(1, 1, 0, 1)
),

看下效果:

2025-06-05 10.03.41.gif

现在就有那种落叶纷飞的感觉了。

不过噪声移动频率还是有点大,改小一点:

image.png

频率改小,力度改大。

2025-06-05 10.06.16.gif

现在效果更好一点。

案例代码上传了小册仓库

总结

这节我们学了如何渲染网格模型粒子,以及噪声效果。

默认粒子是 Sprite 渲染的,永远正对屏幕,不能看到背面。

有时候需要切换成 Mesh 渲染,只要指定 geometry 然后修改 renderMode 就可以了。

首先,我们用 GridEmitter 来发射树叶粒子。

用 startRotation 修改了树叶开始旋转角度,Rotation3DOverLife 修改了树叶运动过程中的旋转角度。

然后用 Noise 给运动过程加了一些连续随机的位置移动。

这样就有树叶上下纷飞的效果了。

评论