引言
此篇我们将介绍绘制策略,绘制策略工厂以及它们如何与我们迄今为止学到的所有系统进行交互。我们还将快速了解Unreal是如何进行绘制网格的!
纯照明通道的EpicGame照片角色实例
绘制政策
虚幻中的绘制策略更像是一个概念,而不是特定的类,因为它们并不都共享相同的基类。从概念上讲,绘制策略确定哪些着色器变体用于绘制某些内容,但它不会选择它绘制的内容与时机!我们将看两个绘制策略,一个用于Unreal的深度预传,另一个用于Unreal的基本传递,这要复杂得多。
一个绘制策略类
FDepthDrawingPolicy
深度绘制策略是绘制策略可以有多么简单的一个很好的例子。在它的构造函数中,它要求材质为特定顶点工厂找到特定类型的着色器:
VertexShader =InMaterial.GetShader<TDepthOnlyVS<false>>(VertexFactory->GetType());
TDepthOnlyVS是FMeshMaterialShader的一个实现,并使用适当的宏将自己声明为着色器。 虚幻的句柄试图编译材质/着色器/顶点工厂的所有可能的排列,所以它应该能够找到它。如果您的ShouldCache函数设置不正确,那么它将在此失败,引擎抛出一个断言并让你修复它!
然后,它会查看它应该绘制的材料,以确定该材质是否启用了曲面细分(tessellation) - 如果它确实已启用,则深度绘制策略会查找外壳(hull)和域着色器(domainshader):
HullShader= InMaterial.GetShader<FDepthOnlyHS>(VertexFactory->GetType());
DomainShader =InMaterial.GetShader<FDepthOnlyDS>(VertexFactory->GetType());
绘制策略还可以通过SetSharedState和SetMeshRenderState函数在着色器上设置参数,但它们通常只是将这些参数传递给当前绑定的着色器。
FBasePassDrawingPolicy
这是Unreal开始使用他们的宏和模板变得棘手的地方。我们来考虑一下延迟渲染中的基本通道。你有许多不同的材质使用不同的硬件功能(如曲面细分),使用不同的顶点工厂,你需要特定于灯光的变化。这是一个庞大的排列,Unreal使用几个宏来实现这一点。如果这没有多大意义,请不要担心。进行更改并不重要,知道它存在即可。
他们首先会制作一个FBasePassDrawingPolicy模板名为template<typename LightMapPolicyType> class TBasePassDrawingPolicy:publicFBasePassDrawingPolicy,构造函数只调用另一个模板函数。这反过来调用另一个模板函数,但这次是针对每种照明类型的特定枚举。
现在他们知道他们所试图获得着色器的照明策略,他们使用与深度绘制策略相同的InMaterial.GetShader函数,但这次他们得到了一个模板化的着色器类!
VertexShader= InMaterial.GetShader<TBasePassVS<TUniformLightMapPolicy<Policy>,false>>(VertexFactoryType);
感谢你一路关注模板链,但重要的是要了解Unreal如何了解所有可能的实现。答案就是几个嵌套的宏!跳到BasePassRendering.cpp的顶部,我们来从头到尾看它们。
第一个宏是IMPLEMENT_BASEPASS_VERTEXSHADER_TYPE,它通过创建新的typedef来为给定的LightMapPolicyType和LightMapPolicyName注册顶点,外壳和域材质着色器(使用我们在Shaders一节中讨论过的IMPLEMENT_MATERIAL_SHADER_TYPE宏)。所以现在我们知道调用IMPLEMENT_BASEPASS_VERTEXSHADER_TYPE为我们注册顶点着色器。
第二个宏是IMPLEMENT_BASEPASS_LIGHTMAPPED_SHADER_TYPE,它接受LightMapPolicyType和LightMapPolicyName并调用IMPLEMENT_BASEPASS_VERTEXSHADER_TYPE和IMPLEMENT_BASEPASS_PIXELSHADER_TYPE(我们没有谈到,但工作方式与顶点相同)。这个宏让我们为任何给定的LightMap创建一个完整的着色器链(顶点和像素)。最后,Unreal将此宏调用16次,传递LightMapPolicyTypes和LightMapPolicyNames的不同组合。
在之前调用InMaterial.GetShader<...>函数的过程中,其中一个函数为每个LightMapPolicyType都有一个大的switch语句,以返回正确的函数。因此,我们知道Unreal正在为我们宣布所有变体,因此GetShader能够做出正确的事情!
绘图政策工厂
因此我们知道绘制策略确定了给定材质和顶点工厂使用哪个特定着色器,这允许Unreal创建诸如“获取仅深度着色器”或“获得具有点光源代码的着色器”的策略。但是是什么生成了一个绘制策略,它如何知道要生成哪一个?它如何知道要绘制什么?这就是绘制策略工厂的用武之地。他们检查材质或顶点工厂的状态,然后可以创建正确的绘制策略。
FDepthDrawingPolicyFactory
我们使用FDepthDrawingPolicyFactory来作为(相对)简单的示例。 只有三个函数,AddStaticMesh,DrawDynamicMesh和DrawStaticMesh。 调用AddStaticMesh时,PolicyFactory将查看有关要绘制的材质和资源的设置,并确定要创建的相应绘制策略。然后,Unreal将该绘制策略放入即将绘制的FScene内的列表中。
例如,FDepthDrawingPolicyFactory查看材质以查看它是否修改了网格位置。 如果它修改了网格位置,那么它会创建一个FDepthDrawingPolicy并将其添加到FScene内部的“MaskedDepthDrawList”中。 如果材质没有修改网格位置,那么它会创建一个FPositionOnlyDepthDrawingPolicy(它会查找不同的着色器变体!)并将其添加到FScene中的不同列表中。
绘制策略工厂创建绘制策略
FDepthDrawingPolicyFactory还能够绘制给定的网格批次,再次检查设置并创建绘制策略。但是,不是将其添加到列表中,而是通过RHI层为GPU设置状态,然后调用另一个绘制策略来实际绘制网格。
告诉绘图政策工厂进行绘制
最终我们知道了它的根源,并了解所有这些部分是如何发挥作用的。 还记得绘制策略或绘图政策工厂没有共享基类吗? 我们已经达到了“代码只是专门了解它们并在不同时间调用它们”的程度。
FStaticMesh::AddToDrawLists
我们的FDepthDrawingPolicyFactory有一个名为AddStaticMesh的函数,因此创建它的类与静态网格物体相关并不奇怪!调用AddToDrawLists时,它会检查资源和项目设置以决定如何处理它。它首先调用
FHitProxyDrawingPolicyFactory :: AddStaticMesh,然后调用
FShadowDepthDrawingPolicyFactory ::AddStaticMesh,然后调用
FDepthDrawingPolicyFactory :: AddStaticMesh,最后调用
FBasePassOpaqueDrawingPolicyFactory ::AddStaticMesh和
FVelocityDrawingPolicyFactory :: AddStaticMesh,哇!
因此,我们知道何时将FStaticMesh标记为要添加到绘制列表中,它会创建各种绘制策略工厂(然后创建绘制策略并将其添加到正确的列表中)。调用此函数的具体细节并不重要(虽然请参阅FPrimitiveSceneInfo :: AddStaticMeshes并从那里开始),但我们知道必须告诉深度传递在基本传递之前绘制以及执行阴影,等等
FDeferredShadingRenderer,这是一个庞大的类,可以处理按正确顺序绘制的所有内容。 FDeferredShadingRenderer:: Render启动整个过程并控制渲染操作的顺序。我们来看看基本绘图政策工厂; Render函数调用
FDeferredShadingSceneRenderer :: RenderBasePass,后者又调用
FDeferredShadingSceneRenderer ::RenderBasePassView调用
FDeferredShadingSceneRenderer ::RenderBasePassDynamicData,它最终在循环中调用我们的
FBasePassOpaqueDrawingPolicyFactory ::DrawDynamicMesh,每次都向它传递一个不同的网格。
综述
绘图策略为给定材质,顶点工厂和着色器组合找到正确的着色器排列。 开发人员根据策略指定着色器类型以完成不同的事情。 绘图策略工厂处理创建绘图策略并将其添加到适当的列表。最后,通过一个长链继承,FDeferredShadingRenderer :: Render最终循环遍历各个列表并调用它们的Draw函数。