1. GPU粒子系统优化

1.1. GPUParticleSystem 基础实现

1.1.1. 使用 BufferGeometry + ShaderMaterial

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;
    }
}

1.2. 高级着色器优化

1.2.1. 顶点着色器(GPU计算)

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; // 透明度二次衰减
        }
    `;
}

1.2.2. 片段着色器

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;
        }
    `;
}

1.3. Transform Feedback 粒子系统(WebGL2)

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;
    }
}

1.4. 计算着色器模拟(Three.js r128+)

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;
    }
}

1.5. InstancedMesh 优化大量粒子

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;
        }
    }
}

1.6. 性能优化技巧

1.6.1. 批处理与合批

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);
        }
    }
}

1.6.2. LOD(Level of Detail)系统

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;
            }
        }
    }
}

1.6.3. 剔除与视锥裁剪

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);
        });
    }
}

1.7. 内存管理最佳实践

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();
                });
            }
        }
    }
}

1.8. 使用示例

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);
}

这些优化技巧可以显著提升粒子系统的性能,特别是当处理数十万甚至百万级别的粒子时。根据具体需求选择合适的优化策略,并注意测试不同设备的性能表现。