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
475As a result, we have both a skybox and improved lighting. (the former due to
476the \l{SceneEnvironment::backgroundMode}{backgroundMode} being set to SkyBox
477and \l{SceneEnvironment::lightProbe}{light probe} being set to a valid \l
478Texture; the latter due to \l{SceneEnvironment::lightProbe}{light probe} being
479set to a valid \l Texture)
480
481\image assetintro_sponza_ibl.jpg {Sponza palace with image-based lighting}
482
483\image assetintro_sponza_ibl_2.jpg
484 {Sponza palace with different IBL orientation}
485
486\section1 Basic Performance Investigations
487
488To get some basic insights into the resource and performance aspects of the
489scene, it is a good idea to add a way to show an interactive \l DebugView item
490early on in the development process. Here we choose to add a \l Button that
491toggles the \l DebugView, both anchored in the top-right corner.
492
493\qml
494 import QtQuick
495 import QtQuick.Controls
496 import QtQuick3D
497 import QtQuick3D.Helpers
498
499 Item {
500 width: 1280
501 height: 720
502
503 View3D {
504 id: view3D
505 anchors.fill: parent
506
507 environment: SceneEnvironment {
508 backgroundMode: SceneEnvironment.SkyBox
509 lightProbe: Texture {
510 textureData: ProceduralSkyTextureData {
511 }
512 }
513 }
514
515 PerspectiveCamera {
516 id: camera
517 y: 100
518 }
519
520 DirectionalLight {
521 }
522
523 Sponza {
524 scale: Qt.vector3d(100, 100, 100)
525 }
526
527 PointLight {
528 y: 200
529 color: "#d9c62b"
530 brightness: 5
531 castsShadow: true
532 shadowFactor: 75
533 Model {
534 source: "#Cube"
535 scale: Qt.vector3d(0.01, 0.01, 0.01)
536 materials: PrincipledMaterial {
537 lighting: PrincipledMaterial.NoLighting
538 }
539 }
540 }
541
542 Suzanne {
543 y: 100
544 scale: Qt.vector3d(50, 50, 50)
545 NumberAnimation on eulerRotation.y {
546 from: 0
547 to: 360
548 duration: 3000
549 loops: Animation.Infinite
550 }
551 }
552
553 WasdController {
554 controlledObject: camera
555 }
556 }
557
558 Button {
559 anchors.right: parent.right
560 text: "Toggle DebugView"
561 onClicked: debugView.visible = !debugView.visible
562 DebugView {
563 id: debugView
564 source: view3D
565 visible: false
566 anchors.top: parent.bottom
567 anchors.right: parent.right
568 }
569 }
570 }
571\endqml
572
573\image assetintro_suzanne_debugview.jpg
574 {Sponza scene with debug view panel showing performance statistics}
575
576This panel shows live timings, allows examining the live list of texture maps
577and meshes, and gives an insight into the render passes that need to be
578performed before the final color buffer can be rendered.
579
580Due to making the \l PointLight a shadow casting light, there are multiple
581render passes involved:
582
583\image assetintro_suzanne_debugview_2.jpg
584 {Suzanne with debug panel showing render passes}
585
586In the \c Textures section we see the texture maps from the Suzanne and Sponza
587assets (the latter has a lot of them), as well as the procedurally generated
588sky texture.
589
590\image assetintro_suzanne_debugview_3.jpg
591 {Scene with debug panel showing texture statistics}
592
593The \c Models page presents no surprises:
594
595\image assetintro_suzanne_debugview_4.jpg
596 {Scene with debug panel showing mesh statistics}
597
598On the \c Tools page there are some interactive controls to toggle
599\l{DebugSettings::wireframeEnabled}{wireframe mode} and various
600\l{DebugSettings::materialOverride}{material overrides}.
601
602Here with wireframe mode enabled and forcing rendering to only use the
603\l{PrincipledMaterial::baseColor}{base color} component of the materials:
604
605\image assetintro_suzanne_override.jpg
606 {Sponza scene in wireframe mode with debug panel}
607
608This concludes our tour of the basics of building a \l{Qt Quick 3D} scene with imported assets.
609
610*/