[Unity Shader]深度值
1. 什么是深度
深度指视图空间中,顶点/片元坐标的z分量,该分量代表到相机(视图空间原点)的距离z。距离z是线性距离(Unity使用的是OpenGL,相机的观察方向是负z轴,有时会使用 -viewZ 表示到相机的正距离)。View.z经过投影矩阵映射到裁剪空间为Clip.z,范围是[-w,w]。裁剪空间的深度值经过透视除法映射到NDC空间中(Clip.z / Clip.w = NDC.z),范围是[0,1](DirectX NDC.z范围是[0,1],OpenGL NDC.z范围是[-1,1])。NDC.z会由GPU写入深度缓冲,并储存在深度纹理中,通过采样深度纹理获取的深度值就是NDC空间的深度值。深度缓冲记录“当前通过深度测试的最接近相机的像素深度”,用于决定哪些片元在前、哪些在后,从而实现隐藏面消除。对于透视投影,深度缓冲值是非线性的将ViewZ映射到[0,1]的范围(靠近平面分辨率高,远处被压缩);对于正交投影,映射通常是线性的。
2.深度纹理
深度值被映射到[0,1]的范围内,顶点/片元在近裁剪面上时,深度缓冲值为0;在远裁剪面上时,深度缓冲值1。将顶点/片元的深度值映射为颜色值,储存在一张纹理的像素中,就是深度纹理图。如图,越靠近0(near)的顶点/片元,深度缓冲值越接近0,映射到深度纹理中的像素颜色越接近黑色;越靠近1(far)的顶点/片元,深度缓冲值越接近1,映射到深度纹理中的像素颜色越接近白色。
3. Shader中的深度值
3.1 View.z
ViewZ是顶点/片元在视图空间坐标的z分量,表示顶点/片元到相机的真实距离,范围是near ~ far的线性深度值。它是正向的真实距离(单位 = 世界单位),与物体大小无关,与相机位置严格对应,用于屏幕空间效果时最理想,但通常无法直接在片元阶段获得(因为贴花/SSAO是后处理)
view.z = - positionVS.z3.2 SV_POSITION.z
在vertex中,通过矩阵可以获取裁剪空间的坐标,变量positionHCS被语义SV_POSITION标识,该语义会告知GPU该变量是屏幕空间/光栅化阶段使用的坐标,GPU会固定执行光栅化、透视除法、屏幕映射保存为光栅化后的片元位置,因此在frag中获取的深度值positionHCS.z是已经执行了透视除法,等同于NDC.z
struct a2v { float4 positionOS : POSITION; }; struct v2f { float4 positionHCS: SV_POSITION; }; v2f vert (a2v v) { v2f o = (v2f)0; o.positionHCS = TransformObjectToHClip(v.positionOS); //裁剪空间坐标 return o; } half4 frag(v2f i): SV_Target { return float4(i.positionHCS.z, o.positionHCS.z, o.positionHCS.z, 1.0); }3.3 rawDepth/deviceDepth
rawDepth是深度纹理中储存的原始深度值,该深度值是由GPU写入到深度缓冲中的NDC空间的深度值,是由裁剪空间深度值执行透视除法后获取的,范围0 ~ 1(near = 0,far = 1)的非线性深度值,靠近0时精度高,靠近1时精度低。可以通过采样深度纹理获取,也可以通过对裁剪空间深度值执行透视除法获取(Clip.z / Clip.w)
//直接采样深度纹理获取 half4 frag(v2f i): SV_Target { float4 rawDepth = SAMPLE_TEXTURE2D_X(_CameraDepthTexture, sampler_CameraDepthTexture, ScreenUV.xy).r; return float4(rawDepth, rawDepth, rawDepth, 1.0); } //通过Clip.z / Clip.w 或取NDC.z half4 frag(v2f i): SV_Target { float4 ndcPos; ndcPos.z = i.screenPos.z / i.screenPos.w; return float4(ndcPos.z, ndcPos.z, ndcPos.z, 1.0); }3.4 Linear01Depth
Linear01Depth是将rawDepth转为0 ~ 1(near = 0,far = 1)的线性深度值,通常用于深度渐变效果、淡入淡出、雾效等后处理效果
float linear01Depth = Linear01Depth(rawDepth, _ZBufferParams);3.5 LinearEyeDepth
LinearEyeDepth是通过rawDepth反推真实的视图空间深度(单位 = 世界单位),范围是near ~ far的线性深度,等价于viewZ(同单位),是后处理重建世界坐标/贴花/SSAO的标准深度,可以用于几何对比。
LinearEyeDepth是通过rawDepth反推真实的视图空间深度(单位 = 世界单位),范围是near ~ far的线性深度,等价于viewZ(同单位),是后处理重建世界坐标/贴花/SSAO的标准深度,可以用于几何对比。4. 在Shader中采样深度纹理
在Shader中,需要使用TEXTURE2D_X宏声明深度纹理,然后使用宏SAMPLE_TEXTURE2D_X采样深度纹理。直接采样深度纹理获取的是NDC空间中的非线性深度,范围在[0,1]之间,LinearEyeDepth()函数可以转换为线性的真实深度值;Linear01Depth()函数可以转换为线性深度值。
4.1 场景材质Shader
Shader "PostProcess/FullSceneDepth" { SubShader { Tags { "RenderType" = "Opaque" "Queue"="Overlay" "RenderPipeline"="UniversalPipeline" } ZWrite Off ZTest Always Cull Off Blend Off HLSLINCLUDE #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" #include "Packages/com.unity.render-pipelines.core/Runtime/Utilities/Blit.hlsl" ENDHLSL Pass { Name "FullSceneDepthPass1" HLSLPROGRAM #pragma vertex vert #pragma fragment frag SAMPLER(sampler_BlitTexture); //声明深度纹理 CBUFFER_START(UnityPerMaterial) TEXTURE2D_X(_CameraDepthTexture); SAMPLER(sampler_CameraDepthTexture); CBUFFER_END struct a2v { uint vertexID:SV_VertexID; float4 positionOS : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float4 positionSV: SV_POSITION; float4 screenPos: TEXCOORD1; float2 uv : TEXCOORD0; }; v2f vert(a2v v) { v2f o = (v2f) 0; o.positionSV = TransformObjectToHClip(v.positionOS); o.screenPos = ComputeScreenPos(o.positionSV); return o; } float4 frag(v2f i): SV_Target { //执行透视除法 float4 ScreenUV = i.screenPos/ i.screenPos.w; //采样深度纹理,获取深度值 float4 rawDepth = SAMPLE_TEXTURE2D_X(_CameraDepthTexture, sampler_CameraDepthTexture, ScreenUV.xy).r; //线性化深度值 float linear01Depth = Linear01Depth(rawDepth, _ZBufferParams); return float4(Depth, Depth, Depth, 1.0); } ENDHLSL }4.2 后处理Shader
Shader "PostProcess/FullSceneDepth" { SubShader { Tags { "RenderType" = "Opaque" "Queue"="Overlay" "RenderPipeline"="UniversalPipeline" } ZWrite Off ZTest Always Cull Off Blend Off HLSLINCLUDE #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" #include "Packages/com.unity.render-pipelines.core/Runtime/Utilities/Blit.hlsl" ENDHLSL Pass { Name "FullSceneDepthPass" HLSLPROGRAM #pragma vertex vert #pragma fragment frag struct a2v { uint vertexID : SV_VertexID; }; struct v2f { float4 positionCS: SV_POSITION; float2 screenPos : TEXCOORD0; }; v2f vert(a2v v) { v2f o; float2 ScreenPos = float2((v.vertexID << 1) & 2, v.vertexID & 2); o.positionCS = float4(ScreenPos * 2.0 - 1.0, 0, 1); o.screenPos = ComputeScreenPos(o.positionCS); return o; } CBUFFER_START(UnityPerMaterial) SAMPLER(sampler_BlitTexture); TEXTURE2D_X(_CameraDepthTexture); SAMPLER(sampler_CameraDepthTexture); CBUFFER_END half4 frag(v2f i) : SV_Target { //深度采样 float RawDepth = SAMPLE_TEXTURE2D_X(_CameraDepthTexture, sampler_CameraDepthTexture, i.screenPos).r; half3 SceneColor = SAMPLE_TEXTURE2D(_BlitTexture, sampler_BlitTexture, float2(i.screenPos.x, 1 - i.screenPos.y)).rgb; float linear01Depth = Linear01Depth(RawDepth, _ZBufferParams); return half4(linearDepth.xxx, 1); } ENDHLSL } } }