Render Target

Starter pack

Quando usamos React Three Fiber simplesmente adicionamos um componente Canvas e colocamos nossa cena 3D dentro dele e ele fará a renderização na tela.

Se você tem alguma experiência com Three.js, sabe que primeiro precisamos criar um WebGLRenderer e depois chamar renderer.render(scene, camera) para renderizar nossa cena na tela.

Felizmente, React Three Fiber faz tudo isso por nós nos bastidores, mas para usos mais avançados, é importante saber como isso funciona.

O papel do renderer é processar a cena 3D para criar a imagem 2D que vemos na tela.

Por padrão, a saída do renderer é configurada para o componente Canvas e é exibida na tela, mas também podemos direcioná-la para uma textura (via um WebGLRenderTarget).

Por enquanto, isso pode ser um pouco conceitual, então vamos ver como isso funciona na prática e em quais maneiras criativas podemos usá-lo.

Câmera de Segurança

Para entender como os Render Targets funcionam e o que podemos fazer com eles, preparei uma cena 3D contendo:

  • Um modelo 3D de sala de estar por Alex Safayan CC-BY via Poly Pizza
  • Um avatar do Ready Player Me (como o que usamos nas lições do portfólio) assistindo Bob Esponja na TV
  • Um controle remoto 2D com múltiplos botões

3D avatar assistindo TV

O objetivo é criar um sistema de vigilância renderizando a cena a partir do ponto de vista de uma câmera de segurança na TV.

Renderizando a cena para uma textura

Para renderizar nossa cena atual para uma textura, precisaremos criar um Render Target.

Graças à biblioteca Drei, podemos facilmente criar um Render Target com o hook useFBO:

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

export const Experience = () => {
  const cornerRenderTarget = useFBO();
  // ...
};

Se você quiser saber como criar um Render Target do zero, você pode conferir o código fonte do hook useFBO aqui. É um bom hábito para entender como as coisas funcionam por baixo dos panos.

Agora que temos um Render Target, precisamos informar ao nosso renderer para renderizar nossa scene usando nossa camera.

Todos esses objetos estão disponíveis no root state da nossa aplicação React Three Fiber. Este é o objeto retornado pelo hook useThree.

A propriedade gl é o renderer.

Mas, como queremos exibir o que está acontecendo em tempo real na TV, faremos o processo a cada frame usando o hook useFrame.

A função de callback inclui o root state para que possamos acessar nossas variáveis diretamente dele:

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

export const Experience = () => {
  // ...
  useFrame(({ gl, camera, scene }) => {
    gl.setRenderTarget(cornerRenderTarget);
    gl.render(scene, camera);
    gl.setRenderTarget(null);
  });
  // ...
};

O que estamos fazendo aqui é:

  • Definindo o Render Target como a saída do renderer
  • Renderizando a scene usando a camera e, como a saída do renderer está definida para o Render Target, ele renderizará a cena nele
  • Definindo a saída do renderer para null para renderizar a cena na tela novamente

Agora que temos nosso Render Target, precisamos exibi-lo na TV. Vamos adicionar uma referência ao material que está renderizando o vídeo:

// ...
import { useRef } from "react";

export const Experience = () => {
  // ...
  const tvMaterial = useRef();
  // ...
  return (
    <>
      {/* ... */}
      <group position-y={-0.5}>
        <group>
          <Sky />
          <Avatar rotation-y={Math.PI} scale={0.45} position-z={0.34} />
          <Gltf src="models/Room.glb" scale={0.3} rotation-y={-Math.PI / 2} />
          <mesh position-x={0.055} position-y={0.48} position-z={-0.601}>
            <planeGeometry args={[0.63, 0.44]} />
            <meshBasicMaterial ref={tvMaterial} />
          </mesh>
        </group>
      </group>
      {/* ... */}
    </>
  );
};

E então, podemos usar o hook useFrame para atualizar a propriedade map do material com nosso Render Target:

// ...
export const Experience = () => {
  // ...
  useFrame(({ gl, camera, scene }) => {
    // ...
    tvMaterial.current.map = cornerRenderTarget.texture;
  });
  // ...
};

3D avatar assistindo à TV com a visão da câmera de segurança

Agora podemos ver a visão da câmera de segurança na TV, mas a tela da TV na visão da câmera de segurança está vazia.

Isso ocorre porque, quando renderizamos a cena para o Render Target, nossa tela está vazia.

Efeito de Inception

Para ver a visão da câmera de segurança dentro da tela da TV da tela da TV 🤯, precisamos:

  • Criar outro render target (vamos nomeá-lo de bufferRenderTarget)
  • Renderizar a cena nele
  • Anexá-lo ao material da tela da TV
  • Renderizar a cena no corderRenderTarget
  • Anexá-lo ao material da TV
// ...
export const Experience = () => {
  const bufferRenderTarget = useFBO();

  // ...
  useFrame(({ gl, camera, scene }) => {
    gl.setRenderTarget(bufferRenderTarget);
    gl.render(scene, camera);
    tvMaterial.current.map = bufferRenderTarget.texture;
    gl.setRenderTarget(cornerRenderTarget);
    gl.render(scene, camera);
    gl.setRenderTarget(null);
    tvMaterial.current.map = cornerRenderTarget.texture;
  });
  // ...
};

Pode parecer assustador, mas na verdade é bem simples. Apenas pense sobre o que está acontecendo em cada passo.

Agora nossa cena é infinitamente renderizada dentro da tela da TV!

Mesmo que seja o efeito mais realista que podemos criar, não é o mais criativo. Vamos remover o efeito de inception e exibir Spongebob Squarepants na tela em vez disso:

End of lesson preview

To get access to the entire lesson, you need to purchase the course.