Programmierbare Materialien, Effekte, Geometrie und Texturdaten
Die integrierten Materialien von Qt Quick 3D, DefaultMaterial und PrincipledMaterial lassen sich zwar über ihre Eigenschaften in hohem Maße anpassen, bieten jedoch keine Programmierbarkeit auf der Ebene der Vertex- und Fragment-Shader. Um dies zu ermöglichen, wird der Typ „ CustomMaterial “ bereitgestellt.
| Ein Modell mit PrincipledMaterial | einem „ CustomMaterial “, der die Scheitelpunkte transformiert |
|---|---|
|
|
Nachbearbeitungseffekte, bei denen ein oder mehrere Verarbeitungsdurchläufe am Farbpuffer durchgeführt werden – optional unter Berücksichtigung des Tiefenpuffers –, bevor die Ausgabe des „ View3D “ an „ Qt Quick “ weitergeleitet wird, gibt es ebenfalls in zwei Varianten:
- integrierte Nachbearbeitungsschritte, die über den „ ExtendedSceneEnvironment “ konfiguriert werden können, wie z. B. Glow/Bloom, Tiefenschärfe, Vignette, Linsenreflexe,
customEffekte, die von der Anwendung in Form von Fragment-Shader-Code und einer Spezifikation der Verarbeitungsdurchläufe in einem ` Effect `-Objekt implementiert werden.
In der Praxis gibt es eine dritte Kategorie von Nachbearbeitungseffekten: 2D-Effekte, die über „ Qt Quick “ implementiert werden und auf die Ausgabe des „ View3D “-Elements einwirken, ohne dass der 3D-Renderer daran beteiligt ist. Um beispielsweise einen Weichzeichner auf ein „ View3D “-Element anzuwenden, ist der einfachste Ansatz die Nutzung der vorhandenen Funktionen von „ Qt Quick “, wie z. B. „ MultiEffect “. Das 3D-Nachbearbeitungssystem bietet Vorteile bei komplexen Effekten, die 3D-Szenenkonzepte wie den Tiefenpuffer oder die Bildschirmtextur beinhalten, HDR-Tonemapping erfordern oder mehrere Durchläufe mit Zwischenpuffern benötigen usw. Einfache 2D-Effekte, die keine Einblicke in die 3D-Szene und den Renderer erfordern, können stattdessen immer mit ShaderEffect oder MultiEffect implementiert werden.
| Szene ohne Effekt | Dieselbe Szene mit einem angepassten Post-Processing-Effekt |
|---|---|
|
|
Neben programmierbaren Materialien und Post-Processing gibt es zwei Arten von Daten, die normalerweise in Form von Dateien bereitgestellt werden (.mesh -Dateien oder Bilder wie .png):
- Vertex-Daten, einschließlich der Geometrie für das zu rendernde Mesh, Texturkoordinaten, Normalen, Farben und anderer Daten,
- den Inhalt für Texturen, die anschließend als Textur-Maps für die gerenderten Objekte verwendet oder bei Skybox- oder bildbasierter Beleuchtung eingesetzt werden.
Auf Wunsch können Anwendungen solche Daten aus C++ in Form eines „ QByteArray “ bereitstellen. Diese Daten können sich im Laufe der Zeit auch ändern, was die prozedurale Generierung und spätere Änderung der Daten für ein „ Model “ oder „ Texture “ ermöglicht.
| Ein Gitter, das durch die dynamische Angabe von Scheitelpunktdaten aus C++ gerendert wird | Ein Würfel, der mit aus C++ generierten Bilddaten texturiert ist |
|---|---|
|
|
Diese vier Ansätze zur Anpassung und Dynamisierung von Materialien, Effekten, Geometrie und Texturen ermöglichen die Programmierbarkeit der Schattierung sowie die prozedurale Generierung der Daten, die den Shadern als Eingabe zur Verfügung stehen. Die folgenden Abschnitte bieten einen Überblick über diese Funktionen. Die vollständige Dokumentation finden Sie auf den Dokumentationsseiten zu den jeweiligen Typen:
Hinweis: Informationen zur vollständigen Kontrolle über die gesamte Rendering-Pipeline, einschließlich mehrerer Render-Pässe und benutzerdefinierter Render-Ziele, finden Sie unter „User-Defined Render Passes“ in „ Qt Quick 3D “. Benutzerdefinierte Render-Pässe ergänzen die auf dieser Seite beschriebenen Funktionen, indem sie Ihnen die Definition der gesamten Architektur der Rendering-Pipeline ermöglichen, während „ CustomMaterial “ und „Effect“ sich auf die Anpassung einzelner Materialien und Nachbearbeitungsschritte innerhalb der Pipeline konzentrieren.
| Funktion | Referenzdokumentation | Relevante Beispiele |
|---|---|---|
| Benutzerdefinierte Materialien | CustomMaterial | Qt Quick 3D - Beispiel für benutzerdefinierte Shader, Qt Quick 3D - Beispiel für benutzerdefinierte Materialien |
| Benutzerdefinierte Nachbearbeitungseffekte | Effect | Qt Quick 3D - Beispiel für benutzerdefinierte Effekte |
| Benutzerdefinierte Geometrie | QQuick3DGeometry, Model::geometry | Qt Quick 3D - Beispiel für benutzerdefinierte Geometrie |
| Benutzerdefinierte Texturdaten | QQuick3DTextureData, Texture::textureData | Qt Quick 3D - Beispiel für prozedurale Texturen |
Programmierbarkeit von Materialien
Nehmen wir eine Szene mit einem Würfel und beginnen wir mit einem Standard- PrincipledMaterial und einem CustomMaterial:
| PrincipledMaterial | CustomMaterial |
|---|---|
| |
Beide führen zu genau demselben Ergebnis, da ein „ CustomMaterial “ im Grunde ein „ PrincipledMaterial “ ist, wenn kein Vertex- oder Fragment-Shader-Code hinzugefügt wird.

