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.