本投稿は「QtQuick3D instanced rendering」の抄訳です。
Qt 6.1のQt Quick 3Dモジュールでは、パフォーマンスを飛躍的に改善するグラフィックスプロセッサー(GPU)機能「インスタンス化レンダリング」が新たにサポートされています。これにより、1回のドローコールで多数のアイテムをレンダリングできます。(ローレベルのOpenGL関数でいえば、glDrawElementsInstanced がその一例です。)

私自身の開発マシンで試した結果、100万個もの3Dキューブを60FPS(フレーム/秒)の速さで描画することができました。CPUの稼働率はわずか2%です。一方、Repeater3Dを使いQt 6.0のレンダリングAPIで同じような3Dキューブのシーンを作ろうとすると、1万個に達した時点で速度が鈍化し、CPU稼働率100%の状態でもわずか42FPSに留まりました。
上のような単純なテスト(ランダムな色、配置、回転のメタリックなドーナツ型2万個の描画)のソースコードもご紹介します。
import QtQuick3D
import QtQuick3D.Helpers
import QtQuick
Window {
width: 800
height: 450
visible: true
View3D {
anchors.fill: parent
camera: camera
environment: SceneEnvironment {
backgroundMode: SceneEnvironment.SkyBox
lightProbe: Texture {
source: "skybox.hdr"
}
probeExposure: 3
}
PerspectiveCamera {
id: camera
position: Qt.vector3d(0, 300, 500)
eulerRotation.x: -25
}
DirectionalLight {
eulerRotation.x: -30
eulerRotation.y: -70
}
RandomInstancing {
id: randomTable
instanceCount: 20000
position: InstanceRange { from: Qt.vector3d(-5000, -2000, -9000); to: Qt.vector3d(5000, 200, 500) }
rotation: InstanceRange { from: Qt.vector3d(20, 0, -45); to: Qt.vector3d(60, 0, 45) }
color: InstanceRange { from: Qt.rgba(0.1, 0.1, 0.1, 1); to: Qt.rgba(1, 1, 1, 1) }
}
Model {
instancing: randomTable
source: "torus.mesh"
materials: PrincipledMaterial { metalness: 1.0; roughness: 0.2; baseColor: "#ffffff" }
}
}
}
APIについて
インスタンス化に用いるAPIの大原則は、現行API上でインスタンス化の余地を自動検知するのではなく、明示的なインスタンス化を行うことです。
開始ポイントは、Instancingオブジェクト(それぞれのコピーの描画方法を定義するテーブル)です。変換タイプ(位置、回転、スケール)、色(モデルマテリアルとのブレンドモード)および、カスタムマテリアルと併用可能なカスタムデータを修正します。Qt 6.1では、以下の2種類のQMLタイプが搭載されています。
その他のインスタンステーブルも、C++ APIで簡単に定義できます。今後のリリースではQMLで作成可能なインスタンステーブル(データモデル別のテーブル定義、外部ソースからのテーブル読み込みなど)をさらに追加します。
テーブルが定義されたら、インスタンス作成プロパティを設定し、モデルに適用します。ひとつのテーブルを多数のモデルに一括適用できます。
カスタムシェーダーコードを記述すると、インスタンスを使って追加プロパティ(物理ベースレンダリングの変数、ボーンアニメーションのウェイト/歪み、カスタムマテリアルで表現可能な各種要素など)を管理できます。現在、インスタンステーブルで利用可能なカスタムデータのサイズは、浮動小数点数型4つまでです。
Qt 6.1のカスタムインスタンシングの例では、C++で実装したインスタンステーブルとカスタムマテリアルを使って、ひとつの3Dキューブを繰り返し複雑なシーンを描画しています。

長所と短所のトレードオフ
パフォーマンスが100倍改善して喜ばない人はいません。だからと言って、モデルを片っ端からインスタンス化すべきでしょうか?もちろん、インスタンス化ですべての問題が解決するわけではありません。ジョブの種類によってはインスタンス化が適さないこともあります。注意すべきポイントを以下にまとめました。
-
そもそもインスタンス化とは、細かく定義された修正内容をもとに、同じモデルのコピーを大量に描画するための手段です。大きく異なる形状を数個だけ描画するような場合には役立ちません。
-
インスタンス化の主な利点は、CPUとメモリ使用量の節減です。GPUの活用は一長一短です。ドローコールの回数や、場合によってはデータ量を低減できる一方、すべてのインスタンスに対する行列演算が加わるため、頂点シェーダーがより複雑になります。
-
Qt Quick 3Dで個々のモデルを描画する際は、半透明オブジェクトと不透明オブジェクトを分けて並び替え、最適な順番で描画します。インスタンス化を使うと、インスタンステーブルの指定どおりにGPUで描画するため、Qt Quick 3Dによるテーブル並べ替え処理は行われません。そのため、CPUの処理時間を大幅に短縮できますが、その分2つの影響が生じます。
テックプレビュー
インスタンス化機能は、Qt 6.1ではテックプレビューとして先行搭載されており、Qt 6.2でフルサポートされる予定です。APIは大きく変えない予定ですが、皆様のフィードバックをもとに微調整する場合があります。バイナリの互換性は今のところ持たせない予定です。現在のバージョンでは、C++の公開APIにGPUのインスタンステーブルのバイナリレイアウトが反映されていますが、この部分は変更になる可能性が最も高いでしょう。
この機会にぜひ、インスタンス化レンダリング機能を備えたQt 6.2への移行をご検討ください。コード修正は最小限で済みます。皆様のニーズにより即したユースケースをQt 6.2に反映できるよう、フィードバックもお寄せください。

最適化の際はパフォーマンス測定もお忘れなく!