Qt Quick Introduction à la 3D avec des actifs glTF
L'exemple Qt Quick 3D - Introduction fournit une introduction rapide à la création d'applications basées sur QML avec Qt Quick 3D, mais en utilisant uniquement les primitives intégrées, telles que les sphères et les cylindres. Cette page fournit une introduction en utilisant les ressources glTF 2.0, en utilisant certains des modèles du référentiel Khronos glTF Sample Models.
Notre application squelette
Commençons par l'application suivante. Cet extrait de code peut être exécuté tel quel avec l'outil de ligne de commande qml. Le résultat est une vue 3D très verte sans rien d'autre.
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 actif
Nous allons utiliser deux modèles glTF 2.0 provenant du dépôt Sample Models : Sponza et Suzanne.
Ces modèles sont généralement accompagnés d'un certain nombre de cartes de texture et de données de maillage (géométrie) stockées dans un fichier binaire distinct, en plus du fichier .gltf :

Comment intégrer tout cela dans notre scène 3D Qt Quick?
Plusieurs options s'offrent à nous :
- Générer des composants QML qui peuvent être instanciés dans la scène. L'outil de ligne de commande pour effectuer cette conversion est l'outil Balsam. Outre la génération d'un fichier .qml, qui est en fait une sous-scène, cet outil reconditionne les données de maillage (géométrie) dans un format optimisé et rapide à charger, et copie également les fichiers d'image de texture.
- Effectuez la même opération à l'aide de
balsamui, une interface graphique pour Balsam. - Si vous utilisez Qt Design Studiole processus d'importation des actifs est intégré aux outils de conception visuelle. L'importation peut être déclenchée, par exemple, en faisant glisser le fichier .gltf sur le panneau approprié.
- Pour les actifs glTF 2.0 en particulier, il existe également une option d'exécution : le type RuntimeLoader. Elle permet de charger un fichier .gltf (et les fichiers de données binaires et de texture associés) au moment 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. D'un autre côté, cette approche est nettement moins efficace en termes de performances. C'est pourquoi nous ne nous concentrerons pas sur cette approche dans cette introduction. Consultez le site Qt Quick 3D - RuntimeLoader Example pour un exemple de cette approche.
Les applications balsam et balsamui sont livrées avec Qt 3D et devraient être présentes dans le répertoire avec d'autres outils exécutables similaires, à condition que Qt Quick 3D soit installé ou construit. Dans de nombreux cas, l'exécution de balsam à partir de la ligne de commande sur le fichier .gltf est suffisante, sans avoir à spécifier d'arguments supplémentaires. Il est toutefois utile de connaître les nombreuses options de la ligne de commande, ou interactives si vous utilisez balsamui ou Qt Design Studio. Par exemple, lorsque l'on travaille avec des lightmaps cuites pour fournir une illumination globale statique, il est probable que l'on veuille passer --generateLightmapUV pour obtenir le canal UV supplémentaire de la lightmap généré au moment de l'importation de l'actif, au lieu d'effectuer ce processus potentiellement coûteux au moment de l'exécution. De même, --generateMeshLevelsOfDetail est essentiel lorsqu'il est souhaitable d'avoir des versions simplifiées des maillages générés afin d'activer le LOD automatique 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 la ligne de commande sont associées à des éléments interactifs :

