Pháo hoa

Starter pack

Chào mừng đến với Cuộc phiêu lưu trên bầu trời, một công ty tương lai cung cấp màn pháo hoa tuyệt nhất trong thiên hà! 🎇

Chúng ta sẽ tạo ra một trang web 3D để giới thiệu màn pháo hoa của chúng ta bằng cách sử dụng Three.js, React Three Fiber và động cơ VFX của chúng ta.

Đây là những gì chúng ta sẽ cùng nhau xây dựng!

Dự án khởi động

Dự án khởi động của chúng ta đã bao gồm các thành phần sau:

  • Thiết lập cơ bản của React Three Fiber với một hòn đảo nổi đẹp mắt từ đó chúng ta sẽ phóng pháo hoa.
  • Hiệu ứng xử lý hậu kỳ để làm nổi bật ánh sáng từ mô hình (và sau này là pháo hoa).
  • Giao diện đơn giản được làm bằng Tailwind CSS với ba nút để sau này phóng pháo hoa.

Bản xem trước Cuộc phiêu lưu trên bầu trời với đảo nổi

Đây là những gì chúng ta nhận được khi chạy dự án khởi động.

Pháo hoa

Để tạo ra pháo hoa, chúng ta sẽ sử dụng động cơ VFX mà chúng ta đã xây dựng ở bài học trước. Động cơ này cho phép chúng ta tạo và quản lý nhiều hệ thống hạt với các hành vi khác nhau.

useFireworks

Để quản lý hiệu quả pháo hoa, chúng ta sẽ tạo một custom hook gọi là useFireworks. Hook này sẽ xử lý việc tạo và quản lý pháo hoa.

Hãy thêm thư viện zustand vào dự án của chúng ta:

yarn add zustand

Bây giờ, trong một thư mục tên là hooks, tạo một file mới gọi là useFireworks.js:

import { create } from "zustand";

const useFireworks = create((set, get) => {
  return {
    fireworks: [],
  };
});

export { useFireworks };

Một kho chứa đơn giản với một mảng trống chứa pháo hoa.

Hãy thêm một phương thức để tạo pháo hoa:

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

const useFireworks = create((set) => {
  return {
    fireworks: [],
    addFirework: () => {
      set((state) => {
        return {
          fireworks: [
            ...state.fireworks,
            {
              id: `${Date.now()}-${randInt(0, 100)}-${state.fireworks.length}`,
              position: [0, 0, 0],
              velocity: [randFloat(-8, 8), randFloat(5, 10), randFloat(-8, 8)],
              delay: randFloat(0.8, 2),
              color: ["skyblue", "pink"],
            },
          ],
        };
      });
    },
  };
});

// ...

addFirework sẽ thêm pháo hoa mới vào kho chứa với:

  • id: một định danh duy nhất để sử dụng làm key trong thành phần React.
  • position: vị trí mà pháo hoa bắt đầu.
  • velocity: hướng và tốc độ của pháo hoa trước khi nổ.
  • delay: thời gian trước khi pháo hoa nổ.
  • color: một mảng các màu để dùng cho các hạt nổ của pháo hoa.

Chúng ta có thể nối kết hook này vào giao diện UI của chúng ta để kích hoạt pháo hoa.

Mở UI.jsx và kết nối phương thức addFirework với các nút:

import { useFireworks } from "../hooks/useFireworks";

export const UI = () => {
  const addFirework = useFireworks((state) => state.addFirework);

  return (
    <section className="fixed inset-0 z-10 flex items-center justify-center">
      {/* ... */}
      <div
      // ...
      >
        {/* ... */}
        <div className="flex gap-4">
          <button
            // ..
            onClick={addFirework}
          >
            🎆 Classic
          </button>
          <button
            // ..
            onClick={addFirework}
          >
            💖 Love
          </button>
          <button
            // ..
            onClick={addFirework}
          >
            🌊 Sea
          </button>
        </div>
        {/* ... */}
      </div>
    </section>
  );
};

Để kiểm tra xem nó có hoạt động không, hãy tạo một thành phần Fireworks.jsx. Chúng ta sẽ đơn giản chỉ ghi log pháo hoa ra console vào lúc này:

