找回密码
 立即注册
查看: 325|回复: 0

深度笔记(二)Unity中的深度

[复制链接]
发表于 2022-3-7 17:58 | 显示全部楼层 |阅读模式
2001年微软推出DirectX8.0,并首创 SM1.0标准时,NVIDIA和 ATI的 GPU事实上并不支持 SM1.0所宣称的像素(片元)编程,直到 2003年 DirectX9.0b携 SM2.0横空出世,着色器作为其标准配置被广泛接受,NVIDIA和 ATI的产品才具备了可编程的顶点着色器和 片元着色器。
Unity 4.X

在Unity 4.X和更早的版本中,由于市面上的设备尚没有普遍提供对深度纹理的支持(SM2.0+),Unity使用了一个叫做Hidden/Camera-DepthTexture的函数,通过替换着色器的方式,在渲染时根据其渲染类型使用对应的深度渲染器,手动生成整体的深度图
#if defined(UNITY_MIGHT_NOT_HAVE_DEPTH_TEXTURE)
    #define UNITY_TRANSFER_DEPTH(oo) oo = o.pos.zw
    #if SHADER_API_FLASH
    #define UNITY_OUTPUT_DEPTH(i) return EncodeFloatRGBA(i.x/i.y)
    #else
    #define UNITY_OUTPUT_DEPTH(i) return i.x/i.y
    #endif
#else
    #define UNITY_TRANSFER_DEPTH(oo)
    #define UNITY_OUTPUT_DEPTH(i) return 0
这个叫做UNITY_MIGHT_NOT_HAVE_DEPTH_TEXTURE的宏是一个是否支持深度纹理的声明。
在确定支持DepthFormat格式的设备,这个函数不会做任何操作,只是一个空实现;在不支持深度纹理的设备上,将深度纹理编码为RGBA格式,在采样时再解码信息。这么做是为了充分利用RGBA的四通道优势以提高精度(8*4 =32位)。
// Encoding/decoding [0..1) floats into 8 bit/channel RGBA. Note that 1.0 will not be encoded properly.
inline float4 EncodeFloatRGBA( float v )
{
    float4 kEncodeMul = float4(1.0, 255.0, 65025.0, 160581375.0);
    float kEncodeBit = 1.0/255.0;
    float4 enc = kEncodeMul * v;
    enc = frac (enc);
    enc -= enc.yzww * kEncodeBit;
    return enc;
}
inline float DecodeFloatRGBA( float4 enc )
{
    float4 kDecodeDot = float4(1.0, 1/255.0, 1/65025.0, 1/160581375.0);
    return dot( enc, kDecodeDot );
}[^SM]: Shader Model,即渲染器模型,旧称优化渲染引擎模式。
Unity 5.X

在Unity 5.X后,除了 Depth Normal Map以外,深度纹理均不再通过替换着色器手动生成,而是转为使用Shadow Caster Pass进行渲染,顶点和片元渲染阶段的宏也就全部改为了空实现。
// Legacy; used to do something on platforms that had to emulate depth textures manually. Now all platforms have native depth textures.
#define UNITY_TRANSFER_DEPTH(oo)
// Legacy; used to do something on platforms that had to emulate depth textures manually. Now all platforms have native depth textures.
#define UNITY_OUTPUT_DEPTH(i) return 0
Shadow Caster本身实现也相当简单,顶点渲染阶段就是MVP变换(前提是不考虑Shadow Bias),而片元渲染阶段也直接做了空实现。
#define SHADOW_CASTER_FRAGMENT(i) return 0;为什么会这么做?正如注释所言,现在所有平台均支持原生深度图格式,因此手动生成并编码再解码的这一套流程也就没有再使用的必要了,直接申请一个Depth Format格式的渲染纹理即可。
Shadow Caster 其实同时也负责对Shadow Map中光方向的深度进行渲染,这二者的主要区别在于VP矩阵的不同。阴影的深度基于光空间进行计算,而屏幕空间深度则是相对于摄像机而言的。
随之而来的则是另一个问题:既然如此简单,为什么不直接使用帧缓存中的Z值,而要额外使用一个独立的缓存进行复制和写入?
很简单,因为现代GPU使用了Early-Z技术对Overdraw进行优化,而Early-Z技术正是建立在Depth Pre-Pass的基础上。
Early-Z就是指将原本在像素/片元阶段之后进行的深度测试额外提前到像素/片元阶段之前进行一次,从而避免那些原本会被深度测试剔除的顶点的光栅化计算,实现优化性能的目的。原本的深度测试流程依然会被保留,以确保经过像素/片元阶段后的物体的最终遮挡关系是正确的。
不过,现在对深度纹理的处理方式与最初形成标准渲染管线概念时已经有了很大的差别。下面我们将对各渲染管线中深度纹理的渲染方式进行简单的介绍。
[^Shadow Bias]: 即阴影偏移,为解决shadow map产生的shadow acne(自遮挡阴影瑕疵)问题而使用的一种算法。
各渲染管线下的深度

Built-in Render Pipeline(BRP)