Hinweis: Eigenschaften wie „ baseColor “, „ metalness “, „ baseColorMap “ und viele andere haben keine entsprechenden Eigenschaften im QML-Typ „ CustomMaterial “. Dies ist beabsichtigt: Die Anpassung des Materials erfolgt über Shader-Code und nicht durch die bloße Angabe einiger fester Werte.
Unser erster Vertex-Shader
Fügen wir einen Ausschnitt eines benutzerdefinierten Vertex-Shaders hinzu. Dies geschieht durch die Referenzierung einer Datei in der Eigenschaft ` vertexShader `. Der Ansatz ist derselbe wie bei Fragment-Shadern. Diese Referenzen funktionieren wie ` Image.source ` oder ` ShaderEffect.vertexShader`: Es handelt sich um lokale oder ` qrc `-URLs, und ein relativer Pfad wird relativ zum Speicherort der Datei ` .qml ` interpretiert. Der übliche Ansatz besteht daher darin, die Dateien „ .vert “ und „ .frag “ im Qt-Ressourcensystem abzulegen (bei Verwendung von CMake unter „qt_add_resources “) und über einen relativen Pfad darauf zu verweisen.
In Qt 6.0 werden Inline-Shader-Strings nicht mehr unterstützt, weder in „ Qt Quick “ noch in „ Qt Quick 3D “. (Beachten Sie, dass es sich bei diesen Eigenschaften um URLs und nicht um Strings handelt.) Aufgrund ihrer von Natur aus dynamischen Beschaffenheit enthalten benutzerdefinierte Materialien und Nachbearbeitungseffekte in „ Qt Quick 3D “ jedoch weiterhin Shader-Schnipsel in Quellcodeform in den referenzierten Dateien. Dies ist ein Unterschied zu ShaderEffect, wo die Shader für sich allein vollständig sind, ohne weitere Anpassungen durch die Engine, und daher als vorbearbeitete Shader-Pakete unter .qsb bereitgestellt werden müssen.
Hinweis: In „ Qt Quick 3D “ dürfen URLs nur auf lokale Ressourcen verweisen. Schemata für Remote-Inhalte werden nicht unterstützt.
Hinweis: Als Shading-Sprache wird Vulkan-kompatibles GLSL verwendet. Die Dateien „ .vert “ und „ .frag “ sind für sich genommen keine vollständigen Shader und werden daher oft als „ snippets “ bezeichnet. Aus diesem Grund enthalten diese Ausschnitte keine Uniform-Blöcke, Eingabe- und Ausgabevariablen oder Sampler-Uniforms. Stattdessen wird die Engine unter Qt Quick 3D diese entsprechend anpassen.
| Änderung in main.qml, material.vert | Ergebnis |
|---|---|
|
|
Ein benutzerdefiniertes Vertex- oder Fragment-Shader-Snippet soll eine oder mehrere Funktionen mit vordefinierten Namen bereitstellen, wie beispielsweise „ MAIN “, „ DIRECTIONAL_LIGHT “, „ POINT_LIGHT “, „ SPOT_LIGHT “, „ AMBIENT_LIGHT “ und „ SPECULAR_LIGHT “. Konzentrieren wir uns vorerst auf „ MAIN “.
Wie hier zu sehen ist, ist das Endergebnis bei einem leeren MAIN() genau dasselbe wie zuvor.
Bevor wir es etwas interessanter gestalten, werfen wir einen Blick auf die am häufigsten verwendeten speziellen Schlüsselwörter in benutzerdefinierten Vertex-Shader-Schnipseln. Dies ist keine vollständige Liste. Eine vollständige Referenz findest du auf der Seite „ CustomMaterial “.
| Schlüsselwort | Typ | Beschreibung |
|---|---|---|
| MAIN | void MAIN() ist der Einstiegspunkt. Diese Funktion muss in jedem benutzerdefinierten Vertex-Shader-Snippet vorhanden sein; andernfalls macht es keinen Sinn, ein solches Snippet zu erstellen. | |
| VERTEX | vec3 | Die Vertex-Position, die der Shader als Eingabe erhält. Ein häufiger Anwendungsfall für Vertex-Shader in benutzerdefinierten Materialien ist es, die x-, y- oder z-Werte dieses Vektors zu ändern (zu verschieben), indem man dem gesamten Vektor oder einigen seiner Komponenten einfach einen Wert zuweist. |
| NORMAL | vec3 | Die Scheitelpunktnormale aus den Eingabedaten des Meshes oder ausschließlich Nullen, falls keine Normalen bereitgestellt wurden. Wie bei VERTEX steht es dem Shader frei, den Wert nach eigenem Ermessen zu ändern. Der geänderte Wert wird anschließend vom Rest der Pipeline verwendet, einschließlich der Beleuchtungsberechnungen in der Fragmentstufe. |
| UV0 | vec2 | Der erste Satz von Texturkoordinaten aus den Eingabedaten des Meshes oder ausschließlich Nullen, falls keine UV-Werte bereitgestellt wurden. Wie bei „VERTEX“ und „NORMAL“ kann der Wert geändert werden. |
| MODELVIEWPROJECTION_MATRIX | mat4 | Die Modell-Ansicht-Projektionsmatrix. Um das Verhalten unabhängig von der verwendeten Grafik-API zu vereinheitlichen, folgen alle Scheitelpunktdaten und Transformationsmatrizen auf dieser Ebene den OpenGL-Konventionen. (Y-Achse zeigt nach oben, OpenGL-kompatible Projektionsmatrix) Nur lesbar. |
| MODEL_MATRIX | mat4 | Die Modell-(Welt-)Matrix. Nur Lesezugriff. |
| NORMAL_MATRIX | mat3 | Die transponierte Inverse des oberen linken 3x3-Ausschnitts der Modellmatrix. Nur-Lese-Zugriff. |
| CAMERA_POSITION | vec3 | Die Kameraposition im Weltraum. In den Beispielen auf dieser Seite ist dies (0, 0, 600). Nur Lesezugriff. |
| CAMERA_DIRECTION | vec3 | Der Richtungsvektor der Kamera. In den Beispielen auf dieser Seite ist dies (0, 0, -1). Nur Lesezugriff. |
| CAMERA_PROPERTIES | vec2 | Die Werte für den nahen und fernen Clip der Kamera. In den Beispielen auf dieser Seite ist dies (10, 10000). Nur Lesezugriff. |
| POINT_SIZE | float | Nur relevant beim Rendern mit einer Punkt-Topologie, beispielsweise weil die „ custom geometry “ eine solche Geometrie für das Mesh bereitstellt. Das Schreiben in diesen Wert entspricht der Einstellung „ pointSize on a PrincipledMaterial “. |
| POSITION | vec4 | Wie bei „ gl_Position “. Ist dieser Wert nicht vorhanden, wird automatisch eine Standardzuweisungsanweisung unter Verwendung von „ MODELVIEWPROJECTION_MATRIX “ und „ VERTEX “ generiert. Aus diesem Grund ist ein leeres „MAIN()“ funktionsfähig, und in den meisten Fällen besteht keine Notwendigkeit, ihm einen benutzerdefinierten Wert zuzuweisen. |
Erstellen wir ein benutzerdefiniertes Material, das die Scheitelpunkte nach einem bestimmten Muster verschiebt. Um es interessanter zu gestalten, fügen wir einige animierte QML-Eigenschaften hinzu, deren Werte schließlich als Uniforms im Shader-Code verfügbar sind. (Genauer gesagt werden die meisten Eigenschaften auf Elemente in einem Uniform-Block abgebildet, der zur Laufzeit durch einen Uniform-Puffer gestützt wird, aber Qt Quick 3D macht solche Details für den Ersteller des benutzerdefinierten Materials praktischerweise transparent.)
| Änderung in main.qml, material.vert | Ergebnis |
|---|---|
|
Uniforms aus QML-Eigenschaften
Benutzerdefinierte Eigenschaften im CustomMaterial -Objekt werden auf Uniforms abgebildet. Im obigen Beispiel sind dies unter anderem uAmplitude und uTime. Sobald sich die Werte ändern, wird der aktualisierte Wert im Shader sichtbar. Dieses Konzept ist möglicherweise bereits von ShaderEffect bekannt.
Der Name der QML-Eigenschaft und der GLSL-Variablen muss übereinstimmen. Es gibt keine separate Deklaration im Shader-Code für die einzelnen Uniforms. Stattdessen kann der Name der QML-Eigenschaft unverändert verwendet werden. Aus diesem Grund kann das obige Beispiel im Vertex-Shader-Ausschnitt einfach auf uTime und uAmplitude verweisen, ohne dass diese zuvor deklariert werden müssen.
Die folgende Tabelle listet die Zuordnung der Typen auf:
| QML-Typ | Shader-Typ | Anmerkungen |
|---|---|---|
| real, int, bool | float, int, bool | |
| Farbe | vec4 | Die Konvertierung von sRGB in lineare Werte erfolgt implizit |
| vector2d | vec2 | |
| Vektor3d | vec3 | |
| vector4d | vec4 | |
| Matrix4x4 | mat4 | |
| Quaternion | vec4 | Der Skalarwert lautet w |
| Rechteck | vec4 | |
| Punkt, Größe | vec2 | |
| TextureInput | sampler2D |
Das Beispiel verbessern
Bevor wir fortfahren, wollen wir das Beispiel optisch etwas ansprechender gestalten. Indem wir ein gedrehtes Rechteck-Mesh hinzufügen und dafür sorgen, dass die „ DirectionalLight “ Schatten werfen, können wir überprüfen, ob sich die Änderung an den Eckpunkten des Würfels korrekt in allen Rendering-Durchläufen widerspiegelt, einschließlich der Schattenkarten. Um einen sichtbaren Schatten zu erhalten, wird die Lichtquelle nun etwas höher auf der Y-Achse platziert und gedreht, sodass sie teilweise nach unten zeigt. (Da es sich um eine „ directional “-Lichtquelle handelt, ist die Drehung von Bedeutung.)
| main.qml, material.vert | Ergebnis |
|---|---|
|
Hinzufügen eines Fragment-Shaders
Viele benutzerdefinierte Materialien benötigen zudem einen Fragment-Shader. Tatsächlich benötigen viele sogar ausschließlich einen Fragment-Shader. Wenn keine zusätzlichen Daten von der Vertex- zur Fragment-Stufe übergeben werden müssen und die Standard-Vertex-Transformation ausreicht, kann die Einstellung der Eigenschaft „ vertexShader “ im „ CustomMaterial “ weggelassen werden.
| Änderung in „main.qml“, „material.frag“ | Ergebnis |
|---|---|
|
|
Unser erster Fragment-Shader enthält eine leere MAIN()-Funktion. Dies unterscheidet sich nicht davon, gar keinen Fragment-Shader-Ausschnitt anzugeben: Das Ergebnis sieht genauso aus wie bei einem Standard- PrincipledMaterial.
Sehen wir uns einige der häufig verwendeten Schlüsselwörter in Fragment-Shadern an. Dies ist keine vollständige Liste; eine umfassende Referenz finden Sie in der Dokumentation zu „ CustomMaterial “. Viele davon sind Lese-Schreib-Eigenschaften, was bedeutet, dass sie einen Standardwert haben, dem Shader ihnen jedoch einen anderen Wert zuweisen kann – und dies oft auch tun wird.
Wie die Namen schon andeuten, entsprechen viele davon ähnlich benannten „ PrincipledMaterial “-Eigenschaften mit derselben Bedeutung und Semantik, die dem Materialmodell „Metallic-Roughness“ folgen. Es liegt an der benutzerdefinierten Materialimplementierung zu entscheiden, wie diese Werte berechnet werden: So kann beispielsweise ein Wert für „ BASE_COLOR “ im Shader fest codiert sein, auf der Abtastung einer Textur basieren oder anhand von QML-Eigenschaften berechnet werden, die als Uniforms verfügbar sind, oder auf interpolierten Daten, die vom Vertex-Shader weitergegeben werden.
| Stichwort | Typ | Beschreibung |
|---|---|---|
| BASE_COLOR | vec4 | Die Grundfarbe und der Alpha-Wert. Entspricht „ PrincipledMaterial::baseColor “. Der endgültige Alpha-Wert des Fragments ist die Deckkraft des Modells multipliziert mit dem Alpha-Wert der Grundfarbe. Der Standardwert ist „ (1.0, 1.0, 1.0, 1.0) “. |
| EMISSIVE_COLOR | vec3 | Die Farbe der Eigenleuchtkraft. Entspricht PrincipledMaterial::emissiveFactor. Der Standardwert ist (0.0, 0.0, 0.0). |
| METALNESS | float | Metalness Wert im Bereich von 0 bis 1. Standardwert ist 0, was bedeutet, dass das Material dielektrisch (nichtmetallisch) ist. |
| ROUGHNESS | float | Roughness Wert im Bereich von 0 bis 1. Der Standardwert ist 0. Größere Werte mildern Spiegelungen ab und verwischen Reflexionen. |
| SPECULAR_AMOUNT | Float | The strength of specularity im Bereich von 0 bis 1. Der Standardwert ist 0.5. Bei metallischen Objekten, bei denen „ metalness “ auf „ 1 “ gesetzt ist, hat dieser Wert keine Auswirkung. Wenn sowohl „ SPECULAR_AMOUNT “ als auch „ METALNESS “ Werte größer als 0, aber kleiner als 1 haben, ergibt sich eine Mischung aus den beiden Materialmodellen. |
| NORMAL | vec3 | Die interpolierte Normale im Weltraum, angepasst an die beidseitige Darstellung, wenn die Flächenausblendung deaktiviert ist. Nur Lesezugriff. |
| UV0 | vec2 | Die interpolierten Texturkoordinaten. Nur Lesezugriff. |
| VAR_WORLD_POSITION | vec3 | Interpolierte Scheitelpunktposition im Weltraum. Nur Lesezugriff. |
Lassen Sie uns die Grundfarbe des Würfels rot gestalten:
| Änderung in main.qml, material.frag | Ergebnis |
|---|---|
|
|
Verstärken wir nun die Selbstbeleuchtung ein wenig:
| Änderung in main.qml, material.frag | Ergebnis |
|---|---|
|
|
Anstatt Werte fest im Shader zu hinterlegen, könnten wir auch QML-Eigenschaften verwenden, die als Uniforms verfügbar sind – sogar animierte:
| Änderung in „main.qml“, „material.frag“ | Ergebnis |
|---|---|
|
Versuchen wir etwas weniger Triviales, etwas, das sich nicht mit einem „ PrincipledMaterial “ und dessen standardmäßigen, integrierten Eigenschaften umsetzen lässt. Das folgende Material visualisiert die Textur-UV-Koordinaten des Würfel-Meshes. U läuft von 0 bis 1, also von Schwarz bis Rot, während V ebenfalls von 0 bis 1 läuft, von Schwarz bis Grün.
| Änderung in main.qml, material.frag | Ergebnis |
|---|---|
|
|
Wenn wir schon dabei sind: Warum visualisieren wir nicht auch die Normalen, diesmal auf einer Kugel? Wie bei den UVs würde, wenn ein benutzerdefiniertes Vertex-Shader-Snippet den Wert von NORMAL ändern würde, der im Fragment-Shader interpolierte Wert pro Fragment – der ebenfalls unter dem Namen NORMAL verfügbar ist – diese Anpassungen widerspiegeln.
| Änderung in main.qml, material.frag | Ergebnis |
|---|---|
|
|
Farben
Wechseln wir kurz zu einem Teekannen-Modell, gestalten wir das Material als Mischung aus metallisch und dielektrisch und versuchen wir, eine grüne Grundfarbe dafür festzulegen. Der Wert „ green “ QColor wird auf „ (0, 128, 0) “ abgebildet, worauf basierend unser erster Versuch wie folgt aussehen könnte:
| main.qml, material.frag |
|---|
|

