Wawa Sensei logo

Render Target

Starter pack

Cuando usamos React Three Fiber simplemente añadimos un componente Canvas y ponemos nuestra escena 3D dentro, y se renderizará en la pantalla.

Si tienes algo de experiencia con Three.js, sabes que primero necesitamos crear un WebGLRenderer y luego llamar a renderer.render(scene, camera) para renderizar nuestra escena en la pantalla.

Afortunadamente, React Three Fiber hace todo esto por nosotros tras bambalinas, pero para casos de uso más avanzados, es importante saber cómo funciona.

El rol del renderer es procesar la escena 3D para crear la imagen 2D real que vemos en la pantalla.

Por defecto, la salida del renderer está configurada para el componente Canvas y se muestra en la pantalla, pero también podemos mostrarla en una textura (a través de un WebGLRenderTarget).

Por ahora puede parecer un poco conceptual, veamos cómo funciona en la práctica y de qué maneras creativas podemos usarlo.

Cámara de Seguridad

Para entender cómo funcionan los Render Targets y qué podemos hacer con ellos, preparé una escena 3D que contiene:

  • Un modelo 3D de una sala de estar de Alex Safayan CC-BY vía Poly Pizza
  • Un avatar de Ready Player Me (como el que usamos en las lecciones de portfolio) viendo a Bob Esponja en la TV
  • Un control remoto 2D con múltiples botones

Avatar 3D viendo la TV

El objetivo es crear un sistema de vigilancia renderizando la escena desde el punto de vista de una cámara de seguridad en la TV.

Renderizando la escena a una textura

Para renderizar nuestra escena actual a una textura, necesitaremos crear un Render Target.

Gracias a la biblioteca Drei, podemos crear fácilmente un Render Target con el hook useFBO:

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

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

Si quieres saber cómo crear un Render Target desde cero, puedes consultar el código fuente del hook useFBO aquí. Es un buen reflejo tener para entender cómo funcionan las cosas bajo el capó.

Ahora que tenemos un Render Target, necesitamos decirle a nuestro renderer que renderice nuestra scene usando nuestra camera.

Todos esos objetos están disponibles en el root state de nuestra aplicación React Three Fiber. Este es el objeto que retorna el hook useThree.

La propiedad gl es el renderer.

Pero, como queremos mostrar lo que está sucediendo en tiempo real en el TV, realizaremos el proceso en cada frame usando el hook useFrame.

La función de callback incluye el root state, por lo que podemos acceder a nuestras variables directamente desde él:

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

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

Lo que estamos haciendo aquí es:

  • Configurar el Render Target como la salida del renderer
  • Renderizar la scene usando la camera y, como la salida del renderer está configurada en el Render Target, renderizará la escena en él
  • Configurar la salida del renderer a null para renderizar la escena en el lienzo de nuevo

Ahora que tenemos nuestro Render Target, necesitamos mostrarlo en el TV. Vamos a añadir una referencia al material que actualmente renderiza el video:

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

Y luego, podemos usar el hook useFrame para actualizar la propiedad map del material con nuestro Render Target:

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

3D avatar watching to the TV with the security camera view

Ahora podemos ver la vista de la cámara de seguridad en el TV, pero la pantalla del TV en la vista de la cámara de seguridad está vacía.

Esto se debe a que cuando renderizamos la escena en el Render Target, nuestra pantalla está vacía.

Efecto Inception

Para ver la vista de la cámara de seguridad dentro de la pantalla de la TV de la pantalla de la TV 🤯, necesitamos:

  • Crear otro render target (lo nombraremos bufferRenderTarget)
  • Renderizar la escena en él
  • Adjuntarlo al material de la pantalla de la TV
  • Renderizar la escena en el cornerRenderTarget
  • Adjuntarlo al material de la 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;
  });
  // ...
};

Podría sonar aterrador, pero en realidad es bastante simple. Solo piensa en lo que está ocurriendo en cada paso.

¡Ahora nuestra escena se renderiza infinitamente dentro de la pantalla de la TV!

Aunque es el efecto más realista que podemos crear, no es el más creativo. Vamos a eliminar el efecto inception y mostrar Spongebob Squarepants en la pantalla en su lugar:

End of lesson preview

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