Auf dieser Seite

Qt Quick 3D Einführung in glTF-Assets

Das Beispiel „Qt Quick 3D – Einführung“ bietet eine kurze Einführung in die Erstellung von QML-basierten Anwendungen mit „ Qt Quick 3D “, verwendet dabei jedoch ausschließlich integrierte Primitive wie Kugeln und Zylinder. Diese Seite bietet eine Einführung unter Verwendung von glTF 2.0-Assets, wobei einige der Modelle aus dem Khronos glTF Sample Models-Repository zum Einsatz kommen.

Unsere Skelett-Anwendung

Beginnen wir mit der folgenden Anwendung. Dieser Codeausschnitt lässt sich unverändert mit dem Befehlszeilentool „ qml “ ausführen. Das Ergebnis ist eine komplett grüne 3D-Ansicht, in der sich sonst nichts befindet.

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

Leere 3D-Ansicht mit grünem Hintergrund

Importieren eines Assets

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

Diese Modelle enthalten in der Regel neben der .gltf-Datei auch eine Reihe von Textur-Maps sowie die Mesh-Daten (Geometrie), die in einer separaten Binärdatei gespeichert sind:

Dateiliste mit den Sponza-Asset-Dateien

Wie bringen wir all das in unsere „ Qt Quick 3D “-Szene?

Es gibt verschiedene Möglichkeiten:

  • QML-Komponenten generieren, die in der Szene instanziiert werden können. Das Befehlszeilentool für diese Konvertierung ist das Balsam-Tool. Neben der Erzeugung einer .qml-Datei, die im Grunde eine Unterszene darstellt, packt es auch die Mesh-Daten (Geometriedaten) in ein optimiertes, schnell ladbares Format um und kopiert zudem die Bilddateien der Textur-Maps.
  • Führen Sie denselben Vorgang mit „ balsamui “ durch, einer grafischen Benutzeroberfläche für Balsam.
  • Bei Verwendung von Qt Design Studioist der Import von Assets in die visuellen Design-Tools integriert. Der Import kann beispielsweise durch Ziehen und Ablegen der .gltf-Datei auf das entsprechende Bedienfeld ausgelöst werden.
  • Insbesondere für glTF 2.0-Assets gibt es zudem eine Laufzeitoption: den Typ „ RuntimeLoader “. Dieser ermöglicht das Laden einer .gltf-Datei (sowie der zugehörigen Binär- und Texturdatendateien) zur Laufzeit, ohne dass eine Vorverarbeitung über Tools wie Balsam erforderlich ist. Dies ist sehr praktisch in Anwendungen, die vom Benutzer bereitgestellte Assets öffnen und laden möchten. Andererseits ist dieser Ansatz hinsichtlich der Leistung deutlich weniger effizient. Daher werden wir uns in dieser Einführung nicht auf diesen Ansatz konzentrieren. Ein Beispiel für diesen Ansatz finden Sie unter „Qt Quick 3D – RuntimeLoader Example “.

Sowohl die Anwendung „ balsam “ als auch „ balsamui “ werden mit Qt ausgeliefert und sollten sich im Verzeichnis zusammen mit anderen ähnlichen ausführbaren Tools befinden, vorausgesetzt, „ Qt Quick 3D “ ist installiert oder kompiliert. In vielen Fällen reicht es aus, „balsam“ über die Befehlszeile auf die .gltf-Datei anzuwenden, ohne zusätzliche Argumente angeben zu müssen. Es lohnt sich jedoch, die zahlreichen Befehlszeilenoptionen – oder interaktive Optionen bei Verwendung von balsamui oder Qt Design Studio – zu kennen. Wenn Sie beispielsweise mit gebackenen Lightmaps arbeiten, um eine statische globale Beleuchtung zu erzielen, möchten Sie wahrscheinlich --generateLightmapUV übergeben, um den zusätzlichen Lightmap-UV-Kanal bereits beim Import der Assets generieren zu lassen, anstatt diesen potenziell ressourcenintensiven Vorgang zur Laufzeit durchzuführen. Ebenso ist „ --generateMeshLevelsOfDetail “ unerlässlich, wenn vereinfachte Versionen der Meshes generiert werden sollen, um automatisches LOD in der Szene zu ermöglichen. Andere Optionen ermöglichen das Generieren fehlender Daten (z. B. „ --generateNormals “) und das Durchführen verschiedener Optimierungen.

In „ balsamui “ werden die Befehlszeilenoptionen interaktiven Elementen zugeordnet:

Einstellungsfenster von Balsam UI

Importieren über „balsam“

Los geht’s! Angenommen, das Repository „https://github.com/KhronosGroup/glTF-Sample-Models“ unter git wurde bereits an einem beliebigen Ort ausgecheckt, können wir „balsam“ einfach aus unserem Beispielanwendungsverzeichnis heraus ausführen, indem wir einen absoluten Pfad zu den .gltf-Dateien angeben:

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

Dadurch erhalten wir eine Datei „ Sponza.qml “, eine Datei „ .mesh “ im Unterverzeichnis „ meshes “ sowie die Textur-Maps, die unter „ maps “ kopiert wurden.

