深入URP之Shader篇4: Depth Only Pass

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 it
            bool 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;
#else
    half alpha = color.a;
#endif

#if defined(_ALPHATEST_ON)
    clip(alpha - cutoff);
#endif

    return 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,一石二鸟!

  • 3
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
URP(Universal Render Pipeline)是Unity引擎中的一种渲染管线,而Compute ShaderURP中的一种功能,用于在GPU上进行并行计算。Compute Shader可以用来执行各种计算任务,例如图像处理、物理模拟、粒子系统等。 URP Compute Shader提供了一种在GPU上进行高性能计算的方式,它可以利用GPU的并行计算能力来加速复杂的计算任务。与传统的图形渲染不同,Compute Shader不需要与图形渲染管线交互,它可以独立于渲染过程进行计算。 使用URP Compute Shader可以带来以下优势: 1. 并行计算:Compute Shader可以同时在多个线程上执行计算任务,充分利用GPU的并行计算能力,提高计算效率。 2. 高性能:由于在GPU上执行,Compute Shader可以利用硬件加速,提供更高的计算性能。 3. 灵活性:Compute Shader可以执行各种类型的计算任务,不仅限于图形渲染,可以用于各种领域的并行计算需求。 使用URP Compute Shader的基本步骤如下: 1. 创建Compute Shader:在Unity中创建一个Compute Shader文件,并编写需要执行的计算任务代码。 2. 创建Compute Buffer:创建一个Compute Buffer对象,用于在CPU和GPU之间传递数据。 3. 设置Compute Shader参数:将需要的参数传递给Compute Shader,例如输入数据、输出数据等。 4. 调度Compute Shader:使用Graphics类的Dispatch方法来调度Compute Shader的执行。 5. 获取计算结果:在计算完成后,可以从Compute Buffer中获取计算结果。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值