Open9

react-reconcilerからみたreact-three-fiberの分析

ハトすけハトすけ

ディレクトリ構造

reactとreact-native両方で利用できるように、イベントを抽象化している。また環境差分はCanvasで吸収している。

src
- core/*
- native/*
- web/*
- index.tsx
- native.tsx
- three-types.ts
フォルダ/ファイル名 説明
core 本体
native react-native用のCanvasとeventハンドラ
web react-dom用のCanvasとeventハンドラ
index.tsx react-dom用のエントリポイント
native.tsx react-natvei用のエントリポイント
three-types.ts 共通の型
ハトすけハトすけ

Canvasコンポーネントでやっていること

rendererの初期化と初回描画の実行をやっている

  • extendによるTHREEクラスの登録(reconcilerにstateとして保持されレンダリング時に利用される)
  • 独自のBridgeコンポーネント機構により、異なるrenderer間でもContextProviderから値を受け取れるようにしている
  • webの場合: canvasをcreateRootしてrendererを初期化、configureで設定したのちに、renderを実行
  • nativeの場合: GLViewから疑似的なcanvasを作成、createRootしてrendererを初期化、configureで設定したのちに、renderを実行
ハトすけハトすけ

renderer.tsxのやっていること

https://github.com/pmndrs/react-three-fiber/blob/master/packages/fiber/src/core/renderer.tsx

  • createRoot
    • storeの初期化
    • reconciler.createContainerにstoreをcontainerとして渡す
      • つまり、reconcilerの監視対象はstoreになる(面白い!)
    • ReconcilerRootインスタンスを返す
      • configureメソッド: storeに設定を渡す
        • gl(レンダラ)
        • raycaster
        • camera
        • scene
        • events
        • dpr
        • frameloop
        • onPointerMissed
        • performance
        • xr
        • shadowmap
      • renderメソッド:
        • configureを実行してなかったら実行
        • reconciler.updateContainerの実行
          • Providerにstoreの値を渡す
          • childrenにrenderの引数ReactNodeを渡す
      • unmountメソッド
        • unmountComponentAtNodeの実行
ハトすけハトすけ

storeの内容

  • zustand を使って React コンテキスト経由で状態をグローバル共有。
  • THREE.WebGLRenderer, THREE.Scene, Camera, Raycaster などの主要リソースを一元管理。
  • 描画ループ、サブスクリプション管理、インタラクション(マウス/タッチイベント)管理を担当。
  • サブスクライブによって camera, size, dpr の変更に応じて gl や viewport を更新。
  • stateが変更されたらinvalidateにより再描画要求
プロパティ 説明
gl Three.js の WebGLRenderer
camera デフォルトカメラ(Perspective/Orthographic)
scene Three.js のルートシーン
raycaster マウスイベント用のレイキャストユーティリティ
events イベント管理用オブジェクト(EventManager)
frameloop 'always', 'demand', 'never':再描画ルールを制御
performance レンダリングパフォーマンス制御
size, viewport Canvas サイズとThree.js空間におけるビューポートサイズ
internal インタラクション、subscriber、初期クリック/ヒットなどの内部状態
invalidate() 再描画を要求(demandモード用)
advance() レンダーループ1ステップの実行(主に外部制御用)
  • frameloop = 'always':毎フレーム描画するのでinvalidateによる再描画要求は無視。
  • frameloop = 'demand':必要なときだけ描画する → 状態が変わったときだけ invalidate したい。
  • frameloop = 'never':ユーザーが advance() を呼ばない限り描画されない。

InternalState – サブスクリプションとイベント処理

useFramehookによってコールバック関数を登録する。ループ処理のupdate時に呼ばれる。

  • subscribers: 描画に関心のある関数のリスト。priorityで順序付け。
  • subscribe(): コンポーネントがレンダーループに参加するときに登録。
  • hovered, interaction, capturedMap: マウスやタッチのイベント管理。
  • lastEvent: 最新のイベントを保持。

https://github.com/pmndrs/react-three-fiber/blob/master/packages/fiber/src/core/hooks.tsx
https://github.com/pmndrs/react-three-fiber/blob/master/packages/fiber/src/core/loop.ts

ハトすけハトすけ

ループ処理について

https://github.com/pmndrs/react-three-fiber/blob/master/packages/fiber/src/core/loop.ts

  • invalidateが実行されると、
    • 現在レンダリング中なら、内部フレームを2にする
    • レンダリング中でないなら、内部フレームを1にする
    • running =falseならloop実行
  • loopが実行されると
    • requestAnimationFrame(loop)で次のループを予約
    • repeatを0にセット
    • frameloop='always'ならrepeat = 1、flrameloop='demand'であれば、内部フレームが2であればrepeat=1、内部フレームが1であればrepeat=0
    • repeat=0であれば、running=falseにして、cancelAnimationFrame(frame)
ハトすけハトすけ

まとめ

初期化時

  1. 設定をいろいろしてrendererの初期化
  2. zustandのstateを初期化
  3. rendererのrender実行

propsの更新時

  1. React 側で zustand の state が更新される。
  2. store.subscribe(() => invalidate(state)) が発火。
  3. frameloopがneverでなければ、invalidate() によって描画ループを開始。
  4. loop() が update() を呼び、gl.render() を行う。frameloop='always'であればループを実行し続ける。frameloop='demand'であれば変化があったフレームの数だけループを実行