Qt Quick Introduction à la 3D avec les ressources glTF
L'exemple « Qt Quick 3D - Introduction » propose une introduction rapide à la création d'applications basées sur QML avec Qt Quick 3D, mais il utilise uniquement des primitives intégrées, telles que des sphères et des cylindres. Cette page propose une introduction à l'aide de ressources glTF 2.0, en utilisant certains des modèles issus du référentiel Khronos glTF Sample Models.
Notre application « Skeleton »
Commençons par l’application suivante. Cet extrait de code est exécutable tel quel à l’aide de l’outil en ligne de commande ` qml `. Le résultat est une vue 3D entièrement verte, sans aucun autre élément.
import QtQuick
import QtQuick3D
import QtQuick3D.Helpers
Item {
width: 1280
height: 720
View3D {
anchors.fill: parent
environment: SceneEnvironment {
backgroundMode: SceneEnvironment.Color
clearColor: "green"
}
PerspectiveCamera {
id: camera
}
WasdController {
controlledObject: camera
}
}
}
Importation d’un élément
Nous allons utiliser deux modèles glTF 2.0 issus du référentiel « Sample Models » : Sponza et Suzanne.
Ces modèles sont généralement fournis avec plusieurs textures et les données de maillage (géométrie) stockées dans un fichier binaire séparé, en plus du fichier .gltf :

Comment intégrer tout cela dans notre scène 3D d’ Qt Quick?
Plusieurs options s’offrent à nous :
- Générer des composants QML pouvant être instanciés dans la scène. L’outil en ligne de commande permettant d’effectuer cette conversion est l’outil Balsam. Outre la génération d’un fichier .qml, qui correspond en réalité à une sous-scène, cet outil reconditionne également les données de maillage (géométrie) dans un format optimisé et rapide à charger, et copie également les fichiers image des cartes de texture.
- Effectuez la même opération à l'aide de
balsamui, une interface graphique pour Balsam. - Si vous utilisez Qt Design Studio, le processus d’importation des ressources est intégré aux outils de conception visuelle. L’importation peut être déclenchée, par exemple, en glissant-déposant le fichier .gltf sur le panneau approprié.
- Pour les ressources glTF 2.0 en particulier, il existe également une option d’exécution : le type RuntimeLoader. Cela permet de charger un fichier .gltf (ainsi que les fichiers binaires et de données de texture associés) lors de l’exécution, sans effectuer de prétraitement via des outils tels que Balsam. C’est très pratique dans les applications qui souhaitent ouvrir et charger des ressources fournies par l’utilisateur. En revanche, cette approche est nettement moins efficace en termes de performances. C’est pourquoi nous ne nous attarderons pas sur cette approche dans cette introduction. Consultez l’exemple « Qt Quick 3D - RuntimeLoader » pour découvrir un exemple de cette approche.
Les applications « balsam » et « balsamui » sont toutes deux fournies avec Qt et devraient se trouver dans le répertoire avec d’autres outils exécutables similaires, à condition que Qt Quick 3D soit installé ou compilé. Dans de nombreux cas, il suffit d’exécuter « balsam » depuis la ligne de commande sur le fichier .gltf, sans avoir à spécifier d’arguments supplémentaires. Il est toutefois utile de connaître les nombreuses options disponibles en ligne de commande, ou en mode interactif si vous utilisez balsamui ou Qt Design Studio. Par exemple, lorsque vous travaillez avec des lightmaps cuites pour fournir un éclairage global statique, vous souhaiterez probablement passer l'option --generateLightmapUV afin d'obtenir le canal UV supplémentaire de la lightmap généré au moment de l'importation de l'élément, plutôt que d'effectuer ce processus potentiellement chronophage au moment de l'exécution. De même, l’option « --generateMeshLevelsOfDetail » est indispensable lorsqu’il est souhaitable de disposer de versions simplifiées des maillages générés afin d’activer automatiquement le LOD dans la scène. D’autres options permettent de générer des données manquantes (par exemple, « --generateNormals ») et d’effectuer diverses optimisations.
Dans « balsamui », les options de ligne de commande sont associées à des éléments interactifs :

