在 3D Web 开发中,粒子系统是一种常用的技术,用于模拟烟雾、火焰、雪花、星空等效果。通过粒子系统,开发者可以创建出非常炫酷的动态视觉效果,提升用户体验。Three.js 是一个广泛使用的 JavaScript 3D 图形库,能够帮助我们轻松实现 3D 粒子效果。

在本教程中,我们将使用 Three.js 来构建一个简单而炫酷的 3D 粒子系统。你将学会如何使用粒子材质、纹理和动力学来创建一个动态粒子效果。

一、环境准备

在开始之前,我们需要准备好以下环境:

  • HTML 和 JavaScript 开发环境。
  • 引入 Three.js 库。你可以通过 CDN 或直接下载到本地使用。

引入 Three.js

在 HTML 文件中引入 Three.js 库:

<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>

二、创建 3D 场景

首先,我们需要创建一个基本的 3D 场景,这包括:

  • 创建场景(Scene)。
  • 创建相机(Camera)。
  • 创建渲染器(Renderer)。

创建场景、相机和渲染器

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Three.js 粒子系统</title>
  <style>
    body { margin: 0; overflow: hidden; }
    canvas { display: block; }
  </style>
</head>
<body>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
  <script>
    // 创建场景
    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 = 5;

    // 渲染循环
    function animate() {
      requestAnimationFrame(animate);
      renderer.render(scene, camera);
    }

    animate();
  </script>
</body>
</html>
  • THREE.Scene():创建一个 3D 场景。
  • THREE.PerspectiveCamera():创建一个透视相机,决定我们观察场景的角度和范围。
  • THREE.WebGLRenderer():创建 WebGL 渲染器,渲染场景和相机。

动态调整窗口大小

为保证在调整浏览器窗口大小时渲染器能自适应,我们需要监听窗口的 resize 事件:

window.addEventListener('resize', () => {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(window.innerWidth, window.innerHeight);
});

三、创建粒子系统

粒子系统通常由多个粒子(point)组成,每个粒子有自己的位置、颜色、大小等属性。在 Three.js 中,我们使用 THREE.Points 来创建粒子系统。

创建粒子材质

粒子的材质可以使用 THREE.PointsMaterial 来实现。它支持各种属性,例如粒子的大小、颜色、透明度等。我们还可以为粒子添加纹理,以便创造更具视觉冲击力的效果。

const textureLoader = new THREE.TextureLoader();
const particleTexture = textureLoader.load('path_to_your_texture.png'); // 加载粒子纹理

const particleMaterial = new THREE.PointsMaterial({
  map: particleTexture, // 设置粒子纹理
  size: 0.1, // 设置粒子大小
  transparent: true, // 使粒子具有透明度
  opacity: 0.8, // 设置粒子透明度
  blending: THREE.AdditiveBlending, // 添加混合效果,使粒子发光
});

创建粒子几何体

粒子的几何体可以通过 THREE.BufferGeometry 来创建,表示粒子的位置。我们可以随机生成粒子的坐标,来模拟动态效果。

const particleCount = 5000; // 粒子数量
const positions = new Float32Array(particleCount * 3); // 存储粒子位置的数组

for (let i = 0; i < particleCount; i++) {
  positions[i * 3] = Math.random() * 10 - 5; // X 坐标
  positions[i * 3 + 1] = Math.random() * 10 - 5; // Y 坐标
  positions[i * 3 + 2] = Math.random() * 10 - 5; // Z 坐标
}

const particleGeometry = new THREE.BufferGeometry();
particleGeometry.setAttribute('position', new THREE.BufferAttribute(positions, 3)); // 设置粒子位置

创建粒子系统

将粒子材质和几何体结合,创建粒子系统:

const particleSystem = new THREE.Points(particleGeometry, particleMaterial);
scene.add(particleSystem); // 将粒子系统添加到场景中

四、添加动画效果

为了让粒子看起来更加生动,我们可以通过动画让粒子的位置随时间变化。比如,我们可以通过简单的物理模型让粒子向外发射并慢慢消失。

动画效果:粒子运动

function animateParticles() {
  const positions = particleGeometry.attributes.position.array;

  for (let i = 0; i < particleCount; i++) {
    positions[i * 3 + 1] -= 0.05; // 向下移动粒子
    if (positions[i * 3 + 1] < -5) {
      positions[i * 3 + 1] = 5; // 如果粒子离开屏幕就重新出现在顶部
    }
  }

  particleGeometry.attributes.position.needsUpdate = true; // 更新粒子位置
}

// 修改渲染循环以加入粒子动画
function animate() {
  requestAnimationFrame(animate);
  animateParticles(); // 更新粒子位置
  renderer.render(scene, camera);
}

增加发射效果

为了让粒子看起来像是从一个发射源发射出来,我们可以根据发射速度和方向来控制粒子的运动。通过设置粒子的位置随机分布和增加速度,可以实现爆炸、烟雾等效果。

五、优化性能

粒子系统可能包含成千上万的粒子,如何保证性能是一个重要问题。Three.js 提供了一些方法来优化性能:

  • 使用 THREE.PointsMaterial 而不是 THREE.MeshBasicMaterial。
  • 避免过度绘制和过度复杂的纹理。
  • 使用 BufferGeometry 代替常规的 Geometry,以减少内存消耗。
  • 只渲染视野中的粒子,使用视锥体剔除技术。

六、完整代码

下面是构建粒子系统的完整代码:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Three.js 粒子系统</title>
  <style>
    body { margin: 0; overflow: hidden; }
    canvas { display: block; }
  </style>
</head>
<body>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
  <script>
    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 = 5;

    const textureLoader = new THREE.TextureLoader();
    const particleTexture = textureLoader.load('https://threejs.org/examples/textures/sprites/disc.png');

    const particleMaterial = new THREE.PointsMaterial({
      map: particleTexture,
      size: 0.1,
      transparent: true,
      opacity: 0.8,
      blending: THREE.AdditiveBlending,
    });

    const particleCount = 5000;
    const positions = new Float32Array(particleCount * 3);
    for (let i = 0; i < particleCount; i++) {
      positions[i * 3] = Math.random() * 10 - 5;
      positions[i * 3 + 1] = Math.random() * 10 - 5;
      positions[i * 3 + 2] = Math.random() * 10 - 5;
    }

    const

 particleGeometry = new THREE.BufferGeometry();
    particleGeometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));

    const particleSystem = new THREE.Points(particleGeometry, particleMaterial);
    scene.add(particleSystem);

    function animateParticles() {
      const positions = particleGeometry.attributes.position.array;
      for (let i = 0; i < particleCount; i++) {
        positions[i * 3 + 1] -= 0.05;
        if (positions[i * 3 + 1] < -5) {
          positions[i * 3 + 1] = 5;
        }
      }
      particleGeometry.attributes.position.needsUpdate = true;
    }

    function animate() {
      requestAnimationFrame(animate);
      animateParticles();
      renderer.render(scene, camera);
    }

    animate();

    window.addEventListener('resize', () => {
      camera.aspect = window.innerWidth / window.innerHeight;
      camera.updateProjectionMatrix();
      renderer.setSize(window.innerWidth, window.innerHeight);
    });
  </script>
</body>
</html>

七、总结

在本教程中,我们使用 Three.js 创建了一个简单的 3D 粒子系统,演示了如何:

  1. 创建粒子材质和几何体。
  2. 通过动画让粒子动态移动。
  3. 优化性能以应对大量粒子的渲染。

通过 Three.js 的强大功能,你可以轻松创建更加复杂的粒子效果,应用于游戏、动画、数据可视化等各种场景。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