Qt 3D レンダリング・フレームグラフ
Qt 3D の「Render」アスペクトにより、レンダリングアルゴリズムを完全にデータ駆動型にすることが可能です。制御を行うデータ構造は「フレームグラフ」と呼ばれます。Qt 3D のECS(エンティティ・コンポーネント・システム)が、エンティティとコンポーネントのツリーからシーンを構築することで、いわゆる「シーングラフ」を定義できるようにしているのと同様に、フレームグラフもツリー構造ですが、その用途は異なります。具体的には、シーンのレンダリング方法を制御するために使用されます。
1フレームのレンダリング過程において、3Dレンダラーは何度も状態を変更することになります。これらの状態変化の数や性質は、シーン内にどのようなマテリアル(シェーダー、メッシュジオメトリ、テクスチャ、ユニフォーム変数)が存在するかだけでなく、使用している高レベルのレンダリング方式によっても異なります。
例えば、従来の単純なフォワード・レンダリング方式を使用する場合と、ディファード・レンダリング方式を使用する場合では、その挙動が大きく異なります。反射、影、複数のビューポート、早期Zフィルパスといったその他の機能も、1フレームの間にレンダラーが設定する必要のある状態や、それらの状態変更が発生すべきタイミングをすべて変化させます。
比較として、『Qt Quick 』2のシーングラフレンダラーは、Qt Quick 2のシーンを描画する役割を担っていますが、プリミティブのバッチ処理や、不透明なオブジェクトのレンダリングに続いて透明なオブジェクトをレンダリングするといった処理を、C++でハードワイヤードに実装されています。『Qt Quick 』2の場合、これがすべての要件を網羅しているため、全く問題ありません。 上記の例からもわかるように、利用可能なレンダリング手法が多岐にわたることを考えると、このようなハードワイヤードなレンダラーでは、汎用的な3Dシーンに対して十分な柔軟性を発揮できない可能性が高いです。あるいは、そのようなすべてのケースをカバーできるほど柔軟なレンダラーを作れたとしても、汎用性が高すぎるためにパフォーマンスが低下してしまうでしょう。 さらに悪いことに、新たなレンダリング手法は絶えず研究されています。したがって、柔軟性と拡張性を兼ね備えつつ、使用や保守が簡単なアプローチが必要でした。そこで登場したのが、フレームグラフです!
フレームグラフ内の各ノードは、レンダラーがシーンをレンダリングする際に使用する設定の一部を定義します。 フレームグラフツリー内でのノードの位置によって、そのノードをルートとするサブツリーが、レンダリングパイプラインにおいていつ、どこでアクティブな構成となるかが決まります。後で見るように、レンダラーはこのツリーを走査し、フレームの各時点におけるレンダリングアルゴリズムに必要な状態を構築します。
もちろん、単に画面上に単純な立方体をレンダリングしたいだけなら、これはやりすぎだと思うかもしれません。しかし、もう少し複雑なシーンを作り始めると、これが大いに役立ちます。一般的なケースについては、Qt 3D に、すぐに使えるサンプルフレームグラフがいくつか用意されています。
ここでは、いくつかの例と、その結果として生成されるフレームグラフを紹介することで、フレームグラフの概念の柔軟性を示します。
なお、エンティティとコンポーネントで構成されるシーングラフとは異なり、フレームグラフはすべて Qt3DRender::QFrameGraphNode のサブクラスであるネストされたノードのみで構成されています。これは、フレームグラフのノードが仮想世界におけるシミュレーション対象のオブジェクトではなく、あくまで補助的な情報であるためです。
まもなく、最初のシンプルなフレームグラフの構築方法を見ていきますが、その前に、利用可能なフレームグラフノードについて紹介します。 また、シーングラフツリーと同様に、QML APIとC++ APIは1対1で対応しているため、お好みのものを選択できます。読みやすさと簡潔さを考慮し、本記事ではQML APIを採用しました。
フレームグラフの優れた点は、これらのシンプルなノードタイプを組み合わせることで、複雑で低レベルのC/C++レンダリングコードに一切手を加えることなく、特定のニーズに合わせてレンダラーを設定できることです。
FrameGraphのルール
正しく機能するフレームグラフツリーを構築するには、そのトラバース方法や、Qt 3D レンダラーへの渡しかたに関するいくつかのルールを理解しておく必要があります。
フレームグラフの設定
FrameGraphツリーは、QRenderSettingsコンポーネントのactiveFrameGraphプロパティに割り当てる必要があります。このQRenderSettingsコンポーネント自体は、Qt 3D シーンのルートエンティティを構成するコンポーネントです。これにより、それがレンダラーにとってのアクティブなフレームグラフとなります。 もちろん、これはQMLのプロパティバインディングであるため、アクティブなフレームグラフ(またはその一部)は実行時に動的に変更可能です。例えば、屋内と屋外のシーンで異なるレンダリング手法を使用したい場合や、特定の特殊効果を有効または無効にしたい場合などが挙げられます。
Entity {
id: sceneRoot
components: RenderSettings {
activeFrameGraph: ... // FrameGraph tree
}
}注:activeFrameGraphは 、QMLにおけるFrameGraphコンポーネントのデフォルトプロパティです。
Entity {
id: sceneRoot
components: RenderSettings {
... // FrameGraph tree
}
}フレームグラフの使用方法
- Qt 3D レンダラーは、フレームグラフツリーに対して深さ優先探索を行います。探索が深さ優先であるため、ノードを定義する順序が重要である点に注意してください。
- レンダラーがフレームグラフのリーフノードに到達すると、そのリーフノードからルートノードまでのパスで指定されたすべての状態を収集します。これにより、フレームの一部をレンダリングするために使用される状態が定義されます。Qt 3D の内部構造に関心がある場合、この状態の集合はRenderViewと呼ばれます。
- RenderViewに含まれる設定に基づき、レンダラーはレンダリング対象のシーングラフ内のすべてのエンティティを収集し、それらからRenderCommandsのセットを構築して、RenderViewに関連付けます。
- RenderView と RenderCommands のセットの組み合わせは、OpenGL への送信のために渡されます。
- これがフレームグラフの各リーフノードに対して繰り返されると、フレームが完成し、レンダラーは `QOpenGLContext::swapBuffers()` を呼び出してフレームを表示します。
本質的に、フレームグラフはQt 3D レンダラーを構成するためのデータ駆動型の手法です。このデータ駆動型の性質により、実行時に構成を変更したり、C++以外の開発者やデザイナーがフレームの構造を変更したり、何千行もの定型コードを記述することなく新しいレンダリング手法を試したりすることが可能になります。
フレームグラフの例
フレームグラフツリーを作成する際に従うべきルールがわかったところで、いくつかの例を取り上げて詳しく解説していきます。
単純なフォワードレンダラー
フォワードレンダリングとは、OpenGLを従来の方法で使用し、オブジェクトを1つずつシェーディングしながら、バックバッファに直接レンダリングしていく手法です。これは、中間Gバッファにレンダリングするディファードレンダリングとは対照的です。以下に、フォワードレンダリングに使用できるシンプルなFrameGraphを示します:
Viewport {
normalizedRect: Qt.rect(0.0, 0.0, 1.0, 1.0)
property alias camera: cameraSelector.camera
ClearBuffers {
buffers: ClearBuffers.ColorDepthBuffer
CameraSelector {
id: cameraSelector
}
}
}ご覧の通り、このツリーにはリーフが1つあり、次の図に示すように合計3つのノードで構成されています。

