深入URP之Shader篇4: Depth Only Pass
创始人
2024-03-17 06:35:28
0

Depth only pass

unlit shader中包含了一个Depth Only Pass,这个pass的代码在Packages\com.unity.render-pipelines.universal\Shaders\DepthOnlyPass.hlsl中。这是一个公共pass,几乎所有的URP shader都会包含这个pass。本篇说一说这个pass的作用以及实现细节。

作用

Depth only pass的作用是生成一张场景的深度图,一般是在渲染不透明物体之前,对所有包含该pass的材质对应的物体执行这个pass,当所有物体执行完毕后,就得到了深度图。这个pass执行的前提是URP判断需要深度图,比如URP assets中配置了:

在这里插入图片描述

或者camera开启了后处理。使用深度图,游戏可以从屏幕空间深度重建出世界空间深度,从而完成很多有意思的效果,比如水面渲染的岸边泡沫,粒子面片进入地面时柔和的过渡。另外很多屏幕空间的后处理效果都会用到深度图。

但是如果只是为了得到深度图,其实也不必执行depth only pass,只要在不透明物体渲染之后,从当前的depth buffer copy出深度就可以。所以这个pass也不是必然执行的。主要看copy depth的条件是否满足,如果满足则执行copy depth,而不是depth only pass。具体的逻辑,可以参考ForwardRenderer.cs代码,注意是URP的c#代码,不是shader:

            // Depth prepass is generated in the following cases:// - If game or offscreen camera requires it we check if we can copy the depth from the rendering opaques pass and use that instead.// - Scene or preview cameras always require a depth texture. We do a depth pre-pass to simplify it and it shouldn't matter much for editor.// - Render passes require itbool requiresDepthPrepass = requiresDepthTexture && !CanCopyDepth(ref renderingData.cameraData);requiresDepthPrepass |= isSceneViewCamera;requiresDepthPrepass |= isPreviewCamera;requiresDepthPrepass |= renderPassInputs.requiresDepthPrepass;requiresDepthPrepass |= renderPassInputs.requiresNormalsTexture;

重点是CanCopyDepth方法:

        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;}

从该函数可以看到,如果Camera开启了MSAA,则就不能使用Copy Depth了,为啥呢?注释有说,URP暂时不能在不损失精度的前提下从MSAA RT resolve出depth。在PC上,这几乎是唯一的限制,如果把MSAA关闭,在FrameDebugger中就会发现Depth Only Pass消失了,而在DrawOpaqueObjects之后多了一个CopyDepth的pass。

Z-Pre Pass和Early-Z测试

一般来说,这种在渲染场景之前,先把场景的深度渲染出来的过程叫做Z-Pre Pass(或者Depth Pre Pass等),其用途除了生成深度图之外,最重要的作用是给硬件Early-Z的执行提供一个优化好的Depth Buffer。Early-Z在Fragment Shader之前执行,如果片段不能通过Early-Z测试,则不会执行Fragment Shader,这可以大大降低Overdraw。但Early-Z可以起作用的条件在于我们绘制的顺序是从近到远(对于不透明物体),而正常渲染时并不能保证这个顺序。通过Z-Pre Pass,渲染一遍场景之后,depth buffer中保存的是离camera最近的这些片段的z值,之后再正常渲染场景,此时进行Early-Z测试就会发现只有和depth buffer中z值相等的片段才需要绘制,即只要进行一个Equal测试就可以排除掉所有潜在的overdraw片段,让Early-Z的作用发挥到最大。另外Early-Z本身也是有条件的,对于一个draw call,如果FS中执行了clip操作,或者修改了深度值,那么就不能进行Early-Z测试,GPU就会使用正常的Late-Z测试。如果我们要绘制大量使用Alpha Test材质的物体,比如一大片草地,这些草本身overdraw就很严重,还不能启用Early-Z,对性能影响很大。但如果使用Z-Pre Pass,就可以在此时对于草的draw call使用alpha test来更新depth buffer,这样那些透明片段就会被丢弃掉,留下的片段的深度值会被写入到depth buffer上(当前前提上通过深度测试),经过Z-Pre Pass之后,再普通pass中绘制草时就可以不使用Alpha Test了,这样Early-Z可以使用了,仍然是简单的进行深度的Equal测试就可以将所有应该被画出来的片段画出来。

看到这儿,相信你心中已经有了想法,既然很多效果需要深度图,而做一个Z-Pre Pass又可以优化Ealry-Z,一举两得嘛,还需要CopyDepth干嘛。想法是很好,可惜的是Unity的Depth only pass并不能作为一个优化Early-Z的Z-Pre Pass,因为Depth only pass的输出是一个depth Render texture,而不是depth buffer,这样后面普通的pass绘制时,执行深度测试没法用这个RT去比较。深度测试使用的当前Render Target的depth attachment,且必须是同一个Render Target。后面的普通Pass的Render Target是_cameraColorTexture,它有自己的depth attachment。总之,可惜了,白白执行了那么多draw call,只得到了深度图,而不能优化Early-Z。

