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
As a result, we have both a skybox and improved lighting. (the former due to
476
the \l{SceneEnvironment::backgroundMode}{backgroundMode} being set to SkyBox
477
and \l{SceneEnvironment::lightProbe}{light probe} being set to a valid \l
478
Texture; the latter due to \l{SceneEnvironment::lightProbe}{light probe} being
479
set 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
488
To get some basic insights into the resource and performance aspects of the
489
scene, it is a good idea to add a way to show an interactive \l DebugView item
490
early on in the development process. Here we choose to add a \l Button that
491
toggles 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
576
This panel shows live timings, allows examining the live list of texture maps
577
and meshes, and gives an insight into the render passes that need to be
578
performed before the final color buffer can be rendered.
579
580
Due to making the \l PointLight a shadow casting light, there are multiple
581
render passes involved:
582
583
\image assetintro_suzanne_debugview_2.jpg
584
{Suzanne with debug panel showing render passes}
585
586
In the \c Textures section we see the texture maps from the Suzanne and Sponza
587
assets (the latter has a lot of them), as well as the procedurally generated
588
sky texture.
589
590
\image assetintro_suzanne_debugview_3.jpg
591
{Scene with debug panel showing texture statistics}
592
593
The \c Models page presents no surprises:
594
595
\image assetintro_suzanne_debugview_4.jpg
596
{Scene with debug panel showing mesh statistics}
597
598
On 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
602
Here 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
608
This concludes our tour of the basics of building a \l{Qt Quick 3D} scene with imported assets.
609
610
*/
qtquick3d
src
quick3d
doc
src
qtquick3d-assetintro.qdoc
Generated on
for Qt by
1.16.1