Importation via balsam
Commençons par le commencement ! En supposant que le dépôt https://github.com/KhronosGroup/glTF-Sample-Models git soit consulté quelque part, nous pouvons simplement exécuter balsam à partir de notre répertoire d'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 sous le sous-répertoire meshes, et les cartes de texture copiées sous maps.
Note : Ce fichier qml n'est pas exécutable seul. Il s'agit d'un composant qui doit être instancié dans une scène 3D associée à View3D.
La structure de notre projet est très simple ici, car les fichiers qml d'actifs se trouvent juste à côté de notre scène .qml principale. Cela nous permet d'instancier simplement le type Sponza en utilisant le système de composants QML standard. (au moment de l'exécution, il cherchera alors Sponza.qml dans le système de fichiers).
Le simple fait d'ajouter le modèle (sous-scène) est cependant inutile, puisque par défaut les matériaux présentent les calculs d'éclairage PBR complets, de sorte que rien n'est montré dans notre scène sans une lumière telle que DirectionalLight, PointLight, ou SpotLight, ou sans avoir activé l'éclairage basé sur l'image par le biais de the environment.
Pour l'instant, nous choisissons d'ajouter un DirectionalLight avec les paramètres par défaut. (c'est-à-dire 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 avec l'outil qml se charge et s'exécute, mais la scène est vide par défaut puisque le modèle Sponza est derrière la caméra. L'échelle n'est pas non plus idéale, par exemple, se déplacer avec les touches WASD et la souris (activée par WasdController) ne semble pas correct.
Pour remédier à cela, nous mettons à l'échelle le modèle Sponza (sous-scène) par 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 procédant ainsi, nous obtenons

Avec la souris et les touches WASDRF, nous pouvons nous déplacer :


Remarque : nous avons mentionné plusieurs fois subscene comme alternative à "model". Pourquoi ? Bien que cela ne soit pas évident avec l'objet Sponza qui, dans sa forme glTF, est un modèle unique avec 103 sous-mailles, correspondant à un objet Model unique avec 103 éléments dans son materials list, un objet peut contenir un nombre quelconque de models, chacun avec plusieurs sous-mailles et matériaux associés. Ces modèles peuvent former des relations parent-enfant et être combinés avec d'autres nodes pour effectuer des transformations telles que la translation, la rotation ou la mise à l'échelle. Il est donc plus approprié de considérer la ressource importée comme une sous-scène complète, un arbre arbitraire de 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 généré à partir de tels actifs, dans un éditeur de texte simple pour avoir une impression de la structure (qui dépend naturellement toujours de la façon dont l'actif source, dans ce cas le fichier glTF, a été conçu).
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 avec deux cartes de texture.

Comme il n'y a pas besoin d'options de configuration supplémentaires, nous pouvons simplement Convertir. Le résultat est le même que celui obtenu avec balsam: un fichier Suzanne.qml et quelques fichiers supplémentaires générés dans le répertoire de sortie spécifique.

À partir de là, l'utilisation des ressources générées est la même 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 de plus, une échelle est appliquée au nœud Suzanne instancié, et la position Y est légèrement modifiée afin que le modèle ne se retrouve pas sur le sol du bâtiment Sponza.

Toutes les propriétés peuvent être modifiées, liées et animées, 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élioration de l'apparence
Plus de lumière
Notre scène est maintenant un peu sombre. Ajoutons une autre lumière. Cette fois-ci, il s'agit d'une lumière 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, nous constatons que l'aspect de la scène s'est amélioré :

Débogage de la lumière
Le PointLight est placé légèrement au-dessus du modèle Suzanne. Lors de la conception de la scène à l'aide d'outils visuels, tels que Qt Design Studio, cela est évident, mais lors du développement sans outils de conception, il peut s'avérer pratique de pouvoir visualiser rapidement l'emplacement de lights et d'autres nodes.
Pour ce faire, nous pouvons ajouter un nœud enfant, Model, au nœud PointLight. La position du nœud enfant est relative à celle du parent, de sorte que le nœud par défaut (0, 0, 0) correspond effectivement à la position du nœud PointLight dans ce cas. Le fait d'enfermer la lumière dans une géométrie (le cube intégré dans ce cas) n'est pas un problème pour les calculs d'éclairage en temps réel standard puisque ce système n'a pas de concept d'occlusion, ce qui signifie que la lumière n'a pas de problème à voyager à travers les "murs". Si nous utilisions des lightmaps précuites, où l'éclairage est calculé à l'aide du raytracing, 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 un peu au-dessus de la lumière.
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 consiste à désactiver l'éclairage pour le matériau utilisé avec le cube. Il apparaîtra simplement avec la couleur de base par défaut (blanc), sans être affecté par l'éclairage. C'est pratique pour les objets utilisés à des fins de débogage et de visualisation.
Le résultat, à savoir l'apparition d'un petit cube blanc, visualise la position du site PointLight:

Skybox et éclairage basé sur l'image
Une autre amélioration évidente est de faire quelque chose pour l'arrière-plan. Ce vert clair n'est pas tout à fait idéal. Que diriez-vous d'un environnement qui contribuerait également à l'éclairage ?
Comme nous ne disposons pas nécessairement d'une image panoramique HDRI appropriée, utilisons une image de ciel à gamme dynamique élevée générée de manière procédurale. C'est facile à faire avec l'aide de ProceduralSkyTextureData et Texture pour les données d'image générées dynamiquement et non basées sur 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 de l'exemple préfère définir les objets en ligne. Ce n'est pas obligatoire, les objets SceneEnvironment ou ProceduralSkyTextureData auraient pu être définis ailleurs dans l'arbre des objets, puis référencés par id.
En conséquence, nous avons à la fois une boîte à ciel et un éclairage amélioré. (le premier est dû au fait que backgroundMode est défini comme SkyBox et que light probe est défini comme Texture valide ; le second est dû au fait que light probe est défini comme Texture valide).


Investigations de base sur les performances
Pour obtenir des informations de base sur les ressources et les performances de la scène, il est judicieux d'ajouter un moyen d'afficher un élément interactif DebugView dès le début du processus de développement. Ici, nous avons choisi d'ajouter un Button qui fait basculer 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 temps en temps réel, permet d'examiner la liste en temps réel des cartes de texture 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.
Le fait que le site PointLight soit une lumière qui projette des ombres implique plusieurs passes de rendu :

Dans la section Textures, nous voyons les cartes de texture des actifs Suzanne et Sponza (ce dernier en a beaucoup), ainsi que la texture du ciel générée de manière procédurale.

La page Models ne présente aucune surprise :

Sur la page Tools, il y a des contrôles interactifs pour basculer entre wireframe mode et divers material overrides.
Ici, le mode fil de fer est activé et le rendu est forcé de n'utiliser que la composante base color des matériaux :

Ceci conclut notre visite des bases de la construction d'une scène 3DQt Quick avec des 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.