前述のルールに従うと、このフレームグラフツリーは、以下の構成を持つ単一のRenderViewを生成します:
- リーフノード → RenderView
- 画面全体を覆うビューポート(ネストされたビューポートのサポートを容易にするため、正規化座標を使用)
- カラーバッファと深度バッファはクリアされるように設定されている
- `camera` プロパティで指定されたカメラ
異なる複数のFrameGraphツリーでも、同じレンダリング結果が得られる場合があります。リーフからルートまで収集された状態が同じであれば、結果も同一になります。最も長く不変の状態は、フレームグラフのルートに近い位置に配置するのが最適です。これにより、リーフノードの数が減り、結果としてRenderViewの総数も削減されます。
Viewport {
normalizedRect: Qt.rect(0.0, 0.0, 1.0, 1.0)
property alias camera: cameraSelector.camera
CameraSelector {
id: cameraSelector
ClearBuffers {
buffers: ClearBuffers.ColorDepthBuffer
}
}
}CameraSelector {
Viewport {
normalizedRect: Qt.rect(0.0, 0.0, 1.0, 1.0)
ClearBuffers {
buffers: ClearBuffers.ColorDepthBuffer
}
}
}マルチビューポートのFrameGraph
ここでは、4台の仮想カメラの視点からシーングラフをウィンドウの4つの象限にレンダリングする、もう少し複雑な例を見ていきましょう。これは3D CADやモデリングツールで一般的な構成ですが、カーレースゲームのバックミラーやCCTVカメラの表示をレンダリングするために調整することも可能です。

