Qt Quick 3D - Beispiel für Screen-Space-Reflexionen
Veranschaulicht Reflexionen mit „ Qt Quick 3D “.

Dieses Beispiel zeigt, wie man mit Screen Space Reflections (SSR) Reflexionen auf einem Modell erzeugt. SSR ist ein Post-Processing-Effekt, der die Szene durch das Hinzufügen von Reflexionen aufwerten kann. Die Idee hinter SSR ist, dass die Reflexionen im Screen Space berechnet werden können, nachdem die Objekte gerendert wurden. Für jedes Fragment wird ein Strahl von der Kamera zu diesem Fragment ausgesendet und dann um die Normale des Fragments herum reflektiert. Anschließend verfolgen wir den reflektierten Strahl und stellen fest, ob er auf ein Objekt trifft oder nicht. Wenn ein Objekt getroffen wurde, reflektiert das Fragment dieses Objekt. Es gibt Situationen, in denen SSR fehlschlägt. Zum Beispiel, wenn der reflektierte Strahl auf ein Objekt hinter der Kamera trifft. Da die Reflexionen im Screen Space berechnet werden, nachdem die Objekte gerendert wurden, liegen keine Informationen über die Farbe der Objekte hinter der Kamera vor. Obwohl SSR einige Nachteile hat, verleiht es der Szene mehr Realismus.
In diesem Beispiel wird SSR mithilfe von „ Custom Materials “ implementiert, die auf einem „ Model “ angewendet werden können, wodurch dieser seine Umgebung reflektiert.
Model {
source: "#Rectangle"
scale: Qt.vector3d(5, 5, 5)
eulerRotation.x: -90
eulerRotation.z: 180
position: Qt.vector3d(0.0, -50.0, 0.0)
materials: ScreenSpaceReflections {
depthBias: depthBiasSlider.value
rayMaxDistance: distanceSlider.value
marchSteps: marchSlider.value
refinementSteps: refinementStepsSlider.value
specular: specularSlider.value
materialColor: materialColorCheckBox.checked ? "transparent" : "dimgray"
}
}Der Rest der Szene enthält einige Objekte, die entweder statisch sind oder sich über der Oberfläche drehen, um die Reflexionen darzustellen.
Node {
Model {
source: "#Cube"
eulerRotation.y: 0
scale: Qt.vector3d(1, 1, 1)
position: Qt.vector3d(50.0, 40.0, 50.0)
materials: PrincipledMaterial {
baseColorMap: Texture {
source: "qt_logo_rect.png"
}
}
}
Node{
Model {
source: "#Sphere"
position: Qt.vector3d(-400.0, screenSpaceReflectionsView.modelHeight, 0.0)
materials: PrincipledMaterial {
baseColor: "magenta"
}
}
}
Node{
eulerRotation.y: screenSpaceReflectionsView.modelRotation
position.y: screenSpaceReflectionsView.modelHeight
Model {
source: "#Sphere"
pivot: Qt.vector3d(0, 0.0, 0.0)
position: Qt.vector3d(200.0, 0.0, 0.0)
materials: PrincipledMaterial {
baseColor: "green"
}
}
}
Node{
eulerRotation.y: screenSpaceReflectionsView.modelRotation
position.y: screenSpaceReflectionsView.modelHeight
Model {
source: "#Sphere"
eulerRotation.y: 45
position: Qt.vector3d(0.0, 0.0, -200.0)
materials: PrincipledMaterial {
baseColor: "blue"
}
}
}
Node{
eulerRotation.y: screenSpaceReflectionsView.modelRotation
position.y: screenSpaceReflectionsView.modelHeight
Model {
source: "#Sphere"
position: Qt.vector3d(0.0, 0.0, 200.0)
materials: PrincipledMaterial {
baseColor: "red"
}
}
}Shader-Code
Bevor wir uns mit dem Shader-Code befassen, wollen wir uns einige Parameter ansehen, mit denen sich die Reflexionen steuern lassen.
depthBias | Dieser Parameter dient dazu, zu prüfen, ob der Unterschied zwischen der Tiefe des Strahls und der des Objekts innerhalb eines bestimmten Schwellenwerts liegt. |
rayMaxDistance | Steuert, wie weit der Endpunkt des Strahls im Ansichtsraum entfernt ist. |
marchSteps | Steuert, wie viele Schritte für die Berechnung verwendet werden. Eine Erhöhung der Schrittanzahl verringert die Anzahl der Fragmente, um die sich der Strahl bei jeder Iteration bewegt, und erhöht die Qualität. |
refinementSteps | Nachdem ermittelt wurde, wo der reflektierte Strahl auf das Objekt trifft, wird ein Verfeinerungsprozess durchgeführt, um die genaue Position des Treffpunkts zu ermitteln. Dieser Parameter legt fest, wie viele Schritte verwendet werden sollen. Er kann zu besseren Ergebnissen führen, wenn der „ marchSteps “ klein ist. |
specular | Ein Wert zwischen 0 und 1, der die Reflektivität des Modells steuert. |
materialColor | Verleiht dem Modell Farbe. Diese Farbe wird mit der Reflexionsfarbe gemischt. |
Der Shader ermittelt zunächst die Richtung von der Kamera zum Fragment und reflektiert den Strahl dann um die Normale des Fragments herum. Der Start- und der Endpunkt des Strahls werden im View-Space berechnet und anschließend in den Screen-Space transformiert. Der Vorteil der Berechnung des reflektierten Strahls im Screen-Space besteht darin, dass dies zu einer besseren Qualität führt. Zudem könnte der Strahl im View-Space eine große Strecke zurücklegen, im Screen-Space jedoch nur wenige Fragmente abdecken.
Es wird ein Vektor berechnet, der vom Startfragment zum Endfragment zeigt, und durch den „ marchSteps “ geteilt.
Anschließend wird die Funktion „ rayMarch “ aufgerufen. Sie führt den Strahl Schritt für Schritt im Bildschirmraum (Screen Space) weiter und transformiert ihn dann zurück in den Ansichtsraum (View Space). Außerdem ermittelt sie das Objekt an diesem Fragment mithilfe der „ DEPTH_TEXTURE “ der Szene. Die Differenz zwischen der Tiefe des Strahls und der des Objekts wird berechnet und mit der „ depthBias “ verglichen. Wird ein Treffer gefunden, wird die Funktion „ refinementStep “ aufgerufen.
void rayMarch(vec2 rayStepVector, vec2 size)
{
for(int i = 0; i < marchSteps; i++)
{
rayData.rayFragCurr += rayStepVector;
rayData.rayCoveredPart = length(rayData.rayFragCurr - rayData.rayFragStart) / length(rayData.rayFragEnd - rayData.rayFragStart);
rayData.rayCoveredPart = clamp(rayData.rayCoveredPart, 0.0, 1.0);
float rayDepth = rayViewDepthFromScreen(size);
rayData.objHitViewPos = viewPosFromScreen(rayData.rayFragCurr, size);
float deltaDepth = rayDepth - rayData.objHitViewPos.z;
if(deltaDepth > 0 && deltaDepth < depthBias)
{
rayData.hit = 1;
refinementStep(rayStepVector, size);
return;
}
}
}Der Verfeinerungsschritt entspricht dem unter „ rayMarch “ beschriebenen, mit dem Unterschied, dass hier versucht wird, die genaue Position des Treffers zu ermitteln; daher wird der Strahl bei jeder Iteration um die Hälfte der Schrittweite vorwärts verschoben.
void refinementStep(vec2 rayStepVector, vec2 size)
{
for(int i = 0; i < refinementSteps; i++)
{
rayData.rayCoveredPart = length(rayData.rayFragCurr - rayData.rayFragStart) / length(rayData.rayFragEnd - rayData.rayFragStart);
rayData.rayCoveredPart = clamp(rayData.rayCoveredPart, 0.0, 1.0);
float rayDepth = rayViewDepthFromScreen(size);
rayData.objHitViewPos = viewPosFromScreen(rayData.rayFragCurr, size);
float deltaDepth = rayDepth - rayData.objHitViewPos.z;
rayStepVector *= 0.5;
if(deltaDepth > 0 && deltaDepth < depthBias)
rayData.rayFragCurr -= rayStepVector;
else
rayData.rayFragCurr += rayStepVector;
}
}Die Sichtbarkeit der Reflexion wird auf den Trefferwert gesetzt, woraufhin einige Sichtbarkeitsprüfungen durchgeführt werden. Wie bereits erwähnt, schlägt SSR fehl, wenn der reflektierte Strahl auf etwas hinter der Kamera trifft; daher wird die Sichtbarkeit entsprechend dem Ausmaß, in dem sich der reflektierte Strahl auf die Kamera zubewegt, ausgeblendet. Die Sichtbarkeit wird zudem entsprechend der Entfernung des getroffenen Objekts vom Startpunkt des Strahls ausgeblendet.
float visibility = rayData.hit;
/* Check if the ray hit an object behind the camera. This means information about the object can not be obtained from SCREEN_TEXTURE.
Start fading the visibility according to how much the reflected ray is moving toward the opposite direction of the camera */
visibility *= (1 - max(dot(-normalize(fragViewPos), reflected), 0));
/* Fade out visibility according how far is the hit object from the fragment */
visibility *= (1 - clamp(length(rayData.objHitViewPos - rayData.rayViewStart) / rayMaxDistance, 0, 1));
visibility = clamp(visibility, 0, 1);Schließlich wird die Reflexionsfarbe aus dem „ SCREEN_TEXTURE “ berechnet und mit der Materialfarbe gemischt.
vec2 uv = rayData.rayFragCurr / size;
uv = correctTextureCoordinates(uv);
vec3 reflectionColor = texture(SCREEN_TEXTURE, uv).rgb;
reflectionColor *= specular;
vec3 mixedColor = mix(materialColor.rgb, reflectionColor, visibility);
BASE_COLOR = vec4(mixedColor, materialColor.a);Hilfsfunktionen
Im Shader-Code werden einige Hilfsfunktionen verwendet. Die Funktion „ correctTextureCoordinates “ passt die Texturkoordinaten entsprechend der verwendeten Grafik-API an. Dies ist im Falle von D3D11 oder Metal erforderlich. Weitere Informationen finden Sie in der Dokumentation unter „ CustomMaterial “.
vec2 correctTextureCoordinates(vec2 uv)
{
if(FRAMEBUFFER_Y_UP < 0 && NDC_Y_UP == 1)
uv.y = 1 - uv.y;
return uv;
}Die Funktion „ rayFragOutOfBound “ prüft, ob sich der Strahl nach dem Marschen außerhalb des Bildschirms befindet. In diesem Fall wird keine Reflexionsfarbe verwendet, da für Bereiche außerhalb des Bildschirms keine Informationen verfügbar sind.
bool rayFragOutOfBound(vec2 rayFrag, vec2 size)
{
if(rayFrag.x > size.x || rayFrag.y > size.y)
return true;
if(rayFrag.x < 0 || rayFrag.y < 0)
return true;
return false;
}Die Funktion „ viewPosFromScreen “ ermittelt die Position des Objekts im Ansichtsraum mithilfe von „ DEPTH_TEXTURE “.
vec3 viewPosFromScreen(vec2 fragPos, vec2 size)
{
vec2 uv = fragPos / size;
vec2 texuv = correctTextureCoordinates(uv);
float depth = textureLod(DEPTH_TEXTURE, texuv, 0).r;
if(NEAR_CLIP_VALUE < 0.0)
depth = 2 * depth - 1.0;
vec3 ndc = vec3(2 * uv - 1, depth);
vec4 viewPos = INVERSE_PROJECTION_MATRIX * vec4(ndc, 1.0);
viewPos /= viewPos.w;
return viewPos.xyz;
}Die Funktion „ rayViewDepthFromScreen “ ermittelt die aktuelle Position des Strahls im Ansichtsraum. Dabei wird der Tiefenwert durch lineare Interpolation des Werts zwischen der Tiefe am Startpunkt und der Tiefe am Endpunkt des Strahls ermittelt.
float rayViewDepthFromScreen(vec2 size)
{
vec2 uv = rayData.rayFragCurr / size;
float depth = mix(rayData.rayFragStartDepth, rayData.rayFragEndDepth, rayData.rayCoveredPart);
vec3 ndc = vec3(2 * uv - 1, depth);
vec4 viewPos = INVERSE_PROJECTION_MATRIX * vec4(ndc, 1.0);
viewPos /= viewPos.w;
return viewPos.z;
}© 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.