这个Pass的目的仅仅是为了把模型的深度信息写入深度缓冲中,从而剔除模型中被自身遮挡的片元。因此,Pass的第一行开启了深度写入。在第二行,我们使用了一个新的渲染命令——ColorMask。
当ColorMask设为0时,意味着该Pass不写入任何颜色通道,即不会输出任何颜色。这正是我们需要的——该Pass只需写入深度缓存即可。
——《Unity Shader入门精要》冯乐乐
在固定渲染管线中,Zprepass(深度预测试通道)的纹理生成方式与冯女士使用的方式类似,即使用一个专有的pass将深度写入渲染纹理,并在完成不透明物体渲染时再渲染一次。两次深度渲染各自独立,互不干扰。


虽然这一pass并不输出任何颜色,也不做任何计算,但CPU依然需要向GPU传输相关的顶点信息。这实际上并不符合Zprepass的规范,是一种在不改动原有方式基础上的取巧用法,而代价则是渲染命令次数的增加。
Universal Render Pipeline(URP)

· Why isn't the depth prepass actually used for depth testing? Will this change in the future?
Long story but generally it comes down to our tech not quite being there for handling all the possible combinations of GPUs that we can expect URP to run on and being able to cleanly fallback when not. The answer is yes, we will support it being a true depth prepass in the future, but it is not too high up on the priority list ATM. In saying that I look forward to the speedup gains a lot of projects will get once this work is done.
—— Andre Mcgrail,Unity Technologies, Nov 13, 2020
URP推出后,原有的depth prepass被整合到Shadow map中(具体而言,与光照模型的LUT信息各占据两个相邻通道)。由于Unity依旧不具备对真正的prepass的支持技术,为了满足提取深度信息的需求,提供了一个深度复制通道,即Copy Depth。
Copy Depth,顾名思义,就是从前面已渲染的pass中复制深度信息,从而在不增加Draw call渲染命令消耗的情况下提取屏幕深度。由于Unity的prepass本身并不是符合规范的prepass,Copy Depth实际上复制的是Opaques Render Buffer中的深度,并因此将渲染次序置于在Opaques之后。





可以看到URP中Zprepass后面均做了Clear清除处理,实际上并没有参与深度测试。

Copy Depth在BRP时代就已出现,但由于这一技术的实现需要硬件设备的支持,出于兼容性上的考量,业界依然使用prepass方案作为深度获取的主流方案。新一代管线URP提供了一个名为CanCopyDepth的方法来自动检测环境是否支持Copy Depth,若结果为否,则进行回退并改为使用原有的Zprepass方案,从而实现了对Draw Call的部分优化。
bool CanCopyDepth(ref CameraData cameraData)
{
    bool msaaEnabledForCamera = cameraData.cameraTargetDescriptor.msaaSamples > 1;
    bool supportsTextureCopy = SystemInfo.copyTextureSupport != CopyTextureSupport.None;
    bool supportsDepthTarget = RenderingUtils.SupportsRenderTextureFormat(RenderTextureFormat.Depth);
    bool supportsDepthCopy = !msaaEnabledForCamera && (supportsDepthTarget || supportsTextureCopy);

    // TODO:  We don't have support to highp Texture2DMS currently and this breaks depth precision.
    // currently disabling it until shader changes kick in.
    //bool msaaDepthResolve = msaaEnabledForCamera && SystemInfo.supportsMultisampledTextures != 0;
    bool msaaDepthResolve = false;
    return supportsDepthCopy || msaaDepthResolve;
}
不过,直到URP 12(随Unity 2021.2版本推出)之前,URP中的Depth Prepass实际上都不支持直接用于深度测试,均会在prepass后重新进行渲染。
URP 12 (Unity2021.2+)

在URP 12中,这个“优先级并不高”的问题终于得到了解决。Depth Prepass的渲染结果现在可以被Render Opaques直接提取,用于深度测试。URP12提供了一套全新的方案,称为Depth priming(暂译深度启动)。
深度启动,顾名思义,就是从深度开始的渲染方式。通常情况下,shader的各个pass独立决定是否开启深度写入和深度测试,只有运行至该pass时才能得知是否使用;而Depth Priming则使用了一个独立的通道DepthOnly pass处理深度写入和测试,将其他pass的写入关闭,比较方法设为equal,从而使任何pass执行的shader计算均是最终显示在屏幕上的结果所需的计算,几乎完全消除了Overdraw现象,而代价只是增加DepthOnly pass所带来的一倍drawcall,计算量极小。
unity的测试结果显示,使用Depth priming后,Mac提升16%GPU耗时,windows vulkan提升3%CPU和7.5%GPU。对CPU的提升在于不需要对不透明排序,对GPU的提升在于消除不透明物体的overdraw,对半透渲染没有做修改。
当然,Unity同样提供了自动识别功能,用于判断当前环境是否支持Depth priming,并提供了一个类型参数供开发者自行决定是否强制启用该技术。当Depth Priming不启用时,Unity将回退并使用传统Shadow map方案,且该深度不会被深度测试读取。



可以看到,启用了Depth priming的Frame中,Depth Prepass之后不再进行清除操作,Copy Depth也不再依赖于Opaque的深度渲染结果,转而直接提取Depth Prepass中的深度,渲染次序也重新回到Opaque之前。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-9-22 17:26 , Processed in 0.067254 second(s), 23 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表