1// Copyright (C) 2023 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
6\title Qt Quick 3D Introduction with glTF Assets
7\page quick3d-asset-intro
9The \l{Qt Quick 3D - Introduction} example provides a quick introduction to
10creating QML-based applications with Qt Quick 3D, but it does so using only
11built-in primitives, such as spheres and cylinders. This page provides an
12introduction using \l{https://en.wikipedia.org/wiki/GlTF#glTF_2.0}{glTF 2.0}
13assets, using some of the models from the
14\l{https://github.com/KhronosGroup/glTF-Sample-Models}{Khronos glTF Sample
17\section1 Our Skeleton Application
19Let's start with the following application. This code snippet is runnable as-is
20with the \c qml command-line tool. The result is a very green 3D view with
26 import QtQuick3D.Helpers
35 environment: SceneEnvironment {
36 backgroundMode: SceneEnvironment.Color
45 controlledObject: camera
51\image assetintro_empty.jpg
53\section1 Importing an Asset
55We are going to use two glTF 2.0 models from the Sample Models repository:
58These models typically come with a number of texture maps and the mesh
59(geometry) data stored in a separate binary file, in addition to the .gltf
62\image assetintro_sponza_dir.jpg
64How do we get all this into our Qt Quick 3D scene?
66There are a number of options:
70\li Generate QML components that can be instantiated in the scene. The
71command-line tool to perform this conversion is the \l{Balsam Asset Import
72Tool}{Balsam} tool. Besides generating a .qml file, that is effectively a
73subscene, this also repacks the mesh (geometry) data into an optimized,
74fast-to-load format, and copies the texture map image files as well.
76\li Perform the same using \c balsamui, a GUI frontend for \l{Balsam Asset
79\li If using \l{https://www.qt.io/product/ui-design-tools}{Qt Design Studio},
80the asset import process is integrated into the visual design tools. Importing
81can be triggered, for example, by dragging and dropping the .gltf file onto the
84\li For glTF 2.0 assets in particular, there is a runtime option as well: the
85\l RuntimeLoader type. This allows loading a .gltf file (and the associated
86binary and texture data files) at runtime, without doing any pre-processing via
87tools such as \l{Balsam Asset Import Tool}{Balsam}. This is very handy in
88applications that wish to open and load user-provided assets. On the other
89hand, this approach is significantly less efficient when it comes to
90performance. Therefore, we will not be focusing on this approach in this
91introduction. Check the \l{Qt Quick 3D - RuntimeLoader Example} for an example
96Both the \c balsam and \c balsamui applications are shipped with Qt, and should
97be present in the directory with other similar executable tools, assuming Qt
98Quick 3D is installed or built. In many cases, running \l{Balsam Asset Import
99Tool}{balsam} from the command-line on the .gltf file is sufficient, without
100having to specify any additional arguments. It is worth being aware however of
101the many command-line, or interactive if using \c balsamui or Qt Design Studio,
102options. For example, when working with \l{quick3d-lightmap}{baked lightmaps to
103provide static global illumination}, it is likely that one will want to pass
104\c{--generateLightmapUV} to get the additional lightmap UV channel generated at
105asset import time, instead of performing this potentially consuming process at
106run-time. Similarly, \c{--generateMeshLevelsOfDetail} is essential when it is
107desirable to have simplified versions of the meshes generated in order to have
108\l{Qt Quick 3D Level of Detail}{automatic LOD} enabled in the scene. Other options
109allow generating missing data (e.g. \c{--generateNormals}) and performing various
112In \c balsamui the command-line options are mapped to interactive elements:
113\image assetintro_balsamui_options.jpg
115\section2 Importing via balsam
117Let's get started! Assuming that the
118\l{https://github.com/KhronosGroup/glTF-Sample-Models} \c git repository is
119checked out somewhere, we can simply run balsam from our example application
120directory, by specifying an absolute path to the .gltf files:
122\c{balsam c:\work\glTF-Sample-Models\2.0\Sponza\glTF\Sponza.gltf}
124This gives us a \c{Sponza.qml}, a \c{.mesh} file under the \c meshes
125subdirectory, and the texture maps copied under \c maps.
127\note This qml file is not runnable on its own. It is a \e component, that
128should be instantiated within a 3D scene associated with a \l View3D.
130Our project structure is very simple here, as the asset qml files live right
131next to our main .qml scene. This allows us to simply instantiate the Sponza
132type using the \l{QML Documents}{standard QML component system}. (at run-time
133this will then look for Sponza.qml in the filesystem)
135Just adding the model (subscene) is pointless however, since by default the
136materials feature the full \l{quick3d-pbr}{PBR lighting calculations}, so
137nothing is shown from our scene without a light such as \l DirectionalLight, \l
138PointLight, or \l SpotLight, or having \l{Using Image-Based
139Lighting}{image-based lighting} enabled via
140\l{SceneEnvironment::lightProbe}{the environment}.
142For now, we choose to add a DirectionalLight with the default settings. (meaning
143the color is \c white, and the light emits in the direction of the Z axis)
148 import QtQuick3D.Helpers
157 environment: SceneEnvironment {
158 backgroundMode: SceneEnvironment.Color
173 controlledObject: camera
179Running this with the \c qml tool will load and run, but the scene is all empty
180by default since the Sponza model is behind the camera. The scale is also not
181ideal, e.g. moving around with WASD keys and the mouse (enabled by the
182\l WasdController) does not feel right.
184To remedy this, we scale the Sponza model (subscene) by \c 100 along the X, Y,
185and Z axis. In addition, the camera's starting Y position is bumped to 100.
190 import QtQuick3D.Helpers
199 environment: SceneEnvironment {
200 backgroundMode: SceneEnvironment.Color
213 scale: Qt.vector3d(100, 100, 100)
217 controlledObject: camera
223Running this gives us:
225\image assetintro_sponza_first.jpg
227With the mouse and the WASDRF keys we can move around:
229\image assetintro_sponza_second.jpg
231\image assetintro_sponza_out.jpg
233\note We mentioned \c{subscene} a number of times above as an alternative to
234"model". Why is this? While not obvious with the Sponza asset, which in its
235glTF form is a single model with 103 submeshes, mapping to a single \l Model
236object with 103 elements in its \l{Model::materials}{materials list}, an asset
237can contain any number of \l{Model}{models}, each with multiple submeshes and
238associated materials. These Models can form parent-child relationships and can
239be combined with additional \l{Node}{nodes} to perform transforms such as
240translate, rotate, or scale. It is therefore more appropriate to look at the
241imported asset as a complete subscene, an arbitrary tree of \l{Node}{nodes},
242even if the rendered result is visually perceived as a single model. Open the
243generated Sponza.qml, or any other QML file generated from such assets, in a
244plain text editor to get an impression of the structure (which naturally always
245depends on how the source asset, in this case the glTF file, was designed).
247\section2 Importing via balsamui
249For our second model, let's use the graphical user interface of \c balsam instead.
251Running \c balsamui opens the tool:
253\image assetintro_balsamui_startup.jpg
256\l{https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/2.0/Suzanne}{Suzanne}
257model. This is a simpler model with two texture maps.
259\image assetintro_balsamui_open.jpg
261As there is no need for any additional configuration options, we can just
262Convert. The result is the same as running \c balsam: a Suzanne.qml and some
263additional files generated in the specific output directory.
265\image assetintro_balsamui_convert.jpg
267From this point on, working with the generated assets is the same as in the
273 import QtQuick3D.Helpers
282 environment: SceneEnvironment {
283 backgroundMode: SceneEnvironment.Color
296 scale: Qt.vector3d(100, 100, 100)
301 scale: Qt.vector3d(50, 50, 50)
306 controlledObject: camera
312Again, a scale is applied to the instantiated Suzanne node, and the Y position
313is altered a bit so that the model does not end up in the floor of the Sponza
316\image assetintro_suzanne_first.jpg
318All properties can be changed, bound to, and animated, just like with Qt Quick.
319For example, let's apply a continuous rotation to our Suzanne model:
324 scale: Qt.vector3d(50, 50, 50)
325 NumberAnimation on eulerRotation.y {
329 loops: Animation.Infinite
334\section1 Making it Look Better
338Now, our scene is a bit dark. Let's add another light. This time a \l
339PointLight, and one that casts a shadow.
344 import QtQuick3D.Helpers
353 environment: SceneEnvironment {
354 backgroundMode: SceneEnvironment.Color
367 scale: Qt.vector3d(100, 100, 100)
380 scale: Qt.vector3d(50, 50, 50)
381 NumberAnimation on eulerRotation.y {
385 loops: Animation.Infinite
390 controlledObject: camera
396Launching this scene and moving the camera around a bit reveals that this
397is indeed starting to look better than before:
399\image assetintro_suzanne_morelight.jpg
401\section2 Light debugging
403The \l PointLight is placed slightly above the Suzanne model. When designing
404the scene using visual tools, such as Qt Design Studio, this is obvious, but
405when developing without any design tools it may become handy to be able to
406quickly visualize the location of \l{Light}{lights} and other \l{Node}{nodes}.
408This we can do by adding a child node, a \l Model to the \l PointLight. The
409position of the child node is relative to the parent, so the default \c{(0, 0,
4100)} is effectively the position of the \l PointLight in this case. Enclosing
411the light within some geometry (the built-in cube in this case) is not a
412problem for the standard real-time lighting calculations since this system has
413no concept of occlusion, meaning the light has no problems with traveling
414through "walls". If we used \l{quick3d-lightmap}{pre-baked lightmaps}, where
415lighting is calculated using raytracing, that would be a different story. In
416that case we would need to make sure the cube is not blocking the light,
417perhaps by moving our debug cube a bit above the light.
428 scale: Qt.vector3d(0.01, 0.01, 0.01)
429 materials: PrincipledMaterial {
430 lighting: PrincipledMaterial.NoLighting
436Another trick we use here is turning off lighting for the material used with
437the cube. It will just appear using the default base color (white), without
438being affected by lighting. This is handy for objects used for debugging and
441The result, note the small, white cube appearing, visualizing the position of
444\image assetintro_suzanne_cube.jpg
446\section2 Skybox and image-based lighting
448Another obvious improvement is doing something about the background. That green
449clear color is not quite ideal. How about some environment that also
450contributes to lighting?
452As we do not necessarily have suitable HDRI panorama image available, let's use
453a procedurally generated high dynamic range sky image. This is easy to do with
454the help of \l ProceduralSkyTextureData and \l{Texture}'s support for non-file
455based, dynamically generated image data. Instead of specifying
456\l{Texture::source}{source}, we rather use the
457\l{Texture::textureData}{textureData} property.
460 environment: SceneEnvironment {
461 backgroundMode: SceneEnvironment.SkyBox
462 lightProbe: Texture {
463 textureData: ProceduralSkyTextureData {
469\note The example code prefers defining objects inline. This is not mandatory,
470the SceneEnvironment or ProceduralSkyTextureData objects could have also been
471defined elsewhere in the object tree, and then referenced by \c id.
473As a result, we have both a skybox and improved lighting. (the former due to
474the \l{SceneEnvironment::backgroundMode}{backgroundMode} being set to SkyBox
475and \l{SceneEnvironment::lightProbe}{light probe} being set to a valid \l
476Texture; the latter due to \l{SceneEnvironment::lightProbe}{light probe} being
477set to a valid \l Texture)
479\image assetintro_sponza_ibl.jpg
481\image assetintro_sponza_ibl_2.jpg
483\section1 Basic Performance Investigations
485To get some basic insights into the resource and performance aspects of the
486scene, it is a good idea to add a way to show an interactive \l DebugView item
487early on in the development process. Here we choose to add a \l Button that
488toggles the \l DebugView, both anchored in the top-right corner.
492 import QtQuick.Controls
494 import QtQuick3D.Helpers
504 environment: SceneEnvironment {
505 backgroundMode: SceneEnvironment.SkyBox
506 lightProbe: Texture {
507 textureData: ProceduralSkyTextureData {
521 scale: Qt.vector3d(100, 100, 100)
532 scale: Qt.vector3d(0.01, 0.01, 0.01)
533 materials: PrincipledMaterial {
534 lighting: PrincipledMaterial.NoLighting
541 scale: Qt.vector3d(50, 50, 50)
542 NumberAnimation on eulerRotation.y {
546 loops: Animation.Infinite
551 controlledObject: camera
556 anchors.right: parent.right
557 text: "Toggle DebugView"
558 onClicked: debugView.visible = !debugView.visible
563 anchors.top: parent.bottom
564 anchors.right: parent.right
570\image assetintro_suzanne_debugview.jpg
572This panel shows live timings, allows examining the live list of texture maps
573and meshes, and gives an insight into the render passes that need to be
574performed before the final color buffer can be rendered.
576Due to making the \l PointLight a shadow casting light, there are multiple
577render passes involved:
579\image assetintro_suzanne_debugview_2.jpg
581In the \c Textures section we see the texture maps from the Suzanne and Sponza
582assets (the latter has a lot of them), as well as the procedurally generated
585\image assetintro_suzanne_debugview_3.jpg
587The \c Models page presents no surprises:
589\image assetintro_suzanne_debugview_4.jpg
591On the \c Tools page there are some interactive controls to toggle
592\l{DebugSettings::wireframeEnabled}{wireframe mode} and various
593\l{DebugSettings::materialOverride}{material overrides}.
595Here with wireframe mode enabled and forcing rendering to only use the
596\l{PrincipledMaterial::baseColor}{base color} component of the materials:
598\image assetintro_suzanne_override.jpg
600This concludes our tour of the basics of building a \l{Qt Quick 3D} scene with imported assets.