입자

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 />
      {/* ... */}
    </>
  );
};

별이 있는 하늘

그리고 voilà, 우리의 하늘은 이제 반짝이는 아름다운 별들로 가득 찼습니다!

우리는 factor와 같은 매개변수를 조정하여 거리 기반 크기를 조정하거나 페이드 효과의 타이밍을 조정할 수 있습니다.

사용 가능한 모든 매개변수에 대해서는 문서를 참조하세요.

Stars 컴포넌트의 소스 코드를 살펴보면서 내부적으로 어떻게 작동하는지 확인해 봅시다.

별을 렌더링하기 위해 points를 사용하여 지오메트리에 세 가지 속성을 채우는 것을 볼 수 있습니다:

  • position: 각각의 별 위치 결정
  • colors: 각각의 별 색상 결정
  • size: 각각의 별 크기 결정

그런 다음 StarfieldMaterial이라는 커스텀 ShaderMaterial이 이 속성 값시간 유니폼을 기반으로 포인트를 올바르게 표시하여 페이드 효과를 담당합니다.

우선, 이 접근 방식은 훌륭하고, 가볍고, 완전히 GPU에서 처리되므로 잠재적으로 매우 많은 수의 별을 구성할 수 있습니다.

그러나 시각적으로 두 가지 개선할 수 있는 점이 보입니다:

  • 별은 사각형으로 표현됩니다.
  • 모든 별이 동기화된 페이드 효과로 인해 깜박이는 효과가 발생합니다.

Stars 컴포넌트로 이 부분에 대한 제어가 불가능하므로, 우리의 별 시스템을 만들어 봅시다!

Custom Stars

별을 보다 쉽게 제어하기 위해 CPU 측에서 인스턴싱을 사용하여 로직을 처리할 것입니다.

비록 최적화된 방법은 아닐지라도, 적절한 수의 별들을 처리하기에는 문제가 없으며 훨씬 더 유연할 것입니다. 이후 이 장에서 간단한 VFX 엔진을 만들고 TSL을 학습할 때 GPU 측에서 파티클을 처리하는 방법을 배우게 될 것입니다.

참고: 인스턴싱은 같은 지오메트리를 가진 많은 객체를 렌더링하는 효율적인 방법이며, 이는 **최적화 수업**에서 설명되었습니다.

인스턴스

새 파일 components/StarrySky.jsxStarrySky 컴포넌트를 만들어 시작해봅시다:

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

우리는 plane geometrymesh basic material을 사용하여 InstancedMesh를 만들고 있습니다.

Drei<Instance /> 컴포넌트를 통해 이 메쉬의 인스턴스를 만들고 각 파티클(인스턴스)을 개별적으로 제어할 수 있습니다.

이제 components/Experience.jsx에서 커스텀 컴포넌트로 Stars 컴포넌트를 대체합시다:

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

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

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

이제 우리는 다음과 같은 혼란스러운 하늘을 갖게 됩니다:

Custom starry sky filled with planes

좋은 출발점입니다!

별의 크기를 조정해 봅시다. 파티클의 위치를 설정하는 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} />;
};

이제 더 나아졌습니다. 우리는 다양한 크기의 별을 가지게 되었습니다:

Custom starry sky with different sizes

하지만 문제가 있습니다. 별들은 randFloatSpread(20)을 사용하여 -20에서 20 사이에 위치하지만, 우리는 별들이 하늘에서 멀리 위치하길 원합니다.

이를 위해 z는 항상 0으로 유지하고 x 위치를 5에서 15 사이로 조정합니다.

Graph explaining the x axis repartition

별들은 x 축에서 5에서 15 사이에 무작위로 위치하게 될 것입니다.

그리고 중심 주위에 모든 별들이 있도록 y 위치를 0에서 2 * Math.PI 사이로 회전시킵니다.

Graph explaining the y axis repartition

중심에는 별이 없으며, 별들은 모든 방향으로 퍼지게 됩니다.

파티클의 위치를 설정하는 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.