Trilhas

Starter pack

Vamos mergulhar no mundo das trilhas! As trilhas são uma ótima maneira de adicionar uma sensação de movimento à sua cena. Elas podem ser usadas para criar uma variedade de efeitos, como trilhas de luz, trilhas de fumaça ou até mesmo a trilha de um objeto em movimento.

Aqui está o projeto final que construiremos juntos:

Começaremos criando um efeito simples de trilha usando um cursor de trilha personalizado. Em seguida, exploraremos o componente Trail do drei para criar os cometas que você viu na pré-visualização.

Projeto Inicial

O projeto inicial contém muitos elementos que já abordamos em lições anteriores:

Além disso, usei o Tailwind CSS para projetar rapidamente a interface do usuário. Se não estiver familiarizado com o Tailwind CSS, você pode pular a parte da interface do usuário e se concentrar na parte do Threejs.

Os modelos WawaCoin e WawaCard são feitos internamente e estão disponíveis no projeto inicial. Usei o MeshTransmissionMaterial do drei para criar esse visual futurista.

Sinta-se à vontade para transformar a cena ao seu gosto. Você pode reutilizar livremente qualquer parte do projeto em seus próprios projetos.

Esqueci de mencionar, mas o conteúdo do site é puramente fictício. Eu não estou lançando uma nova criptomoeda. (Ainda não? 👀)

Cursor customizado com rastro

Vamos começar criando um efeito simples de rastro seguindo o cursor.

Crie um novo arquivo components/Cursor.jsx e adicione o seguinte código:

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

É uma esfera simples que segue uma onda senoidal. Você pode ajustar o tamanho, cor, intensidade e opacidade do cursor usando os controles Leva.

Por enquanto, usamos um movimento fixo para simplificar a visualização do rastro. Iremos substituí-lo pela posição do mouse depois.

Adicione o componente Cursor ao componente Experience:

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

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

// ...

Podemos ver uma esfera em movimento, que será o alvo do nosso rastro.

Componente SimpleTrail

O group é o alvo que nosso rastro seguirá. Vamos criar um novo componente components/SimpleTrail.jsx para criar o efeito de rastro:

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

Os parâmetros são os seguintes:

  • target: o ref do alvo a ser seguido.
  • color: a cor do rastro.
  • intensity: a intensidade da parte emissiva do rastro.
  • numPoints: o número de posições que armazenaremos no rastro. (Quanto maior o número, mais longo o rastro).
  • height: a altura do rastro.
  • minDistance: a distância mínima entre dois pontos.
  • opacity: a opacidade do rastro.
  • duration: o tempo antes de o rastro começar a desaparecer a partir do seu final.

Não se preocupe se você ainda não entende todos os parâmetros. Vamos explicá-los enquanto implementamos o rastro.

Importe o componente SimpleTrail no componente Cursor:

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

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

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

A mesh é composta por um <planeGeometry /> com um número de segmentos igual a numPoints. Vamos atualizar a posição de cada segmento para seguir o alvo.

SimpleTrail

Visualmente, como o tamanho do nosso plano é 1x1, podemos ver um quadrado, mas por causa do número de segmentos, poderemos manipular os vértices para criar o efeito de rastro.

Vamos ver lado a lado um plane com um segmento e um plane com 20 segmentos:

<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>

Este código é apenas para fins de visualização. Você pode removê-lo após entender o conceito.

Nós os escalamos no eixo y para ver a diferença no número de segmentos.

Representation of the segments

Você pode ver que o plano à esquerda tem apenas 4 vértices enquanto o plano à direita tem muitos mais. Vamos manipular esses vértices para construir o efeito de rastro.

Poderíamos usar uma line em vez de um plane para criar o rastro, mas usar um plane nos permite criar um efeito interessante (Funciona melhor para vento, por exemplo).

O componente Trail do drei usa uma line, nós não queremos recodificar a mesma coisa.

Manipulando os vértices

Atualizaremos a posição dos vértices do plano para seguir o alvo ao longo do tempo.

Primeiro, precisaremos armazenar todas as posições do alvo em um array. Usaremos um ref para armazenar as posições.

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

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

Esse array sempre terá um comprimento de numPoints e armazenará as posições do alvo.

Quando o alvo se mover, adicionaremos a nova posição à frente do array, empurrando as outras posições para trás.

Gráfico explicando o array de posições

Para implementar isso, usaremos o hook useFrame para atualizar a posição dos vértices.

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

  // ...
}

Primeiro, calculamos a distância entre o último ponto e o ponto atual. Se a distância for maior que minDistance, adicionamos o ponto atual à frente do array com unshift e removemos o último ponto com pop.

Agora, precisamos atualizar a posição dos vértices do plano para seguir as posições do alvo.

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.