Skip to content

159. 粒子实战:火树银花

Published:

不知道大家小时候有没有放过这种烟花:

image.png

如果要在 3D 场景里实现这种烟花效果,如何做呢?

这显然是一种粒子效果,可以用刚学的 three.quarks 来做。

这种看起来像是用锥形粒子发射器:

2025-06-01 16.05.08.gif

但如何让它下落呢?

three.quarks 支持设置重力,这样,粒子就会落到地上了。

我们来写一下:

npx create-vite three-quarks-fireworks

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 directionLight = new THREE.DirectionalLight(0xffffff);
directionLight.position.set(500, 600, 800);
scene.add(directionLight);

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

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(500, 600, 800);
camera.lookAt(0, 0, 0);

const renderer = new THREE.WebGLRenderer();
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;
}

安装下粒子效果库:

npm install --save three.quarks

在 mesh.js 初始化下 three.quarks

import * as THREE from "three";
import { BatchedParticleRenderer,ConeEmitter, ConstantValue, IntervalValue, ParticleSystem, RandomColor } 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 IntervalValue(0, 10),
    startSpeed: new IntervalValue(0, 1000),
    startSize: new IntervalValue(0, 10),
    startColor: new RandomColor(
        new THREE.Vector4(1, 0.7, 0, 1),
        new THREE.Vector4(1, 1, 1, 1)
    ),
    emissionOverTime: new ConstantValue(1000),
    shape: new ConeEmitter({
        radius: 0,
        arc: Math.PI * 2,
    }),
    material: new THREE.MeshBasicMaterial({
        map: texture,
        transparent: true,
        side: THREE.DoubleSide
    })
});

group.add(particles.emitter);

batchRenderer.addSystem(particles);

export {
    batchRenderer
}

export default group;

用的 ConeEmitter 锥形粒子发射器。

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-01 21.22.46.gif

方向不对,我们旋转一下发射器:

image.png

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

2025-06-01 21.26.16.gif

现在方向就对了。

我们给它加一下重力:

image.png

particles.addBehavior(
    new ApplyForce(
        new THREE.Vector3(0, 0, -1),
        new ConstantValue(1000)
    )
)

ApplyForce 是在某个方向上添加重力。

本来应该是 y 方向,但粒子是旋转过来的,按照原来的方向应该是 z 轴方向的重力。

第一个参数是方向,第二个参数是重力大小。

2025-06-01 21.32.32.gif

现在,粒子发射出去之后会落下来。

只不过,小圆点的粒子看起来不太清晰。

我们可以把渲染模式改为渲染轨迹:

image.png

renderMode: RenderMode.Trail,
rendererEmitterSettings: {
    startLength: new ConstantValue(5)
},

渲染模式改为 Trail 渲染轨迹,开始渲染长度为 5 个粒子。

加上这个再看下:

2025-06-01 21.37.30.gif

现在渲染的就是长条状的轨迹了。

然后碰撞到地面的时候,要反弹,这个 three.quarks 也实现了:

image.png

particles.addBehavior(new ApplyCollision({
    resolve(pos, normal) {
        if(pos.z < 0) {
            normal.set(0, 0, 1);
            return true;
        } else {
            return false;
        }
    }
}, 0.5));

ApplyCollision 的 resolve 函数里,判断下当前位置的 z 是否到了 0, 到了就反弹,设置反弹方向为 z 轴正方向。否则返回 false,不反弹。

0.5 是反弹的高度,为之前的 0.5

2025-06-01 21.41.00.gif

现在粒子落到地面就会反弹了。

此外,现在速度始终不变,应该先快后慢。

用我们上节学的知识来自定义下速度变化:

image.png

particles.addBehavior(
    new SpeedOverLife(new PiecewiseBezier(
        [
            [
                new Bezier(1.5 ,0.8, 0.4, 0),
                0
            ]
        ]
    ))
);

最开始速度是 1.5 倍,最后变到 0。

2025-06-01 21.43.34.gif

现在,每个粒子都是最开始速度快,后来速度慢,更真实了一点。

去掉 axesHelper,我们看下最终效果:

2025-06-01 21.44.34.gif

这样,烟花效果就完成了。

案例代码上传了小册仓库

总结

这节我们基于 three.quarks 实现了烟花的粒子效果。

首先用锥形粒子发射器发射粒子,用 Trail 的渲染模式来渲染轨迹。

设置重力让粒子下落,然后设置反弹,当粒子落到地面的时候反弹。

此外,我们还自定义了粒子的运动过程中的速度变化。

这样,一个较为真实的烟花粒子效果就完成了。

评论