Render Target

Starter pack

React Three Fiberを使用すると、単にCanvasコンポーネントを追加し、3Dシーンを中に配置すると、それが画面にレンダリングされます。

Three.jsの経験がある場合、最初にWebGLRendererを作成し、次にrenderer.render(scene, camera)を呼び出してシーンを画面にレンダリングする必要があることを知っています。

幸いなことに、React Three Fiberはこれをすべて裏で処理してくれますが、より高度な使用例のためには、その仕組みを知っておくことが重要です。

rendererの役割は、3Dシーンを処理して、画面に表示される実際の2Dイメージを作成することです。

デフォルトでは、rendererの出力はCanvasコンポーネントに設定され、画面に表示されますが、テクスチャ(WebGLRenderTarget経由)に出力することもできます。

今のところ、これは少し概念的なものかもしれませんが、実際にどのように機能するのか、そしてどのような創造的な方法でそれを使用できるのかを見てみましょう。

セキュリティカメラ

Render Targetsがどのように機能し、それを使って何ができるかを理解するために、以下を含む3Dシーンを用意しました:

  • Alex Safayanによるリビングルーム3Dモデル CC-BY via Poly Pizza
  • テレビでスポンジボブを見ているReady Player Meアバター(portfolioレッスンで使用するものと同様)
  • 複数のボタンが付いた2Dリモコン

3D avatar watching to the TV

目標は、シーンをセキュリティカメラの視点からテレビにレンダリングすることによって、監視システムを作成することです。

シーンのテクスチャへのレンダリング

現在のシーンをテクスチャにレンダリングするためには、Render Targetを作成する必要があります。

Drei libraryのおかげで、useFBOフックを使用して簡単にRender Targetを作成できます:

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

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

Render Targetをゼロから作成する方法を知りたい方は、useFBOフックのソースコードをこちらで確認できます。裏でどのように動作しているかを理解することは良い習慣です。

Render Targetを作成したら、scenecameraを使ってレンダリングするためにrendererに指示する必要があります。

これらすべてのオブジェクトはReact Three Fiberアプリケーションのroot stateにあります。これはuseThreeフックによって返されるオブジェクトです。

glプロパティがrendererです。

しかし、テレビにリアルタイムで表示したいので、useFrameフックを使用して各フレームでこのプロセスを行います。

コールバック関数にはroot stateが含まれているので、変数に直接アクセスできます:

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

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

ここで行っていることは以下の通りです:

  • Render Targetrendererの出力として設定する
  • cameraを使用してsceneをレンダリングする。rendererの出力がRender Targetに設定されているので、シーンがそれにレンダリングされる
  • rendererの出力をnullに設定して、再びキャンバスにシーンをレンダリングする

Render Targetが作成されたので、次にこれをテレビに表示する必要があります。現在ビデオをレンダリングしているmaterialへの参照を追加します:

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

そして、useFrameフックを使用してmaterialのmapプロパティを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

これでセキュリティカメラのビューがテレビに表示されるようになりましたが、セキュリティカメラビューの中のテレビ画面は空です。

これは、シーンをRender Targetにレンダリングする際、画面が空だからです。

Inception effect

TV画面の中のTV画面の中の監視カメラビューを表示するためには 🤯、以下の手順が必要です:

  • 別のrender targetを作成します(これをbufferRenderTargetと呼びます)
  • シーンをその中でレンダリングします
  • それをTV画面のmaterialに取り付けます
  • シーンをcorderRenderTargetにレンダリングします
  • それをTVのmaterialに取り付けます
// ...
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;
  });
  // ...
};

ちょっと難しそうに見えますが、実際はとてもシンプルです。各ステップで何が起こっているかを考えてください。

これでシーンが無限にTV画面内にレンダリングされるようになりました!

現実味のあるエフェクトではありますが、一番クリエイティブなものではありません。Inceptionエフェクトを取り除いて、代わりに画面にスポンジ・ボブを表示しましょう:

End of lesson preview

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