Fundamentals
Core
Master
Shaders
Render Target
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
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 acamera
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; }); // ... };
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.