什么是 Prepass

Prepass,全称Depth Prepass,也常被称为 Early-Z Pass

它在引擎真正开始画画(Base Pass)前,先跑的一遍极其简化的渲染流程——不执行复杂的Pixel Shader,只跑Vertex Shader,唯一目的是渲染场景深度

为什么需要他

如果没有Prepass,会存在Overdraw这一大问题

众所周知,渲染就是画画,画画如果是从后往前画,很有可能存在很多不需要画的部分——被覆盖的部分,这部分就是Overdraw

如果我们可以保证是从前往后画的,跳过被遮挡部分。这样是可以完全避免OverDraw,但存在一个问题,如何保证呢?不难想到对相机内物体的z值排序,但使用的是CPU排序,时间复杂度一般是NlogN,如果场景顶点少,还阔以,但一旦多了,性能非常堪忧

因此,Prepass由此诞生,使用GPU提前绘制场景的深度图,在渲染时比较绘制顶点的深度与深度图的深度,如果比深度图大,不绘制,以此保证没有多余浪费的draw

如何实现

先渲染场景深度图,在后续Base Pass绘制时,设置深度测试,大于等于的剔除掉

UE PrePass

计算Prepass、Velocity,每帧PrePass开始前clear Depth Stencil

auto RenderPrepassAndVelocity = [&](auto& InViews, auto& InNaniteBasePassVisibility, auto& NaniteRasterResults, auto& PrimaryNaniteViews, FSceneTextures& LocalSceneTextures)
{
    FRDGTextureRef FirstStageDepthBuffer = nullptr;
    {
        // Both compute approaches run earlier, so skip clearing stencil here, just load existing.
        const ERenderTargetLoadAction StencilLoadAction = DepthPass.IsComputeStencilDitherEnabled()
            ? ERenderTargetLoadAction::ELoad
            : ERenderTargetLoadAction::EClear;

        const ERenderTargetLoadAction DepthLoadAction = ERenderTargetLoadAction::EClear;
        AddClearDepthStencilPass(GraphBuilder, LocalSceneTextures.Depth.Target, DepthLoadAction, StencilLoadAction);

        // Draw the scene pre-pass / early z pass, populating the scene depth buffer and HiZ
        if (bNeedsPrePass)
        {
            RenderPrePass(GraphBuilder, InViews, LocalSceneTextures.Depth.Target, InstanceCullingManager, &FirstStageDepthBuffer);
        }
        else
        {
            // We didn't do the prepass, but we still want the HMD mask if there is one
            RenderPrePassHMD(GraphBuilder, InViews, LocalSceneTextures.Depth.Target);
        }

        // special pass for DDM_AllOpaqueNoVelocity, which uses the velocity pass to finish the early depth pass write
        if (bShouldRenderVelocities && Scene->EarlyZPassMode == DDM_AllOpaqueNoVelocity && RendererOutput == ERendererOutput::FinalSceneColor)
        {
            // Render the velocities of movable objects.  Don't bind the velocity render target for custom render passes (it's not used downstream), to avoid needing to clear it again.
            RenderVelocities(GraphBuilder, InViews, LocalSceneTextures, EVelocityPass::Opaque, bForceVelocityOutput, /*bBindRenderTarget=*/ InViews[0].CustomRenderPass == nullptr);
        }
    }

    {
        Scene->WaitForCacheNaniteMaterialBinsTask();

        if (bNaniteEnabled && InViews.Num() > 0)
        {
            RenderNanite(GraphBuilder, InViews, LocalSceneTextures, bIsEarlyDepthComplete, InNaniteBasePassVisibility, NaniteRasterResults, PrimaryNaniteViews, FirstStageDepthBuffer);
        }
    }

    if (FirstStageDepthBuffer)
    {
        LocalSceneTextures.PartialDepth = FirstStageDepthBuffer;
        AddResolveSceneDepthPass(GraphBuilder, InViews, LocalSceneTextures.PartialDepth);
    }
    else
    {
        // Setup default partial depth to be scene depth so that it also works on transparent emitter when partial depth has not been generated.
        LocalSceneTextures.PartialDepth = LocalSceneTextures.Depth;
    }
    LocalSceneTextures.SetupMode = ESceneTextureSetupMode::SceneDepth;
    LocalSceneTextures.UniformBuffer = CreateSceneTextureUniformBuffer(GraphBuilder, &LocalSceneTextures, FeatureLevel, LocalSceneTextures.SetupMode);

    AddResolveSceneDepthPass(GraphBuilder, InViews, LocalSceneTextures.Depth);
};

UEPrePass不仅仅是绘制Depth,总共做了以下几件事:

  • 绘制Depth图

Pre Depth

