深入URP之Shader篇10: 深度值专题(1)

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

之前研究Unlit shader的时候就遇到一些Z值相关的问题一笔带过了比如ComputeFogFactor中的UNITY_Z_0_FAR_FROM_CLIPSPACE。今天就把URP Shader中出现的Z相关的问题做一个专题一起研究下。

深度缓冲的方向和UNITY_REVERSED_Z

先说这个关于z的宏因为这是平台级别的比较底层其他深度相关的宏也会用它。在不同的平台比如DX11/12,Metal,OpenGL等深度缓冲的方向是不一样的分为两种:

DX11/DX12, Metal, Vulkan 等新一代图形API以及一些较新的主机平台如NS使用翻转的方向

  • 深度缓冲中1.0表示的是近裁剪面的深度值0.0表示的是远裁剪面的深度值。
  • clip space的Z值范围为[near,0],即从near plane的深度值递减到far plane的0.0

其他平台如DX9, OpenGL,OpenGL ES 2.0/3.0使用传统的方向

  • 深度缓冲中0.0表示的是近裁剪面的深度值1.0表示的是远裁剪面的深度值。
  • clip space的Z值范围根据不同平台有两类
    • D3D-Like的平台clip space的Z值范围为[0,far],即从near plane的0.0递增到far plane的深度值
    • OpenGL-Like的平台clip space的Z值范围为[-near, far], near plane的深度值为负值递增到far plane。

使用翻转深度值方向的优势

如果深度缓冲是浮点缓冲此时使用翻转的深度值方向可以显著的提升深度缓冲的精度。这可以减轻z值的冲突以及提高阴影质量特别是当使用很小的near plane和很大的far plane时。

UNITY_REVERSED_Z的定义

当平台使用翻转深度值方向时Unity就会定义UNITY_REVERSED_Z为1:

image.png

在这些平台上由于深度缓冲是翻转的那么_CameraDepthTexture的范围也是1(near)到0(far)。同时clip space的z值范围是[near,0]。在shader中就要使用UNITY_REVERSED_Z区分出这种情况做相应的处理。例如当我们要从_CameraDepthTexture中采样深度值时需要这么写:

float z = tex2D(_CameraDepthTexture, uv); 
#if defined(UNITY_REVERSED_Z) 
    z = 1.0f - z; 
#endif

而如果我们要手动操作clip space Z值时也要多加注意了比如下面这个情况。

UNITY_Z_0_FAR_FROM_CLIPSPACE

这个宏是URP封装好了方便我们手动处理clip space的z值的传入的参数是clip space的z,返回的也是clip space的z但是根据不同的情况做了处理保证返回的z值范围是0到far。我搜索了一下这个宏目前只有ComputeFogFactor用了:

real ComputeFogFactor(float z)
{
    float clipZ_01 = UNITY_Z_0_FAR_FROM_CLIPSPACE(z);
    
}

看一下定义:

#if UNITY_REVERSED_Z
    #if SHADER_API_OPENGL || SHADER_API_GLES || SHADER_API_GLES3
        //GL with reversed z => z clip range is [near, -far] -> should remap in theory but dont do it in practice to save some perf (range is close enough)
        #define UNITY_Z_0_FAR_FROM_CLIPSPACE(coord) max(-(coord), 0)
    #else
        //D3d with reversed Z => z clip range is [near, 0] -> remapping to [0, far]
        //max is required to protect ourselves from near plane not being correct/meaningfull in case of oblique matrices.
        #define UNITY_Z_0_FAR_FROM_CLIPSPACE(coord) max(((1.0-(coord)/_ProjectionParams.y)*_ProjectionParams.z),0)
    #endif
#elif UNITY_UV_STARTS_AT_TOP
    //D3d without reversed z => z clip range is [0, far] -> nothing to do
    #define UNITY_Z_0_FAR_FROM_CLIPSPACE(coord) (coord)
#else
    //Opengl => z clip range is [-near, far] -> should remap in theory but dont do it in practice to save some perf (range is close enough)
    #define UNITY_Z_0_FAR_FROM_CLIPSPACE(coord) (coord)
#endif

这里面还有个很重要的unity内置的uniform_ProjectionParams:

   // x = 1 or -1 (-1 if projection is flipped)
    // y = near plane
    // z = far plane
    // w = 1/far plane
    float4 _ProjectionParams;

这儿有四种情况:

  • 平台定义了翻转Z但API是OpenGL。GL原本的clip z范围是[-near,far]平台翻转了就是[near,-far]。这儿直接取了个负其实是变成[-near,far]了然后使用max(0)来直接截断-near到0。这个其实是性能的折中按道理应该映射到[0,far]的。
  • 平台定义了翻转Z且API是D3D这是主要需要处理的情况核心计算就是这个:
(1.0-(coord)/_ProjectionParams.y)*_ProjectionParams.z

这个函数是将[near,0]映射到[0,far]。我换一种形式可能更好懂:

Z1 = far - Z*far/near

当z为near时返回0;z为0时返回far; z取中间的某个值如 near/k 时返回 far/k。

  • 如果平台没定义翻转Z但是定义了UNITY_UV_STARTS_AT_TOP,这个宏一般用来判断API是D3D。此时D3D没有使用翻转Z那么它的clip z范围就是[0,far]因此什么都不做。
  • 如果平台没定义翻转Z也不是D3D那么就是OpenGL由于OpenGL的clip z范围是[-near, far]理论上应该重新映射但实际上为了节约性能消耗什么都不做。这儿也没用max。
    关于这儿的平台和API的区别我只能猜测一下比如NS这种主机是定义了翻转Z的但是它又能用OpenGL是不是这时候就会出现第一种情况?

本篇小结

关于Z的话题果然一篇讲不完这才只是研究了一下Reversed Z。下篇继续。

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6