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)。
Static Render Path
在FScene对象中存在一些static draw list,在PrimitiveSceneProxy被插入场景中时,会通过调用FPrimitveSceneProxy::DrawStaticElements()来收集FStaticMeshElements数据。 然后创建相应的drawing policy对象实例放入到draw list中去。这个drawing policy对象是按照材质排序放入到draw list中的。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顺序并逐一介绍.
- 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;
}
-
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
);
-
PASS_2: Issue Occlusion Queries / BeginOcclusionTests
执行遮挡查询,在绘制下一帧时,InitView会使用这些信息进行可见性判断。遮挡查询的原理是通过绘制几何体的包围盒进行z-depth测试,以粗略地判断该几何体是否被遮挡。
void FDeferredShadingSceneRenderer::RenderOcclusion(FRHICommandListImmediate& RHICmdList, bool bRenderQueries, bool bRenderHZB)
-
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);
}
}
}
}
-
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();
}
-
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)
-
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)
-
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)
-
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
-
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源码中相关类
-
FShader类
FShader类的继承体系如下:
这些类实例对象代表一个GPU shader资源(vs,ps,cs),它由UE4的ShaderCompiler系统生成。同时每个类都对应一个ShaderType实例,用于描述这些类用什么pass shader(即哪个usf源文件). 例如下面几个Shader类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 ...
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;
};
-
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.
- FGlobalShaderType
-
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,提供的接口是什么。
- 从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); }
另外, FMeshMaterialShader的子类根据不同pass shader的实现,会改写SetParameters()和SetMesh()来设置gpu shader参数, 例如:FShadowDepthVS.// 设置跟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()); } }
SetParameters()在FMeshDrawingPolicy::SetSharedState()中被调用:
SetMesh()在FMeshDrawingPolicy::SetMeshRenderState()中被调用/** * 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;
/** * 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);