Qt Quick glTFアセットを使った3D入門
「Qt Quick 3D - 入門」サンプルでは、Qt Quick 3D を使用した QML ベースのアプリケーション作成について簡単に紹介していますが、その際には球体や円柱などの組み込みプリミティブのみが使用されています。このページでは、Khronos glTF サンプルモデルリポジトリにあるモデルの一部を使用し、glTF 2.0アセットを用いた入門ガイドを提供します。
スケルトンアプリケーション
まずは、以下のアプリケーションから始めましょう。このコードスニペットは、qml コマンドラインツールを使用すれば、そのまま実行可能です。その結果、他に何も表示されていない、真っ緑の3Dビューが表示されます。
import QtQuick
import QtQuick3D
import QtQuick3D.Helpers
Item {
width: 1280
height: 720
View3D {
anchors.fill: parent
environment: SceneEnvironment {
backgroundMode: SceneEnvironment.Color
clearColor: "green"
}
PerspectiveCamera {
id: camera
}
WasdController {
controlledObject: camera
}
}
}
アセットのインポート
ここでは、Sample Modelsリポジトリにある2つのglTF 2.0モデル、「Sponza」と「Suzanne」を使用します。
これらのモデルには通常、.gltf ファイルに加えて、いくつかのテクスチャマップや、別のバイナリファイルに保存されたメッシュ(ジオメトリ)データが付属しています:

これらすべてをQt Quick の3Dシーンに取り込むにはどうすればよいでしょうか?
いくつかの方法があります:
- シーン内でインスタンス化できるQMLコンポーネントを生成する。 この変換を行うためのコマンドラインツールが「Balsam」です。このツールは、実質的にサブシーンとなる.qmlファイルを生成するだけでなく、メッシュ(ジオメトリ)データを最適化され、読み込みが高速な形式に再パッケージ化し、テクスチャマップの画像ファイルもコピーします。
- BalsamのGUIフロントエンドである
balsamuiを使用して、同様の処理を行う。 - を使用する場合 Qt Design Studioを使用する場合、アセットのインポート処理はビジュアルデザインツールに統合されています。たとえば、.gltf ファイルを適切なパネルにドラッグ&ドロップすることで、インポートを実行できます。
- 特に glTF 2.0 アセットについては、RuntimeLoader タイプというランタイムオプションも用意されています。これにより、Balsam などのツールによる前処理を行うことなく、実行時に .gltf ファイル(および関連するバイナリデータファイルやテクスチャデータファイル)を読み込むことができます。 これは、ユーザーが提供したアセットを開いて読み込みたいアプリケーションにおいて非常に便利です。一方、パフォーマンスの面では、このアプローチは効率が著しく低下します。そのため、この入門編ではこのアプローチには焦点を当てません。このアプローチの例については、「Qt Quick 3D - RuntimeLoader Example」を参照してください。
balsam およびbalsamui の両アプリケーションはQtに同梱されており、Qt Quick 3Dがインストールまたはビルドされている限り、他の同様の実行可能ツールと同じディレクトリに存在しているはずです。多くの場合、追加の引数を指定することなく、コマンドラインから.gltfファイルに対してbalsamを実行するだけで十分です。 ただし、多くのコマンドラインオプション、あるいはbalsamui やQt Design Studio を使用する場合の対話型オプションについて知っておく価値があります。例えば、ベイクされたライトマップを使用して静的なグローバルイルミネーションを実現する場合、実行時にこの処理を行うとリソースを消費する可能性があるため、アセットのインポート時に追加のライトマップ UV チャンネルを生成するために、--generateLightmapUV を指定したい場合が多いでしょう。 同様に、シーン内で自動LODを有効にするために、生成されたメッシュの簡略化版が必要な場合は、--generateMeshLevelsOfDetail が不可欠です。その他のオプションには、欠落データの生成(例:--generateNormals )や、さまざまな最適化の実行などがあります。
balsamui では、コマンドラインオプションが対話型要素に割り当てられています:

