SSR的核心是在屏幕空间沿反射方向离散步进并查深度图,而非真实光线追踪;需通过screenPos→NDC→UV转换链重建世界位置,自适应步长、深度精度校准与历史帧降噪缺一不可。

SSR 的核心是屏幕空间步进,不是 Ray Marching
SSR 不是真光线追踪,它在屏幕空间内沿视线反射方向做离散采样,靠深度缓冲反推世界位置。关键不是“发射光线”,而是“从当前像素出发,按反射向量在屏幕 UV 和深度图中一步步试探”。如果误用 raymarch 思路(比如固定步长 + 无深度约束),结果会大量漏反射或穿模。
- 必须用
screenPos(裁剪空间)→ndc→uv转换链,不能直接对屏幕坐标线性插值 - 每一步都要用当前
uv查深度图,再用ReconstructWorldPosition(基于invViewProj)还原世界点,再投影回屏幕得下一步uv - 步长要自适应:初始步长小(0.5 像素),越远越快(乘以 1.1~1.3),否则近处模糊、远处跳变
深度图精度和 Z 方向翻转是 SSR 最常见的崩溃点
很多 SSR 实现一跑就全黑或大片噪点,八成卡在深度图没对齐。OpenGL 和 Vulkan 默认 glDepthRange(0,1),但 DirectX 是 0~1 正向,而部分引擎(如 Unity URP)输出的 _CameraDepthTexture 在不同平台可能存的是 LinearEyeDepth 或 RawZ,且 Z 方向可能被翻转。
- 确认深度图是否已转为
Linear01Depth:查tex2D(_CameraDepthTexture, uv).r在远处是否接近 1,近处是否接近 0;否则要用LinearEyeDepth或Linear01Depth函数转换 - 检查
projMatrix[2][2]和[2][3]:若projMatrix[2][2] == 1.0,说明 Z 未翻转;若为 -1.0,则需在重建时用z = 1.0 - z - 避免用
float depth = tex2D(_CameraDepthTexture, uv).r;直接比较:应统一转成视角空间Z再比,例如float viewZ = LinearEyeDepth(depth, _ZBufferParams);
C++ 端只负责数据准备,着色器才是 SSR 主战场
C++ 层不写反射逻辑,只确保 GPU 能拿到正确输入:深度纹理、法线纹理、相机投影/视图逆矩阵、屏幕尺寸。Unity 或 Unreal 的 C++ 插件里,常见错误是传错 invViewProj 或忘了绑定 _CameraDepthTexture。
- Unity URP:用
ScriptableRenderContext.WaitOnAsyncGraphicsTasks()确保深度图已生成后再提交 SSR Pass - Unreal:在
FRHICommandListImmediate::CopyToResolveTarget后,用RHICopyToResolveTarget把SceneDepth拷贝到可读纹理 - 矩阵必须是 float4x4 且行主序:C++ 构造
FMatrix或glm::mat4后,用.GetTransposed().GetArray()或&mat[0][0]传给 shader,否则行列颠倒导致投影失败
SSR 必须配合降噪,否则无法落地
单次步进采样必然稀疏,尤其斜面或小物体边缘会出现明显锯齿和闪烁。靠增加步数只会拖慢 3 倍以上,实际项目都用后处理降噪——不是加高斯模糊,而是用历史帧+运动矢量+深度差做 smart blur。
立即学习“C++免费学习笔记(深入)”;
- 基础方案:用上一帧 SSR 结果 + 当前帧
motionVecwarp,再与当前帧混合(lerp权重由深度差和法线差控制) - 进阶方案:把 SSR 输出拆成 color + hitDistance + roughness,用
hitDistance做 bilateral filter 的 guide,比单纯 blur 更保边 - 别忽略 alpha 混合:SSR 结果通常作为叠加层,最终写入要
blend srcAlpha oneMinusSrcAlpha,否则半透物反射会过曝
SSR 的成败不在公式多炫,而在每一步的坐标系对齐、深度精度控制和降噪策略是否匹配硬件性能边界。最容易被忽略的是:步进终止条件里没加 abs(currentUV.x - startUV.x) > 1.0 || abs(currentUV.y - startUV.y) > 1.0 —— 这会导致反射线跑出屏幕还继续算,直接卡死 GPU。


















