Qt Quick 3D - Beispiel für benutzerdefinierte Materialien
Demonstriert das Schreiben schattierter benutzerdefinierter Materialien.
Dieses Beispiel zeigt, wie man schattierte custom materials schreiben kann. Mit schattierten Materialien müssen wir keine kompletten Shader-Programme schreiben. Stattdessen schreiben wir Funktionen, die die Standard-Shader von Qt modifizieren. Auf diese Weise nimmt das resultierende Material standardmäßig an der Beleuchtung und der Schattenzuordnung teil und ist mit Lichtsonden kompatibel. Wir müssen nur für die Fälle, in denen wir ein spezielles Verhalten wünschen, eine eigene Logik schreiben. Dies wird erreicht, indem wir den Shader-Code, der für PrincipledMaterial generiert wird, mit unseren eigenen benutzerdefinierten Funktionen ergänzen, die in bestimmten Phasen in den Vertex- und Fragment-Shadern aufgerufen werden.
Um ein schattiertes benutzerdefiniertes Material zu erstellen, setzen Sie die Eigenschaft shadingMode auf CustomMaterial.Shaded
.
Ein einfaches Material
Das erste Modell verwendet ein einfaches Material, das keine eigene Logik enthält. Das benutzerdefinierte Material wird auf dem Modell wie jedes andere Material eingestellt:
Model { source: "weirdShape.mesh" scale: Qt.vector3d(100, 100, 100) rotation: Quaternion.fromEulerAngles(-90, 0, 0) x: v3d.radius materials: [ CustomMaterial { shadingMode: CustomMaterial.Shaded fragmentShader: "material_simple.frag" property color uDiffuse: "fuchsia" property real uSpecular: 1.0 } ] }
Zusätzlich zur Einstellung von shadingMode
und fragmentShader
fügen wir dem Material zwei Eigenschaften hinzu: uDiffuse
und Specular
. Diese werden vom Fragment-Shader übernommen.
Der Code für den Fragment-Shader ist kurz:
void MAIN() { SPECULAR_AMOUNT = uSpecular; BASE_COLOR = uDiffuse; }
Alle Shader müssen die Funktion MAIN
implementieren. In diesem verwenden wir die in den Materialien definierten Eigenschaften, um Werte zu setzen, die vom Standard-Shader-Code von Qt verwendet werden. Beachten Sie, dass wir diese nicht als Uniformen deklarieren müssen: Wir müssen nur sicherstellen, dass die Namen übereinstimmen. Wir würden einen Shader-Kompilierfehler erhalten, wenn das Material keine passenden Eigenschaften hätte.
Die speziellen Variablen SPECULAR_AMOUNT
und BASE_COLOR
entsprechen specularAmount und baseColor von PrincipledMaterial. Diese werden dann vom Standard-Shader-Code verwendet, um Beleuchtungsberechnungen durchzuführen, als ob wir PrincipledMaterial verwendet hätten.
Benutzerdefinierte Behandlung von Lichtern
Das nächste Objekt verwendet ein komplexeres Material, das eine benutzerdefinierte Beleuchtung implementiert. Das Material hat andere einheitliche Namen, wird aber ansonsten auf dieselbe Weise verwendet:
materials: [ CustomMaterial { shadingMode: CustomMaterial.Shaded fragmentShader: "material_customlights.frag" property color uDiffuse: "orange" property real uShininess: 150 } ]
Der Fragment-Shader implementiert eine benutzerdefinierte Logik für alle verschiedenen Arten von Licht:
void MAIN() { SPECULAR_AMOUNT = 1.0; ROUGHNESS = 0.5; BASE_COLOR = uDiffuse; } void AMBIENT_LIGHT() { DIFFUSE += uDiffuse.rgb * TOTAL_AMBIENT_COLOR; } void DIRECTIONAL_LIGHT() { DIFFUSE += uDiffuse.rgb * LIGHT_COLOR * SHADOW_CONTRIB * vec3(max(0.0, dot(normalize(NORMAL), TO_LIGHT_DIR))); } void POINT_LIGHT() { DIFFUSE += uDiffuse.rgb * LIGHT_COLOR * LIGHT_ATTENUATION * SHADOW_CONTRIB * vec3(max(0.0, dot(normalize(NORMAL), TO_LIGHT_DIR))); } void SPOT_LIGHT() { DIFFUSE += uDiffuse.rgb * LIGHT_COLOR * LIGHT_ATTENUATION * SPOT_FACTOR * SHADOW_CONTRIB * vec3(max(0.0, dot(normalize(VAR_WORLD_NORMAL), TO_LIGHT_DIR))); }
Hier verwenden wir viele neue spezielle Schlüsselwörter, die sich auf Eigenschaften der verschiedenen Lichttypen beziehen. In der Dokumentation CustomMaterial finden Sie eine Beschreibung der einzelnen Schlüsselwörter. Beachten Sie, dass jeder Lichttyp seine eigene Funktion hat. Jede Funktion, die nicht implementiert ist, verwendet die Standardimplementierung und verhält sich wie PrincipledMaterial. Zum Beispiel: In diesem Shader haben wir SPECULAR_LIGHT()
nicht implementiert, so dass wir die eingebaute spiegelnde Reflexion erhalten.
Hinzufügen eines Vertex-Shaders
Ein benutzerdefiniertes Material kann auch einen Vertex-Shader verwenden, um die Geometrie des Modells zu verändern. Hier legen wir sowohl den Fragment- als auch den Vertex-Shader fest und fügen einige weitere Eigenschaften hinzu, die als einheitliche Werte übernommen werden:
materials: [ CustomMaterial { id: material shadingMode: CustomMaterial.Shaded vertexShader: "material_distortion.vert" fragmentShader: "material_customlights.frag" property real uTime: 0.0 property real uAmplitude: 0.3 property color uDiffuse: "yellow" property real uShininess: 50 NumberAnimation { target: material property: "uTime" from: 0.0 to: 31.4 duration: 10000 loops: Animation.Infinite running: true } } ]
Der Vertex-Shader ist sehr kurz:
// Copyright (C) 2023 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause void MAIN() { VERTEX.y += sin(uTime + VERTEX.x*10.0) * uAmplitude; }
Er verformt das Modell, indem er jeden Scheitelpunkt entsprechend einer Sinuswelle verschiebt, die sich mit der Zeit ändert.
Ein transparentes Material
Schließlich fügen wir eine Kugel mit einem transparenten Material hinzu. Aus Leistungsgründen implementiert Qt die Transparenz nicht auf eine völlig realistische Weise. Stattdessen rendert Qt alle undurchsichtigen Objekte in der Szene in eine Textur, und transparente Materialien lesen dann aus dieser Textur. Das bedeutet, dass transparente Materialien das beste Ergebnis liefern, wenn sie vor anderen Objekten platziert werden:
Model { id: screenSphere source: "#Sphere" scale: Qt.vector3d(0.75, 0.75, 0.75) y: 60 z: 750; materials: [ CustomMaterial { shadingMode: CustomMaterial.Shaded fragmentShader: "material_transparent.frag" } ]
Für dieses Beispiel implementieren wir eine vereinfachte Verzerrungsfunktion, die nicht versucht, eine echte physikalische Brechung durchzuführen:
void MAIN() { vec2 size = vec2(textureSize(SCREEN_TEXTURE, 0)); vec2 uv = FRAGCOORD.xy / size; vec3 view = normalize(VIEW_VECTOR); vec3 projection = view - view * normalize(NORMAL); vec3 refraction = projection * projection; uv += refraction.xy * 0.5; vec3 col = texture(SCREEN_TEXTURE, uv).rgb; col = col * 0.8 + vec3(0.2); BASE_COLOR = vec4(col, 1.0); }
SCREEN_TEXTURE
bezieht sich auf die Textur, die alle undurchsichtigen Objekte in der Szene zeigt. Wir berechnen zunächst die UV-Koordinaten innerhalb dieser Textur, die mit der Position des aktuellen Scheitelpunkts auf dem Bildschirm übereinstimmen. Dann fügen wir einen Offset zu dieser Position hinzu, um einen Brechungseffekt zu simulieren, bevor wir eine Textursuche durchführen.
Zum Schluss mischen wir 20% Weiß hinzu, um eine leichte Bewölkung zu erzielen. Beachten Sie, dass die Ausgabe BASE_COLOR
zugewiesen ist, so dass Qt die Beleuchtung darüber hinaus hinzufügt. Aus diesem Grund sind auf der Oberfläche der Kugel Reflexionen zu sehen.
Unschattierte Materialien
Es ist auch möglich, benutzerdefinierte Materialien zu haben, die komplette Shader-Programme verwenden (und trotzdem die Komfort-Schlüsselwörter nutzen). Das customshaders-Beispiel demonstriert die andere Gruppe von benutzerdefinierten Materialien: nicht schattierte benutzerdefinierte Materialien.
© 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.