Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qtquick3d-assetintro.qdoc
Go to the documentation of this file.
1// Copyright (C) 2023 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
3
4/*!
5
6\title Qt Quick 3D Introduction with glTF Assets
7\page quick3d-asset-intro
8
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
15Models repository}.
16
17\section1 Our Skeleton Application
18
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
21nothing else in it.
22
23\qml
24 import QtQuick
25 import QtQuick3D
26 import QtQuick3D.Helpers
27
28 Item {
29 width: 1280
30 height: 720
31
32 View3D {
33 anchors.fill: parent
34
35 environment: SceneEnvironment {
36 backgroundMode: SceneEnvironment.Color
37 clearColor: "green"
38 }
39
40 PerspectiveCamera {
41 id: camera
42 }
43
44 WasdController {
45 controlledObject: camera
46 }
47 }
48 }
49\endqml
50
51\image assetintro_empty.jpg {Empty 3D view with green background}
52
53\section1 Importing an Asset
54
55We are going to use two glTF 2.0 models from the Sample Models repository:
56Sponza and Suzanne.
57
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
60file:
61
62\image assetintro_sponza_dir.jpg {File listing showing Sponza asset files}
63
64How do we get all this into our Qt Quick 3D scene?
65
66There are a number of options:
67
68\list
69
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.
75
76\li Perform the same using \c balsamui, a GUI frontend for \l{Balsam Asset
77Import Tool}{Balsam}.
78
79\li If using \l{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
82appropriate panel.
83
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
92of this approach.
93
94\endlist
95
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
110optimizations.
111
112In \c balsamui the command-line options are mapped to interactive elements:
113\image assetintro_balsamui_options.jpg {Balsam UI settings panel}
114
115\section2 Importing via balsam
116
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:
121
122\c{balsam c:\work\glTF-Sample-Models\2.0\Sponza\glTF\Sponza.gltf}
123
124This gives us a \c{Sponza.qml}, a \c{.mesh} file under the \c meshes
125subdirectory, and the texture maps copied under \c maps.
126
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.
129
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)
134
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}.
141
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)
144
145\qml
146 import QtQuick
147 import QtQuick3D
148 import QtQuick3D.Helpers
149
150 Item {
151 width: 1280
152 height: 720
153
154 View3D {
155 anchors.fill: parent
156
157 environment: SceneEnvironment {
158 backgroundMode: SceneEnvironment.Color
159 clearColor: "green"
160 }
161
162 PerspectiveCamera {
163 id: camera
164 }
165
166 DirectionalLight {
167 }
168
169 Sponza {
170 }
171
172 WasdController {
173 controlledObject: camera
174 }
175 }
176 }
177\endqml
178
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.
183
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.
186
187\qml
188 import QtQuick
189 import QtQuick3D
190 import QtQuick3D.Helpers
191
192 Item {
193 width: 1280
194 height: 720
195
196 View3D {
197 anchors.fill: parent
198
199 environment: SceneEnvironment {
200 backgroundMode: SceneEnvironment.Color
201 clearColor: "green"
202 }
203
204 PerspectiveCamera {
205 id: camera
206 y: 100
207 }
208
209 DirectionalLight {
210 }
211
212 Sponza {
213 scale: Qt.vector3d(100, 100, 100)
214 }
215
216 WasdController {
217 controlledObject: camera
218 }
219 }
220 }
221\endqml
222
223Running this gives us:
224
225\image assetintro_sponza_first.jpg
226 {Sponza palace interior with directional lighting}
227
228With the mouse and the WASDRF keys we can move around:
229
230\image assetintro_sponza_second.jpg {Sponza palace viewed from different angle}
231
232\image assetintro_sponza_out.jpg {Sponza palace viewed from outside}
233
234\note We mentioned \c{subscene} a number of times above as an alternative to
235"model". Why is this? While not obvious with the Sponza asset, which in its
236glTF form is a single model with 103 submeshes, mapping to a single \l Model
237object with 103 elements in its \l{Model::materials}{materials list}, an asset
238can contain any number of \l{Model}{models}, each with multiple submeshes and
239associated materials. These Models can form parent-child relationships and can
240be combined with additional \l{Node}{nodes} to perform transforms such as
241translate, rotate, or scale. It is therefore more appropriate to look at the
242imported asset as a complete subscene, an arbitrary tree of \l{Node}{nodes},
243even if the rendered result is visually perceived as a single model. Open the
244generated Sponza.qml, or any other QML file generated from such assets, in a
245plain text editor to get an impression of the structure (which naturally always
246depends on how the source asset, in this case the glTF file, was designed).
247
248\section2 Importing via balsamui
249
250For our second model, let's use the graphical user interface of \c balsam instead.
251
252Running \c balsamui opens the tool:
253
254\image assetintro_balsamui_startup.jpg {Balsam UI startup window}
255
256Let's import the
257\l{https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/2.0/Suzanne}{Suzanne}
258model. This is a simpler model with two texture maps.
259
260\image assetintro_balsamui_open.jpg {File browser showing Suzanne asset files}
261
262As there is no need for any additional configuration options, we can just
263Convert. The result is the same as running \c balsam: a Suzanne.qml and some
264additional files generated in the specific output directory.
265
266\image assetintro_balsamui_convert.jpg
267 {Balsam UI showing conversion in progress}
268
269From this point on, working with the generated assets is the same as in the
270previous section.
271
272\qml
273 import QtQuick
274 import QtQuick3D
275 import QtQuick3D.Helpers
276
277 Item {
278 width: 1280
279 height: 720
280
281 View3D {
282 anchors.fill: parent
283
284 environment: SceneEnvironment {
285 backgroundMode: SceneEnvironment.Color
286 clearColor: "green"
287 }
288
289 PerspectiveCamera {
290 id: camera
291 y: 100
292 }
293
294 DirectionalLight {
295 }
296
297 Sponza {
298 scale: Qt.vector3d(100, 100, 100)
299 }
300
301 Suzanne {
302 y: 100
303 scale: Qt.vector3d(50, 50, 50)
304 eulerRotation.y: -90
305 }
306
307 WasdController {
308 controlledObject: camera
309 }
310 }
311 }
312\endqml
313
314Again, a scale is applied to the instantiated Suzanne node, and the Y position
315is altered a bit so that the model does not end up in the floor of the Sponza
316building.
317
318\image assetintro_suzanne_first.jpg {Suzanne monkey head model in dark scene}
319
320All properties can be changed, bound to, and animated, just like with Qt Quick.
321For example, let's apply a continuous rotation to our Suzanne model:
322
323\qml
324 Suzanne {
325 y: 100
326 scale: Qt.vector3d(50, 50, 50)
327 NumberAnimation on eulerRotation.y {
328 from: 0
329 to: 360
330 duration: 3000
331 loops: Animation.Infinite
332 }
333 }
334\endqml
335
336\section1 Making it Look Better
337
338\section2 More light
339
340Now, our scene is a bit dark. Let's add another light. This time a \l
341PointLight, and one that casts a shadow.
342
343\qml
344 import QtQuick
345 import QtQuick3D
346 import QtQuick3D.Helpers
347
348 Item {
349 width: 1280
350 height: 720
351
352 View3D {
353 anchors.fill: parent
354
355 environment: SceneEnvironment {
356 backgroundMode: SceneEnvironment.Color
357 clearColor: "green"
358 }
359
360 PerspectiveCamera {
361 id: camera
362 y: 100
363 }
364
365 DirectionalLight {
366 }
367
368 Sponza {
369 scale: Qt.vector3d(100, 100, 100)
370 }
371
372 PointLight {
373 y: 200
374 color: "#d9c62b"
375 brightness: 5
376 castsShadow: true
377 shadowFactor: 75
378 }
379
380 Suzanne {
381 y: 100
382 scale: Qt.vector3d(50, 50, 50)
383 NumberAnimation on eulerRotation.y {
384 from: 0
385 to: 360
386 duration: 3000
387 loops: Animation.Infinite
388 }
389 }
390
391 WasdController {
392 controlledObject: camera
393 }
394 }
395 }
396\endqml
397
398Launching this scene and moving the camera around a bit reveals that this
399is indeed starting to look better than before:
400
401\image assetintro_suzanne_morelight.jpg {Suzanne with additional lighting}
402
403\section2 Light debugging
404
405The \l PointLight is placed slightly above the Suzanne model. When designing
406the scene using visual tools, such as Qt Design Studio, this is obvious, but
407when developing without any design tools it may become handy to be able to
408quickly visualize the location of \l{Light}{lights} and other \l{Node}{nodes}.
409
410This we can do by adding a child node, a \l Model to the \l PointLight. The
411position of the child node is relative to the parent, so the default \c{(0, 0,
4120)} is effectively the position of the \l PointLight in this case. Enclosing
413the light within some geometry (the built-in cube in this case) is not a
414problem for the standard real-time lighting calculations since this system has
415no concept of occlusion, meaning the light has no problems with traveling
416through "walls". If we used \l{quick3d-lightmap}{pre-baked lightmaps}, where
417lighting is calculated using raytracing, that would be a different story. In
418that case we would need to make sure the cube is not blocking the light,
419perhaps by moving our debug cube a bit above the light.
420
421\qml
422 PointLight {
423 y: 200
424 color: "#d9c62b"
425 brightness: 5
426 castsShadow: true
427 shadowFactor: 75
428 Model {
429 source: "#Cube"
430 scale: Qt.vector3d(0.01, 0.01, 0.01)
431 materials: PrincipledMaterial {
432 lighting: PrincipledMaterial.NoLighting
433 }
434 }
435 }
436\endqml
437
438Another trick we use here is turning off lighting for the material used with
439the cube. It will just appear using the default base color (white), without
440being affected by lighting. This is handy for objects used for debugging and
441visualizing purposes.
442
443The result, note the small, white cube appearing, visualizing the position of
444the \l PointLight:
445
446\image assetintro_suzanne_cube.jpg {Suzanne monkey head on Sponza palace floor}
447
448\section2 Skybox and image-based lighting
449
450Another obvious improvement is doing something about the background. That green
451clear color is not quite ideal. How about some environment that also
452contributes to lighting?
453
454As we do not necessarily have suitable HDRI panorama image available, let's use
455a procedurally generated high dynamic range sky image. This is easy to do with
456the help of \l ProceduralSkyTextureData and \l{Texture}'s support for non-file
457based, dynamically generated image data. Instead of specifying
458\l{Texture::source}{source}, we rather use the
459\l{Texture::textureData}{textureData} property.
460
461\qml
462 environment: SceneEnvironment {
463 backgroundMode: SceneEnvironment.SkyBox
464 lightProbe: Texture {
465 textureData: ProceduralSkyTextureData {
466 }
467 }
468 }
469\endqml
470
471\note The example code prefers defining objects inline. This is not mandatory,
472the SceneEnvironment or ProceduralSkyTextureData objects could have also been
473defined elsewhere in the object tree, and then referenced by \c id.
474
475\note The same sky can also be rendered on the GPU with \l ProceduralSkyMaterial
476by setting \l{SceneEnvironment::backgroundMode}{backgroundMode} to \c SkyMaterial
477and assigning the material to \l{SceneEnvironment::skyMaterial}{skyMaterial}.
478This avoids baking a texture on the CPU and allows animating the sky parameters.
479
480As a result, we have both a skybox and improved lighting. (the former due to
481the \l{SceneEnvironment::backgroundMode}{backgroundMode} being set to SkyBox
482and \l{SceneEnvironment::lightProbe}{light probe} being set to a valid \l
483Texture; the latter due to \l{SceneEnvironment::lightProbe}{light probe} being
484set to a valid \l Texture)
485
486\image assetintro_sponza_ibl.jpg {Sponza palace with image-based lighting}
487
488\image assetintro_sponza_ibl_2.jpg
489 {Sponza palace with different IBL orientation}
490
491\section1 Basic Performance Investigations
492
493To get some basic insights into the resource and performance aspects of the
494scene, it is a good idea to add a way to show an interactive \l DebugView item
495early on in the development process. Here we choose to add a \l Button that
496toggles the \l DebugView, both anchored in the top-right corner.
497
498\qml
499 import QtQuick
500 import QtQuick.Controls
501 import QtQuick3D
502 import QtQuick3D.Helpers
503
504 Item {
505 width: 1280
506 height: 720
507
508 View3D {
509 id: view3D
510 anchors.fill: parent
511
512 environment: SceneEnvironment {
513 backgroundMode: SceneEnvironment.SkyBox
514 lightProbe: Texture {
515 textureData: ProceduralSkyTextureData {
516 }
517 }
518 }
519
520 PerspectiveCamera {
521 id: camera
522 y: 100
523 }
524
525 DirectionalLight {
526 }
527
528 Sponza {
529 scale: Qt.vector3d(100, 100, 100)
530 }
531
532 PointLight {
533 y: 200
534 color: "#d9c62b"
535 brightness: 5
536 castsShadow: true
537 shadowFactor: 75
538 Model {
539 source: "#Cube"
540 scale: Qt.vector3d(0.01, 0.01, 0.01)
541 materials: PrincipledMaterial {
542 lighting: PrincipledMaterial.NoLighting
543 }
544 }
545 }
546
547 Suzanne {
548 y: 100
549 scale: Qt.vector3d(50, 50, 50)
550 NumberAnimation on eulerRotation.y {
551 from: 0
552 to: 360
553 duration: 3000
554 loops: Animation.Infinite
555 }
556 }
557
558 WasdController {
559 controlledObject: camera
560 }
561 }
562
563 Button {
564 anchors.right: parent.right
565 text: "Toggle DebugView"
566 onClicked: debugView.visible = !debugView.visible
567 DebugView {
568 id: debugView
569 source: view3D
570 visible: false
571 anchors.top: parent.bottom
572 anchors.right: parent.right
573 }
574 }
575 }
576\endqml
577
578\image assetintro_suzanne_debugview.jpg
579 {Sponza scene with debug view panel showing performance statistics}
580
581This panel shows live timings, allows examining the live list of texture maps
582and meshes, and gives an insight into the render passes that need to be
583performed before the final color buffer can be rendered.
584
585Due to making the \l PointLight a shadow casting light, there are multiple
586render passes involved:
587
588\image assetintro_suzanne_debugview_2.jpg
589 {Suzanne with debug panel showing render passes}
590
591In the \c Textures section we see the texture maps from the Suzanne and Sponza
592assets (the latter has a lot of them), as well as the procedurally generated
593sky texture.
594
595\image assetintro_suzanne_debugview_3.jpg
596 {Scene with debug panel showing texture statistics}
597
598The \c Models page presents no surprises:
599
600\image assetintro_suzanne_debugview_4.jpg
601 {Scene with debug panel showing mesh statistics}
602
603On the \c Tools page there are some interactive controls to toggle
604\l{DebugSettings::wireframeEnabled}{wireframe mode} and various
605\l{DebugSettings::materialOverride}{material overrides}.
606
607Here with wireframe mode enabled and forcing rendering to only use the
608\l{PrincipledMaterial::baseColor}{base color} component of the materials:
609
610\image assetintro_suzanne_override.jpg
611 {Sponza scene in wireframe mode with debug panel}
612
613This concludes our tour of the basics of building a \l{Qt Quick 3D} scene with imported assets.
614
615*/