javascript
class GPUParticleSystem {
constructor(maxParticles = 1000000) {
this.maxParticles = maxParticles;
this.particleCount = 0;
this.initGeometry();
this.initMaterial();
}
initGeometry() {
// 使用BufferGeometry存储粒子数据
this.geometry = new THREE.BufferGeometry();
// 位置属性(vec3 * maxParticles)
const positions = new Float32Array(this.maxParticles * 3);
this.geometry.setAttribute('position',
new THREE.BufferAttribute(positions, 3).setUsage(THREE.DynamicDrawUsage));
// 速度属性
const velocities = new Float32Array(this.maxParticles * 3);
this.geometry.setAttribute('velocity',
new THREE.BufferAttribute(velocities, 3).setUsage(THREE.DynamicDrawUsage));
// 生命周期
const lifetimes = new Float32Array(this.maxParticles);
this.geometry.setAttribute('lifetime',
new THREE.BufferAttribute(lifetimes, 1).setUsage(THREE.DynamicDrawUsage));
// 颜色属性(rgba)
const colors = new Float32Array(this.maxParticles * 4);
this.geometry.setAttribute('color',
new THREE.BufferAttribute(colors, 4).setUsage(THREE.DynamicDrawUsage));
// 大小属性
const sizes = new Float32Array(this.maxParticles);
this.geometry.setAttribute('size',
new THREE.BufferAttribute(sizes, 1).setUsage(THREE.DynamicDrawUsage));
// 时间属性
const times = new Float32Array(this.maxParticles);
this.geometry.setAttribute('time',
new THREE.BufferAttribute(times, 1).setUsage(THREE.DynamicDrawUsage));
// 索引
const indices = new Uint32Array(this.maxParticles);
for (let i = 0; i < this.maxParticles; i++) indices[i] = i;
this.geometry.setAttribute('index',
new THREE.BufferAttribute(indices, 1));
}
initMaterial() {
this.material = new THREE.ShaderMaterial({
uniforms: {
time: { value: 0 },
deltaTime: { value: 0 },
texture: {
value: this.createParticleTexture()
},
gravity: { value: new THREE.Vector3(0, -9.8, 0) },
noiseTex: {
value: this.createNoiseTexture()
}
},
vertexShader: this.getVertexShader(),
fragmentShader: this.getFragmentShader(),
transparent: true,
blending: THREE.AdditiveBlending,
depthWrite: false,
vertexColors: true
});
this.points = new THREE.Points(this.geometry, this.material);
}
createParticleTexture() {
const canvas = document.createElement('canvas');
canvas.width = 64;
canvas.height = 64;
const ctx = canvas.getContext('2d');
const gradient = ctx.createRadialGradient(32, 32, 0, 32, 32, 32);
gradient.addColorStop(0, 'rgba(255,255,255,1)');
gradient.addColorStop(0.2, 'rgba(255,255,255,0.8)');
gradient.addColorStop(1, 'rgba(255,255,255,0)');
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, 64, 64);
const texture = new THREE.CanvasTexture(canvas);
return texture;
}
createNoiseTexture(size = 256) {
const data = new Uint8Array(size * size * 4);
for (let i = 0; i < size * size; i++) {
const stride = i * 4;
data[stride] = Math.random() * 255;
data[stride + 1] = Math.random() * 255;
data[stride + 2] = Math.random() * 255;
data[stride + 3] = 255;
}
const texture = new THREE.DataTexture(data, size, size, THREE.RGBAFormat);
texture.needsUpdate = true;
return texture;
}
}
javascript
getVertexShader() {
return `
uniform float time;
uniform float deltaTime;
uniform vec3 gravity;
uniform sampler2D noiseTex;
attribute vec3 velocity;
attribute float lifetime;
attribute float size;
attribute vec4 color;
attribute float particleTime;
attribute float index;
varying vec4 vColor;
varying float vLife;
// 伪随机数生成
float rand(vec2 co) {
return fract(sin(dot(co, vec2(12.9898, 78.233))) * 43758.5453);
}
// 噪声采样
float noise(vec3 p) {
vec2 uv = vec2(p.x + p.z * 0.5, p.y) * 0.1;
return texture2D(noiseTex, uv).r;
}
void main() {
// 计算粒子年龄
float age = time - particleTime;
vLife = 1.0 - (age / lifetime);
if (vLife <= 0.0) {
gl_Position = vec4(10000.0); // 移出屏幕
return;
}
// 获取初始位置
vec3 pos = position;
// 应用速度(时间积分)
pos += velocity * age;
// 应用重力
pos += 0.5 * gravity * age * age;
// 添加噪声扰动
float noiseScale = 0.1;
vec3 noisePos = pos * noiseScale + vec3(time * 0.1);
float turbulence = noise(noisePos) * 2.0 - 1.0;
pos += normalize(velocity) * turbulence * 0.5;
// 粒子大小随生命周期变化
float currentSize = size * vLife * (1.0 + sin(time * 2.0 + index) * 0.1);
// 视图空间变换
vec4 mvPosition = modelViewMatrix * vec4(pos, 1.0);
gl_PointSize = currentSize * (300.0 / length(mvPosition.xyz));
gl_Position = projectionMatrix * mvPosition;
// 颜色随生命周期变化
vColor = color;
vColor.a *= vLife * vLife; // 透明度二次衰减
}
`;
}
javascript
getFragmentShader() {
return `
uniform sampler2D texture;
varying vec4 vColor;
varying float vLife;
void main() {
// 圆形粒子
vec2 coord = gl_PointCoord - vec2(0.5);
float dist = length(coord);
if (dist > 0.5) discard;
// 纹理采样
vec4 texColor = texture2D(texture, gl_PointCoord);
// 边缘柔化
float alpha = smoothstep(0.5, 0.3, dist);
// 最终颜色
gl_FragColor = vColor * texColor;
gl_FragColor.a *= alpha * vLife;
}
`;
}
javascript
class TransformFeedbackParticleSystem {
constructor(particleCount = 100000) {
this.particleCount = particleCount;
this.initBuffers();
this.initShaders();
}
initBuffers() {
// 创建两个Buffer用于Ping-Pong
this.buffers = [];
for (let i = 0; i < 2; i++) {
const positions = new Float32Array(this.particleCount * 4);
const velocities = new Float32Array(this.particleCount * 4);
// 初始化数据
for (let j = 0; j < this.particleCount; j++) {
const idx = j * 4;
positions[idx] = (Math.random() - 0.5) * 10;
positions[idx + 1] = Math.random() * 10;
positions[idx + 2] = (Math.random() - 0.5) * 10;
positions[idx + 3] = 1.0; // w分量用于生命周期
velocities[idx] = (Math.random() - 0.5) * 0.1;
velocities[idx + 1] = Math.random() * 0.5;
velocities[idx + 2] = (Math.random() - 0.5) * 0.1;
velocities[idx + 3] = Math.random() * 5 + 1; // 生命周期
}
this.buffers.push({
position: new THREE.BufferAttribute(positions, 4),
velocity: new THREE.BufferAttribute(velocities, 4)
});
}
this.currentBuffer = 0;
}
getTransformShader() {
return {
vertex: `
#version 300 es
in vec4 currentPosition;
in vec4 currentVelocity;
out vec4 nextPosition;
out vec4 nextVelocity;
uniform float deltaTime;
uniform float time;
uniform vec3 emitterPos;
void main() {
vec3 pos = currentPosition.xyz;
vec3 vel = currentVelocity.xyz;
float life = currentPosition.w;
float maxLife = currentVelocity.w;
// 生命周期递减
life -= deltaTime;
if (life <= 0.0) {
// 重置粒子
pos = emitterPos;
vel = vec3(
(fract(sin(float(gl_VertexID) * 12.9898) * 43758.5453) - 0.5) * 2.0,
fract(cos(float(gl_VertexID) * 78.233) * 23421.631) * 3.0,
(fract(sin(float(gl_VertexID) * 45.543) * 78543.123) - 0.5) * 2.0
);
life = maxLife;
}
// 物理模拟
vel += vec3(0.0, -9.8, 0.0) * deltaTime;
pos += vel * deltaTime;
// 添加噪声
float noise = sin(time + float(gl_VertexID) * 0.1) * 0.01;
pos.x += noise;
pos.z += noise;
nextPosition = vec4(pos, life);
nextVelocity = vec4(vel, maxLife);
}
`
};
}
update(deltaTime) {
const gl = this.renderer.getContext();
// 绑定Transform Feedback
gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 0,
this.buffers[(this.currentBuffer + 1) % 2].position.buffer);
gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 1,
this.buffers[(this.currentBuffer + 1) % 2].velocity.buffer);
gl.beginTransformFeedback(gl.POINTS);
// 绘制用于更新
gl.drawArrays(gl.POINTS, 0, this.particleCount);
gl.endTransformFeedback();
// 切换Buffer
this.currentBuffer = (this.currentBuffer + 1) % 2;
}
}
javascript
import { GPUComputationRenderer } from 'three/examples/jsm/misc/GPUComputationRenderer';
class ComputeShaderParticleSystem {
constructor(renderer, width = 256, height = 256) {
this.renderer = renderer;
this.width = width;
this.height = height;
this.particleCount = width * height;
this.initGPUCompute();
}
initGPUCompute() {
this.gpuCompute = new GPUComputationRenderer(this.width, this.height, this.renderer);
// 创建位置纹理
const positionTex = this.gpuCompute.createTexture();
this.fillPositionTexture(positionTex);
// 创建速度纹理
const velocityTex = this.gpuCompute.createTexture();
this.fillVelocityTexture(velocityTex);
// 创建位置变量
this.positionVariable = this.gpuCompute.addVariable(
'positionTexture',
this.getPositionShader(),
positionTex
);
// 创建速度变量
this.velocityVariable = this.gpuCompute.addVariable(
'velocityTexture',
this.getVelocityShader(),
velocityTex
);
// 设置依赖关系
this.gpuCompute.setVariableDependencies(this.positionVariable,
[this.positionVariable, this.velocityVariable]);
this.gpuCompute.setVariableDependencies(this.velocityVariable,
[this.positionVariable, this.velocityVariable]);
// 初始化
this.gpuCompute.init();
}
getPositionShader() {
return `
uniform float time;
uniform float deltaTime;
uniform sampler2D velocityTexture;
void main() {
vec2 uv = gl_FragCoord.xy / resolution.xy;
// 读取当前位置和速度
vec4 position = texture2D(positionTexture, uv);
vec4 velocity = texture2D(velocityTexture, uv);
// 更新位置
position.xyz += velocity.xyz * deltaTime;
// 边界处理
if (position.x > 10.0) position.x = -10.0;
if (position.x < -10.0) position.x = 10.0;
if (position.y > 10.0) position.y = -10.0;
if (position.y < -10.0) {
position.y = -10.0;
velocity.y = -velocity.y * 0.8;
}
if (position.z > 10.0) position.z = -10.0;
if (position.z < -10.0) position.z = 10.0;
// 写入新位置
gl_FragColor = position;
}
`;
}
getVelocityShader() {
return `
uniform float time;
uniform float deltaTime;
uniform sampler2D positionTexture;
void main() {
vec2 uv = gl_FragCoord.xy / resolution.xy;
// 读取当前速度
vec4 velocity = texture2D(velocityTexture, uv);
vec4 position = texture2D(positionTexture, uv);
// 应用力
velocity.y -= 9.8 * deltaTime; // 重力
// 添加随机扰动
float noise = sin(time + position.x * 10.0) *
cos(time + position.z * 10.0) * 0.1;
velocity.xz += vec2(noise, noise * 1.3) * deltaTime;
// 阻尼
velocity.xyz *= 0.99;
// 写入新速度
gl_FragColor = velocity;
}
`;
}
update(deltaTime) {
this.gpuCompute.compute();
// 获取计算后的纹理
const positionTexture = this.gpuCompute.getCurrentRenderTarget(
this.positionVariable
).texture;
// 更新渲染材质
this.material.uniforms.positionTexture.value = positionTexture;
}
}
javascript
class InstancedParticleSystem {
constructor(count = 100000) {
this.count = count;
this.initInstancedMesh();
}
initInstancedMesh() {
// 基础几何体
const baseGeometry = new THREE.SphereGeometry(0.1, 8, 8);
// 实例化材质
const material = new THREE.ShaderMaterial({
uniforms: {
time: { value: 0 },
deltaTime: { value: 0 }
},
vertexShader: `
attribute vec3 offset;
attribute vec3 velocity;
attribute float life;
attribute float maxLife;
attribute vec3 color;
varying vec3 vColor;
varying float vLife;
void main() {
// 计算粒子年龄
float age = mod(time, maxLife);
vLife = 1.0 - age / maxLife;
if (vLife <= 0.0) {
gl_Position = vec4(10000.0);
return;
}
// 位置计算
vec3 pos = position + offset;
pos += velocity * age;
pos.y -= 4.9 * age * age; // 重力
vColor = color;
// 标准变换
gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
}
`,
fragmentShader: `
varying vec3 vColor;
varying float vLife;
void main() {
gl_FragColor = vec4(vColor, vLife);
}
`,
transparent: true
});
// 创建实例化Mesh
this.mesh = new THREE.InstancedMesh(baseGeometry, material, this.count);
this.mesh.instanceMatrix.setUsage(THREE.DynamicDrawUsage);
// 添加自定义属性
const offsets = new Float32Array(this.count * 3);
const velocities = new Float32Array(this.count * 3);
const lifetimes = new Float32Array(this.count * 2); // life, maxLife
const colors = new Float32Array(this.count * 3);
// 初始化数据
for (let i = 0; i < this.count; i++) {
const i3 = i * 3;
const i2 = i * 2;
// 位置偏移
offsets[i3] = (Math.random() - 0.5) * 100;
offsets[i3 + 1] = Math.random() * 50;
offsets[i3 + 2] = (Math.random() - 0.5) * 100;
// 速度
velocities[i3] = (Math.random() - 0.5) * 0.5;
velocities[i3 + 1] = Math.random() * 2 + 1;
velocities[i3 + 2] = (Math.random() - 0.5) * 0.5;
// 生命周期
const maxLife = Math.random() * 5 + 2;
lifetimes[i2] = Math.random() * maxLife;
lifetimes[i2 + 1] = maxLife;
// 颜色
colors[i3] = Math.random() * 0.5 + 0.5;
colors[i3 + 1] = Math.random() * 0.3 + 0.2;
colors[i3 + 2] = Math.random() * 0.2;
}
// 设置实例属性
this.mesh.geometry.setAttribute('offset',
new THREE.InstancedBufferAttribute(offsets, 3));
this.mesh.geometry.setAttribute('velocity',
new THREE.InstancedBufferAttribute(velocities, 3));
this.mesh.geometry.setAttribute('life',
new THREE.InstancedBufferAttribute(lifetimes, 2));
this.mesh.geometry.setAttribute('color',
new THREE.InstancedBufferAttribute(colors, 3));
}
update(deltaTime) {
this.mesh.material.uniforms.time.value += deltaTime;
this.mesh.material.uniforms.deltaTime.value = deltaTime;
// 如果需要更新矩阵
if (this.needsMatrixUpdate) {
const matrix = new THREE.Matrix4();
for (let i = 0; i < this.count; i++) {
// 更新每个实例的矩阵
matrix.makeTranslation(
Math.sin(i * 0.01 + this.mesh.material.uniforms.time.value) * 10,
0,
Math.cos(i * 0.01 + this.mesh.material.uniforms.time.value) * 10
);
this.mesh.setMatrixAt(i, matrix);
}
this.mesh.instanceMatrix.needsUpdate = true;
}
}
}
javascript
class ParticleBatcher {
constructor() {
this.particleGroups = new Map();
this.maxBatchSize = 65535; // WebGL索引限制
}
addParticles(type, particles) {
if (!this.particleGroups.has(type)) {
this.particleGroups.set(type, []);
}
const group = this.particleGroups.get(type);
group.push(...particles);
// 自动分批次
if (group.length > this.maxBatchSize) {
this.createNewBatch(type);
}
}
createNewBatch(type) {
const group = this.particleGroups.get(type);
const batchCount = Math.ceil(group.length / this.maxBatchSize);
for (let i = 0; i < batchCount; i++) {
const start = i * this.maxBatchSize;
const end = Math.min((i + 1) * this.maxBatchSize, group.length);
const batch = group.slice(start, end);
// 创建批次渲染
this.renderBatch(type + '_batch_' + i, batch);
}
}
}
javascript
class ParticleLODSystem {
constructor(camera) {
this.camera = camera;
this.lodLevels = [
{ distance: 50, particleCount: 1000 },
{ distance: 100, particleCount: 500 },
{ distance: 200, particleCount: 200 },
{ distance: Infinity, particleCount: 50 }
];
}
updateLOD(particleSystem, position) {
const distance = this.camera.position.distanceTo(position);
for (const level of this.lodLevels) {
if (distance < level.distance) {
particleSystem.setVisibleCount(level.particleCount);
break;
}
}
}
}
javascript
class ParticleCuller {
constructor(camera) {
this.camera = camera;
this.frustum = new THREE.Frustum();
this.updateFrustum();
}
updateFrustum() {
this.frustum.setFromProjectionMatrix(
new THREE.Matrix4().multiplyMatrices(
this.camera.projectionMatrix,
this.camera.matrixWorldInverse
)
);
}
isVisible(boundingSphere) {
return this.frustum.intersectsSphere(boundingSphere);
}
cullParticles(particles) {
return particles.filter(particle => {
const sphere = new THREE.Sphere(particle.position, particle.radius);
return this.isVisible(sphere);
});
}
}
javascript
class ParticleMemoryManager {
constructor() {
this.pool = new Map();
this.activeParticles = new Set();
}
allocate(type, count) {
if (!this.pool.has(type)) {
this.pool.set(type, []);
}
const pool = this.pool.get(type);
const allocated = [];
// 从池中获取
while (allocated.length < count && pool.length > 0) {
allocated.push(pool.pop());
}
// 如果池中不够,创建新的
while (allocated.length < count) {
const particle = this.createParticle(type);
allocated.push(particle);
}
// 标记为活跃
allocated.forEach(p => this.activeParticles.add(p));
return allocated;
}
release(particles) {
particles.forEach(particle => {
this.activeParticles.delete(particle);
const type = particle.type;
if (!this.pool.has(type)) {
this.pool.set(type, []);
}
// 重置粒子状态
this.resetParticle(particle);
this.pool.get(type).push(particle);
});
}
// 清理未使用的内存
cleanup(maxPoolSize = 1000) {
for (const [type, pool] of this.pool) {
if (pool.length > maxPoolSize) {
// 释放多余的粒子
const excess = pool.splice(maxPoolSize);
excess.forEach(particle => {
particle.geometry.dispose();
particle.material.dispose();
});
}
}
}
}
javascript
// 创建优化的粒子系统
const particleSystem = new GPUParticleSystem(1000000);
// 在主循环中更新
function animate() {
const deltaTime = clock.getDelta();
// 更新粒子系统
particleSystem.update(deltaTime);
// 渲染
renderer.render(scene, camera);
requestAnimationFrame(animate);
}
// 添加粒子
function spawnFireworks(count, position) {
const particles = [];
for (let i = 0; i < count; i++) {
particles.push({
position: position.clone(),
velocity: new THREE.Vector3(
(Math.random() - 0.5) * 2,
Math.random() * 3 + 2,
(Math.random() - 0.5) * 2
),
color: new THREE.Color(
Math.random() * 0.5 + 0.5,
Math.random() * 0.3,
Math.random() * 0.2
),
size: Math.random() * 3 + 1,
lifetime: Math.random() * 3 + 2
});
}
particleSystem.addParticles('firework', particles);
}
这些优化技巧可以显著提升粒子系统的性能,特别是当处理数十万甚至百万级别的粒子时。根据具体需求选择合适的优化策略,并注意测试不同设备的性能表现。