哪些物体会走Pre Depth

  • Opaque:只包含Position的VS,没有PS
  • Masked:包含Position、uv等的VS,有PS

Render Target

  • Color RT:无
  • Depth RT:SceneDepthTexture
    • Stencil:有
  • Velocity RT:无

CPU端

EDepthDrawingMode

  • 什么是EDepthDrawingMode

    EarlyZPassMode是一个enum,决定 PrePass 画哪些物体、是否写 Velocity、Velocity Pass 何时跑、深度读写权限怎么设。存在的目的就是在 PrePass 开销、BasePass Overdraw 节省、 Velocity 精度 之间做权衡

    • 定义
    enum EDepthDrawingMode
    {
    // 不做 Early-Z 
    DDM_None            = 0,
    // 只有 Opaque 
    DDM_NonMaskedOnly   = 1,
    // Opaque and masked materials, but no objects with bUseAsOccluder disabled
    DDM_AllOccluders    = 2,
    // 所有 Opaque + Masked 
    DDM_AllOpaque       = 3,
    // Masked materials only
    DDM_MaskedOnly = 4,
    // 所有 Opaque + Masked,但不写 Velocity
    DDM_AllOpaqueNoVelocity = 5,
    };
    
  • 为什么需要它

    为了优雅处理上述注释中提到的几种情况——几种不同的场景,且EDepthDrawingMode不仅仅在Pre Pass,在Velocity Pass也需要

场景特征 更合适模式
复杂室内、高 Overdraw DDM_AllOpaqueNoVelocity 或 DDM_AllOpaque
Masked 植被极多、三角形巨大 DDM_NonMaskedOnly(看Base Pass开销,若开销高于Prepass PS,就还是DDM_AllOpaqueNoVelocity )
简单场景、Draw 很少 DDM_None
重度 TSR/TAA,动态物体多 DDM_AllOpaqueNoVelocity
  • 解析各Mode