Viewport {
id: mainViewport
normalizedRect: Qt.rect(0, 0, 1, 1)
property alias Camera: cameraSelectorTopLeftViewport.camera
property alias Camera: cameraSelectorTopRightViewport.camera
property alias Camera: cameraSelectorBottomLeftViewport.camera
property alias Camera: cameraSelectorBottomRightViewport.camera
ClearBuffers {
buffers: ClearBuffers.ColorDepthBuffer
}
Viewport {
id: topLeftViewport
normalizedRect: Qt.rect(0, 0, 0.5, 0.5)
CameraSelector { id: cameraSelectorTopLeftViewport }
}
Viewport {
id: topRightViewport
normalizedRect: Qt.rect(0.5, 0, 0.5, 0.5)
CameraSelector { id: cameraSelectorTopRightViewport }
}
Viewport {
id: bottomLeftViewport
normalizedRect: Qt.rect(0, 0.5, 0.5, 0.5)
CameraSelector { id: cameraSelectorBottomLeftViewport }
}
Viewport {
id: bottomRightViewport
normalizedRect: Qt.rect(0.5, 0.5, 0.5, 0.5)
CameraSelector { id: cameraSelectorBottomRightViewport }
}
}このツリーはリーフが5つあり、少し複雑です。前述と同じルールに従い、フレームグラフから5つのRenderViewオブジェクトを構築します。以下の図は、最初の2つのRenderViewの構築過程を示しています。残りのRenderViewは、2番目の図と非常に似ており、サブツリーが異なるだけです。


作成されたRenderViewの全一覧は以下の通りです:
- RenderView (1)
- フルスクリーンビューポートの定義
- カラーバッファと深度バッファはクリアされるように設定されています
- RenderView (2)
- フルスクリーンビューポートが定義されました
- サブビューポートが定義されました(レンダリングビューポートは親ビューポートを基準にスケーリングされます)
- CameraSelectorが指定されました
- RenderView (3)
- フルスクリーンビューポートが定義されました
- サブビューポートが定義されています(レンダリングビューポートは親ビューポートを基準にスケーリングされます)
- CameraSelector が指定されました
- RenderView (4)
- フルスクリーンビューポートが定義されました
- サブビューポートが定義されています(レンダリングビューポートは親ビューポートを基準にスケーリングされます)
- CameraSelector が指定されました
- RenderView (5)
- フルスクリーンビューポートが定義されました
- サブビューポートが定義されています(レンダリングビューポートは親ビューポートを基準にスケーリングされます)
- CameraSelector が指定されました
ただし、この場合は順序が重要です。もし ClearBuffers ノードが最初ではなく最後になっていたら、慎重にレンダリングされた直後にすべてがクリアされてしまうという単純な理由から、画面が真っ黒になってしまうでしょう。 同様の理由から、これをFrameGraphのルートとして使用することはできません。そうすると、各ビューポートごとに画面全体をクリアする呼び出しが発生してしまうからです。
FrameGraph の宣言順序は重要ですが、Qt 3D は各 RenderView を並列で処理することができます。これは、RenderView の状態が有効である間に送信される一連の RenderCommands を生成するという目的において、各 RenderView が他から独立しているためです。
Qt 3D は、利用可能なコア数に応じて自然にスケールアップするタスクベースの並列化アプローチを採用しています。これは、前の例に関する次の図に示されています。

