Qt Quick 3D Einführung mit glTF-Assets

Das Beispiel Qt Quick 3D - Einführung bietet eine schnelle Einführung in die Erstellung von QML-basierten Anwendungen mit Qt Quick 3D. Dabei werden jedoch nur eingebaute Primitive, wie Kugeln und Zylinder, verwendet. Diese Seite bietet eine Einführung mit glTF 2.0-Assets unter Verwendung einiger Modelle aus dem Khronos glTF Sample Models Repository.

Unsere Skelettanwendung

Beginnen wir mit der folgenden Anwendung. Dieses Codeschnipsel kann mit dem qml Kommandozeilentool ausgeführt werden. Das Ergebnis ist eine sehr grüne 3D-Ansicht, die sonst nichts enthält.

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

Importieren eines Assets

Wir werden zwei glTF 2.0-Modelle aus dem Sample Models Repository verwenden: Sponza und Suzanne.

Diese Modelle werden in der Regel mit einer Reihe von Texturkarten und den Mesh- (Geometrie-) Daten geliefert, die in einer separaten Binärdatei zusätzlich zur .gltf-Datei gespeichert sind:

Wie bekommen wir all dies in unsere Qt Quick 3D Szene?

Es gibt eine Reihe von Möglichkeiten:

  • Generierung von QML-Komponenten, die in der Szene instanziiert werden können. Das Kommandozeilenwerkzeug für diese Konvertierung ist das Balsam-Tool. Es erzeugt nicht nur eine .qml-Datei, die praktisch eine Subszene ist, sondern packt auch die Mesh- (Geometrie-) Daten in ein optimiertes, schnell zu ladendes Format und kopiert auch die Texturkarten-Bilddateien.
  • Führen Sie dasselbe mit balsamui durch, einem GUI-Frontend für Balsam.
  • Bei Verwendung von Qt Design Studioist der Asset-Importprozess in die visuellen Design-Werkzeuge integriert. Der Import kann z.B. durch Ziehen und Ablegen der .gltf-Datei auf das entsprechende Panel ausgelöst werden.
  • Speziell für glTF 2.0-Assets gibt es auch eine Laufzeitoption: den RuntimeLoader -Typ. Damit kann eine .gltf-Datei (und die zugehörigen Binär- und Texturdateien) zur Laufzeit geladen werden, ohne dass eine Vorverarbeitung mit Tools wie Balsam erforderlich ist. Dies ist sehr praktisch für Anwendungen, die vom Benutzer bereitgestellte Assets öffnen und laden möchten. Auf der anderen Seite ist dieser Ansatz deutlich weniger effizient, wenn es um die Leistung geht. Daher werden wir uns in dieser Einführung nicht auf diesen Ansatz konzentrieren. Ein Beispiel für diesen Ansatz finden Sie im Qt Quick 3D - RuntimeLoader Example.

Sowohl die balsam als auch die balsamui Anwendungen werden mit Qt ausgeliefert und sollten im Verzeichnis mit anderen ähnlichen ausführbaren Tools vorhanden sein, vorausgesetzt Qt Quick 3D ist installiert oder gebaut. In vielen Fällen reicht es aus, Balsam von der Kommandozeile aus auf die .gltf-Datei anzuwenden, ohne zusätzliche Argumente angeben zu müssen. Es lohnt sich jedoch, die vielen Kommandozeilen- bzw. interaktiven Optionen zu beachten, wenn Sie balsamui oder Qt Design Studio verwenden. Wenn man beispielsweise mit gebackenen Lightmaps arbeitet , um eine statische globale Beleuchtung zu erzeugen, ist es wahrscheinlich, dass man --generateLightmapUV übergeben möchte, um den zusätzlichen Lightmap-UV-Kanal zum Zeitpunkt des Asset-Imports zu erzeugen, anstatt diesen potenziell aufwendigen Prozess zur Laufzeit durchzuführen. In ähnlicher Weise ist --generateMeshLevelsOfDetail wichtig, wenn vereinfachte Versionen der Meshes erzeugt werden sollen, um die automatische LOD-Funktion in der Szene zu aktivieren. Andere Optionen ermöglichen die Erzeugung fehlender Daten (z. B. --generateNormals) und die Durchführung verschiedener Optimierungen.

In balsamui werden die Kommandozeilenoptionen auf interaktive Elemente abgebildet:

Importieren über Balsam

Los geht's! Unter der Annahme, dass das https://github.com/KhronosGroup/glTF-Sample-Models git Repository irgendwo ausgecheckt ist, können wir Balsam einfach von unserem Beispielanwendungsverzeichnis aus starten, indem wir einen absoluten Pfad zu den .gltf-Dateien angeben:

balsam c:\work\glTF-Sample-Models\2.0\Sponza\glTF\Sponza.gltf

So erhalten wir eine Sponza.qml, eine .mesh Datei unter dem meshes Unterverzeichnis und die unter maps kopierten Texturkarten.