balsam を使用したインポート
さっそく始めましょう!https://github.com/KhronosGroup/glTF-Sample-Models git リポジトリがどこかにチェックアウトされていることを前提として、.gltf ファイルへの絶対パスを指定することで、サンプルアプリケーションのディレクトリから balsam を実行できます:
balsam c:\work\glTF-Sample-Models\2.0\Sponza\glTF\Sponza.gltf
これにより、meshes サブディレクトリ下にSponza.qml および.mesh ファイルが生成され、テクスチャマップはmaps にコピーされます。
注:このqmlファイルは 単独では実行できません。これはコンポーネントであり、View3D に関連付けられた3Dシーン内でインスタンス化する必要があります。
ここでは、アセットの QML ファイルがメインの .qml シーンのすぐ隣にあるため、プロジェクト構造は非常にシンプルです。これにより、標準の QML コンポーネントシステムを使用して、Sponza タイプを簡単にインスタンス化できます(実行時には、ファイルシステム内で Sponza.qml が検索されます)。
ただし、モデル(サブシーン)を追加するだけでは意味がありません。デフォルトでは、マテリアルには完全なPBRライティング計算が組み込まれているため、DirectionalLight 、PointLight 、SpotLight などのライトがない場合や、the environment を通じて画像ベースのライティングが有効になっていない限り、シーンは何も表示されません。
ここでは、デフォルト設定のまま「DirectionalLight 」を追加することにします(つまり、色はwhite であり、光は Z 軸の方向に放射されます)。
import QtQuick
import QtQuick3D
import QtQuick3D.Helpers
Item {
width: 1280
height: 720
View3D {
anchors.fill: parent
environment: SceneEnvironment {
backgroundMode: SceneEnvironment.Color
clearColor: "green"
}
PerspectiveCamera {
id: camera
}
DirectionalLight {
}
Sponza {
}
WasdController {
controlledObject: camera
}
}
}qml ツールでこれを実行すると読み込まれて動作しますが、Sponzaモデルがカメラの後ろにあるため、デフォルトではシーン全体が空っぽになっています。また、スケールも理想的ではありません。例えば、WASDキーやマウス(WasdController で有効化されている)を使って移動しても、違和感があります。
これを改善するために、100 を使用して、Sponzaモデル(サブシーン)をX、Y、Z軸に沿ってスケーリングします。さらに、カメラの初期Y座標を100に設定します。
import QtQuick
import QtQuick3D
import QtQuick3D.Helpers
Item {
width: 1280
height: 720
View3D {
anchors.fill: parent
environment: SceneEnvironment {
backgroundMode: SceneEnvironment.Color
clearColor: "green"
}
PerspectiveCamera {
id: camera
y: 100
}
DirectionalLight {
}
Sponza {
scale: Qt.vector3d(100, 100, 100)
}
WasdController {
controlledObject: camera
}
}
}これを実行すると、次のような結果が得られます:

マウスとWASDRFキーを使って移動できます:


注: 上記で、「model」の代替としてsubscene という表記を何度か使用しました。 なぜでしょうか?Sponzaアセットでは一見分かりにくいかもしれませんが、glTF形式のSponzaアセットは103個のサブメッシュを持つ単一のモデルであり、materials list に103個の要素を持つ単一のModel オブジェクトにマッピングされます。しかし、アセットには任意の数のmodels を含めることができ、それぞれが複数のサブメッシュと関連するマテリアルを持っています。 これらのモデルは親子関係を形成でき、追加のnodes と組み合わせて、平行移動、回転、拡大縮小などの変形を行うことができます。したがって、レンダリング結果が視覚的には単一のモデルとして認識される場合でも、インポートされたアセットを完全なサブシーン、つまりnodes の任意のツリー構造として捉える方が適切です。 生成された Sponza.qml や、そのようなアセットから生成されたその他の QML ファイルをプレーンテキストエディタで開いて、その構造を把握してください(もちろん、構造はソースアセット、この場合は glTF ファイルがどのように設計されたかによって常に異なります)。
balsamui 経由でのインポート
2つ目のモデルについては、代わりにbalsam のグラフィカルユーザーインターフェースを使用してみましょう。
balsamui を実行すると、ツールが起動します:

