1. Points对象创建粒子系统

在Three.js中使用Points对象创建粒子系统是一种高效渲染大量小图形的技术。以下是创建和定制粒子系统的完整指南:

1.1. 基础粒子系统创建

1.1.1. 基本步骤

javascript

// 1. 创建几何体并设置顶点
const geometry = new THREE.BufferGeometry();
const count = 5000;

// 创建顶点数组
const positions = new Float32Array(count * 3); // x, y, z 坐标
for (let i = 0; i < count * 3; i++) {
    positions[i] = (Math.random() - 0.5) * 10; // -5 到 5 的范围
}

// 将顶点数据添加到几何体
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));

// 2. 创建材质
const material = new THREE.PointsMaterial({
    color: 0xffffff,
    size: 0.02,
    sizeAttenuation: true // 距离远近影响粒子大小
});

// 3. 创建Points对象
const particles = new THREE.Points(geometry, material);
scene.add(particles);

1.2. 自定义粒子材质

1.2.1. 使用纹理创建样式化粒子

javascript

// 加载纹理
const textureLoader = new THREE.TextureLoader();
const particleTexture = textureLoader.load('path/to/particle.png');

const material = new THREE.PointsMaterial({
    size: 0.1,
    map: particleTexture, // 使用纹理
    transparent: true,
    alphaTest: 0.001, // 透明阈值
    blending: THREE.AdditiveBlending, // 叠加混合模式
    depthWrite: false, // 优化渲染
    vertexColors: true // 启用顶点颜色
});

1.2.2. 着色器材质(高级自定义)

javascript

const vertexShader = `
    attribute float size;
    attribute vec3 customColor;

    varying vec3 vColor;

    void main() {
        vColor = customColor;
        vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
        gl_PointSize = size * (300.0 / -mvPosition.z);
        gl_Position = projectionMatrix * mvPosition;
    }
`;

const fragmentShader = `
    varying vec3 vColor;

    void main() {
        gl_FragColor = vec4(vColor, 1.0);

        // 圆形粒子
        float distance = length(gl_PointCoord - vec2(0.5));
        if (distance > 0.5) {
            discard;
        }
    }
`;

const shaderMaterial = new THREE.ShaderMaterial({
    vertexShader,
    fragmentShader,
    uniforms: {
        time: { value: 0 }
    }
});

1.3. 粒子动画与交互

1.3.1. 添加动画效果

javascript

// 在动画循环中更新粒子
function animateParticles() {
    requestAnimationFrame(animateParticles);

    const positions = particles.geometry.attributes.position.array;
    const count = positions.length / 3;

    for (let i = 0; i < count; i++) {
        const i3 = i * 3;

        // 示例:正弦波运动
        positions[i3 + 1] = Math.sin(clock.elapsedTime + i * 0.01) * 2;

        // 旋转效果
        const angle = clock.elapsedTime * 0.5 + i * 0.01;
        const radius = Math.sqrt(positions[i3] ** 2 + positions[i3 + 2] ** 2);
        positions[i3] = Math.cos(angle) * radius;
        positions[i3 + 2] = Math.sin(angle) * radius;
    }

    particles.geometry.attributes.position.needsUpdate = true;
    renderer.render(scene, camera);
}

1.3.2. 鼠标交互

javascript

// 射线检测与粒子交互
const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();

window.addEventListener('click', (event) => {
    // 计算鼠标归一化坐标
    mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
    mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;

    raycaster.setFromCamera(mouse, camera);
    const intersects = raycaster.intersectObject(particles);

    if (intersects.length > 0) {
        // 获取点击的粒子索引
        const index = intersects[0].index;

        // 修改被点击的粒子
        const positions = particles.geometry.attributes.position.array;
        const i3 = index * 3;
        positions[i3 + 1] += 1; // 向上移动
        particles.geometry.attributes.position.needsUpdate = true;
    }
});

1.4. 性能优化技巧

1.4.1. 使用Instancing提高性能(Three.js r125+)

javascript

const geometry = new THREE.InstancedBufferGeometry();
geometry.instanceCount = count;

// 实例化位置
const positions = new Float32Array(count * 3);
const offsets = new Float32Array(count * 3); // 每个实例的偏移量
const scales = new Float32Array(count); // 每个实例的缩放

// 填充数据
for (let i = 0; i < count; i++) {
    offsets[i * 3] = (Math.random() - 0.5) * 10;
    offsets[i * 3 + 1] = (Math.random() - 0.5) * 10;
    offsets[i * 3 + 2] = (Math.random() - 0.5) * 10;
    scales[i] = Math.random() * 0.5 + 0.5;
}

geometry.setAttribute('offset', new THREE.InstancedBufferAttribute(offsets, 3));
geometry.setAttribute('scale', new THREE.InstancedBufferAttribute(scales, 1));

1.4.2. 优化建议

  1. 减少粒子数量:在保持效果的前提下使用最少粒子

  2. 简化材质:避免复杂着色器计算

  3. 使用实例化:大量相同粒子时使用实例化渲染

  4. 层级细节(LOD):根据距离调整粒子密度

  5. 视锥体裁剪:只渲染可见区域内的粒子

1.5. 完整示例:星空效果

javascript

function createStarfield(count = 10000) {
    const geometry = new THREE.BufferGeometry();
    const positions = new Float32Array(count * 3);
    const colors = new Float32Array(count * 3);

    for (let i = 0; i < count; i++) {
        // 随机位置(球面分布)
        const radius = 100 + Math.random() * 900;
        const theta = Math.random() * Math.PI * 2;
        const phi = Math.acos(2 * Math.random() - 1);

        positions[i * 3] = radius * Math.sin(phi) * Math.cos(theta);
        positions[i * 3 + 1] = radius * Math.sin(phi) * Math.sin(theta);
        positions[i * 3 + 2] = radius * Math.cos(phi);

        // 随机颜色(偏白色)
        colors[i * 3] = 0.8 + Math.random() * 0.2;
        colors[i * 3 + 1] = 0.8 + Math.random() * 0.2;
        colors[i * 3 + 2] = 0.9 + Math.random() * 0.1;
    }

    geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
    geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));

    const material = new THREE.PointsMaterial({
        size: 0.7,
        vertexColors: true,
        transparent: true,
        opacity: 0.8,
        sizeAttenuation: true
    });

    return new THREE.Points(geometry, material);
}

通过组合这些技术,你可以创建从简单点到复杂动态粒子系统的各种视觉效果。