UE4渲染模块分析

UE4的渲染模块是一个独立的模块,这篇文章从该模块的设计理念和思路进行剖析。 通常渲染模块由如下几个部分组成:

  • 场景的描述
  • 场景遍历和拣选
  • 渲染的执行

场景的描述

UE4场景管理相关的数据结构如下:

  • FScene 场景类
  • FPrimitiveSceneProxy 场景里的几何体类
  • FPrimitiveSceneInfo 场景里的结点(拥有几何体和状态信息)

每个几何体具有材质属性,相关的数据结构如下:

  • FMaterial 材质接口类,提供材质属性的查询(eg. blend mode)和shader查找。
  • FMaterialResoruce UMaterial实现的具体的FMaterial
  • FMaterialRenderProxy 渲染线程用的Material对象,提供FMaterial的访问和材质参数的访问(eg. scaler, vector, texture parameter等参数)。

场景的遍历和拣选

在简单的3D渲染引擎中,通常的做法是:遍历场景结点,根据视景体进行判断该结点是否可见,如果可见则保存到渲染队列中,否则忽略之。最后对渲染队列中的结点进行按材质排序,然后绘制它们。

在UE4中,使用了不同于上面的方式进行处理,它对几何体进行分类处理(Static Primitive和Dynamic Primitive)。

  1. Static Render Path
    在FScene对象中存在一些static draw list,在PrimitiveSceneProxy被插入场景中时,会通过调用FPrimitveSceneProxy::DrawStaticElements()来收集FStaticMeshElements数据。 然后创建相应的drawing policy对象实例放入到draw list中去。这个drawing policy对象是按照材质排序放入到draw list中的。

  2. Dynamic Render Path
    对于动态渲染路径,InitViews在判定某个PrimitiveScenceProxy是可见后,通过传入TDynamicPrimitiveDrawer对象调用FPrimitiveSceneProxy::DrawDynamicElements()来收集FMeshElements,供以后的渲染使用。

上面的两种渲染路径并不冲突,一个FPrimitiveSceneProxy可以实现DrawStaticElements()和DrawDynamicElements()来同时支持它们,也就是说这个SceneProxy既有Static FMeshElements又有Dynamic FMeshElements。

可见性判定####

参考代码:

bool FDeferredShadingSceneRenderer::InitViews(FRHICommandListImmediate& RHICmdList, struct FILCUpdatePrimTaskData& ILCTaskData, FGraphEventArray& SortEvents)
void FSceneRenderer::ComputeViewVisibility(FRHICommandListImmediate& RHICmdList)
template<bool UseCustomCulling, bool bAlsoUseSphereTest>
static int32 FrustumCull(const FScene* Scene, FViewInfo& View)
void FSceneRenderer::GatherDynamicMeshElements(
    TArray<FViewInfo>& InViews, 
    const FScene* InScene, 
    const FSceneViewFamily& InViewFamily, 
    const FPrimitiveViewMasks& HasDynamicMeshElementsMasks, 
    const FPrimitiveViewMasks& HasDynamicEditorMeshElementsMasks, 
    FMeshElementCollector& Collector)

重要数据结构:

  • FViewInfo
    针对每个ViewPort分配一个FViewInfo对象,这个用于存放裁剪后的几何体可见性。

渲染的执行

对于简单的渲染引擎,只会简单地对可见的几何体执行渲染(设置渲染状态、GPU Shader和参数、发射Draw指令),而UE4的渲染比较复杂,进行多pass绘制,下面列出它的各个pass顺序并逐一介绍.

  1. PASS_0: PrePass/Depth Only Pass
bool FDeferredShadingSceneRenderer::RenderPrePassView(FRHICommandList& RHICmdList, const FViewInfo& View)
bool RenderPrePassViewDynamic(FRHICommandList& RHICmdList, const FViewInfo& View, const FDrawingPolicyRenderState& DrawRenderState);

该pass使用FDepthDrawingPolicy策略进行绘制,只绘制depth到Depth-Buffer,这个有利于减少后面的Base pass中的pixel填充,节省pixel-shader的执行。
下面分析一下RenderPrePassViewDynamic(),验证上述的Dynamic Render Path的机制。

bool FDeferredShadingSceneRenderer::RenderPrePassViewDynamic(FRHICommandList& RHICmdList, const FViewInfo& View, const FDrawingPolicyRenderState& DrawRenderState)
{
    FDepthDrawingPolicyFactory::ContextType Context(EarlyZPassMode, true);

    for (int32 MeshBatchIndex = 0; MeshBatchIndex < View.DynamicMeshElements.Num(); MeshBatchIndex++)
    {
        const FMeshBatchAndRelevance& MeshBatchAndRelevance = View.DynamicMeshElements[MeshBatchIndex];

        if (MeshBatchAndRelevance.GetHasOpaqueOrMaskedMaterial() && MeshBatchAndRelevance.GetRenderInMainPass())
        {
            const FMeshBatch& MeshBatch = *MeshBatchAndRelevance.Mesh;
            const FPrimitiveSceneProxy* PrimitiveSceneProxy = MeshBatchAndRelevance.PrimitiveSceneProxy;
            bool bShouldUseAsOccluder = true;

            if (EarlyZPassMode < DDM_AllOccluders)
            {
                extern float GMinScreenRadiusForDepthPrepass;
                //@todo - move these proxy properties into FMeshBatchAndRelevance so we don't have to dereference the proxy in order to reject a mesh
                const float LODFactorDistanceSquared = (PrimitiveSceneProxy->GetBounds().Origin - View.ViewMatrices.GetViewOrigin()).SizeSquared() * FMath::Square(View.LODDistanceFactor);

                // Only render primitives marked as occluders
                bShouldUseAsOccluder = PrimitiveSceneProxy->ShouldUseAsOccluder()
                    // Only render static objects unless movable are requested
                    && (!PrimitiveSceneProxy->IsMovable() || bEarlyZPassMovable)
                    && (FMath::Square(PrimitiveSceneProxy->GetBounds().SphereRadius) > GMinScreenRadiusForDepthPrepass * GMinScreenRadiusForDepthPrepass * LODFactorDistanceSquared);
            }

            if (bShouldUseAsOccluder)
            {
                FDepthDrawingPolicyFactory::DrawDynamicMesh(RHICmdList, View, Context, MeshBatch, true, DrawRenderState, PrimitiveSceneProxy, MeshBatch.BatchHitProxyId, View.IsInstancedStereoPass());
            }
        }
    }

    return true;
}
bool FDepthDrawingPolicyFactory::DrawDynamicMesh(
    FRHICommandList& RHICmdList, 
    const FViewInfo& View,
    ContextType DrawingContext,
    const FMeshBatch& Mesh,
    bool bPreFog,
    const FDrawingPolicyRenderState& DrawRenderState,
    const FPrimitiveSceneProxy* PrimitiveSceneProxy,
    FHitProxyId HitProxyId, 
    const bool bIsInstancedStereo, 
    const bool bIsInstancedStereoEmulated
    )
{
    FScopedStrictGraphicsPipelineStateUse UsePSOOnly(RHICmdList);

    return DrawMesh(
        RHICmdList, 
        View,
        DrawingContext,
        Mesh,
        Mesh.Elements.Num()==1 ? 1 : (1<<Mesh.Elements.Num())-1,    // 1 bit set for each mesh element
        DrawRenderState,
        bPreFog,
        PrimitiveSceneProxy,
        HitProxyId, 
        bIsInstancedStereo, 
        bIsInstancedStereoEmulated
        );
}
bool FDepthDrawingPolicyFactory::DrawMesh(
    FRHICommandList& RHICmdList, 
    const FViewInfo& View,
    ContextType DrawingContext,
    const FMeshBatch& Mesh,
    const uint64& BatchElementMask,
    const FDrawingPolicyRenderState& DrawRenderState,
    bool bPreFog,
    const FPrimitiveSceneProxy* PrimitiveSceneProxy,
    FHitProxyId HitProxyId, 
    const bool bIsInstancedStereo, 
    const bool bIsInstancedStereoEmulated
    )
{
    bool bDirty = false;

    //Do a per-FMeshBatch check on top of the proxy check in RenderPrePass to handle the case where a proxy that is relevant 
    //to the depth only pass has to submit multiple FMeshElements but only some of them should be used as occluders.
    if (Mesh.bUseAsOccluder || !DrawingContext.bRespectUseAsOccluderFlag || DrawingContext.DepthDrawingMode == DDM_AllOpaque)
    {
        const FMaterialRenderProxy* MaterialRenderProxy = Mesh.MaterialRenderProxy;
        const FMaterial* Material = MaterialRenderProxy->GetMaterial(View.GetFeatureLevel());
        const EBlendMode BlendMode = Material->GetBlendMode();
        const bool bUsesMobileColorValue = (DrawingContext.MobileColorValue != 0.0f);

        // Check to see if the primitive is currently fading in or out using the screen door effect.  If it is,
        // then we can't assume the object is opaque as it may be forcibly masked.
        const FSceneViewState* SceneViewState = static_cast<const FSceneViewState*>( View.State );

        FMeshDrawingPolicyOverrideSettings OverrideSettings = ComputeMeshOverrideSettings(Mesh);
        OverrideSettings.MeshOverrideFlags |= Material->IsTwoSided() ? EDrawingPolicyOverrideFlags::TwoSided : EDrawingPolicyOverrideFlags::None;

        if ( BlendMode == BLEND_Opaque 
            && Mesh.VertexFactory->SupportsPositionOnlyStream() 
            && !Material->MaterialModifiesMeshPosition_RenderThread()
            && Material->WritesEveryPixel()
            && !bUsesMobileColorValue
            )
        {
            //render opaque primitives that support a separate position-only vertex buffer
            const FMaterialRenderProxy* DefaultProxy = UMaterial::GetDefaultMaterial(MD_Surface)->GetRenderProxy(false);

            OverrideSettings.MeshOverrideFlags |= Material->IsWireframe() ? EDrawingPolicyOverrideFlags::Wireframe : EDrawingPolicyOverrideFlags::None;

            // 临时对象 DrawingPolicy.
            FPositionOnlyDepthDrawingPolicy DrawingPolicy(
                Mesh.VertexFactory, 
                DefaultProxy, 
                *DefaultProxy->GetMaterial(View.GetFeatureLevel()), 
                OverrideSettings
                );

            FDrawingPolicyRenderState DrawRenderStateLocal(&RHICmdList, DrawRenderState);
            DrawingPolicy.SetSharedState(RHICmdList, &View, FPositionOnlyDepthDrawingPolicy::ContextDataType(bIsInstancedStereo, bIsInstancedStereoEmulated), DrawRenderStateLocal);

            int32 BatchElementIndex = 0;
            uint64 Mask = BatchElementMask;
            do
            {
                if(Mask & 1)
                {
                    // We draw instanced static meshes twice when rendering with instanced stereo. Once for each eye.
                    const bool bIsInstancedMesh = Mesh.Elements[BatchElementIndex].bIsInstancedMesh;
                    const uint32 InstancedStereoDrawCount = (bIsInstancedStereo && bIsInstancedMesh) ? 2 : 1;
                    for (uint32 DrawCountIter = 0; DrawCountIter < InstancedStereoDrawCount; ++DrawCountIter)
                    {
                        DrawingPolicy.SetInstancedEyeIndex(RHICmdList, DrawCountIter);

                        TDrawEvent<FRHICommandList> MeshEvent;
                        BeginMeshDrawEvent(RHICmdList, PrimitiveSceneProxy, Mesh, MeshEvent);

                        DrawingPolicy.SetMeshRenderState(RHICmdList, View, PrimitiveSceneProxy, Mesh, BatchElementIndex, DrawRenderStateLocal, FPositionOnlyDepthDrawingPolicy::ElementDataType(), FPositionOnlyDepthDrawingPolicy::ContextDataType());
                        DrawingPolicy.DrawMesh(RHICmdList, Mesh, BatchElementIndex, bIsInstancedStereo);
                    }
                }
                Mask >>= 1;
                BatchElementIndex++;
            } while(Mask);

            bDirty = true;
        }
        else if (!IsTranslucentBlendMode(BlendMode) || Material->IsTranslucencyWritingCustomDepth())
        {
            const bool bMaterialMasked = !Material->WritesEveryPixel() || Material->IsTranslucencyWritingCustomDepth();

            bool bDraw = true;

            switch(DrawingContext.DepthDrawingMode)
            {
            case DDM_AllOpaque:
                break;
            case DDM_AllOccluders: 
                break;
            case DDM_NonMaskedOnly: 
                bDraw = !bMaterialMasked;
                break;
            default:
                check(!"Unrecognized DepthDrawingMode");
            }

            if(bDraw)
            {
                if (!bMaterialMasked && !Material->MaterialModifiesMeshPosition_RenderThread())
                {
                    // Override with the default material for opaque materials that are not two sided
                    MaterialRenderProxy = UMaterial::GetDefaultMaterial(MD_Surface)->GetRenderProxy(false);
                }

                FDepthDrawingPolicy DrawingPolicy(
                    Mesh.VertexFactory, 
                    MaterialRenderProxy, 
                    *MaterialRenderProxy->GetMaterial(View.GetFeatureLevel()), 
                    OverrideSettings,
                    View.GetFeatureLevel(),
                    DrawingContext.MobileColorValue
                    );

                FDrawingPolicyRenderState DrawRenderStateLocal(&RHICmdList, DrawRenderState);
                DrawingPolicy.SetSharedState(RHICmdList, &View, FDepthDrawingPolicy::ContextDataType(bIsInstancedStereo, bIsInstancedStereoEmulated), DrawRenderStateLocal);

                int32 BatchElementIndex = 0;
                uint64 Mask = BatchElementMask;
                do
                {
                    if(Mask & 1)
                    {
                        // We draw instanced static meshes twice when rendering with instanced stereo. Once for each eye.
                        const bool bIsInstancedMesh = Mesh.Elements[BatchElementIndex].bIsInstancedMesh;
                        const uint32 InstancedStereoDrawCount = (bIsInstancedStereo && bIsInstancedMesh) ? 2 : 1;
                        for (uint32 DrawCountIter = 0; DrawCountIter < InstancedStereoDrawCount; ++DrawCountIter)
                        {
                            DrawingPolicy.SetInstancedEyeIndex(RHICmdList, DrawCountIter);

                            TDrawEvent<FRHICommandList> MeshEvent;
                            BeginMeshDrawEvent(RHICmdList, PrimitiveSceneProxy, Mesh, MeshEvent);

                            DrawingPolicy.SetMeshRenderState(RHICmdList, View, PrimitiveSceneProxy, Mesh, BatchElementIndex, DrawRenderStateLocal, FMeshDrawingPolicy::ElementDataType(), FDepthDrawingPolicy::ContextDataType());
                            DrawingPolicy.DrawMesh(RHICmdList, Mesh, BatchElementIndex, bIsInstancedStereo);
                        }
                    }
                    Mask >>= 1;
                    BatchElementIndex++;
                } while(Mask);

                bDirty = true;
            }
        }
    }

    return bDirty;
}
  1. PASS_1: Base pass
    该pass绘制不透明的和masked material的属性的几何体,输入材质属性到G-Buffer; 同时计算Lightmap和sky lighting的贡献量到scene color buffer中。下面罗列出相关函数。