这个事情的原因可参考Unity论坛的官方解释:https://forum.unity.com/threads/need-clarification-on-urps-use-of-the-depth-prepass.1004577/
总之,未来是可以解决的,暂时还不行。

Shader代码分析

由于只是输出depth texture,其VS代码也很简单,就是计算clip space坐标即可。但是URP的这个代码稍微复杂一些:

Varyings DepthOnlyVertex(Attributes input)
{Varyings output = (Varyings)0;UNITY_SETUP_INSTANCE_ID(input);UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(output);output.uv = TRANSFORM_TEX(input.texcoord, _BaseMap);output.positionCS = TransformObjectToHClip(input.position.xyz);return output;
}half4 DepthOnlyFragment(Varyings input) : SV_TARGET
{UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input);Alpha(SampleAlbedoAlpha(input.uv, TEXTURE2D_ARGS(_BaseMap, sampler_BaseMap)).a, _BaseColor, _Cutoff);return 0;
}

VS中会计算uv坐标,为啥呢?在FS中可以看到,会采样贴图中的alpha。这儿有两个函数:

half Alpha(half albedoAlpha, half4 color, half cutoff)
{
#if !defined(_SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A) && !defined(_GLOSSINESS_FROM_BASE_ALPHA)half alpha = albedoAlpha * color.a;
#elsehalf alpha = color.a;
#endif#if defined(_ALPHATEST_ON)clip(alpha - cutoff);
#endifreturn alpha;
}half4 SampleAlbedoAlpha(float2 uv, TEXTURE2D_PARAM(albedoAlphaMap, sampler_albedoAlphaMap))
{return SAMPLE_TEXTURE2D(albedoAlphaMap, sampler_albedoAlphaMap, uv);
}

即,URP的depth only pass会执行Alpha Test,且Alpha值除了来源于颜色本身,也来源于贴图的Alpha,当然需要开启相应的关键字。URP的做法当然是对的,因为对于Alpha Test材质的物体,其深度必然也受Alpha Test影响。其实渲染阴影贴图也一样,对于Alpha Test材质都需要处理。

本篇小结

URP的这个Depth Only Pass Shader本身比较简单,没啥可说,但是这个Depth Only Pass确实要说道说道,必须要了解到当前版本下面这个Depth Only Pass不能起到Z-Pre Pass的作用去优化Early-Z,因此如果可能直接使用Copy Depth可以节省大量的draw call,当然前提是不能使用MSAA,在低端移动设备上是一个可以考虑的优化选项,关闭MSAA,然后使用一个基于后处理的AA代替,即节省了MSAA的开销,又节省了Depth Only Pass的draw call,一石二鸟!

相关内容

热门资讯

汽车油箱结构是什么(汽车油箱结... 本篇文章极速百科给大家谈谈汽车油箱结构是什么,以及汽车油箱结构原理图解对应的知识点,希望对各位有所帮...
美国2年期国债收益率上涨15个... 原标题:美国2年期国债收益率上涨15个基点 美国2年期国债收益率上涨15个基...
嵌入式 ADC使用手册完整版 ... 嵌入式 ADC使用手册完整版 (188977万字)💜&#...
重大消息战皇大厅开挂是真的吗... 您好:战皇大厅这款游戏可以开挂,确实是有挂的,需要了解加客服微信【8435338】很多玩家在这款游戏...
盘点十款牵手跑胡子为什么一直... 您好:牵手跑胡子这款游戏可以开挂,确实是有挂的,需要了解加客服微信【8435338】很多玩家在这款游...
senator香烟多少一盒(s... 今天给各位分享senator香烟多少一盒的知识,其中也会对sevebstars香烟进行解释,如果能碰...
终于懂了新荣耀斗牛真的有挂吗... 您好:新荣耀斗牛这款游戏可以开挂,确实是有挂的,需要了解加客服微信8435338】很多玩家在这款游戏...
盘点十款明星麻将到底有没有挂... 您好:明星麻将这款游戏可以开挂,确实是有挂的,需要了解加客服微信【5848499】很多玩家在这款游戏...
总结文章“新道游棋牌有透视挂吗... 您好:新道游棋牌这款游戏可以开挂,确实是有挂的,需要了解加客服微信【7682267】很多玩家在这款游...
终于懂了手机麻将到底有没有挂... 您好:手机麻将这款游戏可以开挂,确实是有挂的,需要了解加客服微信【8435338】很多玩家在这款游戏...