Suzanneモデルをインポートしてみましょう。これは、2つのテクスチャマップを持つ、よりシンプルなモデルです。

追加の設定オプションは必要ないため、そのまま「変換」を実行します。結果はbalsam を実行した場合と同じで、特定の出力ディレクトリにSuzanne.qmlといくつかの追加ファイルが生成されます。

この時点から、生成されたアセットの操作は前のセクションと同様です。
import QtQuick
import QtQuick3D
import QtQuick3D.Helpers
Item {
width: 1280
height: 720
View3D {
anchors.fill: parent
environment: SceneEnvironment {
backgroundMode: SceneEnvironment.Color
clearColor: "green"
}
PerspectiveCamera {
id: camera
y: 100
}
DirectionalLight {
}
Sponza {
scale: Qt.vector3d(100, 100, 100)
}
Suzanne {
y: 100
scale: Qt.vector3d(50, 50, 50)
eulerRotation.y: -90
}
WasdController {
controlledObject: camera
}
}
}ここでも、インスタンス化されたSuzanneノードにスケーリングを適用し、モデルがSponzaビルの床に埋もれないよう、Y位置を少し調整します。

Qt Quick と同様に、すべてのプロパティを変更、バインド、およびアニメーション化することができます。例えば、Suzanne モデルに連続的な回転を適用してみましょう:
Suzanne {
y: 100
scale: Qt.vector3d(50, 50, 50)
NumberAnimation on eulerRotation.y {
from: 0
to: 360
duration: 3000
loops: Animation.Infinite
}
}見た目をより良くする
照明の追加
現在のシーンは少し暗いです。もう1つライトを追加しましょう。今回はPointLight を使用し、影を落とすものにします。
import QtQuick
import QtQuick3D
import QtQuick3D.Helpers
Item {
width: 1280
height: 720
View3D {
anchors.fill: parent
environment: SceneEnvironment {
backgroundMode: SceneEnvironment.Color
clearColor: "green"
}
PerspectiveCamera {
id: camera
y: 100
}
DirectionalLight {
}
Sponza {
scale: Qt.vector3d(100, 100, 100)
}
PointLight {
y: 200
color: "#d9c62b"
brightness: 5
castsShadow: true
shadowFactor: 75
}
Suzanne {
y: 100
scale: Qt.vector3d(50, 50, 50)
NumberAnimation on eulerRotation.y {
from: 0
to: 360
duration: 3000
loops: Animation.Infinite
}
}
WasdController {
controlledObject: camera
}
}
}このシーンを起動してカメラを少し動かしてみると、確かに以前より見栄えが良くなってきていることがわかります:

ライトのデバッグ
PointLight は、Suzanneモデルの少し上に配置されています。Qt Design Studio などのビジュアルツールを使用してシーンを設計する場合、これは一目瞭然ですが、設計ツールを使わずに開発を行う場合、lights やその他のnodes の位置を素早く可視化できると便利です。
これを行うには、PointLight に子ノードであるModel を追加します。子ノードの位置は親ノードを基準とするため、この場合、デフォルトの(0, 0, 0) は実質的にPointLight の位置となります。 光を何らかのジオメトリ(この場合は組み込みの立方体)で囲むことは、標準的なリアルタイムの照明計算においては問題になりません。このシステムにはオクルージョンの概念がないため、光が「壁」を通り抜けることに何の問題もないからです。 もし、レイトレーシングを使用して照明を計算するプリベイクされたライトマップを使用した場合、話は別になります。その場合は、デバッグ用の立方体をライトの少し上に移動させるなどして、立方体がライトを遮らないようにする必要があります。
PointLight {
y: 200
color: "#d9c62b"
brightness: 5
castsShadow: true
shadowFactor: 75
Model {
source: "#Cube"
scale: Qt.vector3d(0.01, 0.01, 0.01)
materials: PrincipledMaterial {
lighting: PrincipledMaterial.NoLighting
}
}
}ここで使うもう一つのコツは、キューブに使用されているマテリアルのライティングをオフにすることです。そうすることで、ライティングの影響を受けずに、デフォルトのベースカラー(白)のまま表示されます。これは、デバッグや可視化を目的として使用されるオブジェクトにとって便利です。
その結果、PointLight の位置を可視化する小さな白い立方体が表示されていることに注目してください:

