Vệt Sáng

Starter pack

Hãy khám phá thế giới của vệt sáng! Vệt sáng là một cách tuyệt vời để thêm cảm giác chuyển động vào cảnh của bạn. Chúng có thể được sử dụng để tạo ra nhiều hiệu ứng, như vệt sáng, vệt khói, hoặc thậm chí là vệt của một vật thể đang di chuyển.

Đây là dự án cuối cùng mà chúng ta sẽ cùng xây dựng:

Chúng ta sẽ bắt đầu bằng cách tạo một hiệu ứng vệt sáng đơn giản bằng cách sử dụng một con trỏ vệt sáng tùy chỉnh. Sau đó, chúng ta sẽ khám phá Trail component từ drei để tạo ra các sao chổi mà bạn đã thấy trong phần xem trước.

Dự án khởi đầu

Dự án khởi đầu chứa nhiều thứ mà chúng ta đã đề cập trong các bài học trước:

Ngoài ra, tôi đã sử dụng Tailwind CSS để nhanh chóng thiết kế giao diện người dùng. Nếu bạn không quen thuộc với Tailwind CSS, bạn có thể bỏ qua phần giao diện người dùng và tập trung vào phần Threejs.

Các mô hình WawaCoinWawaCard được tạo nội bộ và có sẵn trong dự án khởi đầu. Tôi đã sử dụng MeshTransmissionMaterial từ drei để tạo ra cái nhìn tương lai này.

Cảm giác tự do để biến đổi cảnh theo ý bạn. Bạn có thể tự do sử dụng lại bất kỳ phần nào của dự án trong các dự án của riêng bạn.

Tôi đã quên đề cập, nhưng nội dung của trang web là hoàn toàn hư cấu. Tôi không sắp ra mắt một loại tiền điện tử mới. (Chưa? 👀)

Con trỏ tùy chỉnh với hiệu ứng trail

Hãy bắt đầu bằng cách tạo một hiệu ứng trail đơn giản theo dõi theo con trỏ.

Tạo một tệp mới components/Cursor.jsx và thêm mã sau:

import { useFrame } from "@react-three/fiber";
import { useControls } from "leva";
import { useRef } from "react";
export const Cursor = () => {
  const { color, intensity, opacity, size } = useControls("Cursor", {
    size: { value: 0.2, min: 0.1, max: 3, step: 0.01 },
    color: "#dfbcff",
    intensity: { value: 4.6, min: 1, max: 10, step: 0.1 },
    opacity: { value: 0.5, min: 0, max: 1, step: 0.01 },
  });
  const target = useRef();
  useFrame(({ clock }) => {
    if (target.current) {
      const elapsed = clock.getElapsedTime();
      target.current.position.x = Math.sin(elapsed) * 5;
      target.current.position.y = Math.cos(elapsed * 2) * 4;
      target.current.position.z = Math.sin(elapsed * 4) * 10;
    }
  });
  return (
    <>
      <group ref={target}>
        <mesh>
          <sphereGeometry args={[size / 2, 32, 32]} />
          <meshStandardMaterial
            color={color}
            transparent
            opacity={opacity}
            emissive={color}
            emissiveIntensity={intensity}
          />
        </mesh>
      </group>
    </>
  );
};

Đây là một hình cầu đơn giản di chuyển theo một đường sóng sinus. Bạn có thể điều chỉnh kích thước, màu sắc, cường độ, và độ mờ của con trỏ bằng sử dụng Leva controls.

Tạm thời chúng ta sử dụng chuyển động cố định để đơn giản hóa việc hiển thị trail. Chúng ta sẽ thay thế nó bằng vị trí chuột sau.

Thêm thành phần Cursor vào thành phần Experience:

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

export const Experience = () => {
  // ...
  return (
    <>
      <Cursor />
      {/* ... */}
    </>
  );
};

// ...

Chúng ta có thể thấy một hình cầu di chuyển, nó sẽ là mục tiêu cho trail của chúng ta.

Thành phần SimpleTrail

group là mục tiêu mà đường mòn của chúng ta sẽ theo dõi. Chúng ta sẽ tạo một thành phần mới components/SimpleTrail.jsx để tạo hiệu ứng đường mòn:

import { useRef } from "react";
import * as THREE from "three";

export function SimpleTrail({
  target = null,
  color = "#ffffff",
  intensity = 6,
  numPoints = 20,
  height = 0.42,
  minDistance = 0.1,
  opacity = 0.5,
  duration = 20,
}) {
  const mesh = useRef();

  return (
    <>
      <mesh ref={mesh}>
        <planeGeometry args={[1, 1, 1, numPoints - 1]} />
        <meshBasicMaterial
          color={color}
          side={THREE.DoubleSide}
          transparent={true}
          opacity={opacity}
          depthWrite={false}
        />
      </mesh>
    </>
  );
}