RenderView に対する RenderCommands は、多数のコアにわたって並列に生成することができ、専用の OpenGL 送信スレッド上で RenderView を正しい順序で送信するように注意さえすれば、結果として得られるシーンは正しくレンダリングされます。
ディファード・レンダラー
レンダリングに関して言えば、ディファード・レンダリングは、フォワード・レンダリングと比較して、レンダラーの構成という点で全く異なるものです。各メッシュを描画し、シェーダー効果を適用してシェーディングを行う代わりに、ディファード・レンダリングでは2回のレンダリングパス方式を採用しています。
まず、シーン内のすべてのメッシュが、通常は各フラグメントに対して少なくとも4つの値を出力する同じシェーダーを使用して描画されます。
- ワールド法線ベクトル
- 色(またはその他のマテリアルプロパティ)
- 深度
- ワールド位置ベクトル
これらの各値はテクスチャに格納されます。法線、色、深度、位置の各テクスチャは、いわゆるG-バッファを構成します。最初のパスでは画面上に何も描画されず、後で使用できるようG-バッファに描画されます。
すべてのメッシュの描画が完了すると、G-Bufferには、その時点でカメラから見えるすべてのメッシュのデータが格納されます。 その後、2回目のレンダリングパスでは、G-バッファのテクスチャから法線、色、位置の値を読み取り、フルスクリーンのクワッドに色を出力することで、最終的なカラーシェーディングを施し、シーンをバックバッファにレンダリングします。
この手法の利点は、複雑なエフェクトに必要な膨大な演算能力が、2回目のパスにおいて、カメラから実際に視認されている要素に対してのみ使用される点にあります。1回目のパスでは、すべてのメッシュが単純なシェーダーで描画されるため、処理負荷はそれほど高くありません。 したがって、ディファード・レンダリングは、シェーディングとライティングをシーン内のオブジェクト数から切り離し、代わりに画面(およびGバッファ)の解像度に結びつけます。この手法は、GPUメモリの使用量が増えるという代償を払う代わりに、多数の動的ライトを使用できるため、多くのゲームで採用されています。
Viewport {
id: root
normalizedRect: Qt.rect(0.0, 0.0, 1.0, 1.0)
property GBuffer gBuffer
property alias camera: sceneCameraSelector.camera
property alias sceneLayer: sceneLayerFilter.layers
property alias screenQuadLayer: screenQuadLayerFilter.layers
RenderSurfaceSelector {
CameraSelector {
id: sceneCameraSelector
// Fill G-Buffer
LayerFilter {
id: sceneLayerFilter
RenderTargetSelector {
id: gBufferTargetSelector
target: gBuffer
ClearBuffers {
buffers: ClearBuffers.ColorDepthBuffer
RenderPassFilter {
id: geometryPass
matchAny: FilterKey {
name: "pass"
value: "geometry"
}
}
}
}
}
TechniqueFilter {
parameters: [
Parameter { name: "color"; value: gBuffer.color },
Parameter { name: "position"; value: gBuffer.position },
Parameter { name: "normal"; value: gBuffer.normal },
Parameter { name: "depth"; value: gBuffer.depth }
]
RenderStateSet {
// Render FullScreen Quad
renderStates: [
BlendEquation { blendFunction: BlendEquation.Add },
BlendEquationArguments {
sourceRgb: BlendEquationArguments.SourceAlpha
destinationRgb: BlendEquationArguments.DestinationColor
}
]
LayerFilter {
id: screenQuadLayerFilter
ClearBuffers {
buffers: ClearBuffers.ColorDepthBuffer
RenderPassFilter {
matchAny: FilterKey {
name: "pass"
value: "final"
}
parameters: Parameter {
name: "winSize"
value: Qt.size(1024, 768)
}
}
}
}
}
}
}
}
}(上記のコードはqt3d/tests/manual/deferred-renderer-qml から改変したものです。)
グラフィカルに表現すると、結果のフレームグラフは次のようになります:

また、生成されるRenderViewは次のようになります:
- RenderView (1)
- 使用するカメラを指定する
- 画面全体を覆うビューポートを定義する
- レイヤーコンポーネント「sceneLayer」のすべてのエンティティを選択
gBufferをアクティブなレンダリングターゲットとして設定- 現在バインドされているレンダリングターゲット(
gBuffer)の色と深度をクリアする - シーン内で、RenderPassFilterの注釈と一致するマテリアルおよびテクニックを持つエンティティのみを選択する
- RenderView (2)
- 画面全体を埋めるビューポートを定義する
- レイヤコンポーネント screenQuadLayer に属するすべてのエンティティを選択する
- 現在バインドされているフレームバッファ(画面)上のカラーバッファと深度バッファをクリアします。
- RenderPassFilter内の注釈と一致するマテリアルおよびテクニックを持つシーン内のエンティティのみを選択する
フレームグラフのその他の利点
FrameGraphツリーは完全にデータ駆動型であり、実行時に動的に変更できるため、以下のことが可能です:
- プラットフォームやハードウェアごとに異なるフレームグラフツリーを用意し、実行時に最適なものを選択できる
- シーン内でビジュアルデバッグ機能を簡単に追加・有効化できる
- シーンの特定の領域でレンダリングする必要がある対象の性質に応じて、異なるFrameGraphツリーを使用できる
- Qt 3D の内部構造を変更することなく、新しいレンダリング手法を実装できる
まとめ
FrameGraph と、それを構成するノードの種類について紹介しました。 続いて、FrameGraphの構築ルールや、Qt 3D エンジンが内部でFrameGraphをどのように使用しているかを説明するために、いくつかの例を取り上げました。ここまでで、FrameGraphの概要と、その使用方法(たとえば、フォワードレンダラーに初期のZフィルパスを追加するなど)について、かなりよく理解できたはずです。 また、FrameGraphは、Qt 3D が標準で提供するレンダラーやマテリアルに縛られることなく、自由に活用できるツールであるということを常に念頭に置いておく必要があります。
© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.