为什么要从深度图重建世界坐标
一个很大的应用情景是在后处理的阶段,或是计算一些屏幕空间的效果(如SSR、SSAO等),只能获取到一张深度贴图,而不是每一个几何体的顶点数据,很多的计算中却又需要用到世界坐标或者是视空间的坐标,这时我们就需要通过深度图来重建世界空间的坐标。
重建世界坐标的流程
- 首先要获取屏幕空间的UV,这里记为positionSS,范围是(0, 1)(0, 1)。
- 使用UV采样深度贴图,获取到当前的深度值。
- 使用UV和深度值,得到标准化设备坐标,这里记为positionNDC。
- 使用裁剪空间到视空间的变换矩阵乘以positionNDC,除以W分量,得到视空间坐标,这里记为positionVS。
- 使用视空间到世界空间的变换矩阵乘以positionVS,得到世界空间坐标,这里记为positionWS。
这里使用DepthToPositionShader.shader
,假装是屏幕后处理的shader,来演示一下重建世界坐标的流程,这样比直接写屏幕后处理的shader能够更好的去了解Unity的空间变换的方式。
这个shader有以下几个需要注意的点:
- 为了使用
_CameraDepthTexture
这张深度贴图,需要在srp的设置中开启Depth Texture
这个选项。这样子在渲染的时候会在DepthPrePass
用shader中的Depth Only
这个pass去先渲染出深度贴图。我们就能够在渲染物体的时候直接拿到包含当前物体的深度贴图了。 - 顶点着色器和片元着色器中的
SV_POSITION
并不完全相同。对于顶点着色器来说,SV_POSITION
就是之前所说的\((\frac X {\tan {\frac {fovy} 2} \cdot \frac x y }, -\frac Y {\tan {\frac {fovy} 2}}, \frac {Zn} {f - n} + \frac {fn} {f - n}, -Z)\);但是在片元着色器中,SV_POSITION
的XY分量会乘上屏幕的宽高,Z分量则是已经除以W之后的深度值。屏幕的宽高信息保存在_ScreenParams
这个内置的变量中,它的前两位是屏幕的宽高像素数,后两位是宽高的像素数的倒数加一。 - 要针对DX11和OpenGL不同的透视变换矩阵来调整UV的Y分量的数值,也就是要注意
UNITY_UV_STARTS_AT_TOP
这个宏的使用。出现获得的坐标跟随着摄像机的移动发生奇怪的倾斜的时候,往往都是忘记对Y分量的平台差异进行处理。 - 最后得到的视空间和世界空间的坐标值,要记得除以这个坐标值的W分量,相当于是做了一次归一化,才能得到正确的坐标。
DepthToPositionShader.shader
Shader "zznewclear13/DepthToPositionShader"
{
Properties
{
[Toggle(REQUIRE_POSITION_VS)] _Require_Position_VS("Require Position VS", float) = 0
}
HLSLINCLUDE
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/core.hlsl"
#pragma multi_compile _ REQUIRE_POSITION_VS
sampler2D _CameraDepthTexture;
struct Attributes
{
float4 positionOS : POSITION;
float2 texcoord : TEXCOORD0;
};
struct Varyings
{
float4 positionCS : SV_POSITION;
float2 texcoord : TEXCOORD0;
};
Varyings Vert(Attributes input)
{
Varyings output = (Varyings)0;
VertexPositionInputs vertexPositionInputs = GetVertexPositionInputs(input.positionOS.xyz);
output.positionCS = vertexPositionInputs.positionCS;
output.texcoord = input.texcoord;
return output;
}
float4 Frag(Varyings input) : SV_TARGET
{
float2 positionSS = input.positionCS.xy * (_ScreenParams.zw - 1);
float depth = tex2D(_CameraDepthTexture, positionSS).r;
float3 positionNDC = float3(positionSS * 2 - 1, depth);
#if UNITY_UV_STARTS_AT_TOP
positionNDC.y = -positionNDC.y;
#endif
#if REQUIRE_POSITION_VS
float4 positionVS = mul(UNITY_MATRIX_I_P, float4(positionNDC, 1));
positionVS /= positionVS.w;
float4 positionWS = mul(UNITY_MATRIX_I_V, positionVS);
#else
float4 positionWS = mul(UNITY_MATRIX_I_VP, float4(positionNDC, 1));
positionWS /= positionWS.w;
#endif
return positionWS;
}
float4 DepthFrag(Varyings input) : SV_TARGET
{
return 0;
}
ENDHLSL
SubShader
{
Tags{ "RenderType" = "Opaque" }
LOD 100
Pass
{
Tags{"LightMode"="UniversalForward"}
ZWrite On
ZTest LEqual
HLSLPROGRAM
#pragma vertex Vert
#pragma fragment Frag
ENDHLSL
}
Pass
{
Tags{"LightMode" = "DepthOnly"}
ZWrite On
ZTest LEqual
HLSLPROGRAM
#pragma vertex Vert
#pragma fragment DepthFrag
ENDHLSL
}
}
}