关于正交相机的屏幕空间反射

Posted by 恶毒的狗 on February 28, 2020

关于正交相机的屏幕空间反射

这两天一个老外要求哥的 LWRP/URP SSR Water 能够支持 正交相机

A good asset if you use perspective camera. Unfortunately it doesn’t work correctly with orthographic one which I use in my project. Will change my rating once orthographic is also supported.

下图是我添加支持后 透视相机正交相机 的反射效果对比:

透视相机:

正交相机:

主要修改点

关于深度的计算

透视相机正交相机 的深度计算,最主要的差别是 非线性线性 的差别。

以从 _CameraDepthTexture 获取场景深度为例,透视相机 的写法如下:

1
2
float rawDepth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, sampler_CameraDepthTexture, screenMatchUVZ.xy);
float sceneDepth = LinearEyeDepth(rawDepth, _ZBufferParams);

其中,LinearEyeDepth 是Unity的内置函数,代码如下:

1
2
3
4
5
6
7
8
// Z buffer to linear depth. 
// Does NOT correctly handle oblique view frustums.
// Does NOT work with orthographic projection.
// zBufferParam = { (f-n)/n, 1, (f-n)/n*f, 1/f }
float LinearEyeDepth(float depth, float4 zBufferParam)
{
    return 1.0 / (zBufferParam.z * depth + zBufferParam.w);
}

如果是 正交相机,直接对远近平面 线性插值 就可以搞定了,代码如下:

1
2
3
4
5
6
7
8
9
10
inline float GetOrthoDepthFromZBuffer (float rawDepth) 
{
    #if defined(UNITY_REVERSED_Z)
        #if UNITY_REVERSED_Z == 1
            rawDepth = 1.0f - rawDepth;
        #endif
    #endif

    return lerp(_ProjectionParams.y, _ProjectionParams.z, rawDepth);
}
1
2
float rawDepth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, sampler_CameraDepthTexture, screenMatchUVZ.xy);
float sceneDepth = GetOrthoDepthFromZBuffer(rawDepth);

此外,光线步进 时的 深度比较 计算,情况也很类似:

透视相机的写法:

1
2
3
4
5
6
// 计算起点和终点
clipPos = TransformWorldToHClip(worldPos);
float k = 1.0 / clipPos.w;

screenPos.xy = ComputeScreenPos(clipPos).xy * k;
screenPos.z = k;
1
2
// 光线步进
half rayDepth = 1.0 / screenMatchUVZ.z;

正交相机的写法:

1
2
3
4
5
// 计算起点和终点
clipPos = TransformWorldToHClip(worldPos);

screenPos.xy = ComputeScreenPos(clipPos).xy;
screenPos.z = GetOrthoEyeDepth(clipPos.z);
1
2
// 光线步进
half rayDepth = screenMatchUVZ.z;

关于视线向量的计算

屏幕空间反射 依赖 视线向量 相对于 法线反射向量

为了 正交相机 下反射结果不随相机观察角度而变形,我们还需要区分 视线向量 的计算方式。

透视相机的写法:

1
lightingData.worldViewDir = normalize(_WorldSpaceCameraPos.xyz - lightingData.worldPos);

正交相机的写法:

1
lightingData.worldViewDir = normalize(UNITY_MATRIX_V[2].xyz);

参考

在编码过程中,我参考了 Lux URP/LWRP Essentials 的部分写法,再次墙裂推荐这个插件。

好了,拜拜!