スカイボックスと画像ベースのライティング
もう1つの明らかな改善点は、背景への対応です。あの緑がかった透明な色は、あまり理想的ではありません。照明にも寄与するような環境を追加してみてはいかがでしょうか?
必ずしも適切なHDRIパノラマ画像が利用できるとは限らないため、手続き的に生成されたハイダイナミックレンジの空の画像を使用しましょう。これは、ProceduralSkyTextureData およびTexture が、ファイルベースではない動的に生成された画像データをサポートしているおかげで簡単に実現できます。source を指定する代わりに、textureData プロパティを使用します。
environment: SceneEnvironment {
backgroundMode: SceneEnvironment.SkyBox
lightProbe: Texture {
textureData: ProceduralSkyTextureData {
}
}
}注:このサンプルコードでは 、オブジェクトをインラインで定義することを推奨しています。これは必須ではなく、SceneEnvironment やProceduralSkyTextureData オブジェクトをオブジェクトツリーの別の場所で定義し、id から参照することも可能です。
その結果、スカイボックスと改善された照明の両方が実現されました。(前者は、backgroundMode がSkyBoxに設定され、light probe が有効なTexture に設定されているためです。後者は、light probe が有効なTexture に設定されているためです)


基本的なパフォーマンス調査
シーンのリソースやパフォーマンスに関する基本的な知見を得るためには、開発プロセスの早い段階で、インタラクティブなDebugView アイテムを表示する方法を追加しておくのが良いでしょう。ここでは、DebugView を切り替えるButton を追加することにします。どちらも画面の右上隅にアンカー設定されています。
import QtQuick
import QtQuick.Controls
import QtQuick3D
import QtQuick3D.Helpers
Item {
width: 1280
height: 720
View3D {
id: view3D
anchors.fill: parent
environment: SceneEnvironment {
backgroundMode: SceneEnvironment.SkyBox
lightProbe: Texture {
textureData: ProceduralSkyTextureData {
}
}
}
PerspectiveCamera {
id: camera
y: 100
}
DirectionalLight {
}
Sponza {
scale: Qt.vector3d(100, 100, 100)
}
PointLight {
y: 200
color: "#d9c62b"
brightness: 5
castsShadow: true
shadowFactor: 75
Model {
source: "#Cube"
scale: Qt.vector3d(0.01, 0.01, 0.01)
materials: PrincipledMaterial {
lighting: PrincipledMaterial.NoLighting
}
}
}
Suzanne {
y: 100
scale: Qt.vector3d(50, 50, 50)
NumberAnimation on eulerRotation.y {
from: 0
to: 360
duration: 3000
loops: Animation.Infinite
}
}
WasdController {
controlledObject: camera
}
}
Button {
anchors.right: parent.right
text: "Toggle DebugView"
onClicked: debugView.visible = !debugView.visible
DebugView {
id: debugView
source: view3D
visible: false
anchors.top: parent.bottom
anchors.right: parent.right
}
}
}
このパネルでは、リアルタイムのタイミングが表示され、テクスチャマップやメッシュのリアルタイムリストを確認できるほか、最終的なカラーバッファをレンダリングする前に実行する必要があるレンダーパスについて把握することができます。
「PointLight 」を影を投射するライトとして設定したため、複数のレンダリングパスが関与しています:

「Textures 」セクションでは、「Suzanne」および「Sponza」アセットのテクスチャマップ(後者は非常に多くのテクスチャマップを含んでいます)に加え、手続き的に生成された空のテクスチャも確認できます。

「Models 」ページには特に驚くような点はありません:

「Tools 」ページには、「wireframe mode 」や各種「material overrides 」を切り替えるためのインタラクティブなコントロールがいくつかあります。
ここでは、ワイヤーフレームモードを有効にし、マテリアルからbase color コンポーネントのみを使用するようにレンダリングを強制しています:

これで、インポートしたアセットを使用したQt Quick 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.