Các tham số bao gồm:

  • target: ref của mục tiêu cần theo dõi.
  • color: màu của đường mòn.
  • intensity: độ mạnh của độ phát quang của đường mòn.
  • numPoints: số vị trí sẽ lưu trữ trong đường mòn. (Số càng cao, đường mòn càng dài).
  • height: chiều cao của đường mòn.
  • minDistance: khoảng cách tối thiểu giữa hai điểm.
  • opacity: độ mờ của đường mòn.
  • duration: thời gian trước khi đường mòn bắt đầu mờ dần từ đầu đến cuối.

Không cần lo lắng nếu bạn chưa hiểu hết các tham số này. Chúng ta sẽ giải thích chúng trong quá trình triển khai đường mòn.

Import thành phần SimpleTrail vào thành phần Cursor:

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

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

  return (
    <>
      <group ref={target}>{/* ... */}</group>
      <SimpleTrail
        target={target}
        color={color}
        intensity={intensity}
        opacity={opacity}
        height={size}
      />
    </>
  );
};

mesh được tạo thành từ <planeGeometry /> với số đoạn bằng numPoints. Chúng ta sẽ cập nhật vị trí của từng đoạn để theo dõi mục tiêu.

SimpleTrail

Về trực quan, kích thước của mặt phẳng là 1x1, chúng ta có thể thấy một hình vuông, nhưng dựa vào số lượng đoạn, chúng ta có thể thay đổi các đỉnh để tạo hiệu ứng đường mòn.

Hãy xem bên cạnh một plane với một đoạn và một plane với 20 đoạn:

<group position-x={5}>
  <mesh position-x={4} scale-y={5}>
    <planeGeometry args={[1, 1, 1, numPoints - 1]} />
    <meshBasicMaterial color={"red"} wireframe />
  </mesh>
  <mesh position-x={2} scale-y={5}>
    <planeGeometry args={[1, 1, 1, 1]} />
    <meshBasicMaterial color={"red"} wireframe />
  </mesh>
</group>

Đoạn mã này chỉ dành cho mục đích minh họa. Bạn có thể xóa sau khi đã hiểu rõ khái niệm.

Chúng ta phóng to chúng trên trục y để thấy sự khác biệt về số lượng đoạn.

Representation of the segments

Bạn có thể thấy mặt phẳng bên trái chỉ có 4 đỉnh trong khi mặt phẳng bên phải có nhiều hơn. Chúng ta sẽ điều chỉnh các đỉnh này để xây dựng hiệu ứng đường mòn.

Chúng ta có thể sử dụng line thay vì plane để tạo đường mòn, nhưng việc sử dụng plane cho phép chúng ta tạo ra những hiệu ứng thú vị hơn (Chẳng hạn, phù hợp hơn cho cơn gió).

Thành phần Trail từ drei sử dụng line, chúng ta không muốn lặp lại cùng một thứ.

Thao tác với các đỉnh

Chúng ta sẽ cập nhật vị trí của các đỉnh của mặt phẳng để theo dõi mục tiêu theo thời gian.

Trước tiên, chúng ta cần lưu trữ tất cả các vị trí của mục tiêu trong một mảng. Chúng ta sẽ sử dụng một ref để lưu trữ các vị trí.

// ...
import * as THREE from "three";

export function SimpleTrail(
  {
    // ...
  }
) {
  const mesh = useRef();
  const positions = useRef(
    new Array(numPoints).fill(new THREE.Vector3(0, 0, 0))
  );
  // ...
}

Mảng này sẽ luôn có độ dài của numPoints và sẽ lưu trữ các vị trí của mục tiêu.

Khi mục tiêu di chuyển, chúng ta sẽ thêm vị trí mới vào đầu mảng, đẩy các vị trí khác về cuối.

Graph explaining the position array

Để thực hiện điều này, chúng ta sẽ sử dụng hook useFrame để cập nhật vị trí của các đỉnh.

// ...
import { useFrame } from "@react-three/fiber";

export function SimpleTrail(
  {
    // ...
  }
) {
  // ...

  useFrame(() => {
    if (!mesh.current || !target?.current) {
      return;
    }

    const curPoint = target.current.position;
    const lastPoint = positions.current[0];

    const distanceToLastPoint = lastPoint.distanceTo(target.current.position);

    if (distanceToLastPoint > minDistance) {
      positions.current.unshift(curPoint.clone());
      positions.current.pop();
    }
  });

  // ...
}

Đầu tiên, chúng ta tính toán khoảng cách giữa điểm cuối và điểm hiện tại. Nếu khoảng cách lớn hơn minDistance, chúng ta thêm điểm hiện tại vào đầu mảng bằng unshift và loại bỏ điểm cuối cùng bằng pop.

Bây giờ chúng ta cần cập nhật vị trí của các đỉnh của mặt phẳng để theo dõi các vị trí của mục tiêu.

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.