Sur cette page

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
        }
    }
}

Vue 3D du vide sur fond vert

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 :

Liste de fichiers montrant les fichiers d'actifs de Sponza

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 :

Panneau de configuration de l'interface utilisateur Balsam

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

Intérieur du palais de Sponza avec éclairage directionnel

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

Le palais de Sponza vu sous différents angles

Le palais de Sponza vu de l'extérieur

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 :

Fenêtre de démarrage de l'interface utilisateur Balsam

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

Navigateur de fichiers affichant les fichiers d'actifs de Suzanne

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.

Balsam UI montrant la conversion en cours

À 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.

Suzanne tête de singe modèle dans une scène sombre

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é :

Suzanne avec éclairage supplémentaire

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:

Tête de singe Suzanne sur le sol du palais Sponza

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).

Le palais de Sponza avec un éclairage basé sur l'image

Le palais de Sponza avec une orientation IBL différente

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
        }
    }
}

Scène Sponza avec panneau de débogage montrant les statistiques de performance

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 :

Suzanne avec panneau de débogage montrant les 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.

Scène avec panneau de débogage montrant les statistiques de texture

La page Models ne présente aucune surprise :

Scène avec panneau de débogage montrant les statistiques de maillage

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 :

Scène Sponza en mode filaire avec panneau de débogage

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.