Hinweis: Diese qml-Datei ist nicht eigenständig lauffähig. Sie ist eine Komponente, die innerhalb einer 3D-Szene, die mit einer View3D verbunden ist, instanziiert werden sollte.

Unsere Projektstruktur ist hier sehr einfach, da die Asset-qml-Dateien direkt neben unserer Haupt-.qml-Szene liegen. So können wir den Sponza-Typ einfach mit dem standardmäßigen QML-Komponentensystem instanziieren. (zur Laufzeit wird dann im Dateisystem nach Sponza.qml gesucht)

Das Hinzufügen des Modells (Unterszene) ist jedoch sinnlos, da die Materialien standardmäßig die vollständigen PBR-Beleuchtungsberechnungen enthalten, so dass nichts von unserer Szene angezeigt wird, wenn nicht ein Licht wie DirectionalLight, PointLight oder SpotLight oder eine bildbasierte Beleuchtung über the environment aktiviert ist.

Für den Moment entscheiden wir uns, ein DirectionalLight mit den Standardeinstellungen hinzuzufügen. (d.h. die Farbe ist white, und das Licht strahlt in Richtung der Z-Achse)

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

Wenn Sie dies mit dem Werkzeug qml ausführen, wird es geladen und ausgeführt, aber die Szene ist standardmäßig leer, da sich das Sponza-Modell hinter der Kamera befindet. Die Skalierung ist auch nicht ideal, z.B. fühlt sich das Bewegen mit WASD-Tasten und der Maus (aktiviert durch WasdController) nicht richtig an.

Um dies zu beheben, skalieren wir das Sponza-Modell (Unterszene) mit 100 entlang der X-, Y- und Z-Achse. Außerdem wird die Y-Startposition der Kamera auf 100 erhöht.

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

Wenn wir dies ausführen, erhalten wir:

Mit der Maus und den WASDRF-Tasten können wir uns bewegen:

Hinweis: Wir haben oben mehrmals subscene als Alternative zu "Modell" erwähnt. Warum ist das so? Auch wenn es beim Sponza-Asset nicht offensichtlich ist, dass es sich in seiner glTF-Form um ein einzelnes Modell mit 103 Untermaschen handelt, das einem einzelnen Model -Objekt mit 103 Elementen in seinem materials list zugeordnet ist, kann ein Asset eine beliebige Anzahl von models enthalten, jeweils mit mehreren Untermaschen und zugehörigen Materialien. Diese Modelle können Eltern-Kind-Beziehungen bilden und mit zusätzlichen nodes kombiniert werden, um Transformationen wie Verschieben, Drehen oder Skalieren durchzuführen. Es ist daher sinnvoller, das importierte Asset als eine komplette Unterszene, einen beliebigen Baum von nodes, zu betrachten, auch wenn das gerenderte Ergebnis visuell als ein einziges Modell wahrgenommen wird. Öffnen Sie die generierte Sponza.qml oder jede andere QML-Datei, die aus solchen Assets erzeugt wurde, in einem einfachen Texteditor, um einen Eindruck von der Struktur zu bekommen (die natürlich immer davon abhängt, wie das Quell-Asset, in diesem Fall die glTF-Datei, gestaltet wurde).

Importieren über Balsamui

Für unser zweites Modell verwenden wir stattdessen die grafische Benutzeroberfläche von balsam.

Durch Ausführen von balsamui wird das Tool geöffnet:

Importieren wir das Modell Suzanne. Dies ist ein einfacheres Modell mit zwei Texturkarten.

Da keine zusätzlichen Konfigurationsoptionen erforderlich sind, können wir einfach konvertieren. Das Ergebnis ist dasselbe wie bei balsam: eine Suzanne.qml und einige zusätzliche Dateien, die im spezifischen Ausgabeverzeichnis erzeugt werden.

Ab diesem Zeitpunkt ist die Arbeit mit den generierten Assets dieselbe wie im vorherigen Abschnitt.

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

Auch hier wird eine Skalierung auf den instanziierten Suzanne-Knoten angewandt und die Y-Position ein wenig verändert, damit das Modell nicht im Boden des Sponza-Gebäudes landet.

Alle Eigenschaften können geändert, gebunden und animiert werden, genau wie bei Qt Quick. Wenden wir zum Beispiel eine kontinuierliche Drehung auf unser Suzanne-Modell an:

Suzanne {
    y: 100
    scale: Qt.vector3d(50, 50, 50)
    NumberAnimation on eulerRotation.y {
        from: 0
        to: 360
        duration: 3000
        loops: Animation.Infinite
    }
}

Besser aussehen lassen

Mehr Licht

Jetzt ist unsere Szene ein wenig dunkel. Fügen wir ein weiteres Licht hinzu. Diesmal ein PointLight und eines, das einen Schatten wirft.

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

Wenn Sie diese Szene starten und die Kamera ein wenig bewegen, sehen Sie, dass die Szene tatsächlich besser aussieht als vorher:

