Hạt Phân

Starter pack

Hạt phân là một cách tuyệt vời để thêm sức sống vào cảnh của bạn. Chúng có thể được sử dụng theo nhiều cách khác nhau, chẳng hạn như tuyết, mưa, lửa, khói hoặc hiệu ứng ma thuật. Chúng thường được sử dụng để tạo hiệu ứng không khí, chẳng hạn như sương mù, bụi hoặc tia lửa.

Trong bài học này, chúng ta sẽ khám phá các cách khác nhau để tạo hạt bằng cách sử dụng Threejs và React Three Fiber để tạo ra cảnh tuyết đêm này với bầu trời đầy sao và hiệu ứng tuyết rơi:

Hãy xem cách mà bông tuyết rơi và các ngôi sao lấp lánh trên bầu trời. ❄️✨

Ngôi sao

Mã khởi động của chúng ta chứa "Cảnh Mùa Đông Low Poly" của EdwiixGG trên một khối lập phương và một nguồn sáng hoạt hình.

Trông đẹp nhưng chúng ta có thể làm cho nó thú vị hơn bằng cách thêm sao vào bầu trời.

Hãy bắt đầu bằng cách thêm sao vào bầu trời. Cách đơn giản nhất với React Three Fiber là sử dụng Stars component từ thư viện drei.

Trong components/Experience.jsx:

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

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

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

Bầu trời đầy sao

Và voilà, bầu trời của chúng ta giờ đã đầy những ngôi sao phát sáng, đẹp tuyệt!

Chúng ta có thể chơi với các tham số của nó như factor để điều chỉnh kích thước dựa trên khoảng cách hoặc speed để điều chỉnh thời gian của hiệu ứng fade.

Tham khảo tài liệu để biết tất cả các tham số sẵn có.

Hãy kiểm tra cách nó hoạt động dưới lớp bằng cách duyệt mã nguồn của Stars component.

Chúng ta có thể thấy rằng để render các ngôi sao, họ đang sử dụng points được đổ đầy ba thuộc tính trên hình học:

  • position: để xác định vị trí từng ngôi sao
  • colors: để xác định màu sắc từng ngôi sao
  • size: để xác định kích thước từng ngôi sao

Sau đó, một ShaderMaterial tùy chỉnh có tên StarfieldMaterial chịu trách nhiệm hiển thị các điểm dựa vào attributes valuestime uniform cho hiệu ứng fade.

Trước hết, cách tiếp cận này rất tuyệt, nó nhẹ và hoàn toàn được xử lý trên GPU có nghĩa là bạn có thể tạo ra một số lượng ngôi sao rất lớn.

Nhưng với mặt hình ảnh, tôi thấy hai điều có thể cải thiện:

  • Các ngôi sao được biểu diễn dưới dạng hình vuông.
  • Hiệu ứng fade được đồng bộ hóa giữa tất cả các ngôi sao dẫn đến hiệu ứng nhấp nháy.

Vì chúng ta không có khả năng kiểm soát những khía cạnh đó với Stars component, hãy tạo hệ thống sao riêng của chúng ta!

Ngôi Sao Tuỳ Chỉnh

Để dễ dàng kiểm soát các ngôi sao hơn, chúng ta sẽ quản lý logic của chúng trên phía CPU bằng cách sử dụng instancing.

Đừng lo lắng nếu đây không phải là cách tối ưu nhất, với số lượng ngôi sao hợp lý thì sẽ ổn và linh hoạt hơn nhiều. Chúng ta sẽ học cách xử lý các hạt trên phía GPU khi chúng ta xây dựng VFX engine đơn giản của mình và khi học TSL trong chương này.

PS: Instancing vẫn là một cách hiệu quả để render số lượng lớn các vật thể có cùng hình dạng như đã thấy trong bài học tối ưu hoá.

Instances

Bắt đầu bằng việc tạo component StarrySky của riêng chúng ta trong file mới components/StarrySky.jsx:

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

Chúng ta đang tạo một InstancedMesh sử dụng plane geometry kết hợp với mesh basic material.

Nhờ component <Instance /> từ Drei, chúng ta có thể tạo các instance của mesh này và kiểm soát từng hạt (instance) riêng lẻ.

Bây giờ hãy thay thế component Stars với component tuỳ chỉnh của chúng ta trong components/Experience.jsx:

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

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

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

Bây giờ chúng ta có bầu trời hỗn loạn này:

Custom starry sky filled with planes

Đây là một điểm khởi đầu tốt!

Hãy điều chỉnh kích thước của các ngôi sao. Trong useMemo chịu trách nhiệm đặt vị trí các hạt, chúng ta có thể thêm một thuộc tính 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),
    })),
  []
);

Và trong component Particle, chúng ta có thể truyền thuộc tính size này cho component Instance:

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

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

Bây giờ thì tốt hơn rồi, chúng ta có các ngôi sao với kích thước khác nhau:

Custom starry sky with different sizes

Nhưng chúng ta có một vấn đề, các ngôi sao được đặt giữa -2020 sử dụng randFloatSpread(20) nhưng chúng ta muốn các ngôi sao được đặt xa hơn trên bầu trời.

Để làm điều này, hãy giữ z luôn ở 0 và điều chỉnh vị trí x nằm giữa 515.

Graph explaining the x axis repartition

Các ngôi sao của chúng ta sẽ được đặt ngẫu nhiên giữa 515 trên trục x.

Và để ở xung quanh trung tâm chúng ta xoay vị trí y giữa 02 * Math.PI.

Graph explaining the y axis repartition

Trung tâm sẽ không bao giờ có ngôi sao và các ngôi sao sẽ được phân bố theo mọi hướng.

Trong useMemo chịu trách nhiệm đặt vị trí các hạt, chúng ta có thể điều chỉnh thuộc tính position và thêm một thuộc tính 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.