Qt Quick 3D - 屏幕空间反射示例
演示了Qt Quick 3D 中的反射效果。

本示例演示了如何在模型上使用屏幕空间反射(SSR)创建反射效果。SSR 是一种后处理效果,可通过添加反射来增强场景表现力。SSR 的核心原理是在物体渲染完成后,在屏幕空间内计算反射效果。 对于每个片段,会从摄像机向该片段发射一条光线,然后该光线会沿片段法线方向反射。随后,我们追踪反射光线,并判断其是否会击中某个物体。如果击中了物体,则该片段将反射该物体。但在某些情况下,SSR 可能会失效。 例如,当反射光线击中位于摄像机后方的物体时。由于反射是在物体渲染完成后才在屏幕空间中计算的,因此无法获取摄像机后方物体颜色的相关信息。尽管 SSR 存在一些缺点,但它能让场景更具真实感。
本示例使用Custom Materials 实现SSR,该 可应用于Model ,使其能够反射周围环境。
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"
}
}场景中的其余部分包含一些静态物体,或者在表面上方旋转的物体,以展示反射效果。
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"
}
}
}着色器代码
在深入探讨着色器代码之前,让我们先了解一些可用于控制反射的参数。
depthBias | 该参数用于检查光线与物体之间的深度差是否在特定阈值范围内。 |
rayMaxDistance | 控制光线在视图空间中的终点距离。 |
marchSteps | 控制计算所用的步数。增加步数会减少光线在每次迭代中移动的片段数量,并提高质量。 |
refinementSteps | 在找到反射光线击中物体的位置后,会进行一个细化过程,以尝试找到确切的击中位置。此参数控制应使用多少步。当marchSteps 较小时,它可以提供更好的结果。 |
specular | 0 到 1 之间的数值,用于控制模型的反射率。 |
materialColor | 为模型赋予颜色。该颜色将与反射颜色混合。 |
着色器首先获取从摄像机到片段的方向,然后沿片段法线方向进行反射。 光线的起始点和终点在视图空间中计算,随后这些点被转换到屏幕空间。在屏幕空间中追踪反射光线的好处在于能获得更高的渲染质量。此外,光线在视图空间中可能覆盖很长的距离,但在屏幕空间中可能仅涉及几个片段。
计算出从起始片元指向终点片元的向量,并将其除以marchSteps 。
随后调用rayMarch 函数。该函数在屏幕空间中逐步推进光线,然后将其变换回视图空间。它还会利用场景的DEPTH_TEXTURE 获取该片段处的目标物体。计算光线与物体深度的差值,并与depthBias 进行比较。若检测到命中,则调用refinementStep 函数。
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;
}
}
}细化步骤与rayMarch 相同,不同之处在于它试图找到命中发生的精确位置,因此每次迭代都会将光线向前推进半步距离。
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;
}
}反射的可见度被设置为命中值,随后会进行一些可见度检查。 如前所述,如果反射光线击中了摄像机后方的物体,SSR 将会失败,因此会根据反射光线朝向摄像机的程度来淡化可见度。可见度还会根据被击中物体与光线起点的距离来淡化。
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);最后,根据SCREEN_TEXTURE 计算反射颜色,并与材质颜色进行混合。
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);辅助函数
着色器代码中使用了一些辅助函数。correctTextureCoordinates 函数会根据所使用的图形API对纹理坐标进行校正。在使用D3D11或Metal时必须执行此操作。更多信息请参阅CustomMaterial 文档。
vec2 correctTextureCoordinates(vec2 uv)
{
if(FRAMEBUFFER_Y_UP < 0 && NDC_Y_UP == 1)
uv.y = 1 - uv.y;
return uv;
}rayFragOutOfBound 函数用于检查光线在行进后是否位于屏幕之外。若处于此情况,则不使用反射颜色,因为屏幕之外的区域没有可用信息。
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;
}viewPosFromScreen 函数通过调用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;
}rayViewDepthFromScreen 函数通过 获取物体在视图空间中的位置。此时,深度值是通过线性插值计算得出的,即取光线起始点深度与终点深度之间的中间值。
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.