import { useFireworks } from "../hooks/useFireworks";

export const Fireworks = () => {
  const fireworks = useFireworks((state) => state.fireworks);

  console.log(fireworks);
};

Và thêm nó vào trong thành phần Experience của chúng ta:

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

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

  return (
    <>
      {/* ... */}

      <Float
        speed={0.6}
        rotationIntensity={2}
        position-x={4}
        floatIntensity={2}
      >
        <Fireworks />
        <Gltf src="/models/SkyIsland.glb" />
      </Float>

      {/* ... */}
    </>
  );
};

Chúng ta import thành phần Fireworks và thêm nó bên cạnh SkyIsland trong <Float />.

Fireworks in the console

Khi nhấn các nút, chúng ta có thể thấy trong console rằng pháo hoa được thêm đúng cách vào trong kho.

Trước khi hiển thị chúng trong cảnh 3D, chúng ta cần xử lý vòng đời của pháo hoa. Hiện tại, pháo hoa được thêm vào nhưng không bao giờ bị loại bỏ.

Trong addFirework của useFireworks hook, chúng ta có thể thêm một setTimeout để loại bỏ pháo hoa sau một khoảng thời gian nhất định.

Đầu tiên, chúng ta cần biết khi nào pháo hoa xuất hiện. Chúng ta có thể thêm một thuộc tính time vào đối tượng pháo hoa:

{
  id: `${Date.now()}-${randInt(0, 100)}-${state.fireworks.length}`,
  // ...
  time: Date.now(),
},

Sau đó gọi setTimeout để loại bỏ pháo hoa đã nổ và đã mờ dần:

addFirework: () => {
  set((state) => {
    // ...
  });
  setTimeout(() => {
    set((state) => ({
      fireworks: state.fireworks.filter(
        (firework) => Date.now() - firework.time < 4000 // Max delay of 2 seconds +  Max lifetime of particles of 2 seconds
      ),
    }));
  }, 4000);
},

Chúng ta lọc các pháo hoa đã được thêm vào hơn 4 giây trước. Bằng cách này, chúng ta giữ lại những pháo hoa còn hoạt động.

Điều chỉnh thời gian theo cấu hình cuối cùng bạn sẽ sử dụng cho pháo hoa.

Fireworks in the console

Khi nhấn các nút, chúng ta có thể thấy trong console rằng pháo hoa được loại bỏ đúng cách sau một khoảng thời gian nhất định.

Bây giờ, chúng ta có thể đi sâu vào phần mà bạn đang mong chờ: tạo pháo hoa trong cảnh 3D!

Công cụ VFX (Wawa VFX)

Để không phải sao chép/dán thành phần từ bài học trước, tôi đã xuất bản công cụ VFX dưới dạng một package trên npm: Wawa VFX. Bạn có thể cài đặt nó bằng cách chạy:

yarn add wawa-vfx@^1.0.0

Bằng cách sử dụng @^1.0.0, chúng ta đảm bảo luôn sử dụng phiên bản chính 1 của package nhưng bao gồm các phiên bản phụ và vá lỗi mới nhất. Bằng cách này, chúng ta có thể tận dụng các tính năng mới nhất và sửa lỗi mà không gặp thay đổi phá vỡ.

Giờ đây, chúng ta có thể sử dụng <VFXParticles /><VFXEmitter /> trong dự án của mình!

Trong phần thực nghiệm, hãy thêm thành phần VFXParticles vào cảnh:

// ...
import { VFXParticles } from "wawa-vfx";

export const Experience = () => {
  const controls = useRef();

  return (
    <>
      {/* ... */}

      <VFXParticles
        name="firework-particles"
        settings={{
          nbParticles: 100000,
          gravity: [0, -9.8, 0],
          renderMode: "billboard",
          intensity: 3,
        }}
      />

      <EffectComposer>
        <Bloom intensity={1.2} luminanceThreshold={1} mipmapBlur />
      </EffectComposer>
    </>
  );
};

