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
9
The \l{Qt Quick 3D - Introduction} example provides a quick introduction to
10
creating QML-based applications with Qt Quick 3D, but it does so using only
11
built-in primitives, such as spheres and cylinders. This page provides an
12
introduction using \l{https://en.wikipedia.org/wiki/GlTF#glTF_2.0}{glTF 2.0}
13
assets, using some of the models from the
14
\l{https://github.com/KhronosGroup/glTF-Sample-Models}{Khronos glTF Sample
15
Models repository}.
16
17
\section1 Our Skeleton Application
18
19
Let's start with the following application. This code snippet is runnable as-is
20
with the \c qml command-line tool. The result is a very green 3D view with
21
nothing 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
55
We are going to use two glTF 2.0 models from the Sample Models repository:
56
Sponza and Suzanne.
57
58
These 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
60
file:
61
62
\image assetintro_sponza_dir.jpg {File listing showing Sponza asset files}
63
64
How do we get all this into our Qt Quick 3D scene?
65
66
There are a number of options:
67
68
\list
69
70
\li Generate QML components that can be instantiated in the scene. The
71
command-line tool to perform this conversion is the \l{Balsam Asset Import
72
Tool}{Balsam} tool. Besides generating a .qml file, that is effectively a
73
subscene, this also repacks the mesh (geometry) data into an optimized,
74
fast-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
77
Import Tool}{Balsam}.
78
79
\li If using \l{Qt Design Studio},
80
the asset import process is integrated into the visual design tools. Importing
81
can be triggered, for example, by dragging and dropping the .gltf file onto the
82
appropriate 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
86
binary and texture data files) at runtime, without doing any pre-processing via
87
tools such as \l{Balsam Asset Import Tool}{Balsam}. This is very handy in
88
applications that wish to open and load user-provided assets. On the other
89
hand, this approach is significantly less efficient when it comes to
90
performance. Therefore, we will not be focusing on this approach in this
91
introduction. Check the \l{Qt Quick 3D - RuntimeLoader Example} for an example
92
of this approach.
93
94
\endlist
95
96
Both the \c balsam and \c balsamui applications are shipped with Qt, and should
97
be present in the directory with other similar executable tools, assuming Qt
98
Quick 3D is installed or built. In many cases, running \l{Balsam Asset Import
99
Tool}{balsam} from the command-line on the .gltf file is sufficient, without
100
having to specify any additional arguments. It is worth being aware however of
101
the many command-line, or interactive if using \c balsamui or Qt Design Studio,
102
options. For example, when working with \l{quick3d-lightmap}{baked lightmaps to
103
provide static global illumination}, it is likely that one will want to pass
104
\c{--generateLightmapUV} to get the additional lightmap UV channel generated at
105
asset import time, instead of performing this potentially consuming process at
106
run-time. Similarly, \c{--generateMeshLevelsOfDetail} is essential when it is
107
desirable 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
109
allow generating missing data (e.g. \c{--generateNormals}) and performing various
110
optimizations.
111
112
In \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
117
Let's get started! Assuming that the
118
\l{https://github.com/KhronosGroup/glTF-Sample-Models} \c git repository is
119
checked out somewhere, we can simply run balsam from our example application
120
directory, 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
124
This gives us a \c{Sponza.qml}, a \c{.mesh} file under the \c meshes
125
subdirectory, 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
128
should be instantiated within a 3D scene associated with a \l View3D.
129
130
Our project structure is very simple here, as the asset qml files live right
131
next to our main .qml scene. This allows us to simply instantiate the Sponza
132
type using the \l{QML Documents}{standard QML component system}. (at run-time
133
this will then look for Sponza.qml in the filesystem)
134
135
Just adding the model (subscene) is pointless however, since by default the
136
materials feature the full \l{quick3d-pbr}{PBR lighting calculations}, so
137
nothing is shown from our scene without a light such as \l DirectionalLight, \l
138
PointLight, or \l SpotLight, or having \l{Using Image-Based
139
Lighting}{image-based lighting} enabled via
140
\l{SceneEnvironment::lightProbe}{the environment}.
141
142
For now, we choose to add a DirectionalLight with the default settings. (meaning
143
the 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
179
Running this with the \c qml tool will load and run, but the scene is all empty
180
by default since the Sponza model is behind the camera. The scale is also not
181
ideal, e.g. moving around with WASD keys and the mouse (enabled by the
182
\l WasdController) does not feel right.
183
184
To remedy this, we scale the Sponza model (subscene) by \c 100 along the X, Y,
185
and 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
223
Running this gives us:
224
225
\image assetintro_sponza_first.jpg
226
{Sponza palace interior with directional lighting}
227
228
With 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
236
glTF form is a single model with 103 submeshes, mapping to a single \l Model
237
object with 103 elements in its \l{Model::materials}{materials list}, an asset
238
can contain any number of \l{Model}{models}, each with multiple submeshes and
239
associated materials. These Models can form parent-child relationships and can
240
be combined with additional \l{Node}{nodes} to perform transforms such as
241
translate, rotate, or scale. It is therefore more appropriate to look at the
242
imported asset as a complete subscene, an arbitrary tree of \l{Node}{nodes},
243
even if the rendered result is visually perceived as a single model. Open the
244
generated Sponza.qml, or any other QML file generated from such assets, in a
245
plain text editor to get an impression of the structure (which naturally always
246
depends on how the source asset, in this case the glTF file, was designed).
247
248
\section2 Importing via balsamui
249
250
For our second model, let's use the graphical user interface of \c balsam instead.
251
252
Running \c balsamui opens the tool:
253
254
\image assetintro_balsamui_startup.jpg {Balsam UI startup window}
255
256
Let's import the
257
\l{https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/2.0/Suzanne}{Suzanne}
258
model. This is a simpler model with two texture maps.
259
260
\image assetintro_balsamui_open.jpg {File browser showing Suzanne asset files}
261
262
As there is no need for any additional configuration options, we can just
263
Convert. The result is the same as running \c balsam: a Suzanne.qml and some
264
additional files generated in the specific output directory.
265
266
\image assetintro_balsamui_convert.jpg
267
{Balsam UI showing conversion in progress}
268
269
From this point on, working with the generated assets is the same as in the
270
previous 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
314
Again, a scale is applied to the instantiated Suzanne node, and the Y position
315
is altered a bit so that the model does not end up in the floor of the Sponza
316
building.
317
318
\image assetintro_suzanne_first.jpg {Suzanne monkey head model in dark scene}
319
320
All properties can be changed, bound to, and animated, just like with Qt Quick.
321
For 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
340
Now, our scene is a bit dark. Let's add another light. This time a \l
341
PointLight, 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
398
Launching this scene and moving the camera around a bit reveals that this
399
is indeed starting to look better than before:
400
401
\image assetintro_suzanne_morelight.jpg {Suzanne with additional lighting}
402
403
\section2 Light debugging
404
405
The \l PointLight is placed slightly above the Suzanne model. When designing
406
the scene using visual tools, such as Qt Design Studio, this is obvious, but
407
when developing without any design tools it may become handy to be able to
408
quickly visualize the location of \l{Light}{lights} and other \l{Node}{nodes}.
409
410
This we can do by adding a child node, a \l Model to the \l PointLight. The
411
position of the child node is relative to the parent, so the default \c{(0, 0,
412
0)} is effectively the position of the \l PointLight in this case. Enclosing
413
the light within some geometry (the built-in cube in this case) is not a
414
problem for the standard real-time lighting calculations since this system has
415
no concept of occlusion, meaning the light has no problems with traveling
416
through "walls". If we used \l{quick3d-lightmap}{pre-baked lightmaps}, where
417
lighting is calculated using raytracing, that would be a different story. In
418
that case we would need to make sure the cube is not blocking the light,
419
perhaps 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
438
Another trick we use here is turning off lighting for the material used with
439
the cube. It will just appear using the default base color (white), without
440
being affected by lighting. This is handy for objects used for debugging and
441
visualizing purposes.
442
443
The result, note the small, white cube appearing, visualizing the position of
444
the \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
450
Another obvious improvement is doing something about the background. That green
451
clear color is not quite ideal. How about some environment that also
452
contributes to lighting?
453
454
As we do not necessarily have suitable HDRI panorama image available, let's use
455
a procedurally generated high dynamic range sky image. This is easy to do with
456
the help of \l ProceduralSkyTextureData and \l{Texture}'s support for non-file
457
based, 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,
472
the SceneEnvironment or ProceduralSkyTextureData objects could have also been
473
defined 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
476
by setting \l{SceneEnvironment::backgroundMode}{backgroundMode} to \c SkyMaterial
477
and assigning the material to \l{SceneEnvironment::skyMaterial}{skyMaterial}.
478
This avoids baking a texture on the CPU and allows animating the sky parameters.
479
480
As a result, we have both a skybox and improved lighting. (the former due to
481
the \l{SceneEnvironment::backgroundMode}{backgroundMode} being set to SkyBox
482
and \l{SceneEnvironment::lightProbe}{light probe} being set to a valid \l
483
Texture; the latter due to \l{SceneEnvironment::lightProbe}{light probe} being
484
set 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
493
To get some basic insights into the resource and performance aspects of the
494
scene, it is a good idea to add a way to show an interactive \l DebugView item
495
early on in the development process. Here we choose to add a \l Button that
496
toggles 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
581
This panel shows live timings, allows examining the live list of texture maps
582
and meshes, and gives an insight into the render passes that need to be
583
performed before the final color buffer can be rendered.
584
585
Due to making the \l PointLight a shadow casting light, there are multiple
586
render passes involved:
587
588
\image assetintro_suzanne_debugview_2.jpg
589
{Suzanne with debug panel showing render passes}
590
591
In the \c Textures section we see the texture maps from the Suzanne and Sponza
592
assets (the latter has a lot of them), as well as the procedurally generated
593
sky texture.
594
595
\image assetintro_suzanne_debugview_3.jpg
596
{Scene with debug panel showing texture statistics}
597
598
The \c Models page presents no surprises:
599
600
\image assetintro_suzanne_debugview_4.jpg
601
{Scene with debug panel showing mesh statistics}
602
603
On 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
607
Here 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
613
This concludes our tour of the basics of building a \l{Qt Quick 3D} scene with imported assets.
614
615
*/
qtquick3d
src
quick3d
doc
src
qtquick3d-assetintro.qdoc
Generated on
for Qt by
1.16.1