Importation via Balsam
C'est parti ! En supposant que le dépôt https://github.com/KhronosGroup/glTF-Sample-Models git ait été extrait quelque part, nous pouvons simplement exécuter balsam depuis le répertoire de notre application d'exemple, en spécifiant un chemin absolu vers les fichiers .gltf :
balsam c:\work\glTF-Sample-Models\2.0\Sponza\glTF\Sponza.gltf
Cela nous donne un fichier Sponza.qml, un fichier .mesh dans le sous-répertoire meshes, ainsi que les textures copiées dans maps.
Remarque : ce fichier QML n’est pas exécutable en tant que tel. Il s’agit d’un composant qui doit être instancié au sein d’une scène 3D associée à un fichier View3D.
La structure de notre projet est ici très simple, car les fichiers QML des ressources se trouvent juste à côté de notre scène .qml principale. Cela nous permet d'instancier simplement le type Sponza à l'aide du système de composants QML standard. (Au moment de l'exécution, le système recherchera alors Sponza.qml dans le système de fichiers.)
Cependant, se contenter d’ajouter le modèle (sous-scène) ne sert à rien, car par défaut, les matériaux intègrent l’intégralité des calculs d’éclairage PBR; ainsi, rien ne s’affiche dans notre scène sans une source lumineuse telle que DirectionalLight, PointLight ou SpotLight, ou sans que l’éclairage basé sur des images soit activé via the environment.
Pour l’instant, nous choisissons d’ajouter un DirectionalLight avec les paramètres par défaut (ce qui signifie que la couleur est white et que la lumière est émise dans la direction de l’axe Z).
import QtQuick
import QtQuick3D
import QtQuick3D.Helpers
Item {
width: 1280
height: 720
View3D {
anchors.fill: parent
environment: SceneEnvironment {
backgroundMode: SceneEnvironment.Color
clearColor: "green"
}
PerspectiveCamera {
id: camera
}
DirectionalLight {
}
Sponza {
}
WasdController {
controlledObject: camera
}
}
}L'exécution de cette commande à l'aide de l'outil « qml » chargera et lancera la scène, mais celle-ci est entièrement vide par défaut puisque le modèle Sponza se trouve derrière la caméra. L'échelle n'est pas non plus idéale : par exemple, se déplacer à l'aide des touches WASD et de la souris (fonctionnalité activée par l'option « WasdController ») ne donne pas une sensation naturelle.
Pour y remédier, nous redimensionnons le modèle Sponza (sous-scène) via 100 le long des axes X, Y et Z. De plus, la position Y de départ de la caméra est augmentée à 100.
import QtQuick
import QtQuick3D
import QtQuick3D.Helpers
Item {
width: 1280
height: 720
View3D {
anchors.fill: parent
environment: SceneEnvironment {
backgroundMode: SceneEnvironment.Color
clearColor: "green"
}
PerspectiveCamera {
id: camera
y: 100
}
DirectionalLight {
}
Sponza {
scale: Qt.vector3d(100, 100, 100)
}
WasdController {
controlledObject: camera
}
}
}En exécutant cela, on obtient :

À l’aide de la souris et des touches WASDRF, nous pouvons nous déplacer :


Remarque : nous avons mentionné à plusieurs reprises ci-dessus subscene comme alternative à « model ». Pourquoi cela ? Bien que cela ne soit pas évident avec l’asset Sponza, qui, sous sa forme glTF, est un modèle unique comportant 103 sous-maillages, correspondant à un seul objet « Model » avec 103 éléments dans son « materials list », un asset peut contenir un nombre illimité de « models », chacun comportant plusieurs sous-maillages et des matériaux associés. Ces modèles peuvent former des relations parent-enfant et être combinés à des « nodes » supplémentaires pour effectuer des transformations telles que la translation, la rotation ou la mise à l'échelle. Il est donc plus approprié de considérer l’asset importé comme une sous-scène complète, un arbre arbitraire d’ nodes, même si le résultat rendu est visuellement perçu comme un modèle unique. Ouvrez le fichier Sponza.qml généré, ou tout autre fichier QML issu de ces ressources, dans un éditeur de texte brut pour vous faire une idée de la structure (qui dépend bien sûr toujours de la manière dont la ressource source, en l’occurrence le fichier glTF, a été conçue).
Importation via balsamui
Pour notre deuxième modèle, utilisons plutôt l’interface utilisateur graphique de balsam.
L'exécution de balsamui ouvre l'outil :

Importons le modèle Suzanne. Il s’agit d’un modèle plus simple comportant deux cartes de texture.

Comme aucune option de configuration supplémentaire n’est nécessaire, il suffit de cliquer sur « Convert ». Le résultat est le même que lors de l’exécution de balsam: un fichier Suzanne.qml et quelques fichiers supplémentaires générés dans le répertoire de sortie spécifié.

À partir de là, l’utilisation des ressources générées se déroule de la même manière que dans la section précédente.
import QtQuick
import QtQuick3D
import QtQuick3D.Helpers
Item {
width: 1280
height: 720
View3D {
anchors.fill: parent
environment: SceneEnvironment {
backgroundMode: SceneEnvironment.Color
clearColor: "green"
}
PerspectiveCamera {
id: camera
y: 100
}
DirectionalLight {
}
Sponza {
scale: Qt.vector3d(100, 100, 100)
}
Suzanne {
y: 100
scale: Qt.vector3d(50, 50, 50)
eulerRotation.y: -90
}
WasdController {
controlledObject: camera
}
}
}Une fois encore, un facteur d’échelle est appliqué au nœud Suzanne instancié, et la position Y est légèrement modifiée afin que le modèle ne se retrouve pas au niveau du sol du bâtiment Sponza.

Toutes les propriétés peuvent être modifiées, liées à des variables et animées, exactement comme avec Qt Quick. Par exemple, appliquons une rotation continue à notre modèle Suzanne :
Suzanne {
y: 100
scale: Qt.vector3d(50, 50, 50)
NumberAnimation on eulerRotation.y {
from: 0
to: 360
duration: 3000
loops: Animation.Infinite
}
}Améliorer l’aspect visuel
Plus de lumière
Pour l’instant, notre scène est un peu sombre. Ajoutons une autre source de lumière. Cette fois-ci, un « PointLight » qui projette une ombre.
import QtQuick
import QtQuick3D
import QtQuick3D.Helpers
Item {
width: 1280
height: 720
View3D {
anchors.fill: parent
environment: SceneEnvironment {
backgroundMode: SceneEnvironment.Color
clearColor: "green"
}
PerspectiveCamera {
id: camera
y: 100
}
DirectionalLight {
}
Sponza {
scale: Qt.vector3d(100, 100, 100)
}
PointLight {
y: 200
color: "#d9c62b"
brightness: 5
castsShadow: true
shadowFactor: 75
}
Suzanne {
y: 100
scale: Qt.vector3d(50, 50, 50)
NumberAnimation on eulerRotation.y {
from: 0
to: 360
duration: 3000
loops: Animation.Infinite
}
}
WasdController {
controlledObject: camera
}
}
}En lançant cette scène et en déplaçant un peu la caméra, on constate que le rendu est effectivement meilleur qu’auparavant :

Débogage de l’éclairage
L'PointLight est placé légèrement au-dessus du modèle de Suzanne. Lorsque l'on conçoit la scène à l'aide d'outils visuels, tels que Qt Design Studio, cela va de soi, mais lorsque l'on développe sans aucun outil de conception, il peut s'avérer pratique de pouvoir visualiser rapidement l'emplacement d'lights et d'autres nodes.
Pour ce faire, il suffit d’ajouter un nœud enfant, un « Model », à l’« PointLight ». La position du nœud enfant étant relative à celle du nœud parent, l’« (0, 0, 0) » par défaut correspond donc, dans ce cas, à la position de l’« PointLight ». Enfermer la lumière à l’intérieur d’une géométrie (le cube intégré dans ce cas) ne pose aucun problème pour les calculs d’éclairage en temps réel standard, car ce système ne connaît pas la notion d’occlusion, ce qui signifie que la lumière traverse sans difficulté les « murs ». Si nous utilisions des lightmaps précalculées, où l’éclairage est calculé par ray tracing, ce serait une autre histoire. Dans ce cas, nous devrions nous assurer que le cube ne bloque pas la lumière, peut-être en déplaçant notre cube de débogage légèrement au-dessus de la source lumineuse.
PointLight {
y: 200
color: "#d9c62b"
brightness: 5
castsShadow: true
shadowFactor: 75
Model {
source: "#Cube"
scale: Qt.vector3d(0.01, 0.01, 0.01)
materials: PrincipledMaterial {
lighting: PrincipledMaterial.NoLighting
}
}
}Une autre astuce que nous utilisons ici consiste à désactiver l’éclairage pour le matériau utilisé avec le cube. Il apparaîtra alors simplement avec sa couleur de base par défaut (le blanc), sans être affecté par l’éclairage. Cela s’avère pratique pour les objets utilisés à des fins de débogage et de visualisation.
Le résultat : remarquez le petit cube blanc qui apparaît, visualisant la position de l’ PointLight:

Skybox et éclairage basé sur des images
Une autre amélioration évidente consiste à s’occuper de l’arrière-plan. Cette couleur verte transparente n’est pas tout à fait idéale. Pourquoi ne pas créer un environnement qui contribue également à l’éclairage ?
Comme nous ne disposons pas nécessairement d’une image panoramique HDRI adaptée, utilisons une image de ciel à plage dynamique élevée générée de manière procédurale. C’est facile à réaliser grâce à la prise en charge par ProceduralSkyTextureData et Texture des données d’image générées dynamiquement, sans recourir à des fichiers. Au lieu de spécifier source, nous utilisons plutôt la propriété textureData.
environment: SceneEnvironment {
backgroundMode: SceneEnvironment.SkyBox
lightProbe: Texture {
textureData: ProceduralSkyTextureData {
}
}
}Remarque : le code d’exemple privilégie la définition des objets en ligne. Ce n’est pas obligatoire ; les objets SceneEnvironment ou ProceduralSkyTextureData auraient également pu être définis ailleurs dans l’arborescence des objets, puis référencés par id.
Nous obtenons ainsi à la fois une skybox et un éclairage amélioré. (Le premier grâce au fait que ` backgroundMode ` est défini sur `SkyBox` et que ` light probe ` est défini sur un ` Texture` valide ; le second grâce au fait que ` light probe ` est défini sur un ` Texture` valide.)


Analyses de base des performances
Pour obtenir des informations de base sur les ressources et les performances de la scène, il est judicieux d’ajouter dès le début du processus de développement un moyen d’afficher un élément interactif « DebugView ». Ici, nous choisissons d’ajouter un « Button » qui active ou désactive le « DebugView », tous deux ancrés dans le coin supérieur droit.
import QtQuick
import QtQuick.Controls
import QtQuick3D
import QtQuick3D.Helpers
Item {
width: 1280
height: 720
View3D {
id: view3D
anchors.fill: parent
environment: SceneEnvironment {
backgroundMode: SceneEnvironment.SkyBox
lightProbe: Texture {
textureData: ProceduralSkyTextureData {
}
}
}
PerspectiveCamera {
id: camera
y: 100
}
DirectionalLight {
}
Sponza {
scale: Qt.vector3d(100, 100, 100)
}
PointLight {
y: 200
color: "#d9c62b"
brightness: 5
castsShadow: true
shadowFactor: 75
Model {
source: "#Cube"
scale: Qt.vector3d(0.01, 0.01, 0.01)
materials: PrincipledMaterial {
lighting: PrincipledMaterial.NoLighting
}
}
}
Suzanne {
y: 100
scale: Qt.vector3d(50, 50, 50)
NumberAnimation on eulerRotation.y {
from: 0
to: 360
duration: 3000
loops: Animation.Infinite
}
}
WasdController {
controlledObject: camera
}
}
Button {
anchors.right: parent.right
text: "Toggle DebugView"
onClicked: debugView.visible = !debugView.visible
DebugView {
id: debugView
source: view3D
visible: false
anchors.top: parent.bottom
anchors.right: parent.right
}
}
}
Ce panneau affiche les durées en temps réel, permet de consulter la liste en temps réel des textures et des maillages, et donne un aperçu des passes de rendu qui doivent être effectuées avant que le tampon de couleur final puisse être rendu.
Comme la « PointLight » est une source de lumière projetant des ombres, plusieurs passes de rendu sont nécessaires :

Dans la section « Textures », on voit les textures issues des ressources «Suzanne» et «Sponza» (cette dernière en comporte un grand nombre), ainsi que la texture du ciel générée de manière procédurale.

La page « Models » ne réserve aucune surprise :

Sur la page « Tools », on trouve des commandes interactives permettant d’activer ou de désactiver l’ wireframe mode et divers effets d’ material overrides.
Voici le résultat avec le mode filaire activé et le rendu forcé à n’utiliser que la composante « base color » des matériaux :

Cela conclut notre tour d’horizon des bases de la création d’une scène 3DQt Quick à partir de ressources importées.
© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.