真实烟花效果代码
Erstellt am: 29. Januar 2025
Erstellt am: 29. Januar 2025
</head> <body> <script src="https://cdn.jsdelivr.net/npm/[email protected]/build/three.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/[email protected]/examples/js/shaders/LuminosityHighPassShader.js"></script> <script src="https://cdn.jsdelivr.net/npm/[email protected]/examples/js/shaders/CopyShader.js"></script> <script src="https://cdn.jsdelivr.net/npm/[email protected]/examples/js/postprocessing/EffectComposer.js"></script> <script src="https://cdn.jsdelivr.net/npm/[email protected]/examples/js/postprocessing/BloomPass.js"></script> <script src="https://cdn.jsdelivr.net/npm/[email protected]/examples/js/postprocessing/ShaderPass.js"></script> <script src="https://cdn.jsdelivr.net/npm/[email protected]/examples/js/postprocessing/RenderPass.js"></script> <script src="https://cdn.jsdelivr.net/npm/[email protected]/examples/js/postprocessing/UnrealBloomPass.js"></script> <script> // 轨迹粒子几何体(全局共用) const trailGeometry = new THREE.SphereGeometry(0.2, 8, 8); const trailMaterial = new THREE.MeshBasicMaterial({ color: 0xFFA500, // 橙色轨迹 transparent: true, opacity: 0.7 });textcanvas { display: block; } </style>
</body> </html> 对比与真实的烟花秀,当前代码实现的效果并不真实。请优化一下。text// 场景初始化 const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); const renderer = new THREE.WebGLRenderer(); renderer.setSize(window.innerWidth, window.innerHeight); document.body.appendChild(renderer.domElement); camera.position.z = 50; // 在场景初始化后添加后期处理 const composer = new THREE.EffectComposer(renderer); const renderPass = new THREE.RenderPass(scene, camera); const bloomPass = new THREE.UnrealBloomPass( new THREE.Vector2(window.innerWidth, window.innerHeight), 1.5, // 强度 2, // 减小泛光半径 0.85 // 阈值 ); bloomPass.threshold = 0.1; // 增加泛光阈值,减少过度发光 bloomPass.strength = 0.5; // 减小泛光强度 bloomPass.radius = 0.5; composer.addPass(renderPass); composer.addPass(bloomPass); // 烟花粒子类 class Firework { constructor() { this.particles = []; // 增加更丰富的颜色变化 this.color = new THREE.Color().setHSL( Math.random(), // 色相 0.8 + Math.random() * 0.2, // 提高饱和度 0.4 + Math.random() * 0.3 // 降低亮度范围 ); this.exploded = false; this.dead = false; // 增加更多形状变化 this.baseSize = Math.random() * 1 + 0.5; this.explosionCount = Math.floor(Math.random() * 5) + 1; // 最多三次爆炸 this.explosionPattern = Math.floor(Math.random() * 5); // 0-4种爆炸模式 this.dragCoefficient = 0.97 + Math.random() * 0.03; // 空气阻力系数 // 使用 BufferGeometry 和 Points 来管理粒子 const geometry = new THREE.BufferGeometry(); const positions = []; const colors = []; const sizes = []; this.particleCount = 200; for (let i = 0; i < this.particleCount; i++) { positions.push(0, 0, 0); // 初始位置 colors.push(this.color.r, this.color.g, this.color.b); sizes.push(this.baseSize); } geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3)); geometry.setAttribute('customColor', new THREE.Float32BufferAttribute(colors, 3)); geometry.setAttribute('size', new THREE.Float32BufferAttribute(sizes, 1)); // 添加粒子速度数组 this.velocities = []; for (let i = 0; i < this.particleCount; i++) { this.velocities.push(new THREE.Vector3()); } const material = new THREE.ShaderMaterial({ uniforms: { color: { value: new THREE.Color(0xffffff) }, pointTexture: { value: new THREE.TextureLoader().load('https://threejs.org/examples/textures/sprites/disc.png') } }, vertexShader: ` attribute float size; attribute vec3 customColor; varying vec3 vColor; varying float life; void main() { life = 1.0 - (position.y + 30.0) / 60.0; // 基于高度的生命周期 vColor = customColor * (1.0 + 2.0 * life); // 颜色增强 vec4 mvPosition = modelViewMatrix * vec4(position, 1.0); gl_PointSize = size * (300.0 / -mvPosition.z); gl_Position = projectionMatrix * mvPosition; } `, fragmentShader: ` uniform vec3 color; uniform sampler2D pointTexture; varying vec3 vColor; void main() { gl_FragColor = vec4(color * vColor, 1.0); gl_FragColor = gl_FragColor * texture2D(pointTexture, gl_PointCoord); if (gl_FragColor.a < 0.1) discard; } `, blending: THREE.AdditiveBlending, depthTest: false, transparent: true }); this.particles = new THREE.Points(geometry, material); // 减小发射范围 const randomX = (Math.random() - 0.5) * window.innerWidth * 0.8; // 使用屏幕宽度的80%作为范围 const randomZ = (Math.random() - 0.5) * 40; this.particles.position.set(randomX, -30, randomZ); // 调整发射速度,目标点更分散 // 在空间中随机选择爆炸点 // 确保目标点在视野范围内 const targetX = randomX; // 保持水平位置不变 const targetZ = randomZ; const targetY = 15 + Math.random() * 10; // 控制爆炸高度 // 计算从初始位置到目标位置的方向 const directionX = targetX - randomX; const directionZ = targetZ - randomZ; const directionY = targetY - (-30); // 标准化方向并设置速度 const length = Math.sqrt(directionX * directionX + directionY * directionY + directionZ * directionZ); // 随机化速度大小 const speedMultiplier = 1.2 + Math.random() * 0.3; // 调整速度范围 this.velocity = new THREE.Vector3( directionX / length * speedMultiplier, directionY / length * speedMultiplier, directionZ / length * speedMultiplier ); // 存储目标位置用于判断爆炸 this.targetPosition = new THREE.Vector3(targetX, targetY, targetZ); // 设置允许的爆炸距离误差 this.explosionTolerance = 5; // 初始化每个粒子的速度 for (let i = 0; i < this.particleCount; i++) { this.velocities[i].set( (Math.random() - 0.5) * 1, Math.random() * 2 + 2, (Math.random() - 0.5) * 1 ); } scene.add(this.particles); } explode(phase = 0) { // 使用 Points 系统进行爆炸 const geometry = this.particles.geometry; const positions = geometry.attributes.position.array; const sizes = geometry.attributes.size.array; // 获取当前烟花的位置作为爆炸中心点 const centerX = this.particles.position.x; const centerY = this.particles.position.y; const centerZ = this.particles.position.z; for (let i = 0; i < this.particleCount; i++) { // 设置粒子的初始位置为爆炸中心 positions[i * 3 + 0] = 0; positions[i * 3 + 1] = 0; positions[i * 3 + 2] = 0; // 为每个粒子设置向外扩散的速度 const phi = Math.random() * Math.PI * 2; const theta = Math.random() * Math.PI; const speed = 2 + Math.random() * 3; // 减小爆炸速度 this.velocities[i].set( Math.sin(theta) * Math.cos(phi) * speed, Math.cos(theta) * speed, Math.sin(theta) * Math.sin(phi) * speed ); sizes[i] = Math.random() * 3 + 2; // 减小粒子尺寸 // 更新颜色渐变 const colors = this.particles.geometry.attributes.customColor.array; const baseIndex = i * 3; const currentColor = new THREE.Color( colors[baseIndex], colors[baseIndex + 1], colors[baseIndex + 2] ); const targetColor = new THREE.Color().setHSL( Math.random(), 0.8 + Math.random() * 0.2, 0.4 + Math.random() * 0.3 // 降低爆炸粒子亮度 ); const lerpedColor = currentColor.lerp(targetColor, 0.3); // 减小颜色变化强度 colors[baseIndex] = lerpedColor.r; colors[baseIndex + 1] = lerpedColor.g; colors[baseIndex + 2] = lerpedColor.b; } geometry.attributes.position.needsUpdate = true; geometry.attributes.customColor.needsUpdate = true; geometry.attributes.size.needsUpdate = true; this.exploded = true; // 添加连锁爆炸效果 if (phase === 0 && this.explosionCount > 1) { setTimeout(() => this.explode(1), 400 + Math.random() * 200); } // 修正轨迹材质创建方式 if(phase === 0) { this.trailParticles = new THREE.Group(); const trailColor = this.color.clone().multiplyScalar(1.5); for(let i=0; i<20; i++) { const material = trailMaterial.clone(); material.color = trailColor; // 正确设置材质颜色的方式 material.transparent = true; material.opacity = 0.7; const trail = new THREE.Mesh(trailGeometry, material); trail.position.copy(this.particles.position); this.trailParticles.add(trail); } scene.add(this.trailParticles); } } update() { if (!this.exploded) { this.particles.position.add(this.velocity); this.velocity.y -= 0.015; // 增强上升轨迹的视觉效果 this.particles.scale.set( 1 + Math.sin(Date.now() * 0.01) * 0.3, 1 + Math.cos(Date.now() * 0.01) * 0.3, 1 ); // 检查是否需要爆炸 // 在以下情况爆炸: // 1. 接近目标位置 // 2. 超出视野范围 // 3. 上升速度过小 if ( this.particles.position.distanceTo(this.targetPosition) < this.explosionTolerance || Math.abs(this.particles.position.x) > 50 || Math.abs(this.particles.position.z) > 50 || this.particles.position.y > 25 || // 增加最大高度 this.velocity.y < 0.3 ) { this.explode(); } } else { let allDead = true; const positions = this.particles.geometry.attributes.position.array; for (let i = 0; i < this.particleCount; i++) { // 根据速度更新粒子位置 positions[i * 3 + 0] += this.velocities[i].x * 0.016; positions[i * 3 + 1] += this.velocities[i].y * 0.016; positions[i * 3 + 2] += this.velocities[i].z * 0.016; // 更新速度(重力作用) this.velocities[i].y -= 0.02; // 减小重力影响 // 添加阻力 this.velocities[i].multiplyScalar(this.dragCoefficient); // 应用空气阻力公式 // 检查粒子是否仍在可见范围内 const speed = this.velocities[i].length(); if (speed > 0.1) { // 当粒子速度足够小时才消失 allDead = false; } } this.particles.geometry.attributes.position.needsUpdate = true; this.dead = allDead; // 粒子逐渐变小 const sizes = this.particles.geometry.attributes.size.array; for (let i = 0; i < this.particleCount; i++) { sizes[i] *= 0.99; } this.particles.geometry.attributes.size.needsUpdate = true; } // 添加轨迹粒子自动清理 if(this.trailParticles && this.trailParticles.children.length > 0) { this.trailParticles.children.forEach(particle => { particle.material.opacity *= 0.9; if(particle.material.opacity < 0.05) { this.trailParticles.remove(particle); } }); } } } const fireworks = []; let lastFireworkTime = 0; // 添加一个变量来记录上次创建烟花的时间 // 性能优化 const performanceConfig = { maxFireworks: window.innerWidth < 768 ? 2 : 4, // 减少同时存在的烟花数量 particleMultiplier: window.devicePixelRatio > 1 ? 0.7 : 1, bloomStrength: window.devicePixelRatio > 1 ? 0.8 : 1.0 // 降低泛光强度 }; // 动画循环 function animate() { requestAnimationFrame(animate); const currentTime = Date.now(); // 移除最大烟花数量限制,改为固定时间间隔创建新烟花 if (currentTime - lastFireworkTime > 400) { // 每800毫秒发射一个新烟花 // 每次发射2-30个烟花 const count = Math.floor(Math.random() * 2) + 30; for (let i = 0; i < count; i++) { fireworks.push(new Firework()); } lastFireworkTime = currentTime; } // 更新和清理烟花 for (let i = fireworks.length - 1; i >= 0; i--) { fireworks[i].update(); if (fireworks[i].dead) { scene.remove(fireworks[i].particles); fireworks.splice(i, 1); } } composer.render(); } // 窗口大小调整 window.addEventListener('resize', () => { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); composer.setSize(window.innerWidth, window.innerHeight); }); animate(); </script>
下面给出一段相对更贴近真实烟花效果的示例代码,重点在于:
真实烟花节奏
更自然的上升轨迹
爆炸形状与层次
渐变、闪烁与重力衰减
发射尾迹
下面这段示例代码针对这些方面都做了一些改进,你可以根据需要再做进一步微调(如减少粒子数量、减小 bloom 强度、改变爆炸形状等):
html<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>更真实的3D烟花</title> <style> body { margin: 0; overflow: hidden; background: #000; } canvas { display: block; } </style> </head> <body> <script src="https://cdn.jsdelivr.net/npm/[email protected]/build/three.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/[email protected]/examples/js/shaders/LuminosityHighPassShader.js"></script> <script src="https://cdn.jsdelivr.net/npm/[email protected]/examples/js/shaders/CopyShader.js"></script> <script src="https://cdn.jsdelivr.net/npm/[email protected]/examples/js/postprocessing/EffectComposer.js"></script> <script src="https://cdn.jsdelivr.net/npm/[email protected]/examples/js/postprocessing/BloomPass.js"></script> <script src="https://cdn.jsdelivr.net/npm/[email protected]/examples/js/postprocessing/ShaderPass.js"></script> <script src="https://cdn.jsdelivr.net/npm/[email protected]/examples/js/postprocessing/RenderPass.js"></script> <script src="https://cdn.jsdelivr.net/npm/[email protected]/examples/js/postprocessing/UnrealBloomPass.js"></script> <script> /** * ------------------------------------------- * 场景、摄像机、渲染器和后期处理初始化 * ------------------------------------------- */ const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 2000 ); camera.position.set(0, 20, 100); const renderer = new THREE.WebGLRenderer({ antialias: true }); renderer.setSize(window.innerWidth, window.innerHeight); document.body.appendChild(renderer.domElement); // 后期处理 const composer = new THREE.EffectComposer(renderer); const renderPass = new THREE.RenderPass(scene, camera); composer.addPass(renderPass); const bloomPass = new THREE.UnrealBloomPass( new THREE.Vector2(window.innerWidth, window.innerHeight), 1.2, // 适当控制爆炸的炫光强度 1.0, 0.85 ); composer.addPass(bloomPass); /** * ------------------------------------------- * 烟花类:包括上升(带尾迹)和爆炸 * ------------------------------------------- */ class Firework { constructor() { this.isExploded = false; this.dead = false; // 烟花上升阶段:创造一个简单的“火箭”点 this.rocketGeo = new THREE.SphereGeometry(0.4, 8, 8); this.rocketMat = new THREE.MeshBasicMaterial({ color: 0xffffff }); this.rocket = new THREE.Mesh(this.rocketGeo, this.rocketMat); // 初始位置在底部(随机X和Z),Y稍低于视线 this.rocket.position.set( (Math.random() - 0.5) * 60, -30, (Math.random() - 0.5) * 60 ); scene.add(this.rocket); // 目标爆炸高度 this.targetY = 30 + Math.random() * 20; // 火箭上升速度(略带横向抖动,模拟风) this.velocity = new THREE.Vector3( (Math.random() - 0.5) * 0.2, // 偏小的水平抖动 0.8 + Math.random() * 0.5, // 主向上速度 (Math.random() - 0.5) * 0.2 ); // 尾迹粒子,用 Points 来渲染 // --------------------------------------- const trailCount = 40; const positions = new Float32Array(trailCount * 3); const colors = new Float32Array(trailCount * 3); for (let i = 0; i < trailCount; i++) { // 先都放在 (0,0,0),等会儿动态更新 positions[i * 3] = 0; positions[i * 3 + 1] = 0; positions[i * 3 + 2] = 0; // 尾迹大多是偏白偏黄 const color = new THREE.Color(1, 0.8, 0.4); colors[i * 3] = color.r; colors[i * 3 + 1] = color.g; colors[i * 3 + 2] = color.b; } const trailGeometry = new THREE.BufferGeometry(); trailGeometry.setAttribute( "position", new THREE.BufferAttribute(positions, 3) ); trailGeometry.setAttribute( "color", new THREE.BufferAttribute(colors, 3) ); const trailMaterial = new THREE.PointsMaterial({ size: 0.8, vertexColors: true, blending: THREE.AdditiveBlending, depthWrite: false, transparent: true }); this.trail = new THREE.Points(trailGeometry, trailMaterial); scene.add(this.trail); // 用来存储每个尾迹粒子坐标的队列 this.trailPositions = []; this.trailLength = trailCount; // 爆炸阶段:预先创建爆炸粒子系统,但先隐藏 // --------------------------------------- this.explosionCount = 120 + Math.floor(Math.random() * 60); const ePositions = new Float32Array(this.explosionCount * 3); const eColors = new Float32Array(this.explosionCount * 3); const eSizes = new Float32Array(this.explosionCount); this.velocities = []; // 随机主色 this.fireworkColor = new THREE.Color().setHSL( Math.random(), 0.7 + Math.random() * 0.3, 0.5 ); for (let i = 0; i < this.explosionCount; i++) { // 初始都在 (0,0,0),稍后爆炸时再加上爆心坐标 ePositions[i * 3] = 0; ePositions[i * 3 + 1] = 0; ePositions[i * 3 + 2] = 0; // 爆炸的粒子颜色会在主色基础上稍有随机 const colorVariation = this.fireworkColor.clone().offsetHSL( (Math.random() - 0.5) * 0.1, (Math.random() - 0.5) * 0.2, (Math.random() - 0.5) * 0.1 ); eColors[i * 3] = colorVariation.r; eColors[i * 3 + 1] = colorVariation.g; eColors[i * 3 + 2] = colorVariation.b; // 粒子大小 eSizes[i] = 12 + Math.random() * 8; // 粒子速度(爆炸形态:均匀球面 + 少量的扁平或环形变化) // 先来个球面分布 const theta = Math.random() * Math.PI * 2; const phi = Math.random() * Math.PI; const r = 15 + Math.random() * 10; // 爆炸半径 // 也可以额外判断,做一些环形或“柳絮状”分布 let sx = r * Math.sin(phi) * Math.cos(theta); let sy = r * Math.cos(phi); let sz = r * Math.sin(phi) * Math.sin(theta); // 加一点“扁平”的概率:让y方向更扁 if (Math.random() < 0.2) { sy *= 0.4; } // 记录速度向量 this.velocities.push(new THREE.Vector3(sx, sy, sz)); } const explosionGeometry = new THREE.BufferGeometry(); explosionGeometry.setAttribute( "position", new THREE.BufferAttribute(ePositions, 3) ); explosionGeometry.setAttribute( "color", new THREE.BufferAttribute(eColors, 3) ); explosionGeometry.setAttribute( "size", new THREE.BufferAttribute(eSizes, 1) ); // 使用 ShaderMaterial 来控制粒子大小衰减和纹理 const explosionMaterial = new THREE.ShaderMaterial({ uniforms: { pointTexture: { value: new THREE.TextureLoader().load( "https://threejs.org/examples/textures/sprites/disc.png" ) }, uTime: { value: 0 }, }, vertexShader: ` attribute float size; varying vec3 vColor; uniform float uTime; void main() { vColor = color; // 让粒子随时间变小 // 爆炸后 t 越大,粒子越小 float t = uTime * 0.6; float s = max(size - t*18.0, 0.0); vec4 mvPosition = modelViewMatrix * vec4(position, 1.0); gl_PointSize = s * (300.0 / -mvPosition.z); gl_Position = projectionMatrix * mvPosition; } `, fragmentShader: ` varying vec3 vColor; uniform sampler2D pointTexture; void main() { gl_FragColor = vec4(vColor, 1.0); gl_FragColor *= texture2D(pointTexture, gl_PointCoord); // 让边缘更柔和 if (gl_FragColor.a < 0.1) discard; } `, blending: THREE.AdditiveBlending, depthTest: false, transparent: true, vertexColors: true }); this.explosion = new THREE.Points(explosionGeometry, explosionMaterial); this.explosion.visible = false; // 初始隐藏,等到爆炸时再显示 scene.add(this.explosion); // 时间 this.clock = new THREE.Clock(); this.timeOffset = Math.random() * 1000; // 让每朵烟花有点时间差 } update(delta) { if (!this.isExploded) { // 上升阶段 this.rocket.position.add(this.velocity); // 模拟空气阻力和重力对火箭的影响 this.velocity.y -= 0.005; this.velocity.multiplyScalar(0.995); // 微弱衰减 // 上升尾迹:记录历史位置,用于绘制 this.recordTrail(); if (this.rocket.position.y >= this.targetY || this.velocity.y < 0) { // 到达预定高度或速度衰减后爆炸 this.explode(); } } else { // 爆炸阶段 const time = this.clock.getElapsedTime() + this.timeOffset; this.explosion.material.uniforms.uTime.value = time; // 让粒子向外扩散 + 重力 const positions = this.explosion.geometry.attributes.position.array; for (let i = 0; i < this.velocities.length; i++) { const idx = i * 3; // velocities[i] 本身是爆心坐标系的“初速度”, // 每帧要叠加到 positions 上 positions[idx] += this.velocities[i].x * delta; positions[idx + 1] += this.velocities[i].y * delta; positions[idx + 2] += this.velocities[i].z * delta; // 模拟重力 this.velocities[i].y -= 0.08 * delta; // 空气阻力 this.velocities[i].multiplyScalar(0.98); } this.explosion.geometry.attributes.position.needsUpdate = true; // 判断是否所有粒子已经衰减得几乎不可见,则回收 if (time > 3.5) { // 粒子大约 3-4 秒后就可以消失 this.dead = true; } } } recordTrail() { // 只记录固定长度的尾迹 this.trailPositions.push(this.rocket.position.clone()); if (this.trailPositions.length > this.trailLength) { this.trailPositions.shift(); // 移除最早的 } // 更新 trail 的 geometry const positions = this.trail.geometry.attributes.position.array; for (let i = 0; i < this.trailPositions.length; i++) { const p = this.trailPositions[i]; positions[i * 3] = p.x - this.rocket.position.x; positions[i * 3 + 1] = p.y - this.rocket.position.y; positions[i * 3 + 2] = p.z - this.rocket.position.z; } // 若不足 trailLength,则其余位置填充在末尾 for (let i = this.trailPositions.length; i < this.trailLength; i++) { positions[i * 3] = 9999; // 放到很远处,看不见就行 positions[i * 3 + 1] = 9999; positions[i * 3 + 2] = 9999; } this.trail.geometry.attributes.position.needsUpdate = true; // 随时间衰减尾迹整体不透明度 this.trail.material.opacity = 0.7 * (this.velocity.y + 1.0); } explode() { // 移除火箭模型 scene.remove(this.rocket); scene.remove(this.trail); // 显示爆炸粒子 this.explosion.visible = true; // 将爆炸粒子整体移到火箭消失的位置 this.explosion.position.copy(this.rocket.position); this.isExploded = true; this.clock.start(); } } /** * ------------------------------------------- * 全局控制:每隔一段时间随机发射烟花 * ------------------------------------------- */ const fireworks = []; let lastSpawnTime = 0; function spawnFireworks(time) { // 真实烟花:发射频率不会太快,可以在0.5秒到3秒之间随机 const interval = 1000 + Math.random() * 2000; if (time - lastSpawnTime > interval) { // 一次可以发射1~3个 const count = 1 + Math.floor(Math.random() * 3); for (let i = 0; i < count; i++) { fireworks.push(new Firework()); } lastSpawnTime = time; } } /** * ------------------------------------------- * 动画循环 * ------------------------------------------- */ const clock = new THREE.Clock(); function animate() { requestAnimationFrame(animate); const delta = clock.getDelta(); // 距离上次帧的时间 // 检查是否需要新发射烟花 const currentTime = performance.now(); spawnFireworks(currentTime); // 更新每个烟花 for (let i = fireworks.length - 1; i >= 0; i--) { fireworks[i].update(delta); if (fireworks[i].dead) { // 移除 scene.remove(fireworks[i].explosion); fireworks.splice(i, 1); } } composer.render(); } animate(); /** * ------------------------------------------- * 窗口自适应 * ------------------------------------------- */ window.addEventListener("resize", () => { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); composer.setSize(window.innerWidth, window.innerHeight); }); </script> </body> </html>
上升阶段与爆炸阶段分离
Points
充当“火箭”,负责上升轨迹及尾迹;火箭尾迹
Points
管理尾迹粒子,记录火箭过去的若干位置,并让这些粒子保持在相对于火箭的偏移量上,看起来就像一条拖尾。爆炸形状
随机发射频率
后期泛光 (Bloom) 调整
bloomStrength
、threshold
和 radius
参数,既能让烟花看起来炫目,又不至于过曝。通过以上改动,可以让整体观感与真实的烟花秀更为接近。你还可以进一步对色彩、粒子数量、速度衰减系数、爆炸形状等作深度微调,或利用音频(模拟爆炸声、尾迹声)等增强沉浸感。祝你创作愉快!