Partículas

Starter pack

As partículas são uma ótima maneira de adicionar vida à sua cena. Elas podem ser usadas de várias maneiras, como neve, chuva, fogo, fumaça ou efeitos mágicos. Muitas vezes são usadas para criar efeitos atmosféricos, como neblina, poeira ou faíscas.

Nesta lição, vamos explorar diferentes maneiras de criar partículas usando Threejs e React Three Fiber para criar esta cena noturna com um céu estrelado e efeito de queda de neve:

Veja como os flocos de neve caem e as estrelas piscam no céu. ❄️✨

Estrelas

Nosso código inicial contém esta "Cena de Inverno Low Poly" por EdwiixGG em cima de um cubo e uma fonte de luz animada.

Parece bom, mas podemos torná-lo mais interessante adicionando estrelas ao céu.

Vamos começar adicionando estrelas ao céu. A maneira mais simples com React Three Fiber é usar o componente Stars da biblioteca drei.

No components/Experience.jsx:

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

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

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

Céu estrelado

E voilà, nosso céu agora está preenchido com lindas estrelas brilhantes!

Podemos brincar com seus parâmetros, como factor para ajustar o tamanho com base na distância ou speed para ajustar o tempo do efeito de fade.

Consulte a documentação para todos os parâmetros disponíveis.

Vamos ver como funciona por dentro navegando pelo código fonte do componente Stars.

Podemos ver que para renderizar as estrelas, estão usando pontos preenchidos com três atributos na geometria:

  • position: para determinar a posição de cada estrela
  • colors: para determinar a cor de cada estrela
  • size: para determinar o tamanho de cada estrela

Então, um ShaderMaterial personalizado chamado StarfieldMaterial é responsável por exibir os pontos corretamente com base nesses atributos e no uniform time para o efeito de fade.

Antes de tudo, essa abordagem é ótima, é leve e processada completamente na GPU, o que significa que você poderia potencialmente colocar um número muito grande de estrelas.

Mas visualmente vejo duas coisas que poderiam ser melhoradas:

  • As estrelas são representadas como quadrados.
  • O efeito de fade é sincronizado entre todas as estrelas, resultando em um efeito de pisca-pisca.

Como não temos controle sobre esses aspectos com o componente Stars, vamos criar nosso próprio sistema de estrelas!

Estrelas Personalizadas

Para ter um controle mais fácil sobre as estrelas, lidaremos com sua lógica no lado da CPU usando instancing.

Não se preocupe se não for a maneira mais otimizada; para um número razoável de estrelas, isso funcionará bem e será muito mais flexível. Aprenderemos a lidar com nossas partículas no lado da GPU quando construirmos nosso simples motor de VFX e ao aprender TSL mais adiante neste capítulo.

PS: Instancing ainda é uma maneira eficiente de renderizar um grande número de objetos com a mesma geometria, como visto na lição de otimização.

Instâncias

Vamos começar criando nosso próprio componente StarrySky em um novo arquivo 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} />;
};

Estamos criando uma InstancedMesh usando uma plane geometry combinada com um mesh basic material.

Graças ao componente <Instance /> do Drei, somos capazes de criar instâncias desse mesh e controlar cada partícula (instância) individualmente.

Agora vamos substituir o componente Stars pelo nosso personalizado em components/Experience.jsx:

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

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

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

Agora temos este céu caótico:

Custom starry sky filled with planes

É um bom ponto de partida!

Vamos ajustar o tamanho das estrelas. No useMemo responsável por definir as posições das partículas, podemos adicionar um atributo 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),
    })),
  []
);

E no componente Particle, podemos passar esse atributo size para o componente Instance:

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

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

Agora está melhor, temos estrelas de diferentes tamanhos:

Custom starry sky with different sizes

Mas temos um problema, as estrelas estão posicionadas entre -20 a 20 usando randFloatSpread(20), mas queremos que as estrelas sejam posicionadas longe no céu.

Para fazer isso, vamos manter o z sempre em 0 e ajustar a posição x para estar entre 5 e 15.

Graph explaining the x axis repartition

Nossas estrelas serão posicionadas aleatoriamente entre 5 e 15 no eixo x.

E para estar ao redor do centro, giramos a posição y entre 0 e 2 * Math.PI.

Graph explaining the y axis repartition

O centro nunca conterá estrelas e as estrelas serão espalhadas em todas as direções.

No useMemo responsável por definir as posições das partículas, podemos ajustar o atributo position e adicionar um atributo 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.