Hinweis: Diese QML-Datei ist für sich allein nicht ausführbar. Es handelt sich um eine Komponente, die innerhalb einer 3D-Szene instanziiert werden sollte, die mit einer „ View3D “ verknüpft ist.

Unsere Projektstruktur ist hier sehr einfach, da sich die QML-Dateien der Assets direkt neben unserer Haupt-QML-Szene befinden. Dies ermöglicht es uns, den Typ „Sponza“ einfach mithilfe des Standard-QML-Komponentensystems zu instanziieren. (Zur Laufzeit wird dann im Dateisystem nach „Sponza.qml“ gesucht.)

Das bloße Hinzufügen des Modells (Subszene) ist jedoch sinnlos, da die Materialien standardmäßig die vollständigen PBR-Beleuchtungsberechnungen enthalten; daher wird von unserer Szene nichts angezeigt, solange keine Lichtquelle wie „ DirectionalLight “, „ PointLight “ oder „ SpotLight “ vorhanden ist oder die bildbasierte Beleuchtung über „ the environment “ aktiviert ist.

Vorerst fügen wir ein „ DirectionalLight “ mit den Standardeinstellungen hinzu. (Das bedeutet, 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 man dies mit dem „ qml “-Tool ausführt, wird die Szene zwar geladen und ausgeführt, ist aber standardmäßig komplett leer, da sich das Sponza-Modell hinter der Kamera befindet. Auch die Skalierung ist nicht ideal; so fühlt sich beispielsweise die Bewegung mit den WASD-Tasten und der Maus (aktiviert durch „ WasdController “) nicht richtig an.

Um dies zu beheben, skalieren wir das Sponza-Modell (Teilszene) mit „ 100 “ entlang der X-, Y- und Z-Achse. Außerdem wird die Startposition der Kamera auf der Y-Achse 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 das ausführen, erhalten wir:

Innenraum des Sponza-Palasts mit gerichteter Beleuchtung

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

Der Sponza-Palast aus einem anderen Blickwinkel

Der Sponza-Palast von außen gesehen

Hinweis: Wir haben oben mehrfach „ subscene “ als Alternative zu „model“ erwähnt. Warum ist das so? Während dies beim Sponza-Asset nicht offensichtlich ist – das in seiner glTF-Form ein einzelnes Modell mit 103 Submeshes ist, das einem einzelnen „ Model “-Objekt mit 103 Elementen in seiner „ materials list “ zugeordnet ist –, kann ein Asset eine beliebige Anzahl von „ models “ enthalten, von denen jedes mehrere Submeshes und zugehörige Materialien aufweist. 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 vollständige Unterszene zu betrachten – als einen beliebigen Baum aus „ nodes “ –, auch wenn das gerenderte Ergebnis visuell als ein einziges Modell wahrgenommen wird. Öffnen Sie die generierte Datei „Sponza.qml“ oder eine beliebige andere aus solchen Assets generierte QML-Datei in einem einfachen Texteditor, um sich einen Eindruck von der Struktur zu verschaffen (die natürlich immer davon abhängt, wie das Quell-Asset, in diesem Fall die glTF-Datei, gestaltet wurde).

Import über balsamui

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

Durch Aufrufen von balsamui wird das Tool geöffnet:

Startfenster von Balsam UI

Importieren wir nun das Suzanne-Modell. Dabei handelt es sich um ein einfacheres Modell mit zwei Textur-Maps.

Dateibrowser mit den Asset-Dateien von Suzanne

Da keine zusätzlichen Konfigurationsoptionen erforderlich sind, können wir einfach auf „Convert“ klicken. Das Ergebnis ist dasselbe wie bei der Ausführung von balsam: Eine Suzanne.qml-Datei und einige zusätzliche Dateien, die im entsprechenden Ausgabeverzeichnis generiert werden.

Balsam-Benutzeroberfläche, die die laufende Konvertierung anzeigt

Ab diesem Punkt verläuft die Arbeit mit den generierten Assets genauso 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 auf den instanziierten Suzanne-Knoten eine Skalierung angewendet, und die Y-Position wird leicht angepasst, damit das Modell nicht im Boden des Sponza-Gebäudes landet.

Suzanne – Affenkopf-Modell in einer dunklen Szene

Alle Eigenschaften können geändert, gebunden und animiert werden, genau wie bei „ Qt Quick “. Wenden wir beispielsweise 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
    }
}

Das Aussehen verbessern

Mehr Licht

Im Moment ist unsere Szene etwas dunkel. Fügen wir eine weitere Lichtquelle hinzu. Diesmal ein „ PointLight “, das 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 wir diese Szene starten und die Kamera ein wenig bewegen, sehen wir, dass es tatsächlich schon besser aussieht als zuvor:

Suzanne mit zusätzlicher Beleuchtung

Fehlerbehebung bei der Beleuchtung