/**
 * Renders the scene's base pass 
 * @return true if anything was rendered
 */
bool FDeferredShadingSceneRenderer::RenderBasePass(FRHICommandListImmediate& RHICmdList)
bool FDeferredShadingSceneRenderer::RenderBasePassView(FRHICommandListImmediate& RHICmdList, FViewInfo& View)
{
    bool bDirty = false; 
    FDrawingPolicyRenderState DrawRenderState(&RHICmdList, View);
    SetupBasePassView(RHICmdList, View, DrawRenderState, ViewFamily.EngineShowFlags.ShaderComplexity);
    
    // 绘制静态几何体
    bDirty |= RenderBasePassStaticData(RHICmdList, View, DrawRenderState);
    // 绘制动态几何体
    RenderBasePassDynamicData(RHICmdList, View, DrawRenderState, bDirty);

    return bDirty;
}
bool FBasePassOpaqueDrawingPolicyFactory::DrawDynamicMesh(
    FRHICommandList& RHICmdList, 
    const FViewInfo& View,
    ContextType DrawingContext,
    const FMeshBatch& Mesh,
    bool bPreFog,
    const FDrawingPolicyRenderState& DrawRenderState,
    const FPrimitiveSceneProxy* PrimitiveSceneProxy,
    FHitProxyId HitProxyId, 
    const bool bIsInstancedStereo
    );
        // 使用临时对象DrawingPolicy
        TBasePassDrawingPolicy<LightMapPolicyType> DrawingPolicy(
            Parameters.Mesh.VertexFactory,
            Parameters.Mesh.MaterialRenderProxy,
            *Parameters.Material,
            Parameters.FeatureLevel,
            LightMapPolicy,
            Parameters.BlendMode,
            Parameters.TextureMode,
            bRenderSkylight,
            bRenderAtmosphericFog,
            ComputeMeshOverrideSettings(Parameters.Mesh),
            View.Family->GetDebugViewShaderMode(),
            Parameters.bEditorCompositeDepthTest,
            bEnableReceiveDecalOutput
            );
  1. PASS_2: Issue Occlusion Queries / BeginOcclusionTests
    执行遮挡查询,在绘制下一帧时,InitView会使用这些信息进行可见性判断。遮挡查询的原理是通过绘制几何体的包围盒进行z-depth测试,以粗略地判断该几何体是否被遮挡。
void FDeferredShadingSceneRenderer::RenderOcclusion(FRHICommandListImmediate& RHICmdList, bool bRenderQueries, bool bRenderHZB)
  1. PASS_3: ShadowMap(阴影计算)
    针对每个光源渲染相应的Shadowmap, 光源也被累积到translucency lighting volumes中(这块不明白,理解估计有误)。
void FSceneRenderer::RenderShadowDepthMaps(FRHICommandListImmediate& RHICmdList);
void FSceneRenderer::RenderShadowDepthMapAtlases(FRHICommandListImmediate& RHICmdList);

具体实现

