世界坐标恢复
在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 是根据平台进行了特殊调整的,目前还没有仔细研究。
- 假设投影变换会让坐标由(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。
这样就恢复出了摄像机坐标。
- 第二点,我们从摄像机坐标恢复到世界坐标的时候使用的矩阵是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
}
}
}