深度重建世界坐标¶
在后处理 Shader 还有一些屏幕空间效果中,常需要使用深度还原世界坐标。
逆矩阵法¶
推导¶
根据 uv 和 depth 可以还原出 positionNDC.xyz
。由
positionCS = mul(MatrixVP, positionWS);
positionNDC = positionCS / positionCS.w; // positionNDC.w 一定为 1
可得
positionWS = mul(MatrixInvVP, positionCS.w * positionNDC);
w
分量的公式为
positionWS.w = dot(MatrixInvVP[3], positionCS.w * positionNDC);
因为 positionWS.w == 1
所以
positionCS.w = 1 / dot(MatrixInvVP[3], positionNDC);
带回去得到
positionWS = mul(MatrixInvVP, positionNDC) / dot(MatrixInvVP[3], positionNDC);
化简得到
positionWS = mul(MatrixInvVP, positionNDC);
positionWS /= positionWS.w;
URP 实现¶
在 Packages/com.unity.render-pipelines.universal/ShaderLibrary/Common.hlsl
里有相关的实现。
注意此处 URP 的
positionNDC.xy
是屏幕uv
,范围是[0, 1]
,与前面推导时用的positionNDC
的定义不同。
float4 ComputeClipSpacePosition(float2 positionNDC, float deviceDepth)
{
float4 positionCS = float4(positionNDC * 2.0 - 1.0, deviceDepth, 1.0);
#if UNITY_UV_STARTS_AT_TOP
// Our world space, view space, screen space and NDC space are Y-up.
// Our clip space is flipped upside-down due to poor legacy Unity design.
// The flip is baked into the projection matrix, so we only have to flip
// manually when going from CS to NDC and back.
positionCS.y = -positionCS.y;
#endif
return positionCS;
}
float3 ComputeWorldSpacePosition(float2 positionNDC, float deviceDepth, float4x4 invViewProjMatrix)
{
// 这个 positionCS 才是前面推导时的 positionNDC
float4 positionCS = ComputeClipSpacePosition(positionNDC, deviceDepth);
float4 hpositionWS = mul(invViewProjMatrix, positionCS);
return hpositionWS.xyz / hpositionWS.w;
}
使用方法
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/DeclareDepthTexture.hlsl"
// ...
float deviceDepth = SampleSceneDepth(input.texcoord.xy);
#if !UNITY_REVERSED_Z
deviceDepth = deviceDepth * 2.0 - 1.0;
#endif
float3 positionWS = ComputeWorldSpacePosition(input.texcoord.xy, deviceDepth, unity_MatrixInvVP);
相似三角形法¶
这个方法来自 SIGGRAPH 2011 中的 Secrets of CryENGINE 3 Graphics Technology。
推导¶
假设要重建点 C 的世界坐标。由
\[ \triangle \text{ABC} \sim \triangle \text{ADE} \]
得到
\[ \dfrac{\left | \text{AB} \right |}{\left | \text{AD} \right |} = \dfrac{\left | \text{AC} \right |}{\left | \text{AE} \right |} \]
如果深度是在 Linear01 空间的话,\(\left | \text{AD} \right | =1\),\(\left | \text{AB} \right |\) 就是点 C 的 Linear01Depth,所以
\[ \overrightarrow{\text{AC}} = \left | \text{AB} \right | \overrightarrow{\text{AE}} \]
等号两边同时变换到世界空间
\[ \overrightarrow{\text{AC}}_\text{ws} = \left | \text{AB} \right | \overrightarrow{\text{AE}}_\text{ws} \]
再加上点 A(相机)的世界坐标就能算出点 C 的世界坐标。
URP 实现¶
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/DeclareDepthTexture.hlsl"
先在 Vertex Shader 里计算 \(\overrightarrow{\text{AE}}_\text{ws}\)
// 在 Blit Shader 中 texcoord 需要使用 API 计算出来,参考 Packages/com.unity.render-pipelines.core/Runtime/Utilities/Blit.hlsl
float3 positionWS = ComputeWorldSpacePosition(texcoord, UNITY_RAW_FAR_CLIP_VALUE, unity_MatrixInvVP);
output.viewRayWS = positionWS - GetCameraPositionWS();
在 Fragment Shader 里,还原世界坐标
float depth = Linear01Depth(SampleSceneDepth(input.texcoord.xy), _ZBufferParams);
float3 positionWS = depth * input.viewRayWS + GetCameraPositionWS();
这个方法的矩阵运算在 Vertex Shader 里进行,计算量小一点,但是 URP 的 Blit Vertex Shader 改起来相对麻烦一点。第一种方法只需要改 Fragment Shader 就行了,更好写。
参考资料¶
- Unity URP中根据深度重建世界坐标_computeworldspaceposition-CSDN博客
- Reconstruct the world space positions of pixels from the depth texture | Universal RP | 14.0.11 (unity3d.com)
相关文章