Licht-Debugging

Die PointLight befindet sich etwas oberhalb des Suzanne-Modells. Wenn man die Szene mit visuellen Werkzeugen wie Qt Design Studio entwirft, ist dies offensichtlich, aber wenn man ohne Designwerkzeuge entwickelt, kann es praktisch sein, wenn man die Position von lights und anderen nodes schnell sehen kann.

Dazu fügen wir dem PointLight einen untergeordneten Knoten, Model, hinzu. Die Position des untergeordneten Knotens ist relativ zum übergeordneten Knoten, so dass der Standardknoten (0, 0, 0) in diesem Fall die Position des PointLight ist. Das Umschließen des Lichts innerhalb einer Geometrie (in diesem Fall des eingebauten Würfels) ist kein Problem für die Standard-Echtzeit-Beleuchtungsberechnungen, da dieses System kein Konzept der Okklusion hat, was bedeutet, dass das Licht keine Probleme mit dem Durchdringen von "Wänden" hat. Wenn wir vorgefertigte Lichtkarten verwenden würden, bei denen die Beleuchtung mit Raytracing berechnet wird, wäre das eine andere Sache. In diesem Fall müssten wir sicherstellen, dass der Würfel das Licht nicht blockiert, vielleicht indem wir unseren Debug-Würfel ein wenig über das Licht bewegen.

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

Ein weiterer Trick, den wir hier anwenden, ist das Ausschalten der Beleuchtung für das mit dem Würfel verwendete Material. Der Würfel erscheint dann in der Standard-Grundfarbe (Weiß), ohne von der Beleuchtung beeinflusst zu werden. Dies ist praktisch für Objekte, die zu Debugging- und Visualisierungszwecken verwendet werden.

Das Ergebnis ist ein kleiner, weißer Würfel, der die Position des PointLight visualisiert:

Skybox und bildbasierte Beleuchtung

Eine weitere offensichtliche Verbesserung besteht darin, etwas mit dem Hintergrund zu machen. Die grüne, klare Farbe ist nicht ganz ideal. Wie wäre es mit einer Umgebung, die auch zur Beleuchtung beiträgt?

Da wir nicht unbedingt ein geeignetes HDRI-Panoramabild zur Verfügung haben, sollten wir ein prozedural generiertes Himmelsbild mit hohem Dynamikumfang verwenden. Dies ist mit Hilfe von ProceduralSkyTextureData und der Unterstützung von Texture für nicht dateibasierte, dynamisch generierte Bilddaten leicht zu bewerkstelligen. Anstatt source anzugeben, verwenden wir lieber die Eigenschaft textureData.

environment: SceneEnvironment {
    backgroundMode: SceneEnvironment.SkyBox
    lightProbe: Texture {
        textureData: ProceduralSkyTextureData {
        }
    }
}

Hinweis: Der Beispielcode zieht es vor, Objekte inline zu definieren. Dies ist nicht zwingend erforderlich, die Objekte SceneEnvironment oder ProceduralSkyTextureData hätten auch an anderer Stelle im Objektbaum definiert und dann von id referenziert werden können.

Als Ergebnis haben wir sowohl eine Skybox als auch eine verbesserte Beleuchtung. (ersteres, weil backgroundMode auf SkyBox und light probe auf ein gültiges Texture gesetzt wurde; letzteres, weil light probe auf ein gültiges Texture gesetzt wurde)

Grundlegende Performance-Untersuchungen

Um einige grundlegende Einblicke in die Ressourcen- und Leistungsaspekte der Szene zu erhalten, ist es eine gute Idee, schon früh im Entwicklungsprozess eine Möglichkeit zur Anzeige eines interaktiven DebugView Elements hinzuzufügen. Hier fügen wir ein Button hinzu, das die DebugView umschaltet, die beide in der oberen rechten Ecke verankert sind.

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

Dieses Panel zeigt Live-Timings an, ermöglicht die Prüfung der Live-Liste von Texturkarten und Meshes und gibt einen Einblick in die Render-Durchgänge, die durchgeführt werden müssen, bevor der endgültige Farbpuffer gerendert werden kann.

Da PointLight ein schattenwerfendes Licht ist, sind mehrere Rendervorgänge erforderlich:

Im Abschnitt Textures sehen wir die Texturkarten aus den Suzanne- und Sponza-Assets (letztere haben eine Menge davon) sowie die prozedural erzeugte Himmelstextur.

Die Seite Models bietet keine Überraschungen:

Auf der Seite Tools gibt es einige interaktive Steuerelemente zum Umschalten zwischen wireframe mode und verschiedenen material overrides.

Hier ist der Drahtgittermodus aktiviert und das Rendering wird gezwungen, nur die base color Komponente der Materialien zu verwenden:

Dies ist der Abschluss unserer Tour durch die Grundlagen der Erstellung einer Qt Quick 3D Szene mit importierten Assets.

© 2025 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.