whoimi

A geek blog

View on GitHub

世界坐标恢复

在unity当中所有的屏幕空间技术都依赖于世界坐标恢复。例如:SSR屏幕空间反射,SSS屏幕空间阴影,ContactShadow细节阴影等。依赖于屏幕空间的Ray trace。

这里主要是讨论如何从深度图中恢复世界坐标并进行使用。之后有时间会制作一些相关特效。

世界坐标恢复

我们这里分析的是PostProcessing V2@2.14版本当中SSR。主要的代码在ScreenSpaceReflections.hlsl当中。

具体步骤如下:

第一步,需要[0,1]区间的纹理坐标。

这部分可以在顶点着色器当中设置。

VaryingsDefault VertDefault(AttributesDefault v)
{
    VaryingsDefault o;
    o.vertex = float4(v.vertex.xy, 0.0, 1.0);
    o.texcoord = (v.vertex.xy + 1.0) * 0.5;
	
    // 处理正反y轴
    #if UNITY_UV_STARTS_AT_TOP
    o.texcoord = o.texcoord * float2(1.0, -1.0) + float2(0.0, 1.0);
    #endif

    return o;
}

第二步,需要根据屏幕uv坐标深度值组成出标准设备坐标NDC,通过NDC和投影矩阵的逆矩阵就可以计算出摄像机坐标,下面是函数:

float3 GetViewSpacePosition(float2 uv)
{
    // 采样深度纹理,注意这里不是HDRP的采样方式
    float depth = _CameraDepthTexture.SampleLevel(sampler_CameraDepthTexture, UnityStereoTransformScreenSpaceTex(uv), 0).r;
    // 使用[-1 ,1]区间的uv坐标和,深度纹理当中的值,可以得到标准设备坐标NDC。
    // 这里需要注意需要使用_InverseProjectionMatrix 投影矩阵的逆矩阵。
    float4 result = mul(_InverseProjectionMatrix, float4(2.0 * uv - 1.0, depth, 1.0));
    return result.xyz / result.w;
}

按照上面的步骤我们可以得到屏幕空间中,渲染过深度的物体的世界坐标。

但是,非HDRP的渲染中我们没有内置的_InverseProjectionMatrix矩阵,这个矩阵是后处理代码中设置到shader里的。

而在HDRP当中,代码如下:

real4 frag (v2f i) : SV_Target
{
    real2 uv = i.uv * _ScreenParams.xy;
    // 这个是HDRP读取深度图的方式
    real depthsrc = LOAD_TEXTURE2D(_CameraDepthTexture, uv).r;
	// 构造标准设备坐标
    real4 cullPos = real4((i.uv.xy * 2 - 1), depthsrc, 1.0);
    // y坐标是坐下角转换到右上角
    cullPos.y =- cullPos.y;

    real4 viewPos = mul(UNITY_MATRIX_I_P,cullPos);
    viewPos = viewPos / viewPos.w;
    real4 worldPos = mul(unity_MatrixInvV,viewPos);

}

我们可以看到这里使用了UNITY_MATRIX_I_P矩阵,这个矩阵是UnityHDRP中新增加的。

深度图的采样方式需要注意和原始方式不同。

HDRP部分的语法用的是hlsl而非cg。

关于恢复原理的说明

_InverseProjectionMatrix矩阵的来源:

GL.GetGPUProjectionMatrix(context.camera.projectionMatrix, false).inverse;

UNITY_MATRIX_P 等价于 GL.GetGPUProjectionMatrix,而UNITY_MATRIX_P 是根据平台进行了特殊调整的,目前还没有仔细研究。

  1. 假设投影变换会让坐标由(x,y,z,w)变成(x’,y’,z’,w’)。

我们知道投影矩阵会让w向量变成当前点所在的z向量( w’ = z )。而x’,y’,z’需要在[-w’, w’]之间(也就是-z到z)来进行剪裁。

也就是说投影矩阵会让w=1 变成w’ = z(当前点深度坐标).

所以逆矩阵会让w’=1 变成 w = 1 / z. x’[-1 1] => x[x/w](正向:x[x]=>x[-w , w])

在xyz乘逆矩阵之后,我们得到的范围缩小了z所以需要乘上z,也就是除w。

这样就恢复出了摄像机坐标。

  1. 第二点,我们从摄像机坐标恢复到世界坐标的时候使用的矩阵是unity_MatrixInvV,而不是UNITY_MATRIX_I_V(_InvViewMatrix)。

HDRP从深度图到世界坐标源代码

Shader "Unlit/RevertWorldPosition"
{
    SubShader
    {
        Tags { "RenderType"="ForwardOnly" }
        LOD 100

        Pass
        {
            HLSLPROGRAM
            #pragma target 4.5
            #pragma vertex vert
            #pragma fragment frag
            #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl"
            #include "Packages/com.unity.render-pipelines.high-definition/Runtime/ShaderLibrary/ShaderVariables.hlsl"

            struct appdata
            {
                real4 vertex : POSITION;
                real2 uv : TEXCOORD0;
            };

            struct v2f
            {
                real2 uv : TEXCOORD0;
                real4 screenposition : TEXCOORD1;
                real4 vertex : SV_POSITION;
            };

            real _DistancePost;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = mul(UNITY_MATRIX_VP,mul(UNITY_MATRIX_M,v.vertex));
                o.uv = v.uv;
                return o;
            }

            real4 frag (v2f i) : SV_Target
            {
                real2 uv = i.uv * _ScreenParams.xy;
                real depthsrc = LOAD_TEXTURE2D(_CameraDepthTexture, uv).r;
                real depth = Linear01Depth(LOAD_TEXTURE2D(_CameraDepthTexture, uv), _ZBufferParams);

                real4 cullPos = real4((i.uv.xy * 2 - 1), depthsrc, 1.0);
                cullPos.y =- cullPos.y;

                real4 viewPos = mul(UNITY_MATRIX_I_P,cullPos);
                viewPos = viewPos / viewPos.w;
                real4 worldPos = mul(unity_MatrixInvV,viewPos);
                real4 worldPos1 = mul(_InvViewMatrix,viewPos);
                real4 worldPos3 = mul(UNITY_MATRIX_M,viewPos);
                real4 worldPos2 = mul(UNITY_MATRIX_M,mul(UNITY_MATRIX_P,mul(UNITY_MATRIX_V,viewPos)));

                return float4(worldPos.xyz,1);
            }
            ENDHLSL
        }
    }
}