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

如果要在 3D 场景里实现这种烟花效果,如何做呢?
这显然是一种粒子效果,可以用刚学的 three.quarks 来做。
这种看起来像是用锥形粒子发射器:

但如何让它下落呢?
three.quarks 支持设置重力,这样,粒子就会落到地上了。
我们来写一下:
npx create-vite three-quarks-fireworks

进入项目,安装依赖:
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 从这里下载:


在 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


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

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

现在方向就对了。
我们给它加一下重力:

particles.addBehavior(
new ApplyForce(
new THREE.Vector3(0, 0, -1),
new ConstantValue(1000)
)
)
ApplyForce 是在某个方向上添加重力。
本来应该是 y 方向,但粒子是旋转过来的,按照原来的方向应该是 z 轴方向的重力。
第一个参数是方向,第二个参数是重力大小。

现在,粒子发射出去之后会落下来。
只不过,小圆点的粒子看起来不太清晰。
我们可以把渲染模式改为渲染轨迹:

renderMode: RenderMode.Trail,
rendererEmitterSettings: {
startLength: new ConstantValue(5)
},
渲染模式改为 Trail 渲染轨迹,开始渲染长度为 5 个粒子。
加上这个再看下:

现在渲染的就是长条状的轨迹了。
然后碰撞到地面的时候,要反弹,这个 three.quarks 也实现了:

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

现在粒子落到地面就会反弹了。
此外,现在速度始终不变,应该先快后慢。
用我们上节学的知识来自定义下速度变化:

particles.addBehavior(
new SpeedOverLife(new PiecewiseBezier(
[
[
new Bezier(1.5 ,0.8, 0.4, 0),
0
]
]
))
);
最开始速度是 1.5 倍,最后变到 0。

现在,每个粒子都是最开始速度快,后来速度慢,更真实了一点。
去掉 axesHelper,我们看下最终效果:

这样,烟花效果就完成了。
案例代码上传了小册仓库
总结
这节我们基于 three.quarks 实现了烟花的粒子效果。
首先用锥形粒子发射器发射粒子,用 Trail 的渲染模式来渲染轨迹。
设置重力让粒子下落,然后设置反弹,当粒子落到地面的时候反弹。
此外,我们还自定义了粒子的运动过程中的速度变化。
这样,一个较为真实的烟花粒子效果就完成了。