Die „ PointLight “ ist etwas oberhalb des Suzanne-Modells platziert. Beim Entwerfen der Szene mit visuellen Werkzeugen wie „ Qt Design Studio “ ist dies offensichtlich, aber bei der Entwicklung ohne Design-Tools kann es praktisch sein, die Position von „ lights “ und anderen „ nodes “ schnell visualisieren zu können.

Dies erreichen wir, indem wir dem „ PointLight “ einen untergeordneten Knoten, einen „ Model “, hinzufügen. Die Position des untergeordneten Knotens ist relativ zum übergeordneten Knoten, sodass das standardmäßige „ (0, 0, 0) “ in diesem Fall effektiv die Position des „ PointLight “ darstellt. Das Einschließen der Lichtquelle in eine Geometrie (in diesem Fall den integrierten Würfel) stellt für die standardmäßigen Echtzeit-Beleuchtungsberechnungen kein Problem dar, da dieses System kein Konzept der Okklusion kennt – das Licht kann also problemlos durch „Wände“ hindurchdringen. Würden wir vorgefertigte Lightmaps verwenden, bei denen die Beleuchtung mittels Raytracing berechnet wird, sähe die Sache anders aus. In diesem Fall müssten wir sicherstellen, dass der Würfel das Licht nicht blockiert, beispielsweise indem wir unseren Debug-Würfel etwas über die Lichtquelle hinaus verschieben.

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, besteht darin, die Beleuchtung für das des Würfels verwendete Material zu deaktivieren. Er erscheint dann einfach in der standardmäßigen Grundfarbe (Weiß), ohne von der Beleuchtung beeinflusst zu werden. Dies ist praktisch für Objekte, die zu Debugging- und Visualisierungszwecken verwendet werden.

Das Ergebnis: Beachten Sie den kleinen, weißen Würfel, der die Position des „ PointLight “ veranschaulicht:

Affenkopf der Suzanne auf dem Boden des Sponza-Palasts

Skybox und bildbasierte Beleuchtung

Eine weitere naheliegende Verbesserung besteht darin, etwas am Hintergrund zu ändern. Diese grüne, durchsichtige Farbe ist nicht ganz ideal. Wie wäre es mit einer Umgebung, die ebenfalls zur Beleuchtung beiträgt?

Da wir nicht unbedingt über ein geeignetes HDRI-Panoramabild verfügen, verwenden wir ein prozedural generiertes High-Dynamic-Range-Himmelbild. Dies lässt sich mithilfe der Unterstützung von „ ProceduralSkyTextureData “ und „ Texture “ für nicht dateibasierte, dynamisch generierte Bilddaten leicht umsetzen. Anstatt „ source “ anzugeben, verwenden wir stattdessen die Eigenschaft „ textureData “.

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

Hinweis: Im Beispielcode werden Objekte vorzugsweise inline definiert. Dies ist jedoch nicht zwingend erforderlich; die Objekte „ SceneEnvironment “ oder „ ProceduralSkyTextureData “ hätten auch an anderer Stelle im Objektbaum definiert und anschließend über „ id “ referenziert werden können.

Das Ergebnis ist 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.)

Der Sponza-Palast mit bildbasierter Beleuchtung

Der Sponza-Palast mit unterschiedlicher Ausrichtung der IBL

Grundlegende Leistungsanalysen

Um grundlegende Einblicke in die Ressourcen- und Leistungsaspekte der Szene zu gewinnen, empfiehlt es sich, bereits früh im Entwicklungsprozess eine Möglichkeit zur interaktiven Anzeige eines „ DebugView “-Elements einzubauen. Hier entscheiden wir uns dafür, ein „ Button “ hinzuzufügen, das die „ DebugView “ umschaltet; beide sind in der oberen rechten Ecke verankert.

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

Sponza-Szene mit Debug-Ansichtsfenster, das Leistungsstatistiken anzeigt

Dieses Fenster zeigt Echtzeit-Zeitangaben an, ermöglicht die Überprüfung der Live-Liste der Textur-Maps und Meshes und gibt einen Einblick in die Render-Durchläufe, die durchgeführt werden müssen, bevor der endgültige Farbpuffer gerendert werden kann.

Da die „ PointLight “ als schattenwerfende Lichtquelle fungiert, sind mehrere Render-Durchläufe erforderlich:

Suzanne mit Debug-Panel, das die Render-Durchläufe anzeigt

Im Abschnitt „ Textures “ sehen wir die Textur-Maps der Assets „Suzanne“ und „Sponza“ (letzteres verfügt über eine Vielzahl davon) sowie die prozedural generierte Himmelstextur.

Szene mit Debug-Fenster, das Texturstatistiken anzeigt

Die Seite „ Models “ hält keine Überraschungen bereit:

Szene mit Debug-Fenster, in dem die Mesh-Statistiken angezeigt werden

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 so eingestellt, dass ausschließlich die „ base color “-Komponente der Materialien verwendet wird:

Sponza-Szene im Drahtgittermodus mit Debug-Fenster

Damit ist unser Überblick über die Grundlagen der Erstellung einer Qt Quick 3D Szene mit importierten Assets.

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