1. 烟花绽放效果的实现原理
烟花效果的本质是粒子系统在三维空间中的动态表现。每个烟花粒子都遵循经典物理规律:初始速度、重力加速度、空气阻力以及生命周期衰减。现代图形学中,实现烟花效果主要依赖以下核心算法:
- 粒子发射器:控制烟花弹的发射位置、角度和初始速度。通常采用圆锥形发射区域,通过随机函数在锥角范围内生成发射方向
- 粒子动力学:每个粒子需计算位置(P)、速度(V)、加速度(A)的实时变化:
code复制V(t) = V₀ + A*t P(t) = P₀ + V₀*t + 0.5*A*t² - 颜色衰减曲线:使用HSL色彩空间实现颜色渐变,比RGB空间更符合视觉规律:
glsl复制vec3 hsl2rgb(vec3 c) { vec3 rgb = clamp(abs(mod(c.x*6.0+vec3(0.0,4.0,2.0),6.0)-3.0)-1.0,0.0,1.0); return c.z + c.y * (rgb-0.5)*(1.0-abs(2.0*c.z-1.0)); }
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. WebGL实现方案
2.1 基础架构设计
采用Three.js框架构建场景:
javascript复制const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth/window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer({ antialias: true });
// 粒子系统容器
const particleSystems = [];
2.2 粒子系统实现
创建可复用的烟花粒子类:
javascript复制class FireworkParticle {
constructor(position, velocity, color, size, life) {
this.mesh = new THREE.Mesh(
new THREE.SphereGeometry(size, 8, 8),
new THREE.MeshBasicMaterial({ color, transparent: true })
);
this.velocity = velocity.clone();
this.life = life;
this.currentLife = 0;
scene.add(this.mesh);
}
update(deltaTime) {
this.velocity.y -= 9.8 * deltaTime; // 重力加速度
this.mesh.position.add(this.velocity.clone().multiplyScalar(deltaTime));
this.currentLife += deltaTime;
this.mesh.material.opacity = 1 - (this.currentLife / this.life);
return this.currentLife < this.life;
}
}
3. 性能优化技巧
3.1 实例化渲染
当需要渲染大量粒子时(超过5000个),应使用实例化缓冲几何体:
javascript复制const COUNT = 10000;
const geometry = new THREE.InstancedBufferGeometry();
geometry.instanceCount = COUNT;
// 位置属性
const positions = new THREE.BufferAttribute(new Float32Array(COUNT * 3), 3);
// 颜色属性
const colors = new THREE.BufferAttribute(new Float32Array(COUNT * 3), 3);
3.2 粒子池技术
预先创建对象池避免频繁GC:
javascript复制class ParticlePool {
constructor(size) {
this.pool = Array(size).fill().map(() => new Particle());
this.index = 0;
}
get() {
const particle = this.pool[this.index];
this.index = (this.index + 1) % this.pool.length;
return particle.reset();
}
}
4. 高级视觉效果实现
4.1 轨迹光效
使用着色器实现粒子拖尾效果:
glsl复制// 片段着色器
varying float vLife;
void main() {
gl_FragColor = vec4(color.rgb, smoothstep(1.0, 0.0, vLife));
}
4.2 冲击波效果
通过后期处理添加屏幕空间扭曲:
javascript复制const composer = new EffectComposer(renderer);
composer.addPass(new RenderPass(scene, camera));
const shockwavePass = new ShaderPass(ShockwaveShader);
composer.addPass(shockwavePass);
5. 交互设计实践
5.1 鼠标触发实现
javascript复制window.addEventListener('click', (event) => {
const mouse = new THREE.Vector2(
(event.clientX / window.innerWidth) * 2 - 1,
-(event.clientY / window.innerHeight) * 2 + 1
);
const raycaster = new THREE.Raycaster();
raycaster.setFromCamera(mouse, camera);
const explosionPos = raycaster.ray.at(10);
createExplosion(explosionPos);
});
5.2 多点触控适配
javascript复制window.addEventListener('touchstart', (event) => {
Array.from(event.changedTouches).forEach(touch => {
const touchPos = new THREE.Vector2(
(touch.clientX / window.innerWidth) * 2 - 1,
-(touch.clientY / window.innerHeight) * 2 + 1
);
// 触发烟花逻辑...
});
});
6. 音效同步方案
6.1 Web Audio API集成
javascript复制const audioContext = new (window.AudioContext || window.webkitAudioContext)();
const gainNode = audioContext.createGain();
gainNode.gain.value = 0.3;
gainNode.connect(audioContext.destination);
function playExplosionSound() {
const oscillator = audioContext.createOscillator();
oscillator.type = 'sawtooth';
oscillator.frequency.setValueAtTime(100, audioContext.currentTime);
oscillator.frequency.exponentialRampToValueAtTime(20, audioContext.currentTime + 0.5);
oscillator.connect(gainNode);
oscillator.start();
oscillator.stop(audioContext.currentTime + 0.5);
}
7. 移动端适配要点
7.1 性能分级策略
javascript复制const qualityLevel = navigator.hardwareConcurrency > 2 ? 'high' :
(/Android|iPhone|iPad/i.test(navigator.userAgent) ? 'mobile' : 'low');
const settings = {
high: { particleCount: 5000, textureSize: 2048 },
mobile: { particleCount: 1000, textureSize: 512 },
low: { particleCount: 500, textureSize: 256 }
};
7.2 触觉反馈增强
javascript复制if ('vibrate' in navigator) {
navigator.vibrate([50, 50, 50]); // 短震动序列
}
8. 调试与性能监控
8.1 Stats.js集成
javascript复制const stats = new Stats();
stats.showPanel(0); // 0: fps, 1: ms, 2: mb
document.body.appendChild(stats.dom);
function animate() {
stats.begin();
// 渲染逻辑...
stats.end();
requestAnimationFrame(animate);
}
8.2 内存泄漏检测
javascript复制const particleTracker = new WeakMap();
function createParticle() {
const particle = new Particle();
particleTracker.set(particle, new Error().stack);
return particle;
}
9. 创意扩展方向
9.1 主题皮肤系统
javascript复制const themes = {
classic: {
colors: ['#ff0000', '#ffff00', '#00ff00'],
trailLength: 1.5
},
galaxy: {
colors: ['#9b59b6', '#3498db', '#2ecc71'],
trailLength: 2.0
}
};
9.2 粒子物理交互
javascript复制particles.forEach(p1 => {
particles.forEach(p2 => {
const distance = p1.position.distanceTo(p2.position);
if (distance < 5) {
const force = p1.position.clone()
.sub(p2.position)
.normalize()
.multiplyScalar(10 / (distance * distance));
p1.velocity.add(force);
}
});
});
10. 完整实现示例
以下是一个可直接运行的完整示例结构:
html复制<!DOCTYPE html>
<html>
<head>
<title>烟花模拟器</title>
<style>
body { margin: 0; overflow: hidden; }
canvas { display: block; }
</style>
</head>
<body>
<script src="https://cdn.jsdelivr.net/npm/three@0.132.2/build/three.min.js"></script>
<script>
// 初始化代码...
class Firework {
constructor() {
this.particles = [];
this.initScene();
this.setupEvents();
this.animate();
}
// 其他方法实现...
}
new Firework();
</script>
</body>
</html>
在实现过程中,我发现控制粒子初始速度的随机分布对最终效果影响很大。经过多次测试,采用正态分布(高斯分布)生成随机速度比均匀分布更接近真实烟花效果:
javascript复制function gaussianRandom(mean=0, stdev=1) {
const u=1-Math.random(), v=Math.random();
const z=Math.sqrt(-2*Math.log(u))*Math.cos(2*Math.PI*v);
return z*stdev+mean;
}
另一个关键发现是:粒子尺寸应该与其生命周期相关联。短寿命的粒子应该更亮更大,这可以通过在片段着色器中动态计算尺寸来实现:
glsl复制gl_PointSize = 10.0 * (1.0 - lifeProgress) * visibility;