Das sieht nicht ganz richtig aus. Vergleichen Sie dies mit dem zweiten Ansatz:
| Änderung in main.qml, material.frag | Ergebnis |
|---|---|
|
|
Wenn wir zu einem „ PrincipledMaterial “ wechseln, können wir bestätigen, dass bei der Einstellung von „ PrincipledMaterial::baseColor “ auf „green“ und unter Berücksichtigung der „Metalness“ sowie anderer Eigenschaften das Ergebnis mit unserem zweiten Ansatz identisch ist:
| Änderung in main.qml | Ergebnis |
|---|---|
|
|
Würde der Typ der Eigenschaft „ uColor “ auf „ vector4d “ oder einen anderen Typ als „ color “ geändert, würden sich die Ergebnisse schlagartig ändern und mit unserem ersten Ansatz übereinstimmen.
Warum ist das so?
Die Antwort liegt in der impliziten Konvertierung von sRGB in lineare Werte, die für die Farbeigenschaften von `DefaultMaterial`, ` PrincipledMaterial` sowie für benutzerdefinierte Eigenschaften mit dem Typ ` color ` in einem ` CustomMaterial` durchgeführt wird. Eine solche Konvertierung wird für keinen anderen Wert durchgeführt. Wenn der Shader also einen Farbwert fest codiert oder diesen auf eine QML-Eigenschaft stützt, deren Typ sich von „ color “ unterscheidet, ist es Aufgabe des Shaders, eine Linearisierung durchzuführen, falls der Quellwert im sRGB-Farbraum lag. Die Umwandlung in den linearen Farbraum ist wichtig, da „ Qt Quick 3D “ die Ergebnisse des Fragment-Shadings einer „ tonemapping “ unterzieht und dieser Prozess Werte im sRGB-Farbraum als Eingabe voraussetzt.
Die integrierten „ QColor “-Konstanten, wie beispielsweise "green", sind alle im sRGB-Farbraum angegeben. Daher reicht es nicht aus, im ersten Schritt einfach vec4(0.0, 0.5, 0.0, 1.0) an BASE_COLOR zuzuweisen, wenn wir ein Ergebnis erzielen möchten, das einem RGB-Wert (0, 128, 0) im sRGB-Farbraum entspricht. Eine Formel zur Linearisierung solcher Farbwerte finden Sie in der Dokumentation zu „ BASE_COLOR “ unter CustomMaterial. Dasselbe gilt für Farbwerte, die durch Abtasten von Texturen abgerufen werden: Wenn die Quellbilddaten nicht im sRGB-Farbraum vorliegen, ist eine Konvertierung erforderlich (es sei denn, „ tonemapping “ ist deaktiviert).
Überblendung
Es reicht nicht aus, lediglich einen Wert kleiner als „ 1.0 “ in „ BASE_COLOR.a “ zu schreiben, wenn Alpha-Blending erzielt werden soll. Bei solchen Materialien werden sehr häufig die Werte der Eigenschaften „ sourceBlend “ und „ destinationBlend “ angepasst, um die gewünschten Ergebnisse zu erzielen.
Beachten Sie außerdem, dass der kombinierte Alpha-Wert dem Wert von Node opacity multipliziert mit dem Material-Alpha entspricht.
Zur Veranschaulichung verwenden wir einen Shader, der „ BASE_COLOR “ mit der Farbe Rot und dem Alpha- 0.5 zuweist:
| main.qml, material.frag | Ergebnis |
|---|---|
|
|
Der erste Würfel schreibt den Wert 0,5 in den Alpha-Wert der Farbe, was jedoch keine sichtbaren Ergebnisse liefert, da das Alpha-Blending nicht aktiviert ist. Der zweite Würfel aktiviert einfaches Alpha-Blending über die „ CustomMaterial “-Eigenschaften. Der dritte Würfel weist dem Modell zudem eine Deckkraft von 0,5 zu, was bedeutet, dass die effektive Deckkraft 0,25 beträgt.
Datenübergabe zwischen Vertex- und Fragment-Shader
Berechnung eines Werts pro Scheitelpunkt (beispielsweise bei einem einzelnen Dreieck für die drei Ecken des Dreiecks) und anschließende Weitergabe an die Fragmentstufe, wo für jedes Fragment (beispielsweise jedes Fragment, das vom gerasterten Dreieck abgedeckt wird) ein interpolierter Wert zur Verfügung gestellt wird. In benutzerdefinierten Material-Shader-Schnipseln wird dies durch das Schlüsselwort „ VARYING “ ermöglicht. Dies bietet eine Syntax ähnlich wie bei GLSL 120 und GLSL ES 100, funktioniert jedoch unabhängig von der zur Laufzeit verwendeten Grafik-API. Die Engine übernimmt das entsprechende Umschreiben der „varying“-Deklaration.
Schauen wir uns an, wie das klassische Textur-Sampling mit UV-Koordinaten aussehen würde. Texturen werden in einem späteren Abschnitt behandelt; konzentrieren wir uns vorerst darauf, wie wir die UV-Koordinaten erhalten, die an die Funktion ` texture() ` im Shader übergeben werden können.
| main.qml, material.vert, material.frag |
|---|
|
| qt_logo_rect.png | Ergebnis |
|---|---|
|
|
Beachten Sie die Deklarationen von ` VARYING `. Name und Typ müssen übereinstimmen; ` uv ` im Fragment-Shader gibt die interpolierte UV-Koordinate für das aktuelle Fragment zurück.
Jede andere Art von Daten kann auf ähnliche Weise an die Fragmentstufe weitergegeben werden. Es ist erwähnenswert, dass in vielen Fällen die Einrichtung eigener „varyings“ für das Material nicht erforderlich ist, da es integrierte Funktionen gibt, die viele typische Anforderungen abdecken. Dazu gehören die Erzeugung der (interpolierten) Normalen, UVs, der Weltposition (VAR_WORLD_POSITION) oder des Vektors, der zur Kamera zeigt (VIEW_VECTOR).
Das obige Beispiel lässt sich tatsächlich wie folgt vereinfachen, da UV0 auch in der Fragmentphase automatisch verfügbar ist:
| Änderung in main.qml, material.frag | Ergebnis |
|---|---|
|
|
Um die Interpolation für eine Variable zu deaktivieren, verwenden Sie das Schlüsselwort „ flat “ sowohl im Vertex- als auch im Fragment-Shader-Schnipsel. Zum Beispiel:
VARYING flat vec2 v;Texturen
Ein „ CustomMaterial “ verfügt über keine integrierten Textur-Maps, d. h., es gibt kein Äquivalent zu beispielsweise „ PrincipledMaterial::baseColorMap “. Dies liegt daran, dass die Implementierung solcher Funktionen oft trivial ist, dabei jedoch wesentlich mehr Flexibilität bietet als die in „DefaultMaterial“ und „ PrincipledMaterial “ integrierten Funktionen. Neben dem einfachen Abrufen einer Textur können benutzerdefinierte Fragment-Shader-Schnipsel Daten aus verschiedenen Quellen frei kombinieren und mischen, wenn sie die Werte berechnen, die sie „ BASE_COLOR “, „ EMISSIVE_COLOR “, „ ROUGHNESS “ usw. zuweisen. Sie können diese Berechnungen auf Daten stützen, die über QML-Eigenschaften bereitgestellt werden, auf interpolierte Daten, die aus der Vertex-Stufe weitergeleitet werden, auf Werte, die beim Abrufen von Texturen ermittelt wurden, sowie auf fest codierte Werte.
Wie das vorherige Beispiel zeigt, ähnelt die Bereitstellung einer Textur für den Vertex-, den Fragment- oder beide Shader stark der Bereitstellung von skalaren und vektoriellen Uniform-Werten: Eine QML-Eigenschaft vom Typ „ TextureInput “ wird im Shader-Code automatisch mit einem „ sampler2D “ verknüpft. Wie immer ist es nicht erforderlich, diesen Sampler im Shader-Code zu deklarieren.
Ein „ TextureInput “ verweist auf einen „ Texture “ mit einer zusätzlichen Eigenschaft „ enabled “. Ein „ Texture “ kann seine Daten auf drei Arten beziehen: „ from an image file “, from a texture with live Qt Quick contentoder can be provided from C++ über QQuick3DTextureData.
Hinweis: Bei den „ Texture “-Eigenschaften werden bei benutzerdefinierten Materialien nur die Eigenschaften bezüglich Quelle, Kachelung und Filterung implizit berücksichtigt, während die Umsetzung der übrigen Eigenschaften (wie z. B. UV-Transformationen) den benutzerdefinierten Shadern überlassen bleibt, die diese nach eigenem Ermessen implementieren können.
Sehen wir uns ein Beispiel an, bei dem ein Modell – in diesem Fall eine Kugel – mit Live- Qt Quick -Inhalten texturiert wird:
| main.qml, material.frag |
|---|
|
Hier wird der 2D-Teilbaum (Rechteck mit zwei untergeordneten Elementen: einem weiteren Rechteck und dem Text) jedes Mal, wenn sich diese Miniszene ändert, in eine 512x512-2D-Textur gerendert. Die Textur wird dann unter dem Namen „ someTextureMap “ dem benutzerdefinierten Material zugewiesen.
Beachten Sie die Umkehrung der V-Koordinate im Shader. Wie oben erwähnt, bieten benutzerdefinierte Materialien, bei denen eine vollständige Programmierbarkeit auf Shader-Ebene besteht, nicht die „festen“ Eigenschaften von „ Texture “ und „ PrincipledMaterial “. Das bedeutet, dass alle Transformationen der UV-Koordinaten vom Shader angewendet werden müssen. In diesem Fall wissen wir, dass die Textur über Texture::sourceItem generiert wird, sodass die V-Koordinate gespiegelt werden muss, um ein Ergebnis zu erhalten, das mit dem UV-Satz des von uns verwendeten Meshes übereinstimmt.
Dieses Beispiel zeigt, dass dies auch mit einem „ PrincipledMaterial “ möglich ist. Machen wir es etwas interessanter, indem wir zusätzlich einen einfachen Emboss-Effekt hinzufügen:
| material.frag | Ergebnis |
|---|---|
|
Mit den bisher behandelten Funktionen eröffnen sich vielfältige Möglichkeiten, Materialien zu erstellen, die die Meshes auf visuell beeindruckende Weise schattieren. Zum Abschluss der grundlegenden Einführung sehen wir uns ein Beispiel an, bei dem Höhen- und Normalkarten auf ein ebenes Mesh angewendet werden. (Hier wird eine eigene „ .mesh “-Datei verwendet, da das integrierte „ #Rectangle “ nicht über genügend Unterteilungen verfügt.) Für bessere Beleuchtungsergebnisse verwenden wir bildbasierte Beleuchtung mit einem 360-Grad-HDR-Bild. Das Bild wird außerdem als Skybox festgelegt, um die Vorgänge besser zu veranschaulichen.
Beginnen wir zunächst mit einer leeren „ CustomMaterial “-Datei:
| main.qml | Ergebnis |
|---|---|
|
|
Nun erstellen wir einige Shader, die eine Höhen- und eine Normalenkarte auf das Mesh anwenden:
| Höhenkarte | Normal-Map |
|---|---|
|
|
| material.vert, material.frag |
|---|
|
| Änderung in main.qml | Ergebnis |
|---|---|
|
|
Hinweis: Das Objekt „ WasdController “ kann während der Entwicklung und Fehlerbehebung äußerst hilfreich sein, da es die Navigation und den Blick in die Szene auf vertraute Weise mit Tastatur und Maus ermöglicht. Eine von „ WasdController “ gesteuerte Kamera einzurichten, ist ganz einfach:
import QtQuick3D.Helpers
View3D {
PerspectiveCamera {
id: camera
}
// ...
}
WasdController {
controlledObject: camera
}Tiefen- und Bildschirmtexturen
Wenn ein benutzerdefiniertes Shader-Snippet die Schlüsselwörter „ DEPTH_TEXTURE “ oder „ SCREEN_TEXTURE “ verwendet, wird damit festgelegt, dass die entsprechenden Texturen in einem separaten Renderdurchlauf erzeugt werden. Dies ist zwar nicht unbedingt ein ressourcenschonender Vorgang, ermöglicht jedoch die Umsetzung einer Vielzahl von Techniken, wie beispielsweise die Lichtbrechung bei glasartigen Materialien.
DEPTH_TEXTURE „ sampler2D “ ermöglicht das Abtasten einer Textur mit dem Inhalt des Tiefenpuffers, wobei alle „ opaque “-Objekte in der Szene gerendert werden. In ähnlicher Weise ist „ SCREEN_TEXTURE “ ein „ sampler2D “, der das Abtasten einer Textur ermöglicht, die den Inhalt der Szene enthält, wobei transparente Materialien sowie alle Materialien, die ebenfalls „SCREEN_TEXTURE“ verwenden, ausgeschlossen werden. Die Textur kann für Materialien verwendet werden, die den Inhalt des Framebuffers benötigen, in den sie gerendert werden. Die Textur „SCREEN_TEXTURE“ verwendet denselben Clear-Modus wie „ View3D “. Die Größe dieser Texturen entspricht in Pixeln der Größe des „ View3D “.
Sehen wir uns eine einfache Demonstration an, bei der der Inhalt des Tiefenpuffers über ` DEPTH_TEXTURE` visualisiert wird. Der ` far clip value ` der Kamera wird hier vom Standardwert 10000 auf 2000 reduziert, um einen kleineren Bereich zu erhalten und so die visualisierten Unterschiede in den Tiefenwerten deutlicher hervorzuheben. Das Ergebnis ist ein Rechteck, das den Tiefenpuffer der Szene über seine Oberfläche visualisiert.
| main.qml, material.frag | Ergebnis |
|---|---|
|
Beachten Sie, dass der Zylinder in „ DEPTH_TEXTURE “ nicht zu sehen ist, da er auf Halbtransparenz basiert und somit in eine andere Kategorie fällt als die anderen Objekte, die alle undurchsichtig sind. Diese Objekte schreiben nicht in den Tiefenpuffer, obwohl sie die von undurchsichtigen Objekten geschriebenen Tiefenwerte prüfen und darauf angewiesen sind, in der Reihenfolge „von hinten nach vorne“ gerendert zu werden. Daher sind sie auch in „ DEPTH_TEXTURE “ nicht vorhanden.
Was passiert, wenn wir den Shader stattdessen so umstellen, dass er „ SCREEN_TEXTURE “ abtastet?
| material.frag | Ergebnis |
|---|---|
|
|
Hier wird das Rechteck mit „ SCREEN_TEXTURE “ texturiert, wobei transparente Pixel durch violette ersetzt werden.
Funktionen des Lichtprozessors
Eine erweiterte Funktion von „ CustomMaterial “ ist die Möglichkeit, im Fragment-Shader Funktionen zu definieren, die die Beleuchtungsgleichungen neu implementieren, mit denen die Fragmentfarbe berechnet wird. Eine Lichtprozessorfunktion wird, sofern vorhanden, einmal pro Lichtquelle in der Szene und für jedes Fragment aufgerufen. Es gibt spezielle Funktionen für verschiedene Lichttypen sowie für den Beitrag des Umgebungslichts und des Spiegelungslichts. Ist keine entsprechende Lichtprozessorfunktion vorhanden, werden die Standardberechnungen verwendet, genau wie es ein „ PrincipledMaterial “ tun würde. Ist ein Lichtprozessor vorhanden, der Funktionskörper jedoch leer, bedeutet dies, dass es keinen Beitrag eines bestimmten Lichttyps in der Szene gibt.
Weitere Informationen zu Funktionen wie „ DIRECTIONAL_LIGHT “, „ POINT_LIGHT “, „ SPOT_LIGHT “, „ AMBIENT_LIGHT “ und „ SPECULAR_LIGHT “ finden Sie in der Dokumentation zu „ CustomMaterial “.
Nicht schattierte benutzerdefinierte Materialien
Es gibt eine weitere Art von „ CustomMaterial “: benutzerdefinierte Materialien vom Typ „ unshaded “. In allen bisherigen Beispielen wurden benutzerdefinierte Materialien vom Typ „ shaded “ verwendet, wobei die Eigenschaft „ shadingMode “ auf ihrem Standardwert „ CustomMaterial.Shaded“ belassen wurde.
Was passiert, wenn wir diese Eigenschaft auf „ CustomMaterial.Unshaded“ umstellen?
Zunächst einmal haben Schlüsselwörter wie „ BASE_COLOR “, „ EMISSIVE_COLOR “, „ METALNESS “ usw. nicht mehr die gewünschte Wirkung. Das liegt daran, dass ein „Unshaded“-Material, wie der Name schon sagt, nicht automatisch mit einem Großteil des Standard-Shading-Codes ergänzt wird und somit Lichter, bildbasierte Beleuchtung, Schatten und Umgebungsokklusion in der Szene ignoriert. Stattdessen überträgt ein „unshaded“ Material über das Schlüsselwort „ FRAGCOLOR “ die vollständige Kontrolle an den Shader. Dies ähnelt „gl_FragColor“: Die „ FRAGCOLOR “ zugewiesene Farbe ist das Ergebnis und die endgültige Farbe des Fragments, ohne weitere Anpassungen durch „ Qt Quick 3D “.
| main.qml, material.frag, material2.frag | Ergebnis |
|---|---|
|
|
Beachten Sie, wie der rechte Zylinder die „ DirectionalLight “ in der Szene ignoriert. Seine Schattierung berücksichtigt die Beleuchtung der Szene nicht, die endgültige Fragmentfarbe ist vollständig weiß.
Der Vertex-Shader in einem ungeschatteten Material verfügt weiterhin über die typischen Eingaben: „ VERTEX “, „ NORMAL “, „ MODELVIEWPROJECTION_MATRIX “ usw. und kann in „ POSITION “ schreiben. Dem Fragment-Shader stehen diese Annehmlichkeiten jedoch nicht mehr zur Verfügung: „ NORMAL “, „ UV0 “ oder „ VAR_WORLD_POSITION “ sind im Fragment-Shader eines ungeschatteten Materials nicht verfügbar. Stattdessen ist es nun Aufgabe des Shader-Codes, alles, was zur Bestimmung der endgültigen Fragmentfarbe benötigt wird, zu berechnen und mithilfe von ` VARYING ` weiterzugeben.
Sehen wir uns ein Beispiel an, das sowohl einen Vertex- als auch einen Fragment-Shader enthält. Die geänderte Vertex-Position wird an den Fragment-Shader weitergegeben, wobei jedem Fragment ein interpolierter Wert zur Verfügung gestellt wird.
| main.qml, material.vert, material.frag |
|---|
|
Unschattierte Materialien sind nützlich, wenn keine Interaktion mit der Szenenbeleuchtung erforderlich oder gewünscht ist und das Material die volle Kontrolle über die endgültige Fragmentfarbe benötigt. Beachten Sie, dass das obige Beispiel weder einen „ DirectionalLight “ noch andere Lichtquellen enthält, die Kugel mit dem benutzerdefinierten Material jedoch wie erwartet dargestellt wird.
Hinweis: Ein ungeschattetes Material, das nur einen Vertex-Shader-Ausschnitt enthält, aber die Eigenschaft „fragmentShader“ nicht angibt, ist zwar weiterhin funktionsfähig, die Ergebnisse entsprechen jedoch denen, als wäre der „shadingMode“ auf „Shaded“ gesetzt. Daher macht es wenig Sinn, den „shadingMode“ für Materialien zu ändern, die nur über einen Vertex-Shader verfügen.
Programmierbarkeit für Effekte
Post-Processing-Effekte wenden einen oder mehrere Fragment-Shader auf das Ergebnis eines „ View3D “ an. Die Ausgabe dieser Fragment-Shader wird dann anstelle der ursprünglichen Renderergebnisse angezeigt. Dies ist konzeptionell sehr ähnlich zu den „ ShaderEffect “ und „ ShaderEffectSource “ von „ Qt Quick “.
Hinweis: Post-Processing-Effekte sind nur verfügbar, wenn der „ renderMode “ für den „ View3D “ auf „ View3D.Offscreen“ gesetzt ist.
Für einen Effekt können auch benutzerdefinierte Vertex-Shader-Schnipsel angegeben werden, diese sind jedoch nur von begrenztem Nutzen und werden daher voraussichtlich relativ selten verwendet. Die Vertex-Eingabe für einen Nachbearbeitungseffekt ist ein Quad (entweder zwei Dreiecke oder ein Dreiecksstreifen); eine Transformation oder Verschiebung der Vertexpunkte dieses Quads ist oft nicht hilfreich. Es kann jedoch sinnvoll sein, einen Vertex-Shader zu verwenden, um Daten zu berechnen und diese mithilfe des Schlüsselworts „ VARYING “ an den Fragment-Shader weiterzugeben. Wie üblich erhält der Fragment-Shader dann einen interpolierten Wert, der auf der aktuellen Fragmentkoordinate basiert.
Die Syntax der mit einem „ Effect “ verbundenen Shader-Schnipsel ist identisch mit der der Shader für ein nicht schattiertes „ CustomMaterial “. Was die integrierten speziellen Schlüsselwörter betrifft, so funktionieren „ VARYING “, „ MAIN “, „ FRAGCOLOR “ (nur Fragment-Shader), „ POSITION “ (nur Vertex-Shader), „ VERTEX “ (nur Vertex-Shader) und „ MODELVIEWPROJECTION_MATRIX “ genauso wie „ CustomMaterial “.
Die wichtigsten speziellen Schlüsselwörter für Fragment-Shader in „ Effect “ sind die folgenden:
| Name | Typ | Beschreibung |
|---|---|---|
| INPUT | sampler2D oder sampler2DArray | Der Sampler für die Eingabetextur. Ein Effekt nimmt in der Regel mit ` INPUT_UV` Proben davon. |
| INPUT_UV | vec2 | UV-Koordinaten für das Abtasten von „ INPUT “. |
| INPUT_SIZE | vec2 | Die Größe der Textur „ INPUT “ in Pixeln. Dies ist eine praktische Alternative zum Aufruf von textureSize(). |
| OUTPUT_SIZE | vec2 | Die Größe der Ausgabetextur in Pixeln. Entspricht in vielen Fällen der „ INPUT_SIZE “, doch bei einem Multi-Pass-Effekt können einzelne Durchläufe in Zwischentexturen mit unterschiedlichen Größen ausgegeben werden. |
| DEPTH_TEXTURE | sampler2D | Tiefentextur mit dem Inhalt des Tiefenpuffers, der die undurchsichtigen Objekte in der Szene enthält. Wie bei „ CustomMaterial “ löst das Vorhandensein dieses Schlüsselworts im Shader die automatische Erzeugung der Tiefentextur aus. |
Hinweis: Wenn Multiview-Rendering aktiviert ist, handelt es sich bei der Eingabetextur um ein 2D-Textur-Array. GLSL-Funktionen wie `texture()` und `textureSize()` nehmen bzw. geben dann einen `vec3` bzw. `ivec3` zurück. Verwenden Sie „ VIEW_INDEX “ für die Ebene. In VR-/AR-Anwendungen, die sowohl mit als auch ohne Multiview-Rendering funktionieren sollen, ist es am portabelsten, den Shader-Code wie folgt zu schreiben:
#if QSHADER_VIEW_COUNT >= 2
vec4 c = texture(INPUT, vec3(INPUT_UV, VIEW_INDEX));
#else
vec4 c = texture(INPUT, INPUT_UV);
#endifEin Nachbearbeitungseffekt
Beginnen wir mit einer einfachen Szene, diesmal mit einigen weiteren Objekten, darunter ein texturiertes Rechteck, das eine Schachbrettmuster-Textur als Basis-Farbkarte verwendet.
| main.qml | Ergebnis |
|---|---|
|
|
Wenden wir nun einen Effekt auf die gesamte Szene an. Genauer gesagt auf das „ View3D “. Wenn sich mehrere „ View3D “-Elemente in der Szene befinden, verfügt jedes über eine eigene „ SceneEnvironment “ und somit über eine eigene Post-Processing-Effektkette. Im Beispiel gibt es ein einziges „ View3D “, das das gesamte Fenster abdeckt.
| Änderung in main.qml | effect.frag |
|---|---|
| |
Dieser einfache Effekt verändert den Wert des roten Farbkanals. Das Offenlegen von QML-Eigenschaften als Uniforms funktioniert bei Effekten genauso wie bei benutzerdefinierten Materialien. Der Shader beginnt mit einer Zeile, die beim Schreiben von Fragment-Shadern für Effekte sehr häufig vorkommt: das Abtasten von INPUT an den UV-Koordinaten INPUT_UV. Anschließend führt er die gewünschten Berechnungen durch und weist die endgültige Fragmentfarbe FRAGCOLOR zu.
Viele der im Beispiel festgelegten Eigenschaften stehen im Plural (Effekte, Durchgänge, Shader). Zwar kann die Syntax der Liste „ [ ] “ weggelassen werden, wenn nur ein einziges Element vorhanden ist, doch handelt es sich bei all diesen Eigenschaften um Listen, die mehr als ein Element enthalten können. Warum ist das so?
- effects ist eine Liste, da „ View3D “ die Verkettung mehrerer Effekte ermöglicht. Die Effekte werden in der Reihenfolge angewendet, in der sie der Liste hinzugefügt werden. Dies ermöglicht die einfache gemeinsame Anwendung von zwei oder mehr Effekten auf „ View3D “ und ähnelt dem, was man in „ Qt Quick “ durch Verschachtelung von „ ShaderEffect “-Elementen erreichen kann. Die Textur „
INPUT“ des nächsten Effekts ist immer eine Textur, die die Ausgabe des vorherigen Effekts enthält. Die Ausgabe des letzten Effekts in dieser Kette wird als endgültige Ausgabe von „ View3D “ verwendet. - passes ist eine Liste, da „Effect“ im Gegensatz zu „ ShaderEffect “ eine integrierte Unterstützung für mehrere Durchläufe bietet. Ein Multi-Pass-Effekt ist leistungsfähiger als die Verkettung mehrerer unabhängiger Effekte in „ effects “: Ein Durchlauf kann zusätzlich zur ursprünglichen Eingabetextur des Effekts in eine temporäre Zwischentextur ausgeben, die dann als Eingabe für nachfolgende Durchläufe verwendet werden kann. Dies ermöglicht die Erstellung komplexer Effekte, die mehrere Texturen berechnen, rendern und miteinander überblenden, um die endgültige Fragmentfarbe zu erhalten. Dieser fortgeschrittene Anwendungsfall wird hier nicht behandelt. Weitere Informationen finden Sie auf der Dokumentationsseite „ Effect “.
- shaders ist eine Liste, da ein Effekt sowohl einen Vertex- als auch einen Fragment-Shader haben kann.
Verkettung mehrerer Effekte
Sehen wir uns ein Beispiel an, bei dem der Effekt aus dem vorherigen Beispiel durch einen weiteren Effekt ergänzt wird, der dem integrierten „ DistortionSpiral “-Effekt ähnelt.
| Änderung in „main.qml“ | effect2.frag |
|---|---|
| |
Nun die vielleicht überraschende Frage: Warum ist dies ein schlechtes Beispiel?
Genauer gesagt ist es nicht schlecht, sondern zeigt vielmehr ein Muster, dessen Vermeidung oft von Vorteil sein kann.
Das Verkettung von Effekten auf diese Weise kann nützlich sein, doch es ist wichtig, die Auswirkungen auf die Leistung im Auge zu behalten: Zwei Render-Durchläufe (einen zur Erzeugung einer Textur mit dem angepassten roten Farbkanal und einen weiteren zur Berechnung der Verzerrung) sind ziemlich verschwenderisch, wenn einer ausreichen würde. Hätte man die Fragment-Shader-Schnipsel kombiniert, hätte man dasselbe Ergebnis mit einem einzigen Effekt erzielen können.
Definition von Mesh- und Texturdaten in C++
Die prozedurale Generierung von Mesh- und Texturbilddaten erfolgt in ähnlichen Schritten:
- Erstellen Sie eine Unterklasse von ` QQuick3DGeometry ` oder QQuick3DTextureData
- Legen Sie die gewünschten Vertex- oder Bilddaten bei der Instanziierung fest, indem Sie die geschützten Memberfunktionen der Basisklasse aufrufen
- Sollten später zu einem bestimmten Zeitpunkt dynamische Änderungen erforderlich sein, setzen Sie die neuen Daten und rufen Sie `update()` auf
- Sobald die Implementierung abgeschlossen ist, muss die Klasse registriert werden, damit sie in QML sichtbar ist
- Model und „ Texture “-Objekte in QML können nun den benutzerdefinierten Anbieter für Vertex- oder Bilddaten nutzen, indem sie die Eigenschaft „ Model::geometry “ oder „ Texture::textureData “ setzen
Benutzerdefinierte Vertex-Daten
Vertexdaten beziehen sich auf die Abfolge von (typischerweise „ float “) Werten, aus denen sich ein Mesh zusammensetzt. Anstelle des Ladens von „ .mesh “-Dateien ist ein benutzerdefinierter Geometrie-Provider dafür zuständig, dieselben Daten bereitzustellen. Die Scheitelpunktdaten bestehen aus Attributen ( attributes), wie beispielsweise Position, Texturkoordinaten (UV) oder Normalen. Die Angabe der Attribute beschreibt, welche Art von Attributen vorhanden sind, den Komponententyp (zum Beispiel ein 3-komponentiger Float-Vektor für die Scheitelpunktposition, bestehend aus x, y- und z-Werten), bei welchem Offset sie in den bereitgestellten Daten beginnen und wie groß der Stride (das Inkrement, das zum Offset addiert werden muss, um auf das nächste Element desselben Attributs zu verweisen) ist.
Dies kommt Ihnen vielleicht bekannt vor, wenn Sie bereits direkt mit Grafik-APIs wie OpenGL oder Vulkan gearbeitet haben, da die Art und Weise, wie die Scheitelpunkteingabe bei diesen APIs spezifiziert wird, in etwa dem entspricht, was eine „ .mesh “-Datei oder eine „ QQuick3DGeometry “-Instanz definiert.
Zudem muss die Mesh-Topologie (Primitivtyp) angegeben werden. Für das indizierte Zeichnen müssen außerdem die Daten für einen Indexpuffer bereitgestellt werden.
Es gibt eine integrierte Implementierung für benutzerdefinierte Geometrie: Das Modul „ QtQuick3D.Helpers“ enthält einen Typ „ GridGeometry “. Dies ermöglicht das Rendern eines Gitters in der Szene mit Linienprimitiven, ohne dass eine benutzerdefinierte Unterklasse von „ QQuick3DGeometry “ implementiert werden muss.
Ein weiterer häufiger Anwendungsfall ist das Rendern von Punkten. Dies ist relativ einfach, da die Attributspezifikation minimal ist: Wir geben für jeden Scheitelpunkt drei Floats (x, y, z) an, sonst nichts. Eine Unterklasse von ` QQuick3DGeometry ` könnte eine aus 2000 Punkten bestehende Geometrie ähnlich wie folgt implementieren:
clear();
const int N = 2000;
const int stride = 3 * sizeof(float);
QByteArray v;
v.resize(N * stride);
float *p = reinterpret_cast<float *>(v.data());
QRandomGenerator *rg = QRandomGenerator::global();
for (int i = 0; i < N; ++i) {
const float x = float(rg->bounded(200.0f) - 100.0f) / 20.0f;
const float y = float(rg->bounded(200.0f) - 100.0f) / 20.0f;
*p++ = x;
*p++ = y;
*p++ = 0.0f;
}
setVertexData(v);
setStride(stride);
setPrimitiveType(QQuick3DGeometry::PrimitiveType::Points);
addAttribute(QQuick3DGeometry::Attribute::PositionSemantic, 0, QQuick3DGeometry::Attribute::F32Type);In Kombination mit einem Material von
DefaultMaterial {
lighting: DefaultMaterial.NoLighting
cullMode: DefaultMaterial.NoCulling
diffuseColor: "yellow"
pointSize: 4
}sieht das Endergebnis in etwa so aus (hier aus einem veränderten Kamerawinkel betrachtet, mithilfe von ` WasdController`):

Hinweis: Beachten Sie , dass Punktgrößen und Linienbreiten, die nicht 1 betragen, je nach der zugrunde liegenden Grafik-API zur Laufzeit möglicherweise nicht unterstützt werden. Darauf hat Qt keinen Einfluss. Daher kann es erforderlich werden, alternative Techniken zu implementieren, anstatt sich auf das Zeichnen von Punkten und Linien zu verlassen.
Benutzerdefinierte Texturdaten
Bei Texturen sind die bereitzustellenden Daten strukturell wesentlich einfacher: Es handelt sich um die rohen Pixeldaten mit einer je nach Texturformat variierenden Anzahl von Bytes pro Pixel. Beispielsweise erwartet eine RGBA -Textur vier Bytes pro Pixel, während RGBA16F vier Half-Floats pro Pixel vorsieht. Dies entspricht in etwa dem, was ein QImage intern speichert. Qt Quick 3D -Texturen können jedoch Formate aufweisen, deren Daten nicht durch ein „ QImage “ dargestellt werden können. Beispiele hierfür sind HDR-Texturen mit Gleitkommadaten oder komprimierte Texturen. Daher werden die Daten für „ QQuick3DTextureData “ stets als rohe Byte-Sequenz bereitgestellt. Dies dürfte bekannt vorkommen, wenn man bereits direkt mit Grafik-APIs wie OpenGL oder Vulkan gearbeitet hat.
Weitere Informationen finden Sie auf den Dokumentationsseiten unter QQuick3DGeometry und QQuick3DTextureData.
Siehe auch CustomMaterial, Effect, QQuick3DGeometry, QQuick3DTextureData, Qt Quick 3D – Beispiel für benutzerdefinierte Effekte, Qt Quick 3D – Beispiel für benutzerdefinierte Shader, Qt Quick 3D – Beispiel für benutzerdefinierte Materialien, Qt Quick 3D – Beispiel für benutzerdefinierte Geometrie und Qt Quick 3D – Beispiel für prozedurale Texturen.
© 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.