Name 作用 优点 缺点 适用场景
DDM_None 无 PrePass → BasePass 自己写深度 + GBuffer 少一遍几何绘制
实现简单
BasePass Overdraw 最严重
复杂场景 GPU 压力大
简单场景、移动端部分路径、不需要 Early-Z
DDM_NonMaskedOnly PrePass:只画 Opaque
Masked:留到 BasePass 再 clip
BasePass:ZTest = Equal(Opaque 覆盖区)
PrePass 更快(Masked 多、三角形多
避免 Masked 在 PrePass 里大量 clip
Masked 多时光栅化开销后移到 BasePass
植被场景不划算
Masked 少、Opaque 为主的场
DDM_AllOpaque PrePass:Opaque + Masked,同时写 Velocity
BasePass:ZTest = Equal
Velocity Pass:通常只读深度(DepthRead)
深度覆盖最全
PrePass 写 Velocity
PrePass 更重
带宽和 PS 开销更大
需要 PrePass 就输出 Velocity
DDM_AllOpaqueNoVelocity PrePass:Opaque + Masked,只写 Depth,不写 Velocity
Velocity Pass(BasePass 前):动态物体写 Velocity + 修正深度
BasePass:ZTest = Equal
PrePass 极快(不绑 Velocity RT)
BasePass Overdraw 大幅下降
管线更复杂,多一次 Velocity Pass PC 延迟渲染
  • 整体流程

逐View

  • 做了什么
    • 设定颜色写入、深度模板测试
    • 绑定常量缓冲区
    • 设置viewport,并Draw
for (int32 ViewIndex = 0; ViewIndex < InViews.Num(); ++ViewIndex)
{
    FViewInfo& View = InViews[ViewIndex];

    FMeshPassProcessorRenderState DrawRenderState;
    SetupDepthPassState(DrawRenderState);

    const bool bShouldRenderView = View.ShouldRenderView() && (bSecondStageDepthPass ? View.bUsesSecondStageDepthPass : true);
    if (bShouldRenderView)
    {
        FDepthPassParameters* PassParameters = GetDepthPassParameters(GraphBuilder, View, SceneDepthTexture);

        auto* Pass = View.ParallelMeshDrawCommandPasses[DepthMeshPass]
        Pass->BuildRenderingCommands(GraphBuilder, Scene->GPUScene, PassParameters->InstanceCullingDrawParams);

        if(bParallelDepthPass)
        {
            GraphBuilder.AddDispatchPass(
    bSecondStageDepthPass ? RDG_EVENT_NAME("SecondStageDepthPassParallel") : RDG_EVENT_NAME("DepthPassParallel"),
    PassParameters,
    ERDGPassFlags::Raster,
    [Pass, PassParameters, DepthMeshPass](FRDGDispatchPassBuilder& DispatchPassBuilder)
{
    Pass->Dispatch(DispatchPassBuilder, &PassParameters->InstanceCullingDrawParams);
});
        }
        else
        {
            GraphBuilder.AddPass(
    bSecondStageDepthPass ? RDG_EVENT_NAME("SecondStageDepthPass") : RDG_EVENT_NAME("DepthPass"),
    PassParameters,
    ERDGPassFlags::Raster,
    [&View, Pass, PassParameters, DepthMeshPass](FRDGAsyncTask, FRHICommandList& RHICmdList)
{
    SetStereoViewport(RHICmdList, View, 1.0f);
    Pass->Draw(RHICmdList, &PassParameters->InstanceCullingDrawParams);
});
        }
    }
}
  • 关闭颜色写入,开启深度测试 + 深度写入

    void SetupDepthPassState(FMeshPassProcessorRenderState& DrawRenderState)
    {
    // Disable color writes, enable depth tests and writes.
    DrawRenderState.SetBlendState(TStaticBlendState<CW_NONE>::GetRHI());
    DrawRenderState.SetDepthStencilState(TStaticDepthStencilState<true, CF_DepthNearOrEqual>::GetRHI());
    }
    
    • 深度测试: LessEqual
  • 获取Pass Constant Buffer
    BEGIN_SHADER_PARAMETER_STRUCT(FDepthPassParameters, )
    SHADER_PARAMETER_STRUCT_INCLUDE(FViewShaderParameters, View)
    SHADER_PARAMETER_STRUCT_INCLUDE(FInstanceCullingDrawParams, InstanceCullingDrawParams)
    RENDER_TARGET_BINDING_SLOTS()
    END_SHADER_PARAMETER_STRUCT()
    

    可以看到Pre Pass的Pass Parameter包含:

    • View
    • InstanceCullingDrawParams
    • Render Target
    FDepthPassParameters* GetDepthPassParameters(FRDGBuilder& GraphBuilder, const FViewInfo& View, FRDGTextureRef DepthTexture)
    {
    auto* PassParameters = GraphBuilder.AllocParameters<FDepthPassParameters>();
    PassParameters->View = View.GetShaderParameters();
    PassParameters->RenderTargets.DepthStencil = FDepthStencilBinding(DepthTexture, ERenderTargetLoadAction::ELoad, ERenderTargetLoadAction::ELoad, FExclusiveDepthStencil::DepthWrite_StencilWrite);
    return PassParameters;
    }
    

    绑定View、RenderTargets.DepthStencil,无Color RT

  • 获取Mesh Draw Command

    Pass->BuildRenderingCommands(GraphBuilder, Scene->GPUScene, PassParameters->InstanceCullingDrawParams);
    

    UE5统一在 InitViews()初始化时,对所有PassMeshProcessor AddMeshBatch Process,并存储,在render 对应pass时,再使用BuildRenderingCommands拿到可使用的Mesh Draw Command

    • AddMeshBatch:满足一定条件的才会执行后续PrePass
  • 设置Pass:设置视口,并绘制
    GraphBuilder.AddDispatchPass(
      bSecondStageDepthPass ? RDG_EVENT_NAME("SecondStageDepthPassParallel") : RDG_EVENT_NAME("DepthPassParallel"),
      PassParameters,
      ERDGPassFlags::Raster,
      [Pass, PassParameters, DepthMeshPass](FRDGDispatchPassBuilder& DispatchPassBuilder)
    {
      Pass->Dispatch(DispatchPassBuilder, &PassParameters->InstanceCullingDrawParams);
    });
    

GPU Draw设定

  • 绑定渲染状态
    • 获取DepthPassShader

    • 取 Vertex Factory

      顶点工厂决定:顶点从哪来、怎么蒙皮、怎么实例化

    DepthPass需要判断material材质是否是opaque,来判断是否需要用到pixel shader

    if (!GetDepthPassShaders<bPositionOnly>(
        MaterialResource,
        VertexFactory->GetType(),
        FeatureLevel,
        MaterialResource.MaterialUsesPixelDepthOffset_RenderThread(),
        DepthPassShaders.VertexShader,
        DepthPassShaders.PixelShader,
        ShaderPipeline))
    {
        return false;
    }
    

    TMeshProcessorShaders:把 VS+PS 打包,后面交给 BuildMeshDrawCommands

    • 获取shader的vs、ps字段的bytecode
      FMaterialShaderTypes ShaderTypes;
      ShaderTypes.AddShaderType<TDepthOnlyVS<bPositionOnly>>();
      
      ShaderTypes.AddShaderType<FDepthOnlyPS>();
      

      FMaterialShaderTypes:向材质系统声明要哪些Shader类型,Depth Pass 至少要有 VS

    • 获取PipelineType

      if (bPositionOnly)
      {
        ShaderTypes.PipelineType = &DepthPosOnlyNoPixelPipeline;
      }
      else
      {
        const bool bVFTypeSupportsNullPixelShader = VertexFactoryType->SupportsNullPixelShader();
        const bool bNeedsPixelShader = !Material.WritesEveryPixel(false, bVFTypeSupportsNullPixelShader) || bMaterialUsesPixelDepthOffset || Material.IsTranslucencyWritingCustomDepth();
        if (bNeedsPixelShader)
        {
            ShaderTypes.AddShaderType<FDepthOnlyPS>();
            ShaderTypes.PipelineType = &DepthPipeline;
        }
        else
        {
            ShaderTypes.PipelineType = &DepthNoPixelPipeline;
        }
      }
      
      • bPositionOnly 为 true:只有 VS、没有 PS 的PipelineType
      • bPositionOnly 为 false
      • 看 VF 是否支持 空像素着色器
      • 是否PS并不是每pixel写入(如masked)、使用PDO、是否半透明自定义Depth
      • 要 PS → 加入 FDepthOnlyPS,管线类型 DepthPipeline(VS + PS)
      • Opaque 且 不写 PDO、无特殊 CustomDepth → 不 AddShaderType,用 DepthNoPixelPipeline(仅 VS,最快)
    • 根据 Vertex Factory、Shader Type从Shader变体查找Shader、ShaderPipeline(根据PipelineType查找)
      FMaterialShaders Shaders;
      if (!Material.TryGetShaders(ShaderTypes, VertexFactoryType, Shaders))
      {
        return false;
      }
      
      Shaders.TryGetPipeline(ShaderPipeline);
      Shaders.TryGetVertexShader(VertexShader);
      Shaders.TryGetPixelShader(PixelShader);
      

      ShaderPipeline得到的Shader指针是个泛型,后续通过TryGetVertexShader转换成指定类型(泛型加载Shader拿到shader反射信息——绑定的SRV、UAV、CBV具体位置与顺序,由于不止一种,因此UE为shader设计了泛型,通过TryGetVertexShader绑定资源地址)

    • 设置Stencil

    if (!bDitheredLODFadingOutMaskPass && !bShadowProjection)
    {
        TOptional<uint32> DitheringFlag = GetOptionalDitheringFlag(ViewIfDynamicMeshCommand, MeshBatch, StaticMeshId);
        if (DitheringFlag.IsSet())
    {
        DrawRenderState.SetDepthStencilState(GetDitheredLODTransitionDepthStencilState());
        DrawRenderState.SetStencilRef(DitheringFlag.GetValue());
    }
    }
    

    非DitherMask 、 非Shadow、DitheringFlag有值(若Mesh处于LOD淡入淡出,则有值),则设置Depth Stencil测试

    • 绑定Uniform Buffer
    FMeshMaterialShaderElementData ShaderElementData;
    ShaderElementData.InitializeMeshMaterialData(ViewIfDynamicMeshCommand, PrimitiveSceneProxy, MeshBatch, StaticMeshId, true);
    
    void FMeshMaterialShaderElementData::InitializeMeshMaterialData()
    {
        FadeUniformBuffer = GDistanceCullFadedInUniformBuffer.GetUniformBufferRHI();
        DitherUniformBuffer = GDitherFadedInUniformBuffer.GetUniformBufferRHI();
    }
    
    void FMeshMaterialShaderElementData::InitializeMeshMaterialData(const FSceneView* SceneView, const FPrimitiveSceneProxy* RESTRICT PrimitiveSceneProxy, int32 sStaticMeshId, bool bDitheredLODTransition, bool bAllowStencilDither)
    {
        InitializeMeshMaterialData();
    
        if (SceneView)
        {
            checkSlow(SceneView->bIsViewInfo);
            const FViewInfo* ViewInfo = (FViewInfo*)SceneView;
    
            if (StaticMeshId >= 0 && bDitheredLODTransition && !(bAllowStencilDither && ViewInfo->bAllowStencilDither))
            {
                if (ViewInfo->StaticMeshFadeOutDitheredLODMap[StaticMeshId])
                {
                    DitherUniformBuffer = ViewInfo->DitherFadeOutUniformBuffer;
                }
                else if (ViewInfo->StaticMeshFadeInDitheredLODMap[StaticMeshId])
                {
                    DitherUniformBuffer = ViewInfo->DitherFadeInUniformBuffer;
                }
            }
    
            if (PrimitiveSceneProxy)
            {
                int32 const PrimitiveIndex = PrimitiveSceneProxy->GetPrimitiveSceneInfo()->GetIndex();
    
                if (ViewInfo->PrimitiveFadeUniformBufferMap.IsValidIndex(PrimitiveIndex) && ViewInfo->PrimitiveFadeUniformBufferMap[PrimitiveIndex])
                {
                    FadeUniformBuffer = ViewInfo->PrimitiveFadeUniformBuffers[PrimitiveIndex];
                }
            }
        }
    }
    

    绑定Fade、Dither Buffer,用于处理LOD 抖动渐变、距离渐变

    • Depth排序
    const bool bIsMasked = IsMaskedBlendMode(MaterialResource);
    const FMeshDrawCommandSortKey SortKey = CalculateDepthPassMeshStaticSortKey(bIsMasked, DepthPassShaders.VertexShader.GetShader(), DepthPassShaders.PixelShader.GetShader());
    
    FMeshDrawCommandSortKey CalculateDepthPassMeshStaticSortKey(bool bIsMasked, const FMeshMaterialShader* VertexShader, const FMeshMaterialShader* PixelShader)
    {
        FMeshDrawCommandSortKey SortKey;
        if (GEarlyZSortMasked)
        {
            SortKey.BasePass.VertexShaderHash = (VertexShader ? VertexShader->GetSortKey() : 0) & 0xFFFF;
            SortKey.BasePass.PixelShaderHash = PixelShader ? PixelShader->GetSortKey() : 0;
            SortKey.BasePass.Masked = bIsMasked ? 1 : 0;
        }
        else
        {
            SortKey.Generic.VertexShaderHash = VertexShader ? VertexShader->GetSortKey() : 0;
            SortKey.Generic.PixelShaderHash = PixelShader ? PixelShader->GetSortKey() : 0;
        }
    
        return SortKey;
    }
    
    • 在排序时,需要考虑opaque、masked两种情况
      • 当场景里有许多masked物体,且他们互相还有大量遮挡,此时拆分opaque、masked,先排opaque再排masked可以节省不少性能。因为opaque不需要fragment shader,而masked需要,这样可以尽量让masked少画
      • 当场景基本没有masked物体,此时就无需拆分了,根据shader分组即可
    • 构建MeshDrawCommand(包含许多渲染状态参数)

    BuildMeshDrawCommands(
        MeshBatch,
        BatchElementMask,
        PrimitiveSceneProxy,
        MaterialRenderProxy,
        MaterialResource,
        DrawRenderState,
        DepthPassShaders,
        MeshFillMode,
        MeshCullMode,
        SortKey,
        bPositionOnly ? EMeshPassFeatures::PositionOnly : EMeshPassFeatures::Default,
        ShaderElementData);
    
    • bPositionOnly ? EMeshPassFeatures::PositionOnly : EMeshPassFeatures::Default
      • bPositionOnly 为true:struct VSInput只包含Position
      • bPositionOnly 为false:struct VSInput不仅包含Position,也包含其他数据
  • 绑定组装shader算法

    IMPLEMENT_MATERIAL_SHADER_TYPE(template<>,TDepthOnlyVS<true>,TEXT("/Engine/Private/PositionOnlyDepthVertexShader.usf"),TEXT("Main"),SF_Vertex);
    IMPLEMENT_MATERIAL_SHADER_TYPE(template<>,TDepthOnlyVS<false>,TEXT("/Engine/Private/DepthOnlyVertexShader.usf"),TEXT("Main"),SF_Vertex);
    
    IMPLEMENT_MATERIAL_SHADER_TYPE(,FDepthOnlyPS,TEXT("/Engine/Private/DepthOnlyPixelShader.usf"),TEXT("Main"),SF_Pixel);
    
    IMPLEMENT_SHADERPIPELINE_TYPE_VS(DepthNoPixelPipeline, TDepthOnlyVS<false>, true);
    IMPLEMENT_SHADERPIPELINE_TYPE_VS(DepthPosOnlyNoPixelPipeline, TDepthOnlyVS<true>, true);
    IMPLEMENT_SHADERPIPELINE_TYPE_VSPS(DepthPipeline, TDepthOnlyVS<false>, FDepthOnlyPS, true);
    
    • 绑定两个VS Shader
    template <bool bUsePositionOnlyStream>
    class TDepthOnlyVS : public FMeshMaterialShader
    {
        DECLARE_SHADER_TYPE(TDepthOnlyVS,MeshMaterial);
    protected:
    
        TDepthOnlyVS() {}
    
        TDepthOnlyVS(const FMeshMaterialShaderType::CompiledShaderInitializerType& Initializer) :
            FMeshMaterialShader(Initializer)
        {}
    
    public:
    
        static bool ShouldCompilePermutation(const FMeshMaterialShaderPermutationParameters& Parameters)
        {
            // Only the local vertex factory supports the position-only stream
            if (bUsePositionOnlyStream)
            {
                return Parameters.VertexFactoryType->SupportsPositionOnly() && Parameters.MaterialParameters.bIsSpecialEngineMaterial;
            }
    
            if (IsTranslucentBlendMode(Parameters.MaterialParameters))
            {
                return Parameters.MaterialParameters.bIsTranslucencyWritingCustomDepth;
            }
    
            // Only compile for the default material and masked materials
            return (
                Parameters.MaterialParameters.bIsSpecialEngineMaterial ||
                !Parameters.MaterialParameters.bWritesEveryPixel ||
                Parameters.MaterialParameters.bMaterialMayModifyMeshPosition)
                && !Parameters.VertexFactoryType->SupportsNaniteRendering();
        }
    
    • ShouldCompilePermutation:判断非opaque的其他材质的shader是否编译成二进制
      • bUsePositionOnlyStream为true:若支持PositionOnly也是默认材质(引擎默认的灰白相间的棋盘格),则编译
      • bUsePositionOnlyStream为false
      • 判断是不是半透明材质,是且启用了半透明深度写入,则编译
      • 最后兜底,看是不是默认材质或Masked材质或支持WPO的材质,且不支持Nanite
    • Opaque VS Shader
      你可能会疑惑这里绑定的两个VS Shader都不是用于opaque material的,那opaque 是在哪处理的,如何处理的呢?

    真相是,对于Opaque Material,UE直接使用默认材质替换

    bool FDepthPassMeshProcessor::ShouldRender(const FMaterial& Material, bool bMaterialModifiesMeshPosition, bool bSupportPositionOnlyStream, bool bVFTypeSupportsNullPixelShader, bool& bUseDefaultMaterial, bool& bPositionOnly)
    {
        bool bShouldRender = false;
        bUseDefaultMaterial = false;
        bPositionOnly = false;
    
        if (FeatureLevel == ERHIFeatureLevel::ES3_1 && EarlyZPassMode == DDM_None)
        {
            // Do not cache MDC and do not pre-cache PSOs for a depth pass if it's never going to be used on mobile platforms
            return false;
        }
    
        if (IsOpaqueBlendMode(Material)
            && EarlyZPassMode != DDM_MaskedOnly
            && bSupportPositionOnlyStream
            && !bMaterialModifiesMeshPosition
            && Material.WritesEveryPixel(false, bVFTypeSupportsNullPixelShader))
        {
            bShouldRender = true;
            bUseDefaultMaterial = true;
            bPositionOnly = true;
        }
        else
        {
            // still possible to use default material
            const bool bMaterialMasked = !Material.WritesEveryPixel(false, bVFTypeSupportsNullPixelShader) || Material.IsTranslucencyWritingCustomDepth();
            if ((!bMaterialMasked && EarlyZPassMode != DDM_MaskedOnly) || (bMaterialMasked && EarlyZPassMode != DDM_NonMaskedOnly))
            {
                bShouldRender = true;
    
                if (!bMaterialMasked && !bMaterialModifiesMeshPosition)
                {
                    bUseDefaultMaterial = true;
                    bPositionOnly = false;
                }
            }
        }
    
        return bShouldRender;
    }
    

    对于非mask、支持PositionOnly、没有用到WPO的material,ShouldRender才返回true,也就是opaque material

    bool FDepthPassMeshProcessor::TryAddMeshBatch(const FMeshBatch& RESTRICT MeshBatch, uint64 BatchElementMask, const FPrimitiveSceneProxy* RESTRICT PrimitiveSceneProxy, int32 StaticMeshId, const FMaterialRenderProxy& MaterialRenderProxy, const FMaterial& Material)
    {
        const bool bIsTranslucent = IsTranslucentBlendMode(Material);
        bool ShouldRenderInDepthPass = (!PrimitiveSceneProxy || PrimitiveSceneProxy->ShouldRenderInDepthPass());
    
        bool bResult = true;
        if (!bIsTranslucent
            && ShouldRenderInDepthPass
            && ShouldIncludeDomainInMeshPass(Material.GetMaterialDomain())
            && ShouldIncludeMaterialInDefaultOpaquePass(Material))
        {
            const bool bSupportPositionOnlyStream = MeshBatch.VertexFactory->SupportsPositionOnlyStream();
            const bool bVFTypeSupportsNullPixelShader = MeshBatch.VertexFactory->SupportsNullPixelShader();
            const bool bModifiesMeshPosition = DoMaterialAndPrimitiveModifyMeshPosition(Material, PrimitiveSceneProxy);
            bool bPositionOnly = false;
            bool bUseDefaultMaterial = false;
            if (ShouldRender(Material, bModifiesMeshPosition, bSupportPositionOnlyStream, bVFTypeSupportsNullPixelShader, bUseDefaultMaterial, bPositionOnly))
            {
                const FMaterialRenderProxy* EffectiveMaterialRenderProxy = &MaterialRenderProxy;
                const FMaterial* EffectiveMaterial = &Material;
                if (bUseDefaultMaterial)
                {
                    // Override with the default material
                    EffectiveMaterialRenderProxy = UMaterial::GetDefaultMaterial(MD_Surface)->GetRenderProxy();
                    EffectiveMaterial = EffectiveMaterialRenderProxy->GetMaterialNoFallback(FeatureLevel);
                    check(EffectiveMaterial);
                }
    
                const FMeshDrawingPolicyOverrideSettings OverrideSettings = ComputeMeshOverrideSettings(MeshBatch);
                const ERasterizerFillMode MeshFillMode = ComputeMeshFillMode(Material, OverrideSettings);
                const ERasterizerCullMode MeshCullMode = ComputeMeshCullMode(Material, OverrideSettings);
    
                if (bPositionOnly)
                {
                    bResult = Process<true>(MeshBatch, BatchElementMask, StaticMeshId, PrimitiveSceneProxy, *EffectiveMaterialRenderProxy, *EffectiveMaterial, MeshFillMode, MeshCullMode);
                }
                else
                {
                    bResult = Process<false>(MeshBatch, BatchElementMask, StaticMeshId, PrimitiveSceneProxy, *EffectiveMaterialRenderProxy, *EffectiveMaterial, MeshFillMode, MeshCullMode);
                }
            }
        }
    
        return bResult;
    }
    

    对于opaque material,直接使用默认材质渲染计算深度,避免额外开销与非模板化

GPU端

只包含Position的VS Shader

struct FPositionOnlyVertexFactoryInput
{
    float4  Position    : ATTRIBUTE0;
    VF_GPUSCENE_DECLARE_INPUT_BLOCK(1)
    VF_INSTANCED_STEREO_DECLARE_INPUT_BLOCK()
    VF_MOBILE_MULTI_VIEW_DECLARE_INPUT_BLOCK() 
};
void Main(
    FPositionOnlyVertexFactoryInput Input,
    out INVARIANT_OUTPUT float4 OutPosition : SV_POSITION,
    out FStereoVSOutput StereoOutput
#if USE_GLOBAL_CLIP_PLANE
    , out float OutGlobalClipPlaneDistance : SV_ClipDistance
#endif
    )
{
    StereoSetupVF(Input, StereoOutput);

    float4 WorldPos = VertexFactoryGetWorldPosition(Input);

    {
        OutPosition = INVARIANT(mul(WorldPos, ResolvedView.TranslatedWorldToClip));
    }

#if USE_GLOBAL_CLIP_PLANE
    OutGlobalClipPlaneDistance = dot(ResolvedView.GlobalClippingPlane, float4(WorldPos.xyz, 1));
#endif
}

可以看到只计算了clip space position

不仅仅包含Positon的VS Shader

struct FDepthOnlyVSToPS
{
    INVARIANT_OUTPUT float4 Position : SV_POSITION;

    // 非opaque或pixel depth offset
    #if !MATERIALBLENDING_SOLID || OUTPUT_PIXEL_DEPTH_OFFSET
        FVertexFactoryInterpolantsVSToPS FactoryInterpolants;
        float4 PixelPosition : TEXCOORD6;
    #endif

    #if USE_RAW_WORLD_POSITION
        float3 PixelPositionExcludingWPO : TEXCOORD7;
    #endif
};
void Main(
    FVertexFactoryInput Input,
    out FDepthOnlyVSOutput Output
#if USE_GLOBAL_CLIP_PLANE
    , out float OutGlobalClipPlaneDistance : SV_ClipDistance
#endif
    , out FStereoVSOutput StereoOutput
    )
{
#if USE_GLOBAL_CLIP_PLANE
    OutGlobalClipPlaneDistance = dot(ResolvedView.GlobalClippingPlane, float4(WorldPos.xyz, 1));
#endif

#if !MATERIALBLENDING_SOLID || OUTPUT_PIXEL_DEPTH_OFFSET
    // Masked and transparent materials need texture coords to clip, and tessellated
    // materials need texture coords to displace
    Output.FactoryInterpolants = VertexFactoryGetInterpolants(Input, VFIntermediates, VertexParameters);

#endif

#if !MATERIALBLENDING_SOLID || OUTPUT_PIXEL_DEPTH_OFFSET
    Output.PixelPosition = WorldPos;
#endif

#if USE_RAW_WORLD_POSITION
    Output.PixelPositionExcludingWPO = WorldPositionExcludingWPO.xyz;
#endif
}
  • 解包,准备数据
    FVertexFactoryIntermediates VFIntermediates = GetVertexFactoryIntermediates(Input);
    float4 WorldPos = VertexFactoryGetWorldPosition(Input, VFIntermediates);
    float4 WorldPositionExcludingWPO = WorldPos;
    
    half3x3 TangentToLocal = VertexFactoryGetTangentToLocal(Input, VFIntermediates);
    FMaterialVertexParameters VertexParameters = GetMaterialVertexParameters(Input, VFIntermediates, WorldPos.xyz, TangentToLocal);
    
  • 应用WPO
    WorldPos.xyz += GetMaterialWorldPositionOffset(VertexParameters);
    
  • 打包需要传给PS插值的数据
    #if !MATERIALBLENDING_SOLID || OUTPUT_PIXEL_DEPTH_OFFSET
    // Masked and transparent materials need texture coords to clip, and tessellated
    // materials need texture coords to displace
    Output.FactoryInterpolants = VertexFactoryGetInterpolants(Input, VFIntermediates, VertexParameters);
    
    #endif
    

Depth Only Pixel Shader

  • 计算材质参数
    FMaterialPixelParameters MaterialParameters = GetMaterialPixelParameters(FactoryInterpolants, SvPosition);
    FPixelMaterialInputs PixelMaterialInputs;
    
    CalcMaterialParameters(MaterialParameters, PixelMaterialInputs, SvPosition, bIsFrontFace);
    
  • 计算pixel depth offset
    #if OUTPUT_PIXEL_DEPTH_OFFSET
      #if ALLOW_DEBUG_VIEW_MODES
      OutDepth = SvPosition.z;
      if (!ResolvedView.DebugViewModeMask)
      #endif
      {
          ApplyPixelDepthOffsetToMaterialParameters(MaterialParameters, PixelMaterialInputs, OutDepth);
      }
    
      #if APPLE_DEPTH_BIAS_HACK
      OutDepth -= APPLE_DEPTH_BIAS_VALUE;
      #endif
    #endif
    
  • 剔除不需要的像素
    #if MATERIALBLENDING_TRANSLUCENT
      clip(MaterialOpacity - GetMaterialOpacityMaskClipValue());
    #elif MATERIALBLENDING_MASKED_USING_COVERAGE
      OutCoverage = DiscardMaterialWithPixelCoverage(MaterialParameters, PixelMaterialInputs);
    #else
      GetMaterialCoverageAndClipping(MaterialParameters, PixelMaterialInputs);
    #endif
    
    • 剔除半透明,半透明不应该走depth only

    • 为Alpha To Coverage计算SV_Coverage

    • Dither Clip

    void GetMaterialCoverageAndClipping(FMaterialPixelParameters Parameters, FPixelMaterialInputs PixelMaterialInputs)
    {
        ClipLODTransition(Parameters);
    
    #if MATERIALBLENDING_MASKED && !SINGLE_LAYER_WATER_NO_DISCARD
        clip(GetDitheredMaterialMask(PixelMaterialInputs, Parameters.SvPosition.xy, View.StateFrameIndex));
    #endif
    }
    
    void ClipLODTransition(float2 SvPosition, float DitherFactor)
    {
        if (abs(DitherFactor) > .001)
        {
            float ArgCos = dot(floor(SvPosition.xy), float2(347.83451793, 3343.28371963));
    
            float RandCos = cos(ArgCos);
            float RandomVal = frac(RandCos * 1000.0);
            half RetVal = (DitherFactor < 0.0) ?
                (DitherFactor + 1.0 > RandomVal) :
                (DitherFactor < RandomVal);
            clip(RetVal - .001);
        }
    }
    

    对LOD像素进行抖动裁剪。让物体在几帧内逐渐显现,而不是突然出现,防止切换LOD时硬切

    float GetDitheredMaterialMask(FPixelMaterialInputs PixelMaterialInputs, uint2 SvPosition, uint FrameIndex)
    {
        #if MATERIAL_DITHER_OPACITY_MASK
            // New dithering based on Blue noise
            float Noise2  = ViewScalarBlueNoise(SvPosition.xy, FrameIndex);
            const float LegacyAdjustement = 0.83f; // This is required to get the same look and empty areas as the legacy one.
            float Dither = LegacyAdjustement * Noise2;
    
            return GetMaterialMask(PixelMaterialInputs) + Dither - 0.5f;
        #else
            return GetMaterialMask(PixelMaterialInputs);
        #endif
    }
    

    基于屏幕空间 + TAA Dither,防止clip(OpacityMask - 0.5)造成的边缘闪烁


他们曾如此骄傲的活过,贯彻始终