粒子

Starter pack

粒子是为场景增添生命力的绝佳方式。它们可以用于多种场合,例如雪、雨、火、烟雾或魔法效果。它们通常用于创造大气效果,如雾气、灰尘或火花。

在本课程中,我们将研究使用 Threejs 和 React Three Fiber 创建粒子的不同方法,以创建这个带有星空和降雪效果的夜景:

看雪花如何飘落,星星如何在天空闪烁。❄️✨

星星

我们的起始代码包含了这段由 EdwiixGG 制作的 "Low Poly Winter Scene",位于一个立方体和一个动画光源之上。

看起来不错,但如果在天空中添加星星,会更有趣。

让我们从在天空中添加星星开始。使用 React Three Fiber 最简单的方法是使用 drei 库中的 Stars 组件

components/Experience.jsx 中:

// ...
import { Stars } from "@react-three/drei";

export const Experience = () => {
  // ...

  return (
    <>
      <Stars />
      {/* ... */}
    </>
  );
};

星空

瞧,我们的天空现在充满了闪烁的可爱星星!

我们可以调整其参数,例如 factor 以根据距离调整大小或 speed 以调整 fade 效果的时间。

有关所有可用参数,请参阅文档

让我们通过浏览 Stars 组件的源代码来了解其底层工作原理。

我们可以看到,为了渲染星星,他们使用了 points,并在几何上填充了三个属性:

  • position:用于确定每个星星的位置
  • colors:用于确定每个星星的颜色
  • size:用于确定每个星星的大小

然后,一个名为 StarfieldMaterial 的自定义 ShaderMaterial 负责根据这些属性值时间统一变量正确显示点效果。

首先,这种方法是非常出色的,轻量化,完全在 GPU 上处理,这意味着您可以放置大量星星。

但在视觉上,我看到有两个方面可以提高:

  • 星星表示为方块。
  • 所有星星的渐变效果同步,导致 闪烁 效果。

由于我们无法通过 Stars 组件控制这些方面,因此让我们来创建我们自己的星星系统!

自定义星空

为了更轻松地控制星星,我们将在CPU端处理它们的逻辑,使用实例化技术。

不用担心这不是最优化的方法,对于合理数量的星星来说,这样做是足够的并且更加灵活。我们将在本章稍后的部分构建我们的简单VFX 引擎和学习TSL时,了解如何在GPU端处理我们的粒子。

PS: 即使对于几何形状相同的大量物体,实例化仍然是一种有效的渲染方式,正如在**优化课程**中提到的。

实例

让我们从一个新的文件 components/StarrySky.jsx 中创建我们自己的 StarrySky 组件开始:

import { Instance, Instances } from "@react-three/drei";
import { useMemo, useRef } from "react";
import { randFloatSpread } from "three/src/math/MathUtils.js";

export const StarrySky = ({ nbParticles = 1000 }) => {
  const particles = useMemo(
    () =>
      Array.from({ length: nbParticles }, (_, idx) => ({
        position: [
          randFloatSpread(20),
          randFloatSpread(20),
          randFloatSpread(20),
        ],
      })),
    []
  );

  return (
    <Instances range={nbParticles} limit={nbParticles} frustumCulled={false}>
      <planeGeometry args={[1, 1]} />
      <meshBasicMaterial />
      {particles.map((props, i) => (
        <Particle key={i} {...props} />
      ))}
    </Instances>
  );
};

const Particle = ({ position }) => {
  const ref = useRef();

  return <Instance ref={ref} position={position} />;
};

我们使用平面几何体结合mesh basic material创建了一个InstancedMesh

感谢来自Drei<Instance /> 组件,我们能够创建这个mesh的实例并且单独控制每个粒子(实例)。

现在让我们在 components/Experience.jsx 中用我们自定义的组件替换掉 Stars 组件:

// ...
import { StarrySky } from "./StarrySky";

export const Experience = () => {
  // ...

  return (
    <>
      <StarrySky />
      {/* ... */}
    </>
  );
};

我们现在有一个混乱的天空:

用平面填充的自定义星空

这是一个好的起点!

让我们调整星星的大小。在负责设置粒子位置的 useMemo 中,我们可以添加一个 size 属性:

import { randFloat, randFloatSpread } from "three/src/math/MathUtils.js";

// ...

const particles = useMemo(
  () =>
    Array.from({ length: nbParticles }, (_, idx) => ({
      position: [randFloatSpread(20), randFloatSpread(20), randFloatSpread(20)],
      size: randFloat(0.1, 0.25),
    })),
  []
);

Particle 组件中,我们可以将这个 size 属性传递给 Instance 组件:

const Particle = ({ position, size }) => {
  const ref = useRef();

  return <Instance ref={ref} position={position} scale={size} />;
};

现在效果更好了,星星有不同的尺寸:

具有不同大小的自定义星空

但我们有个问题,星星的位置在 -2020 之间,使用的是 randFloatSpread(20),但我们希望星星远在天空中。

为此,我们让 z 始终为 0 并将 x 位置调整到 515 之间。

解释 x 轴分布的图

我们的星星将随机地位于 x 轴上 515 之间。

为了围绕中心,我们将 y 位置旋转在 02 * Math.PI 之间。

解释 y 轴分布的图

中心将永远没有星星,并且星星将散布在所有方向。

在负责设置粒子位置的 useMemo 中,我们可以调整 position 属性并添加一个 rotation 属性:

const particles = useMemo(
  () =>
    Array.from({ length: nbParticles }, (_, idx) => ({
      position: [randFloat(5, 15), randFloatSpread(20), 0],
      rotation: [0, randFloat(0, Math.PI * 2), 0],
      size: randFloat(0.1, 0.25),
    })),
  []
);
Three.js logoReact logo

React Three Fiber: The Ultimate Guide to 3D Web Development

✨ You have reached the end of the preview ✨

Go to the next level with Three.js and React Three Fiber!

Get full access to this lesson and the complete course when you enroll:

  • 🔓 Full lesson videos with no limits
  • 💻 Access to the final source code
  • 🎓 Course progress tracking & completion
  • 💬 Invite to our private Discord community
Unlock the Full Course – Just $85

One-time payment. Lifetime updates included.