void FSceneRenderer::RenderShadowDepthMaps(FRHICommandListImmediate& RHICmdList)
{
    FSceneRenderTargets& SceneContext = FSceneRenderTargets::Get(RHICmdList);

    SCOPED_DRAW_EVENT(RHICmdList, ShadowDepths);
    SCOPED_GPU_STAT(RHICmdList, Stat_GPU_ShadowDepths);

    FSceneRenderer::RenderShadowDepthMapAtlases(RHICmdList);

    for (int32 CubemapIndex = 0; CubemapIndex < SortedShadowsForShadowDepthPass.ShadowMapCubemaps.Num(); CubemapIndex++)
    {
        const FSortedShadowMapAtlas& ShadowMap = SortedShadowsForShadowDepthPass.ShadowMapCubemaps[CubemapIndex];
        FSceneRenderTargetItem& RenderTarget = ShadowMap.RenderTargets.DepthTarget->GetRenderTargetItem();
        FIntPoint TargetSize = ShadowMap.RenderTargets.DepthTarget->GetDesc().Extent;

        check(ShadowMap.Shadows.Num() == 1);
        FProjectedShadowInfo* ProjectedShadowInfo = ShadowMap.Shadows[0];

        GRenderTargetPool.VisualizeTexture.SetCheckPoint(RHICmdList, ShadowMap.RenderTargets.DepthTarget.GetReference());

        FString LightNameWithLevel;
        GetLightNameForDrawEvent(ProjectedShadowInfo->GetLightSceneInfo().Proxy, LightNameWithLevel);
        SCOPED_DRAW_EVENTF(RHICmdList, EventShadowDepths, TEXT("Cubemap %s %u^2"), *LightNameWithLevel, TargetSize.X, TargetSize.Y);

        auto SetShadowRenderTargets = [this, &RenderTarget, &SceneContext](FRHICommandList& InRHICmdList, bool bPerformClear)
        {
            FRHISetRenderTargetsInfo Info(0, nullptr, FRHIDepthRenderTargetView(RenderTarget.TargetableTexture, 
                bPerformClear ? ERenderTargetLoadAction::EClear : ERenderTargetLoadAction::ELoad, 
                ERenderTargetStoreAction::EStore, 
                ERenderTargetLoadAction::ELoad, 
                ERenderTargetStoreAction::EStore));

            check(Info.DepthStencilRenderTarget.Texture->GetDepthClearValue() == 1.0f); 
            Info.ColorRenderTarget[0].StoreAction = ERenderTargetStoreAction::ENoAction;

            if (!GSupportsDepthRenderTargetWithoutColorRenderTarget)
            {
                Info.NumColorRenderTargets = 1;
                Info.ColorRenderTarget[0].Texture = SceneContext.GetOptionalShadowDepthColorSurface(InRHICmdList, Info.DepthStencilRenderTarget.Texture->GetTexture2D()->GetSizeX(), Info.DepthStencilRenderTarget.Texture->GetTexture2D()->GetSizeY());
                InRHICmdList.TransitionResource(EResourceTransitionAccess::EWritable, Info.ColorRenderTarget[0].Texture);
            }
            InRHICmdList.TransitionResource(EResourceTransitionAccess::EWritable, Info.DepthStencilRenderTarget.Texture);
            InRHICmdList.SetRenderTargetsAndClear(Info);
        };
            
        {
            bool bDoClear = true;

            if (ProjectedShadowInfo->CacheMode == SDCM_MovablePrimitivesOnly 
                && Scene->CachedShadowMaps.FindChecked(ProjectedShadowInfo->GetLightSceneInfo().Id).bCachedShadowMapHasPrimitives)
            {
                // Skip the clear when we'll copy from a cached shadowmap
                bDoClear = false;
            }

            SCOPED_CONDITIONAL_DRAW_EVENT(RHICmdList, Clear, bDoClear);
            SetShadowRenderTargets(RHICmdList, bDoClear);   
        }

        ProjectedShadowInfo->RenderDepth(RHICmdList, this, SetShadowRenderTargets, ShadowDepthRenderMode_Normal);

        RHICmdList.TransitionResource(EResourceTransitionAccess::EReadable, RenderTarget.TargetableTexture);
    }
            
    if (SortedShadowsForShadowDepthPass.PreshadowCache.Shadows.Num() > 0)
    {
        FSceneRenderTargetItem& RenderTarget = SortedShadowsForShadowDepthPass.PreshadowCache.RenderTargets.DepthTarget->GetRenderTargetItem();

        GRenderTargetPool.VisualizeTexture.SetCheckPoint(RHICmdList, SortedShadowsForShadowDepthPass.PreshadowCache.RenderTargets.DepthTarget.GetReference());

        SCOPED_DRAW_EVENT(RHICmdList, PreshadowCache);

        for (int32 ShadowIndex = 0; ShadowIndex < SortedShadowsForShadowDepthPass.PreshadowCache.Shadows.Num(); ShadowIndex++)
        {
            FProjectedShadowInfo* ProjectedShadowInfo = SortedShadowsForShadowDepthPass.PreshadowCache.Shadows[ShadowIndex];

            if (!ProjectedShadowInfo->bDepthsCached)
            {
                auto SetShadowRenderTargets = [this, ProjectedShadowInfo](FRHICommandList& InRHICmdList, bool bPerformClear)
                {
                    FTextureRHIParamRef PreShadowCacheDepthZ = Scene->PreShadowCacheDepthZ->GetRenderTargetItem().TargetableTexture.GetReference();
                    InRHICmdList.TransitionResources(EResourceTransitionAccess::EWritable, &PreShadowCacheDepthZ, 1);

                    // Must preserve existing contents as the clear will be scissored
                    SetRenderTarget(InRHICmdList, FTextureRHIRef(), PreShadowCacheDepthZ, ESimpleRenderTargetMode::EExistingColorAndDepth);
                    ProjectedShadowInfo->ClearDepth(InRHICmdList, this, 0, nullptr, PreShadowCacheDepthZ, bPerformClear);
                };

                SetShadowRenderTargets(RHICmdList, true);

                ProjectedShadowInfo->RenderDepth(RHICmdList, this, SetShadowRenderTargets, ShadowDepthRenderMode_Normal);
                ProjectedShadowInfo->bDepthsCached = true;
            }
        }

        RHICmdList.TransitionResource(EResourceTransitionAccess::EReadable, RenderTarget.TargetableTexture);
    }

    for (int32 AtlasIndex = 0; AtlasIndex < SortedShadowsForShadowDepthPass.TranslucencyShadowMapAtlases.Num(); AtlasIndex++)
    {
        const FSortedShadowMapAtlas& ShadowMapAtlas = SortedShadowsForShadowDepthPass.TranslucencyShadowMapAtlases[AtlasIndex];
        FIntPoint TargetSize = ShadowMapAtlas.RenderTargets.ColorTargets[0]->GetDesc().Extent;

        SCOPED_DRAW_EVENTF(RHICmdList, EventShadowDepths, TEXT("TranslucencyAtlas%u %u^2"), AtlasIndex, TargetSize.X, TargetSize.Y);

        FSceneRenderTargetItem ColorTarget0 = ShadowMapAtlas.RenderTargets.ColorTargets[0]->GetRenderTargetItem();
        FSceneRenderTargetItem ColorTarget1 = ShadowMapAtlas.RenderTargets.ColorTargets[1]->GetRenderTargetItem();

        FTextureRHIParamRef RenderTargetArray[2] =
        {
            ColorTarget0.TargetableTexture,
            ColorTarget1.TargetableTexture
        };
        SetRenderTargets(RHICmdList, ARRAY_COUNT(RenderTargetArray), RenderTargetArray, FTextureRHIParamRef(), 0, NULL, true);

        for (int32 ShadowIndex = 0; ShadowIndex < ShadowMapAtlas.Shadows.Num(); ShadowIndex++)
        {
            FProjectedShadowInfo* ProjectedShadowInfo = ShadowMapAtlas.Shadows[ShadowIndex];
            ProjectedShadowInfo->RenderTranslucencyDepths(RHICmdList, this);
        }

        RHICmdList.TransitionResource(EResourceTransitionAccess::EReadable, ColorTarget0.TargetableTexture);
        RHICmdList.TransitionResource(EResourceTransitionAccess::EReadable, ColorTarget1.TargetableTexture);
    }

    for (int32 AtlasIndex = 0; AtlasIndex < SortedShadowsForShadowDepthPass.RSMAtlases.Num(); AtlasIndex++)
    {
        const FSortedShadowMapAtlas& ShadowMapAtlas = SortedShadowsForShadowDepthPass.RSMAtlases[AtlasIndex];
        FSceneRenderTargetItem ColorTarget0 = ShadowMapAtlas.RenderTargets.ColorTargets[0]->GetRenderTargetItem();
        FSceneRenderTargetItem ColorTarget1 = ShadowMapAtlas.RenderTargets.ColorTargets[1]->GetRenderTargetItem();
        FSceneRenderTargetItem DepthTarget = ShadowMapAtlas.RenderTargets.DepthTarget->GetRenderTargetItem();
        FIntPoint TargetSize = ShadowMapAtlas.RenderTargets.DepthTarget->GetDesc().Extent;

        SCOPED_DRAW_EVENTF(RHICmdList, EventShadowDepths, TEXT("RSM%u %ux%u"), AtlasIndex, TargetSize.X, TargetSize.Y);

        for (int32 ShadowIndex = 0; ShadowIndex < ShadowMapAtlas.Shadows.Num(); ShadowIndex++)
        {
            FProjectedShadowInfo* ProjectedShadowInfo = ShadowMapAtlas.Shadows[ShadowIndex];

            FSceneViewState* ViewState = (FSceneViewState*)ProjectedShadowInfo->DependentView->State;
            FLightPropagationVolume* LightPropagationVolume = ViewState->GetLightPropagationVolume(FeatureLevel);
            
            // 计算 FLightPropagationVolume
            auto SetShadowRenderTargets = [this, LightPropagationVolume, ProjectedShadowInfo, &ColorTarget0, &ColorTarget1, &DepthTarget](FRHICommandList& InRHICmdList, bool bPerformClear)
            {
                FTextureRHIParamRef RenderTargets[2];
                RenderTargets[0] = ColorTarget0.TargetableTexture;
                RenderTargets[1] = ColorTarget1.TargetableTexture;

                // Hook up the geometry volume UAVs
                FUnorderedAccessViewRHIParamRef Uavs[4];
                Uavs[0] = LightPropagationVolume->GetGvListBufferUav();
                Uavs[1] = LightPropagationVolume->GetGvListHeadBufferUav();
                Uavs[2] = LightPropagationVolume->GetVplListBufferUav();
                Uavs[3] = LightPropagationVolume->GetVplListHeadBufferUav();
                    
                InRHICmdList.TransitionResources(EResourceTransitionAccess::ERWBarrier, EResourceTransitionPipeline::EGfxToGfx, Uavs, ARRAY_COUNT(Uavs));
                SetRenderTargets(InRHICmdList, ARRAY_COUNT(RenderTargets), RenderTargets, DepthTarget.TargetableTexture, ARRAY_COUNT(Uavs), Uavs);

                ProjectedShadowInfo->ClearDepth(InRHICmdList, this, ARRAY_COUNT(RenderTargets), RenderTargets, DepthTarget.TargetableTexture, bPerformClear);
            };                  

            {
                SCOPED_DRAW_EVENT(RHICmdList, Clear);
                SetShadowRenderTargets(RHICmdList, true);   
            }               

            LightPropagationVolume->SetVplInjectionConstants(*ProjectedShadowInfo, ProjectedShadowInfo->GetLightSceneInfo().Proxy);

            ProjectedShadowInfo->RenderDepth(RHICmdList, this, SetShadowRenderTargets, ShadowDepthRenderMode_Normal);

            // Render emissive only meshes as they are held in a separate list.
            ProjectedShadowInfo->RenderDepth(RHICmdList, this, SetShadowRenderTargets, ShadowDepthRenderMode_EmissiveOnly);
            // Render gi blocking volume meshes.
            ProjectedShadowInfo->RenderDepth(RHICmdList, this, SetShadowRenderTargets, ShadowDepthRenderMode_GIBlockingVolumes);

            {
                // Resolve the shadow depth z surface.
                RHICmdList.CopyToResolveTarget(DepthTarget.TargetableTexture, DepthTarget.ShaderResourceTexture, false, FResolveParams());
                RHICmdList.CopyToResolveTarget(ColorTarget0.TargetableTexture, ColorTarget0.ShaderResourceTexture, false, FResolveParams());
                RHICmdList.CopyToResolveTarget(ColorTarget1.TargetableTexture, ColorTarget1.ShaderResourceTexture, false, FResolveParams());

                FUnorderedAccessViewRHIParamRef UavsToReadable[2];
                UavsToReadable[0] = LightPropagationVolume->GetGvListBufferUav();
                UavsToReadable[1] = LightPropagationVolume->GetGvListHeadBufferUav();   
                RHICmdList.TransitionResources(EResourceTransitionAccess::EReadable, EResourceTransitionPipeline::EGfxToGfx, UavsToReadable, ARRAY_COUNT(UavsToReadable));

                // Unset render targets
                FTextureRHIParamRef RenderTargets[2] = {NULL};
                FUnorderedAccessViewRHIParamRef Uavs[2] = {NULL};
                SetRenderTargets(RHICmdList, ARRAY_COUNT(RenderTargets), RenderTargets, FTextureRHIParamRef(), ARRAY_COUNT(Uavs), Uavs);
            }
        }
    }
}
  1. PASS_4: Lighting(光照计算)
    分为如下子阶段:
  • Pre-lighting composition lighting stage
    预处理组合型光照(eg. deferred decals, SSAO)
  • Render lights
    光照计算