Chúng ta thêm thành phần VFXParticles với các cài đặt sau:

  • 100000 hạt. Khi chúng ta đạt giới hạn, các hạt cũ nhất sẽ bị xóa, số lượng này nên đủ cho nhiều pháo hoa cùng lúc.
  • gravity để mô phỏng lực hấp dẫn trên các hạt. -9.8 là lực hấp dẫn trên Trái Đất. (Nhưng chờ đã, chúng ta đang ở trong không gian! 👀)
  • renderModebillboard để luôn hướng về phía camera.
  • intensity3 để làm cho các hạt sáng lên trong bầu trời đêm.

Vị trí đặt thành phần <VFXParticles /> không quá quan trọng. Chỉ cần đảm bảo nó ở cấp độ cao nhất của cảnh.

Và hãy thêm thành phần VFXEmitter bên cạnh VFXParticles để định hình vụ nổ trong chế độ debug và có quyền truy cập vào các điều khiển hiển thị:

// ...
import { VFXEmitter, VFXParticles } from "wawa-vfx";

export const Experience = () => {
  const controls = useRef();

  return (
    <>
      {/* ... */}

      <VFXParticles
        name="firework-particles"
        settings={{
          nbParticles: 100000,
          gravity: [0, -9.8, 0],
          renderMode: "billboard",
          intensity: 3,
        }}
      />
      <VFXEmitter emitter="firework-particles" debug />

      {/* ... */}
    </>
  );
};

Hãy chắc chắn rằng giá trị emitter được đặt giống với name của thành phần VFXParticles và đặt debugtrue để thấy các điều khiển.

Phác thảo một phiên bản đầu tiên của vụ nổ pháo hoa. 💥

Khi bạn đã hài lòng với các cài đặt, bạn có thể loại bỏ thuộc tính debug từ thành phần VFXEmitter, nhấn nút xuất và dán các cài đặt vào thành phần VFXEmitter.

<VFXEmitter
  emitter="firework-particles"
  settings={{
    nbParticles: 5000,
    delay: 0,
    spawnMode: "burst",
    colorStart: ["skyblue", "pink"],
    particlesLifetime: [0.1, 2],
    size: [0.01, 0.4],
    startPositionMin: [-0.1, -0.1, -0.1],
    startPositionMax: [0.1, 0.1, 0.1],
    directionMin: [-1, -1, -1],
    directionMax: [1, 1, 1],
    startRotationMin: [degToRad(-90), 0, 0],
    startRotationMax: [degToRad(90), 0, 0],
    rotationSpeedMin: [0, 0, 0],
    rotationSpeedMax: [3, 3, 3],
    speed: [1, 12],
  }}
/>

Chúng ta đã sẵn sàng để kết nối pháo hoa với công cụ VFX!

Pháo hoa nổ

Bên trong file Fireworks.jsx, hãy tạo một component Firework đại diện cho một quả pháo hoa:

// ...
import { useRef } from "react";
import { VFXEmitter } from "wawa-vfx";

export const Fireworks = () => {
  // ...
};

const Firework = ({ velocity, delay, position, color }) => {
  const ref = useRef();
  return (
    <>
      <group ref={ref} position={position}>
        <VFXEmitter
          emitter="firework-particles"
          settings={{
            nbParticles: 5000,
            delay: 0,
            spawnMode: "burst",
            colorStart: ["skyblue", "pink"],
            particlesLifetime: [0.1, 2],
            size: [0.01, 0.4],
            startPositionMin: [-0.1, -0.1, -0.1],
            startPositionMax: [0.1, 0.1, 0.1],
            directionMin: [-1, -1, -1],
            directionMax: [1, 1, 1],
            startRotationMin: [degToRad(-90), 0, 0],
            startRotationMax: [degToRad(90), 0, 0],
            rotationSpeedMin: [0, 0, 0],
            rotationSpeedMax: [3, 3, 3],
            speed: [1, 12],
          }}
        />
      </group>
    </>
  );
};

Đơn giản là cắt/dán VFXEmitter từ component Experience sang component Firework.

Chúng ta bọc nó trong một thẻ <group /> để có thể di chuyển pháo hoa trong không gian dựa trên positionvelocity.

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.