参考代码如下:

   // Pre-lighting composition lighting stage
   // e.g. deferred decals, SSAO
   if (FeatureLevel >= ERHIFeatureLevel::SM4)
   {
       SCOPE_CYCLE_COUNTER(STAT_FDeferredShadingSceneRenderer_AfterBasePass);

       GRenderTargetPool.AddPhaseEvent(TEXT("AfterBasePass"));

       for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
       {
           SCOPED_CONDITIONAL_DRAW_EVENTF(RHICmdList, EventView, Views.Num() > 1, TEXT("View%d"), ViewIndex);
           GCompositionLighting.ProcessAfterBasePass(RHICmdList, Views[ViewIndex]);
       }
       ServiceLocalQueue();
   }

   // TODO: Could entirely remove this by using STENCIL_SANDBOX_BIT in ShadowRendering.cpp and DistanceFieldSurfaceCacheLighting.cpp
   if (!IsForwardShadingEnabled(FeatureLevel))
   {
       SCOPED_DRAW_EVENT(RHICmdList, ClearStencilFromBasePass);

       FRHISetRenderTargetsInfo Info(0, NULL, FRHIDepthRenderTargetView(
           SceneContext.GetSceneDepthSurface(),
           ERenderTargetLoadAction::ENoAction,
           ERenderTargetStoreAction::ENoAction,
           ERenderTargetLoadAction::EClear,
           ERenderTargetStoreAction::EStore,
           FExclusiveDepthStencil::DepthNop_StencilWrite));

       // Clear stencil to 0 now that deferred decals are done using what was setup in the base pass
       // Shadow passes and other users of stencil assume it is cleared to 0 going in
       RHICmdList.SetRenderTargetsAndClear(Info);

       RHICmdList.TransitionResource(EResourceTransitionAccess::EReadable, SceneContext.GetSceneDepthSurface());
   }

   // Render lighting.
   if (ViewFamily.EngineShowFlags.Lighting
       && FeatureLevel >= ERHIFeatureLevel::SM4
       && ViewFamily.EngineShowFlags.DeferredLighting
       && bUseGBuffer)
   {
       SCOPE_CYCLE_COUNTER(STAT_FDeferredShadingSceneRenderer_Lighting);

       GRenderTargetPool.AddPhaseEvent(TEXT("Lighting"));

       // These modulate the scenecolor output from the basepass, which is assumed to be indirect lighting
       RenderIndirectCapsuleShadows(
           RHICmdList, 
           SceneContext.GetSceneColorSurface(), 
           SceneContext.bScreenSpaceAOIsValid ? SceneContext.ScreenSpaceAO->GetRenderTargetItem().TargetableTexture : NULL);

       TRefCountPtr<IPooledRenderTarget> DynamicBentNormalAO;
       // These modulate the scenecolor output from the basepass, which is assumed to be indirect lighting
       RenderDFAOAsIndirectShadowing(RHICmdList, VelocityRT, DynamicBentNormalAO);

       // Clear the translucent lighting volumes before we accumulate
       if ((GbEnableAsyncComputeTranslucencyLightingVolumeClear && GSupportsEfficientAsyncCompute) == false)
       {
           ClearTranslucentVolumeLighting(RHICmdList);
       }

       RHICmdList.SetCurrentStat(GET_STATID(STAT_CLM_Lighting));
       RenderLights(RHICmdList);
       RHICmdList.SetCurrentStat(GET_STATID(STAT_CLM_AfterLighting));
       ServiceLocalQueue();

       GRenderTargetPool.AddPhaseEvent(TEXT("AfterRenderLights"));

       InjectAmbientCubemapTranslucentVolumeLighting(RHICmdList);
       ServiceLocalQueue();

       // Filter the translucency lighting volume now that it is complete
       FilterTranslucentVolumeLighting(RHICmdList);
       ServiceLocalQueue();

       // Pre-lighting composition lighting stage
       // e.g. LPV indirect
       for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ++ViewIndex)
       {
           FViewInfo& View = Views[ViewIndex]; 

           if(IsLpvIndirectPassRequired(View))
           {
               SCOPED_CONDITIONAL_DRAW_EVENTF(RHICmdList, EventView,Views.Num() > 1, TEXT("View%d"), ViewIndex);

               GCompositionLighting.ProcessLpvIndirect(RHICmdList, View);
               ServiceLocalQueue();
           }
       }

       RenderDynamicSkyLighting(RHICmdList, VelocityRT, DynamicBentNormalAO);
       ServiceLocalQueue();

       // SSS need the SceneColor finalized as an SRV.
       SceneContext.FinishRenderingSceneColor(RHICmdList);

       // Render reflections that only operate on opaque pixels
       RenderDeferredReflections(RHICmdList, DynamicBentNormalAO, VelocityRT);
       ServiceLocalQueue();

       // Post-lighting composition lighting stage
       // e.g. ScreenSpaceSubsurfaceScattering
       for(int32 ViewIndex = 0; ViewIndex < Views.Num(); ++ViewIndex)
       {   
           SCOPED_CONDITIONAL_DRAW_EVENTF(RHICmdList, EventView,Views.Num() > 1, TEXT("View%d"), ViewIndex);
           GCompositionLighting.ProcessAfterLighting(RHICmdList, Views[ViewIndex]);
       }
       ServiceLocalQueue();
   }

   FLightShaftsOutput LightShaftOutput;

   // Draw Lightshafts
   if (ViewFamily.EngineShowFlags.LightShafts)
   {
       SCOPE_CYCLE_COUNTER(STAT_FDeferredShadingSceneRenderer_RenderLightShaftOcclusion);
       RenderLightShaftOcclusion(RHICmdList, LightShaftOutput);
       ServiceLocalQueue();
   }
  1. PASS_5: Draw atmosphere
    对非透明表面绘制大气效果.
    // Draw atmosphere
    if (ShouldRenderAtmosphere(ViewFamily))
    {
        SCOPE_CYCLE_COUNTER(STAT_FDeferredShadingSceneRenderer_RenderAtmosphere);
        if (Scene->AtmosphericFog)
        {
            // Update RenderFlag based on LightShaftTexture is valid or not
            if (LightShaftOutput.LightShaftOcclusion)
            {
                Scene->AtmosphericFog->RenderFlag &= EAtmosphereRenderFlag::E_LightShaftMask;
            }
            else
            {
                Scene->AtmosphericFog->RenderFlag |= EAtmosphereRenderFlag::E_DisableLightShaft;
            }
#if WITH_EDITOR
            if (Scene->bIsEditorScene)
            {
                // Precompute Atmospheric Textures
                Scene->AtmosphericFog->PrecomputeTextures(RHICmdList, Views.GetData(), &ViewFamily);
            }
#endif
            RenderAtmosphere(RHICmdList, LightShaftOutput);
            ServiceLocalQueue();
        }
    }

负责该效果函数

void FDeferredShadingSceneRenderer::RenderAtmosphere(FRHICommandListImmediate& RHICmdList, const FLightShaftsOutput& LightShaftsOutput)
  1. PASS_6: Draw Fog
    针对非透明表面逐像素就算Fog.
    // Draw fog.
    if (ShouldRenderFog(ViewFamily))
    {
        SCOPE_CYCLE_COUNTER(STAT_FDeferredShadingSceneRenderer_RenderFog);
        RenderFog(RHICmdList, LightShaftOutput);
        ServiceLocalQueue();
    }
bool FDeferredShadingSceneRenderer::RenderFog(FRHICommandListImmediate& RHICmdList, const FLightShaftsOutput& LightShaftsOutput)
  1. PASS_7: Draw translucency
    绘制半透明几何体.
    Translucency is accumulated into an offscreen render target where it has fogging applied per-vertex so it can integrate into the scene. Lit translucency computes final lighting in a single pass to blend correctly.
    // Draw translucency.
    if (ViewFamily.EngineShowFlags.Translucency)
    {
        SCOPE_CYCLE_COUNTER(STAT_TranslucencyDrawTime);

        RHICmdList.SetCurrentStat(GET_STATID(STAT_CLM_Translucency));
        RenderTranslucency(RHICmdList);
        ServiceLocalQueue();

        if(GetRefractionQuality(ViewFamily) > 0)
        {
            // To apply refraction effect by distorting the scene color.
            // After non separate translucency as that is considered at scene depth anyway
            // It allows skybox translucency (set to non separate translucency) to be refracted.
            RHICmdList.SetCurrentStat(GET_STATID(STAT_CLM_RenderDistortion));
            RenderDistortion(RHICmdList);
            ServiceLocalQueue();
        }
        RHICmdList.SetCurrentStat(GET_STATID(STAT_CLM_AfterTranslucency));
    }
void FDeferredShadingSceneRenderer::RenderTranslucency(FRHICommandListImmediate& RHICmdList)
  1. PASS_8: Post Processing
    绘制后处理效果
    // Resolve the scene color for post processing.
    SceneContext.ResolveSceneColor(RHICmdList, FResolveRect(0, 0, ViewFamily.FamilySizeX, ViewFamily.FamilySizeY));

    GetRendererModule().RenderPostResolvedSceneColorExtension(RHICmdList, SceneContext);

    CopySceneCaptureComponentToTarget(RHICmdList);
    
    // Finish rendering for each view.
    if (ViewFamily.bResolveScene)
    {
        SCOPED_DRAW_EVENT(RHICmdList, PostProcessing);
        SCOPED_GPU_STAT(RHICmdList, Stat_GPU_Postprocessing);

        SCOPE_CYCLE_COUNTER(STAT_FinishRenderViewTargetTime);

        RHICmdList.SetCurrentStat(GET_STATID(STAT_CLM_PostProcessing));
        for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
        {
            SCOPED_CONDITIONAL_DRAW_EVENTF(RHICmdList, EventView, Views.Num() > 1, TEXT("View%d"), ViewIndex);

            // 执行后处理效果!!
            GPostProcessing.Process(RHICmdList, Views[ ViewIndex ], VelocityRT);
        }

        // End of frame, we don't need it anymore
        FSceneRenderTargets::Get(RHICmdList).FreeSeparateTranslucencyDepth();

        // we rendered to it during the frame, seems we haven't made use of it, because it should be released
        check(!FSceneRenderTargets::Get(RHICmdList).SeparateTranslucencyRT);
    }
    else
    {
        // Release the original reference on the scene render targets
        SceneContext.AdjustGBufferRefCount(RHICmdList, -1);
    }

后处理类

/**
 * The center for all post processing activities.
 */
class FPostProcessing
{
public:

    bool AllowFullPostProcessing(const FViewInfo& View, ERHIFeatureLevel::Type FeatureLevel);

    // @param VelocityRT only valid if motion blur is supported
    void Process(FRHICommandListImmediate& RHICmdList, const FViewInfo& View, TRefCountPtr<IPooledRenderTarget>& VelocityRT);

    void ProcessES2(FRHICommandListImmediate& RHICmdList, const FViewInfo& View, bool bViewRectSource);

    void ProcessPlanarReflection(FRHICommandListImmediate& RHICmdList, FViewInfo& View, TRefCountPtr<IPooledRenderTarget>& VelocityRT, TRefCountPtr<IPooledRenderTarget>& OutFilteredSceneColor);
};

Shaders and Materials

参考UE4官方文档

  • Global Shaders
    全局shader是用于操作固定几何体(如:full screen quad)的shader,并且它不需要与materials产生交互,例如:阴影过滤、后处理。这种类型的shader在内存中只有一份实例。
  • Material
    材质由一些控制材质如何渲染的状态和一些控制材质如何与渲染Pass shader交互的材质输入(material inputs)组成。
  • Vertex Factories And Mesh Types
    Material必须能够赋给各种类型的Mesh Types, 这个是通过和Vertex factories配合完成的。 一个FVertexFactoryType表示唯一的Mesh Type。一个FVertexFactory的实例存有每个几何体的数据,以便支持特定的Mesh Type. 例如:FGPUSkinVertexFactory存放用于蒙皮的bone matrics、各种Vertex Buffers,GPU Skin vertex factory shader代码需要它们作为输入。Vertex Factory shader code是一个隐式接口,抽象了不同类型的Mesh types,屏蔽了这些mesh types的差异,方便各种pass shaders访问mesh数据。vertex factories shader提供如下接口:
函数 描述
FVertexFactoryInput Defines what the vertex factory needs as input to the vertex shader. These must match the vertex declaration in the C++ side FVertexFactory. For example, LocalVertexFactory's FVertexFactoryInput has float4 Position : POSITION;, which corresponds to the position stream declaration in FStaticMeshLODResources::SetupVertexFactory.
FVertexFactoryIntermediates Used to store cached intermediate data that will be used in multiple vertex factory functions. A common example is the TangentToLocal matrix, which had to be computed from unpacked vertex inputs.
FVertexFactoryInterpolantsVSToPS Vertex factory data to be passed from the vertex shader to the pixel shader.
VertexFactoryGetWorldPosition This is called from the vertex shader to get the world space vertex position. For Static Meshes this merely transforms the local space positions from the vertex buffer into world space using the LocalToWorld matrix. For GPU skinned meshes, the position is skinned first and then transformed to world space.
VertexFactoryGetInterpolantsVSToPS Transforms the FVertexFactoryInput to FVertexFactoryInterpolants, which will be interpolated by the graphics hardware before getting passed into the pixel shader.
GetMaterialPixelParameters This is called in the pixel shader and converts vertex factory specific interpolants (FVertexFactoryInterpolants) to the FMaterialPixelParameters structure which is used by the pass pixel shaders.
  • Material Shaders
    FMaterialShaderType类型的shaders是特定的渲染pass的shaders, 它需要访问材质属性(material's attributes), 针对每个material都要相应地编译一下,这种类型的shaders不需要访问mesh attributes(例如: light function pass shaders)。

    FMeshMaterialShaderType类型的shaders是特定的渲染pass的shaders,它依赖于材质属性和mesh type, 因此针对每个material, vertex factory组合都需要相应地编译一下,成为最终的可供gpu认可的shader(ie. vs, ps), 例如:TBasePassVS/TBasePassPS需要和所有的material、mesh type进行组合编译。
    一个material引发出的最终gpu shaders(FMaterialShader实例或FMeshMaterialShader实例)放在FMaterialShaderMap中。
    综上所述,一个最终的gpu shader的合成方式如下表:

所属Shader类型 组成部分
GlobalShaderType pass_shader_x.usf
MaterialShaderType material_x.usf + pass_shader_x.usf
MeshMaterialShaderType vertex_factory_x.usf + material_x.usf + pass_shader_x.usf

Note: pass_shader_x.usf和vertex_factory_x.usf表示引擎Shders目录下的usf文件, material_x.usf表示由UE4材质编辑器生成的材质结点代码

  • UE4源码中相关类
  1. FShader类
    FShader类的继承体系如下:
    FShader
        FGlobalShader
            FClearBufferReplacementCS
            FClearReplacementPS
            FClearReplacementVS
            FClearTexture2DReplacementCS
            FClearTexture2DReplacementScissorCS
            FCopyTexture2DCS
            FCubemapTexturePropertiesPS
            FCubemapTexturePropertiesVS
            FFillTextureCS
            FHdrCustomResolve2xPS
            FHdrCustomResolve4xPS
            FHdrCustomResolve8xPS
            FHdrCustomResolveVS
            FIESLightProfilePS
            FLongGPUTaskPS
            FMedaShadersVS
            FNULLPS
            FOneColorPS
            TOneClorPixelShaderMRT
            FResolveDepthNonMSPS
            FResolveDepthPS
            FResolveSingleSamplePS
            FResolveVS
            FScreenPS
            FScreenVS
            FScreenVSForGS
            FSimpleElementColorChannelMaskPS
            FSimpleElementHitProxyPS
            FSimpleElementPS
            FSimpleElementAlphaOnlyPS
            FSimpleElementGammaBasePS
            FSimpleElementGammaAlphaOnlyPS
            FSimpleElementGammaPS
            FSimpleElementMaskedGammaBasePS
            FSimpleElementDistanceFieldGammaPS
            FSimpleElementMaskedGammaPS
            FSimpleElementVS
            FUpdateTexture2DSubresouceCS
            FUpdateTexture3DSubresouceCS
            FYCbCrConvertShaderPS
            TOneColorVS
        FMaterialShader
            FPostProcessMaterialVS
            FPostProcessMaterialPS
            FLightFunctionVS
            FLightFunctionPS
                ...
            FMeshMaterialShader
                TDepthOnlyVS
                TDepthOnlyPS
                TDistortionMeshVS
                TDistortionMeshPS
                FShadowDepthVS
                FShadowDepthPS
                ...
    
    这些类实例对象代表一个GPU shader资源(vs,ps,cs),它由UE4的ShaderCompiler系统生成。同时每个类都对应一个ShaderType实例,用于描述这些类用什么pass shader(即哪个usf源文件). 例如下面几个Shader类
class FClearReplacementVS : public FGlobalShader
{
    // 声明ShaderType对象,注意关键字Global.
    DECLARE_EXPORTED_SHADER_TYPE(FClearReplacementVS, Global, UTILITYSHADERS_API);
public:
    FClearReplacementVS() {}
    FClearReplacementVS(const ShaderMetaType::CompiledShaderInitializerType& Initializer)
    : FGlobalShader( Initializer )
    {
    }
    
    // FShader interface.
    virtual bool Serialize(FArchive& Ar) override
    {
        bool bShaderHasOutdatedParameters = FGlobalShader::Serialize(Ar);
        return bShaderHasOutdatedParameters;
    }
    
    static bool ShouldCache(EShaderPlatform Platform)
    {
        return IsFeatureLevelSupported(Platform, ERHIFeatureLevel::SM4);
    }
};
    // 使用的pass shader是ClearReplacementShaders.usf, 入口为ClearVS.
    IMPLEMENT_SHADER_TYPE(,FClearReplacementVS,TEXT("ClearReplacementShaders"),TEXT("ClearVS"),SF_Vertex);
    ```
    ```cpp
    template<EPostProcessMaterialTarget MaterialTarget>
    class FPostProcessMaterialVS : public FMaterialShader
    {
       // 声明ShaderType对象,注意关键字Material.
        DECLARE_SHADER_TYPE(FPostProcessMaterialVS, Material);
    public:

        FPostProcessMaterialVS( )   { }
        FPostProcessMaterialVS(const ShaderMetaType::CompiledShaderInitializerType& Initializer)
            : FMaterialShader(Initializer)
        {
            PostprocessParameter.Bind(Initializer.ParameterMap);
        }
    private:
        FPostProcessPassParameters PostprocessParameter;
    };
    typedef FPostProcessMaterialVS<EPostProcessMaterialTarget::HighEnd> FPostProcessMaterialVS_HighEnd;
    typedef FPostProcessMaterialVS<EPostProcessMaterialTarget::Mobile> FPostProcessMaterialVS_Mobile;
   // 使用的pass shader是PostProcessMaterialShaders.usf, 入口为MainVS.
   IMPLEMENT_MATERIAL_SHADER_TYPE(template<>,FPostProcessMaterialVS_HighEnd,TEXT("PostProcessMaterialShaders"),TEXT("MainVS"),SF_Vertex);
    ```
    ```cpp
 // A vertex shader for rendering the depth of a mesh.
class FShadowDepthVS : public FMeshMaterialShader
{
    // 声明ShaderType对象,注意关键字MeshMaterial.
    DECLARE_SHADER_TYPE(FShadowDepthVS,MeshMaterial);
public:
    static bool ShouldCache(EShaderPlatform Platform,const FMaterial* Material,const FVertexFactoryType* VertexFactoryType)
    {
        return false;
    }
    FShadowDepthVS(const ShaderMetaType::CompiledShaderInitializerType& Initializer):
        FMeshMaterialShader(Initializer)
    {
        ShadowParameters.Bind(Initializer.ParameterMap);
        ShadowViewProjectionMatrices.Bind(Initializer.ParameterMap, TEXT("ShadowViewProjectionMatrices"));
        MeshVisibleToFace.Bind(Initializer.ParameterMap, TEXT("MeshVisibleToFace"));
        InstanceCount.Bind(Initializer.ParameterMap, TEXT("InstanceCount"));
    }
    FShadowDepthVS() {}
private:
    FShadowDepthShaderParameters ShadowParameters;
    FShaderParameter ShadowViewProjectionMatrices;
    FShaderParameter MeshVisibleToFace;
    FShaderParameter InstanceCount;
};
  1. FShaderType类
    该类用于描述Shader的类型。

    FShaderType
        FGlobalShaderType
         FMaterialShaderType
         FMeshMaterialShaderType
    
    • FGlobalShaderType
      A shader meta type for the simplest shaders; shaders which are not material or vertex factory linked. There should only a single instance of each simple shader type
    • FMaterialShaderType
      A shader meta type for material-linked shaders.
    • FMeshMaterialShaderType
      A shader meta type for material-linked shaders which use a vertex factory.
  2. Material Classes
    用于描述材质的类按所属子系统可以分为2组:

    • 对象子系统
    类名 描述
    UMaterialInterface [抽象类] 声明访问材质属性接口供game thread使用
    UMaterial 材质资源,可用材质编辑器打开进行材质编程,提供材质根结点中的material attribute实现代码,另外提供blend mode,cull mode等等渲染状态
    UMaterialInstance [抽象类] 基于UMaterial,它提供一些材质编程中的结点参数(scalars, vectors, textures, static switchs)。Each instance has a parent UMaterialInterface. Therefore a material instance's parent may be a UMaterial or another UMaterialInstance. This creates a chain that will eventually lead to a UMaterial.
    UMaterialInstanceConstant 提供材质编程中的结点参数(scalars, vectors, textures, static switchs), 这些参数只能在材质编辑器中调整。
    UMaterialInstanceDynamic A UMaterialInstance that may be modified at runtime. May provide scalar, vector, and texture parameters. It cannot provide static switch parameters and it cannot be the parent of another UMaterialInstance.

    类层次图:

    UObject
        UMaterialInterface
            UMaterial
            UMaterialInstance
                UMaterialInstanceConstant
                UMaterialInstanceDynamic
    
    • 渲染子系统
    类名 描述
    FMaterial An interface to a material used for rendering. Provides access to material properties (e.g. blend mode). Contains a shader map used by the renderer to retrieve individual shaders.
    FMaterialResource UMaterial's implementation of the FMaterial interface. UMaterial对象会生成FMaterialResource对象
    FMaterialRenderProxy A material's representation on the rendering thread. Provides access to an FMaterial interface and the current value of each scalar, vector, and texture parameter. 它带有材质参数值,这些值是从UMaterialInstance对象同步过来的, 供render-thread访问

    类层次图:

    FMaterial 
        FMaterialResource
    
    FRenderResource
        FMaterialRenderProxy
    

    现在来看一下这几个类的关系:
    UMaterial提供下面的接口可以获得FMaterialRenderProxy对象和FMaterialResource对象.

    virtual FMaterialResource* GetMaterialResource(ERHIFeatureLevel::Type InFeatureLevel, EMaterialQualityLevel::Type QualityLevel = EMaterialQualityLevel::Num) override;
    virtual FMaterialRenderProxy* GetRenderProxy(bool Selected, bool bHovered=false) const override;
    

    FMaterialRenderProxy提供下面的接口获得FMaterial(或FMaterialResource)对象

    virtual const class FMaterial* GetMaterial(ERHIFeatureLevel::Type InFeatureLevel) const = 0;
    

    FMaterial提供如下接口获取FShader对象

      template<typename ShaderType>
      ShaderType* GetShader(FVertexFactoryType* VertexFactoryType) const
      {
          return (ShaderType*)GetShader(&ShaderType::StaticType, VertexFactoryType);
      }
    

    综上所述,渲染器获得了FMaterialRenderProxy实例,就可以针对要渲染的Mesh取得最终的GPU Shader。

绘制Mesh

  • 场景中几何体需要提供给渲染器的信息
    现在以源码中的UJCustomMeshComponent的实现为研究对象来了解一下绘制一个Mesh时需要哪些信息。
    在Engine\Plugins\Runtime\CustomMeshComponent\Source\CustomMeshComponent\Private\JCustomMeshComponent.cpp中
    FJCustomMeshSceneProxy的对象是自定义的场景几何体Proxy,它通过GetDynamicMeshElements()接口向渲染层提供要绘制的dynamic Mesh.
   virtual void GetDynamicMeshElements(const TArray<const FSceneView*>& Views, const FSceneViewFamily& ViewFamily, uint32 VisibilityMap, FMeshElementCollector& Collector) const override
   {
       QUICK_SCOPE_CYCLE_COUNTER( STAT_CustomMeshSceneProxy_GetDynamicMeshElements );

       const bool bWireframe = AllowDebugViewmodes() && ViewFamily.EngineShowFlags.Wireframe;

       auto WireframeMaterialInstance = new FColoredMaterialRenderProxy(
           GEngine->WireframeMaterial ? GEngine->WireframeMaterial->GetRenderProxy(IsSelected()) : NULL,
           FLinearColor(0, 0.5f, 1.f)
           );

       Collector.RegisterOneFrameMaterialProxy(WireframeMaterialInstance);

       FMaterialRenderProxy* MaterialProxy = NULL;
       if(bWireframe)
       {
           MaterialProxy = WireframeMaterialInstance;
       }
       else
       {
           MaterialProxy = Material->GetRenderProxy(IsSelected());
       }

       for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
       {
           if (VisibilityMap & (1 << ViewIndex))
           {
               const FSceneView* View = Views[ViewIndex];
               // Draw the mesh.
               FMeshBatch& Mesh = Collector.AllocateMesh();
               FMeshBatchElement& BatchElement = Mesh.Elements[0];
               BatchElement.IndexBuffer = &IndexBuffer;     // element需要IndexBuffer
               Mesh.bWireframe = bWireframe;                // 是否线框模式绘制
               Mesh.VertexFactory = &VertexFactory;         // Vertex Factory对象
               Mesh.MaterialRenderProxy = MaterialProxy;    // Material Render Proxy对象
               BatchElement.PrimitiveUniformBuffer = CreatePrimitiveUniformBufferImmediate(GetLocalToWorld(), GetBounds(), GetLocalBounds(), true, UseEditorDepthTest());  // mesh element的传递给shader的mvp参数
               BatchElement.FirstIndex = 0;
               BatchElement.NumPrimitives = IndexBuffer.Indices.Num() / 3;
               BatchElement.MinVertexIndex = 0;
               BatchElement.MaxVertexIndex = VertexBuffer.Vertices.Num() - 1;
               Mesh.ReverseCulling = IsLocalToWorldDeterminantNegative();
               Mesh.Type = PT_TriangleList;
               Mesh.DepthPriorityGroup = SDPG_World;
               Mesh.bCanApplyViewModeOverrides = false;
               Collector.AddMesh(ViewIndex, Mesh);
           }
       }
   }
名称 描述
Vertex Factory Object 提供GPU的输入数据流, GPU shader参数
Matrial Render Proxy Object 提供渲染时的状态设置、GPU Shader对象、Shader的参数
Batch Element Mesh的sub-mesh (它们共用材质和顶点数据)
Mesh Types mesh的图元类型

一个FMeshBatch的绘制,主要需要如下信息:

名称 描述
Vertex Factory Object 提供GPU的输入数据流, GPU shader参数
Matrial Render Proxy Object 提供渲染时的状态设置、GPU Shader对象、Shader的参数
Batch Element Mesh的sub-mesh (它们共用材质和顶点数据)
Mesh Types mesh的图元类型
  • Render Policy
    RenderPolicy负责渲染meshes,不同类型的Render Policy使用不同的pass shader。它从FMeshBatch对象中知道了Vertex Factory, Material Render Proxy, Mesh Elements, 图元类型。通过执行如下步骤来绘制mesh.
  • 绑定Vertex Factory的Vertex Buffers,设置数据流input layout
  • 通过VertexFactory的类型和Pass Shader类型,从FMaterial对象获取特定的GPU Shader(vs, ps. etc), 设置当前的vertex shader和pixel shader。
  • 设置GPU shader参数
  • 发射RHI Draw命令
Function Description
Constructor Finds the appropriate shader from the given vertex factory and material shader map, stores these references
CreateBoundShaderState Creates an RHI bound shader state for the drawing policy.
Matches/Compare Provides methods to sort the drawing policy with others in the static draw lists. Matches must compare on all the factors that DrawShared depends on.
DrawShared Sets RHI state that is constant between drawing policies that return true from Matches. For example, most drawing policies sort on material and vertex factory, so shader parameters depending only on the material can be set, and the vertex buffers specific to the vertex factory can be bound. State should always be set here if possible instead of SetMeshRenderState, since DrawShared is called less times in the static rendering path.
SetMeshRenderState Sets RHI state that is specific to this mesh, or anything not set in DrawShared. This is called many more times than DrawShared so performance is especially critical here.
DrawMesh Actually issues the RHI draw call.

FMeshDrawingPolicy的方法

Function Description
Constructor Finds the appropriate shader from the given vertex factory and material shader map, stores these references
CreateBoundShaderState Creates an RHI bound shader state for the drawing policy.
Matches/Compare Provides methods to sort the drawing policy with others in the static draw lists. Matches must compare on all the factors that DrawShared depends on.
DrawShared Sets RHI state that is constant between drawing policies that return true from Matches. For example, most drawing policies sort on material and vertex factory, so shader parameters depending only on the material can be set, and the vertex buffers specific to the vertex factory can be bound. State should always be set here if possible instead of SetMeshRenderState, since DrawShared is called less times in the static rendering path.
SetMeshRenderState Sets RHI state that is specific to this mesh, or anything not set in DrawShared. This is called many more times than DrawShared so performance is especially critical here.
DrawMesh Actually issues the RHI draw call.

FMeshDrawingPolicy的类层次图:

FMeshDrawingPolicy
    FLandscapeGrassWeightDrawingPolicy
    FMeshDecalsDrawingPolicy
    FBasePassDrawingPolicy
    FConvertToUniformMeshDrawingPolicy
    FDepthDrawingPolicy
    FPositionOnlyDepthDrawingPolicy
    TDistortionMeshDrawingPolicy
    TLightMapDensityDrawingPolicy
    FHitProxyDrawingPolicy
    FShadowDepthDrawingPolicy
    FTranslucencyShadowDepthDrawingPolicy
    FVelocityDrawingPolicy
    FVoxelizeVolumeDrawingPolicy

下面阅读分析一下BasepassRender时,RenderPolicy的使用:

       /**
       * Renders the basepass for the dynamic data of a given DPG and View.
       *
       * @return true if anything was rendered to scene color
       */
       void FDeferredShadingSceneRenderer::RenderBasePassDynamicData(FRHICommandList& RHICmdList, const FViewInfo& View, const FDrawingPolicyRenderState& DrawRenderState, bool& bOutDirty)
       {
           SCOPE_CYCLE_COUNTER(STAT_DynamicPrimitiveDrawTime);
           SCOPED_DRAW_EVENT(RHICmdList, Dynamic);

           FBasePassOpaqueDrawingPolicyFactory::ContextType Context(false, ESceneRenderTargetsMode::DontSet);

           for (int32 MeshBatchIndex = 0; MeshBatchIndex < View.DynamicMeshElements.Num(); MeshBatchIndex++)
           {
               const FMeshBatchAndRelevance& MeshBatchAndRelevance = View.DynamicMeshElements[MeshBatchIndex];

               if ((MeshBatchAndRelevance.GetHasOpaqueOrMaskedMaterial() || ViewFamily.EngineShowFlags.Wireframe)
                   && MeshBatchAndRelevance.GetRenderInMainPass())
               {
                   const FMeshBatch& MeshBatch = *MeshBatchAndRelevance.Mesh;
                   FBasePassOpaqueDrawingPolicyFactory::DrawDynamicMesh(RHICmdList, View, Context, MeshBatch, true, DrawRenderState, MeshBatchAndRelevance.PrimitiveSceneProxy, MeshBatch.BatchHitProxyId, View.IsInstancedStereoPass());
               }
           }
       }
bool FBasePassOpaqueDrawingPolicyFactory::DrawDynamicMesh(
   FRHICommandList& RHICmdList, 
   const FViewInfo& View,
   ContextType DrawingContext,
   const FMeshBatch& Mesh,
   bool bPreFog,
   const FDrawingPolicyRenderState& DrawRenderState,
   const FPrimitiveSceneProxy* PrimitiveSceneProxy,
   FHitProxyId HitProxyId, 
   const bool bIsInstancedStereo
   )
{
   // Determine the mesh's material and blend mode.
   const FMaterial* Material = Mesh.MaterialRenderProxy->GetMaterial(View.GetFeatureLevel());
   const EBlendMode BlendMode = Material->GetBlendMode();

   // Only draw opaque materials.
   if (!IsTranslucentBlendMode(BlendMode) && ShouldIncludeDomainInMeshPass(Material->GetMaterialDomain()))
   {
       ProcessBasePassMesh(
           RHICmdList, 
           FProcessBasePassMeshParameters(
               Mesh,
               Material,
               PrimitiveSceneProxy,
               !bPreFog,
               DrawingContext.bEditorCompositeDepthTest,
               DrawingContext.TextureMode,
               View.GetFeatureLevel(), 
               bIsInstancedStereo
               ),
           FDrawBasePassDynamicMeshAction(
               RHICmdList,
               View,
               Mesh.DitheredLODTransitionAlpha,
               DrawRenderState,
               HitProxyId
               )
           );
       return true;
   }
   else
   {
       return false;
   }
}
   // BasePassDrawPolicy的使用流程,请看下面代码中的注释
   /** The action used to draw a base pass dynamic mesh element. */
   class FDrawBasePassDynamicMeshAction
   {
   public:
       const FViewInfo& View;
       FDrawingPolicyRenderState DrawRenderState;
       FHitProxyId HitProxyId;

       /** Initialization constructor. */
       FDrawBasePassDynamicMeshAction(
           FRHICommandList& InRHICmdList,
           const FViewInfo& InView,
           float InDitheredLODTransitionAlpha,
           const FDrawingPolicyRenderState& InDrawRenderState,
           const FHitProxyId InHitProxyId
           )
           : View(InView)
           , DrawRenderState(InDrawRenderState)
           , HitProxyId(InHitProxyId)
       {
           DrawRenderState.SetDitheredLODTransitionAlpha(InDitheredLODTransitionAlpha);
       }
       bool UseTranslucentSelfShadowing() const { return false; }
       const FProjectedShadowInfo* GetTranslucentSelfShadow() const { return NULL; }
       bool AllowIndirectLightingCache() const 
       { 
           const FScene* Scene = (const FScene*)View.Family->Scene;
           return View.Family->EngineShowFlags.IndirectLightingCache && Scene && Scene->PrecomputedLightVolumes.Num() > 0;
       }

       bool AllowIndirectLightingCacheVolumeTexture() const
       {
           return true;
       }

       /** Draws the translucent mesh with a specific light-map type, and shader complexity predicate. */
       template<typename LightMapPolicyType>
       void Process(
           FRHICommandList& RHICmdList, 
           const FProcessBasePassMeshParameters& Parameters,
           const LightMapPolicyType& LightMapPolicy,
           const typename LightMapPolicyType::ElementDataType& LightMapElementData
           )
       {
   #if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
           //TODO this codepath is probably disabled by SetDepthStencilStateForBasePass
           if(View.Family->EngineShowFlags.ShaderComplexity)
           {
               // When rendering masked materials in the shader complexity viewmode, 
               // We want to overwrite complexity for the pixels which get depths written,
               // And accumulate complexity for pixels which get killed due to the opacity mask being below the clip value.
               // This is accomplished by forcing the masked materials to render depths in the depth only pass, 
               // Then rendering in the base pass with additive complexity blending, depth tests on, and depth writes off.
               DrawRenderState.SetDepthStencilState(TStaticDepthStencilState<false,CF_DepthNearOrEqual>::GetRHI());
           }
           else if (View.Family->UseDebugViewPS() && View.Family->GetDebugViewShaderMode() != DVSM_OutputMaterialTextureScales)
           {
               if (Parameters.PrimitiveSceneProxy && Parameters.PrimitiveSceneProxy->IsSelected())
               {
                   DrawRenderState.SetDepthStencilState(TStaticDepthStencilState<true, CF_DepthNearOrEqual>::GetRHI());
               }
               else // If not selected, use depth equal to make alpha test stand out (goes with EarlyZPassMode = DDM_AllOpaque) 
               {
                   DrawRenderState.SetDepthStencilState(TStaticDepthStencilState<false, CF_Equal>::GetRHI());
               }
           }
   #endif
           const FScene* Scene = Parameters.PrimitiveSceneProxy ? Parameters.PrimitiveSceneProxy->GetPrimitiveSceneInfo()->Scene : NULL;

           const bool bRenderSkylight = Scene && Scene->ShouldRenderSkylightInBasePass(Parameters.BlendMode) && Parameters.ShadingModel != MSM_Unlit;
           const bool bRenderAtmosphericFog = IsTranslucentBlendMode(Parameters.BlendMode) && (Scene && Scene->HasAtmosphericFog() && Scene->ReadOnlyCVARCache.bEnableAtmosphericFog) && View.Family->EngineShowFlags.AtmosphericFog;

           bool bEnableReceiveDecalOutput = Scene != nullptr;
           // NOTE:
           //    临时RenderPolicy对象
           //
           TBasePassDrawingPolicy<LightMapPolicyType> DrawingPolicy(
               Parameters.Mesh.VertexFactory,
               Parameters.Mesh.MaterialRenderProxy,
               *Parameters.Material,
               Parameters.FeatureLevel,
               LightMapPolicy,
               Parameters.BlendMode,
               Parameters.TextureMode,
               bRenderSkylight,
               bRenderAtmosphericFog,
               ComputeMeshOverrideSettings(Parameters.Mesh),
               View.Family->GetDebugViewShaderMode(),
               Parameters.bEditorCompositeDepthTest,
               bEnableReceiveDecalOutput
               );

           // 设置渲染状态
           SetDepthStencilStateForBasePass(DrawRenderState, View, Parameters.Mesh, Parameters.PrimitiveSceneProxy, bEnableReceiveDecalOutput, DrawingPolicy.UseDebugViewPS(), nullptr, Parameters.bEditorCompositeDepthTest);
           DrawingPolicy.SetupPipelineState(DrawRenderState, View);
           // 设置GPU Shaders
           CommitGraphicsPipelineState(RHICmdList, DrawingPolicy, DrawRenderState, DrawingPolicy.GetBoundShaderStateInput(View.GetFeatureLevel()));
           // 设置GPU Shaders的uniforms参数
           DrawingPolicy.SetSharedState(RHICmdList, DrawRenderState, &View, typename TBasePassDrawingPolicy<LightMapPolicyType>::ContextDataType(Parameters.bIsInstancedStereo));

           for( int32 BatchElementIndex = 0, Num = Parameters.Mesh.Elements.Num(); BatchElementIndex < Num; BatchElementIndex++ )
           {
               // We draw instanced static meshes twice when rendering with instanced stereo. Once for each eye.
               const bool bIsInstancedMesh = Parameters.Mesh.Elements[BatchElementIndex].bIsInstancedMesh;
               const uint32 InstancedStereoDrawCount = (Parameters.bIsInstancedStereo && bIsInstancedMesh) ? 2 : 1;
               for (uint32 DrawCountIter = 0; DrawCountIter < InstancedStereoDrawCount; ++DrawCountIter)
               {
                   DrawingPolicy.SetInstancedEyeIndex(RHICmdList, DrawCountIter);

                   TDrawEvent<FRHICommandList> MeshEvent;
                   BeginMeshDrawEvent(RHICmdList, Parameters.PrimitiveSceneProxy, Parameters.Mesh, MeshEvent);

                   // 设置状态和参数
                   DrawingPolicy.SetMeshRenderState(
                       RHICmdList,
                       View,
                       Parameters.PrimitiveSceneProxy,
                       Parameters.Mesh,
                       BatchElementIndex,
                       DrawRenderState,
                       typename TBasePassDrawingPolicy<LightMapPolicyType>::ElementDataType(LightMapElementData),
                       typename TBasePassDrawingPolicy<LightMapPolicyType>::ContextDataType()
                       );
                   // 发射Draw命令
                   DrawingPolicy.DrawMesh(RHICmdList, Parameters.Mesh, BatchElementIndex, Parameters.bIsInstancedStereo);
               }
           }

   #if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
           //TODO this codepath is probably disabled by SetDepthStencilStateForBasePass
           if(View.Family->EngineShowFlags.ShaderComplexity)
           {
               DrawRenderState.SetDepthStencilState(TStaticDepthStencilState<true,CF_DepthNearOrEqual>::GetRHI());
           }
   #endif
       }
   };

Shader和Shader Parameters

每个GPU Shader编译后,都会有个shader参数表(用FShaderParameterMap表示), FShaderParameter描述shader参数名与分配的寄存器信息。因为一个GPU Shader是由vertex-factory shader、material shader和 pass shader组合成的,所以这3个部分均可以产生shader parameters。下面我们要弄清楚,在RenderPolicy渲染mesh时,哪些对象参与设置了shader parameters,提供的接口是什么。

  1. 从FShader(含子类)对象考虑:
  • FGlobalShader
  template<typename TViewUniformShaderParameters, typename ShaderRHIParamRef, typename TRHICmdList>
  inline void SetParameters(TRHICmdList& RHICmdList, const ShaderRHIParamRef ShaderRHI, const FUniformBufferRHIParamRef ViewUniformBuffer)
  {
      const auto& ViewUniformBufferParameter = static_cast<const FShaderUniformBufferParameter&>(GetUniformBufferParameter<TViewUniformShaderParameters>());
      CheckShaderIsValid();
      SetUniformBufferParameter(RHICmdList, ShaderRHI, ViewUniformBufferParameter, ViewUniformBuffer);
  }
  • FMaterialShader
  template<typename ShaderRHIParamRef>
  FORCEINLINE_DEBUGGABLE void SetViewParameters(FRHICommandList& RHICmdList, const ShaderRHIParamRef ShaderRHI, const FSceneView& View, const TUniformBufferRef<FViewUniformShaderParameters>& ViewUniformBuffer)
  {
      const auto& ViewUniformBufferParameter = GetUniformBufferParameter<FViewUniformShaderParameters>();
      const auto& BuiltinSamplersUBParameter = GetUniformBufferParameter<FBuiltinSamplersParameters>();
      CheckShaderIsValid();
      SetUniformBufferParameter(RHICmdList, ShaderRHI, ViewUniformBufferParameter, ViewUniformBuffer);
#if USE_GBuiltinSamplersUniformBuffer
      SetUniformBufferParameter(RHICmdList, ShaderRHI, BuiltinSamplersUBParameter, GBuiltinSamplersUniformBuffer.GetUniformBufferRHI());
#endif

      if (View.bShouldBindInstancedViewUB && View.Family->Views.Num() > 0)
      {
          // When drawing the left eye in a stereo scene, copy the right eye view values into the instanced view uniform buffer.
          const EStereoscopicPass StereoPassIndex = (View.StereoPass != eSSP_FULL) ? eSSP_RIGHT_EYE : eSSP_FULL;

          const FSceneView& InstancedView = View.Family->GetStereoEyeView(StereoPassIndex);
          const auto& InstancedViewUniformBufferParameter = GetUniformBufferParameter<FInstancedViewUniformShaderParameters>();
          SetUniformBufferParameter(RHICmdList, ShaderRHI, InstancedViewUniformBufferParameter, InstancedView.ViewUniformBuffer);
      }
  }
  /** Sets pixel parameters that are material specific but not FMeshBatch specific. */
  template< typename ShaderRHIParamRef >
  void SetParameters(
      FRHICommandList& RHICmdList,
      const ShaderRHIParamRef ShaderRHI, 
      const FMaterialRenderProxy* MaterialRenderProxy, 
      const FMaterial& Material,
      const FSceneView& View, 
      const TUniformBufferRef<FViewUniformShaderParameters>& ViewUniformBuffer,
      bool bDeferredPass, 
      ESceneRenderTargetsMode::Type TextureMode);
  • FMeshMaterialShader
    // 设置材质(material)需要的参数, 不排除其它参数
     template< typename ShaderRHIParamRef >
     void SetParameters(
         FRHICommandList& RHICmdList,
         const ShaderRHIParamRef ShaderRHI,
         const FMaterialRenderProxy* MaterialRenderProxy,
         const FMaterial& Material,
         const FSceneView& View,
         const TUniformBufferRef<FViewUniformShaderParameters>& ViewUniformBuffer,
         ESceneRenderTargetsMode::Type TextureMode)
     {
         FMaterialShader::SetParameters(RHICmdList, ShaderRHI, MaterialRenderProxy, Material, View, ViewUniformBuffer, false, TextureMode);
     }
    
    // 设置Vertex Factory的shader参数
     void SetVFParametersOnly(FRHICommandList& RHICmdList, const FVertexFactory* VertexFactory,const FSceneView& View,const FMeshBatchElement& BatchElement)
     {
         VertexFactoryParameters.SetMesh(RHICmdList, this,VertexFactory,View,BatchElement, 0);
     }
    
    // 设置跟mesh相关的参数
     template< typename ShaderRHIParamRef >
     void FMeshMaterialShader::SetMesh(
         FRHICommandList& RHICmdList,
         const ShaderRHIParamRef ShaderRHI,
         const FVertexFactory* VertexFactory,
         const FSceneView& View,
         const FPrimitiveSceneProxy* Proxy,
         const FMeshBatchElement& BatchElement,
         const FDrawingPolicyRenderState& DrawRenderState,
         uint32 DataFlags )
     {
         // Set the mesh for the vertex factory
         VertexFactoryParameters.SetMesh(RHICmdList, this,VertexFactory,View,BatchElement, DataFlags);
    
         // 设置LocalToworld, WorldToLocal等参数!!
         if(IsValidRef(BatchElement.PrimitiveUniformBuffer))
         {
             SetUniformBufferParameter(RHICmdList, ShaderRHI,GetUniformBufferParameter<FPrimitiveUniformShaderParameters>(),BatchElement.PrimitiveUniformBuffer);
         }
         else
         {
             check(BatchElement.PrimitiveUniformBufferResource);
             SetUniformBufferParameter(RHICmdList, ShaderRHI,GetUniformBufferParameter<FPrimitiveUniformShaderParameters>(),*BatchElement.PrimitiveUniformBufferResource);
         }
    
         TShaderUniformBufferParameter<FDistanceCullFadeUniformShaderParameters> LODParameter = GetUniformBufferParameter<FDistanceCullFadeUniformShaderParameters>();
         if( LODParameter.IsBound() )
         {
             SetUniformBufferParameter(RHICmdList, ShaderRHI,LODParameter,GetPrimitiveFadeUniformBufferParameter(View, Proxy));
         }
         if (NonInstancedDitherLODFactorParameter.IsBound())
         {
             SetShaderValue(RHICmdList, ShaderRHI, NonInstancedDitherLODFactorParameter, DrawRenderState.GetDitheredLODTransitionAlpha());
         }
     }
    
    另外, FMeshMaterialShader的子类根据不同pass shader的实现,会改写SetParameters()和SetMesh()来设置gpu shader参数, 例如:FShadowDepthVS.
    SetParameters()在FMeshDrawingPolicy::SetSharedState()中被调用:
     /**
      * Executes the draw commands which can be shared between any meshes using this drawer.
      * @param CI - The command interface to execute the draw commands on.
      * @param View - The view of the scene being drawn.
      */
      void SetSharedState(FRHICommandList& RHICmdList, const FDrawingPolicyRenderState& DrawRenderState, const FSceneView* View, const FMeshDrawingPolicy::ContextDataType PolicyContext) const;
    
    SetMesh()在FMeshDrawingPolicy::SetMeshRenderState()中被调用
    /**
     * Sets the render states for drawing a mesh.
     * @param PrimitiveSceneProxy - The primitive drawing the dynamic mesh.  If this is a view element, this will be NULL.
     */
    FORCEINLINE_DEBUGGABLE void SetMeshRenderState(
        FRHICommandList& RHICmdList, 
        const FSceneView& View,
        const FPrimitiveSceneProxy* PrimitiveSceneProxy,
        const FMeshBatch& Mesh,
        int32 BatchElementIndex,
        const FDrawingPolicyRenderState& DrawRenderState,
        const ElementDataType& ElementData,
        const ContextDataType PolicyContext
        ) const;
    

Uniform Buffer

源码路径[Engine\Source\Runtime\RenderCore\Public\UniformBuffer.h]
Uniform Buffer用于从CPU传送shader parameters到GPU。下面的宏用于在CPP代码中定义Uniform Buffer结构体,并且被ShaderCompiler使用,产生usf文件中uniform buffer的结构定义,例如:Primitive, View, InstancedView, BuiltinSamplers, MobileDirectionalLight。
在C++中的定义如下。

IMPLEMENT_UNIFORM_BUFFER_STRUCT(FPrimitiveUniformShaderParameters,TEXT("Primitive"));
IMPLEMENT_UNIFORM_BUFFER_STRUCT(FViewUniformShaderParameters,TEXT("View"));
IMPLEMENT_UNIFORM_BUFFER_STRUCT(FInstancedViewUniformShaderParameters, TEXT("InstancedView"));
IMPLEMENT_UNIFORM_BUFFER_STRUCT(FBuiltinSamplersParameters, TEXT("BuiltinSamplers"));
IMPLEMENT_UNIFORM_BUFFER_STRUCT(FMobileDirectionalLightShaderParameters, TEXT("MobileDirectionalLight"));
//
// Macros for declaring uniform buffer structures.
//

#define IMPLEMENT_UNIFORM_BUFFER_STRUCT(StructTypeName,ShaderVariableName) \
    FUniformBufferStruct StructTypeName::StaticStruct( \
    FName(TEXT(#StructTypeName)), \
    TEXT(#StructTypeName), \
    ShaderVariableName, \
    StructTypeName::ConstructUniformBufferParameter, \
    sizeof(StructTypeName), \
    StructTypeName::zzGetMembers(), \
    true);

/** Begins a uniform buffer struct declaration. */
#define BEGIN_UNIFORM_BUFFER_STRUCT_EX(StructTypeName,PrefixKeywords,ConstructorSuffix) \
    MS_ALIGN(UNIFORM_BUFFER_STRUCT_ALIGNMENT) class PrefixKeywords StructTypeName \
    { \
    public: \
        StructTypeName () ConstructorSuffix \
        static FUniformBufferStruct StaticStruct; \
        static FShaderUniformBufferParameter* ConstructUniformBufferParameter() { return new TShaderUniformBufferParameter<StructTypeName>(); } \
        static FUniformBufferRHIRef CreateUniformBuffer(const StructTypeName& InContents, EUniformBufferUsage InUsage) \
        { \
            return RHICreateUniformBuffer(&InContents,StaticStruct.GetLayout(),InUsage); \
        } \
    private: \
        typedef StructTypeName zzTThisStruct; \
        struct zzFirstMemberId { enum { HasDeclaredResource = 0 }; }; \
        static TArray<FUniformBufferStruct::FMember> zzGetMembersBefore(zzFirstMemberId) \
        { \
            return TArray<FUniformBufferStruct::FMember>(); \
        } \
        typedef zzFirstMemberId

/** Declares a member of a uniform buffer struct. */
#define DECLARE_UNIFORM_BUFFER_STRUCT_MEMBER_EXPLICIT(MemberType,MemberName,ArrayDecl,Precision,OptionalShaderType) \
        zzMemberId##MemberName; \
    public: \
        typedef MemberType zzA##MemberName ArrayDecl; \
        typedef TUniformBufferTypeInfo<zzA##MemberName>::TAlignedType zzT##MemberName; \
        zzT##MemberName MemberName; \
        static_assert(TUniformBufferTypeInfo<zzA##MemberName>::BaseType != UBMT_INVALID, "Invalid type " #MemberType " of member " #MemberName "."); \
        static_assert(TUniformBufferTypeInfo<zzA##MemberName>::BaseType != UBMT_UAV, "UAV is not yet supported in resource tables for " #MemberName " of type " #MemberType "."); \
    private: \
        struct zzNextMemberId##MemberName { enum { HasDeclaredResource = zzMemberId##MemberName::HasDeclaredResource || TUniformBufferTypeInfo<zzT##MemberName>::IsResource }; }; \
        static TArray<FUniformBufferStruct::FMember> zzGetMembersBefore(zzNextMemberId##MemberName) \
        { \
            static_assert(TUniformBufferTypeInfo<zzT##MemberName>::IsResource == 1 || zzMemberId##MemberName::HasDeclaredResource == 0, "All resources must be declared last for " #MemberName "."); \
            static_assert(TUniformBufferTypeInfo<zzT##MemberName>::IsResource == 0 || IS_TCHAR_ARRAY(OptionalShaderType), "No shader type for " #MemberName "."); \
            /* Route the member enumeration on to the function for the member following this. */ \
            TArray<FUniformBufferStruct::FMember> OutMembers = zzGetMembersBefore(zzMemberId##MemberName()); \
            /* Add this member. */ \
            OutMembers.Add(FUniformBufferStruct::FMember( \
                TEXT(#MemberName), \
                OptionalShaderType, \
                STRUCT_OFFSET(zzTThisStruct,MemberName), \
                (EUniformBufferBaseType)TUniformBufferTypeInfo<zzA##MemberName>::BaseType, \
                Precision, \
                TUniformBufferTypeInfo<zzA##MemberName>::NumRows, \
                TUniformBufferTypeInfo<zzA##MemberName>::NumColumns, \
                TUniformBufferTypeInfo<zzA##MemberName>::NumElements, \
                TUniformBufferTypeInfo<zzA##MemberName>::GetStruct() \
                )); \
            static_assert( \
                (STRUCT_OFFSET(zzTThisStruct,MemberName) & (TUniformBufferTypeInfo<zzA##MemberName>::Alignment - 1)) == 0, \
                "Misaligned uniform buffer struct member " #MemberName "."); \
            return OutMembers; \
        } \
        typedef zzNextMemberId##MemberName

#define DECLARE_UNIFORM_BUFFER_STRUCT_MEMBER_ARRAY_EX(MemberType,MemberName,ArrayDecl,Precision) DECLARE_UNIFORM_BUFFER_STRUCT_MEMBER_EXPLICIT(MemberType,MemberName,ArrayDecl,Precision,TEXT(""))

#define DECLARE_UNIFORM_BUFFER_STRUCT_MEMBER_ARRAY(MemberType,MemberName,ArrayDecl) DECLARE_UNIFORM_BUFFER_STRUCT_MEMBER_EXPLICIT(MemberType,MemberName,ArrayDecl,EShaderPrecisionModifier::Float,TEXT(""))

#define DECLARE_UNIFORM_BUFFER_STRUCT_MEMBER(MemberType,MemberName) DECLARE_UNIFORM_BUFFER_STRUCT_MEMBER_EXPLICIT(MemberType,MemberName,,EShaderPrecisionModifier::Float,TEXT(""))

#define DECLARE_UNIFORM_BUFFER_STRUCT_MEMBER_EX(MemberType,MemberName,Precision) DECLARE_UNIFORM_BUFFER_STRUCT_MEMBER_EXPLICIT(MemberType,MemberName,,Precision,TEXT(""))

#define DECLARE_UNIFORM_BUFFER_STRUCT_MEMBER_SRV(ShaderType,MemberName) DECLARE_UNIFORM_BUFFER_STRUCT_MEMBER_EXPLICIT(FShaderResourceViewRHIParamRef,MemberName,,EShaderPrecisionModifier::Float,TEXT(#ShaderType))

// NOT SUPPORTED YET
//#define DECLARE_UNIFORM_BUFFER_STRUCT_MEMBER_UAV(ShaderType,MemberName) DECLARE_UNIFORM_BUFFER_STRUCT_MEMBER_EXPLICIT(FUnorderedAccessViewRHIParamRef,MemberName,,EShaderPrecisionModifier::Float,TEXT(#ShaderType))

#define DECLARE_UNIFORM_BUFFER_STRUCT_MEMBER_SAMPLER(ShaderType,MemberName) DECLARE_UNIFORM_BUFFER_STRUCT_MEMBER_EXPLICIT(FSamplerStateRHIParamRef,MemberName,,EShaderPrecisionModifier::Float,TEXT(#ShaderType))

#define DECLARE_UNIFORM_BUFFER_STRUCT_MEMBER_TEXTURE(ShaderType,MemberName) DECLARE_UNIFORM_BUFFER_STRUCT_MEMBER_EXPLICIT(FTextureRHIParamRef,MemberName,,EShaderPrecisionModifier::Float,TEXT(#ShaderType))

/** Ends a uniform buffer struct declaration. */
#define END_UNIFORM_BUFFER_STRUCT(Name) \
            zzLastMemberId; \
        static TArray<FUniformBufferStruct::FMember> zzGetMembers() { return zzGetMembersBefore(zzLastMemberId()); } \
    } GCC_ALIGN(UNIFORM_BUFFER_STRUCT_ALIGNMENT); \
    template<> class TUniformBufferTypeInfo<Name> \
    { \
    public: \
        enum { BaseType = UBMT_STRUCT }; \
        enum { NumRows = 1 }; \
        enum { NumColumns = 1 }; \
        enum { Alignment = UNIFORM_BUFFER_STRUCT_ALIGNMENT }; \
        static const FUniformBufferStruct* GetStruct() { return &Name::StaticStruct; } \
    };

#define BEGIN_UNIFORM_BUFFER_STRUCT(StructTypeName,PrefixKeywords) BEGIN_UNIFORM_BUFFER_STRUCT_EX(StructTypeName,PrefixKeywords,{})
#define BEGIN_UNIFORM_BUFFER_STRUCT_WITH_CONSTRUCTOR(StructTypeName,PrefixKeywords) BEGIN_UNIFORM_BUFFER_STRUCT_EX(StructTypeName,PrefixKeywords,;)

/** Finds the FUniformBufferStruct corresponding to the given name, or NULL if not found. */
extern RENDERCORE_API FUniformBufferStruct* FindUniformBufferStructByName(const TCHAR* StructName);
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容