UE4渲染代码逻辑总结(上)
01 Jan 2018 shader UE4
本文是对UE4中渲染代码逻辑的总结。
当前使用的UE4版本为4.18.3。
本文内容是之前提到的这段时间的笔记的总结,内容主要来自于社区文章、官方文档以及引擎源码的阅读。
由于渲染系统比较复杂,没有时间遍历所有的代码,很多地方掺杂了自己的臆测,如有错误,欢迎指正。
FShader
FShader是负责Shader的代码端基类,继承自FDeferredCleanupInterface。
结构
FDeferredCleanupInterface
FDeferredCleanupInterface这个基类是用于游戏线程与渲染线程的同步的,只有一个纯虚函数:FinishCleanup。
由于渲染用的资源在两边都有在使用,所以当要删除一个资源时,会向渲染线程推送资源删除请求,同时通过BeginCleanup将其加入待删除列表,等到渲染线程结束时FinishCleanup就会被调用,这时候就可以安全的在游戏线程中完全释放资源了。
除了FShader之外,其他与渲染有关的资源如FLightMap以及FShadowMap等都有继承自这个类。
FShaderResource
这个类也继承自FDeferredCleanupInterface,如其名称,它负责保管Shader编译之后的资源。
为了减小材质编译等对资源的占用,同一个FShaderResource会被多个FShader引用。例如Material Function就利用了这个机制。
使用
FShader共有两种类型的之类,分别是FGlobalShader与FMaterialShader,对应不同的使用用途。
FGlobalShader
这个是全局的Shader,只允许存在一个实例。
渲染的核心Shader部分有许多就是FGlobalShader。
FMaterialShader
这个是用于具体材质的Shader,会拥有很多实例。
进一步被实现为FMeshMaterialShader,可以将Mesh的顶点数据引入到Shader端。
渲染数据
渲染数据通过FPrimitiveSceneProxy经由结构FVertexFactory绑定到Shader上。
FVertexFactory
这个类负责将顶点数据从C++端带到Shader端,继承自FRenderResource,是渲染资源的一种。
FLocalVertexFactory
提供本地空间到全局空间的转换,Static Mesh以及Cables、Procedual Mesh等都使用的是它。
FGPUBaseSkinVertexFactory
这个是Skeletel Mesh用的,因为需要一些更多的数据。但是似乎还要配合继承自LocalVertexFactory的FGPUSkinPassthroughVertexFactory。
FLandscapeVertexFactory
Landscape是基于VTF(Vertex Texture Fetch),使用高度图来修改顶点位置实现的,所以需要额外的处理。
FParticleVertexFactoryBase
粒子系统用的。
FPrimitiveSceneProxy
这个类似UPrimitiveComponent在渲染线程中的对应版本,负责维护每个Component在渲染线程上需要的数据。
UE4的核心类大多有自己在渲染线程中的对应
游戏线程 | 渲染线程 |
---|---|
UWorld | FScene |
UPrimitiveComponent | FPrimitiveSceneProxy / FPrimitiveSceneInfo |
ULocalPlayer | FSceneViewState |
ULightComponent | FLightSceneProxy / FLightSceneInfo |
不同的UPrimitiveComponent类通过重载CreateSceneProxy()来创建自己的FPrimitiveSceneProxy。
UCableComponent
非常的直观,在CreateSceneProxy()中直接创建FCableSceneProxy,然后进行初始化。
然后在SendRenderDyamicData_Concurrent()中将数据发送到渲染线程并借由SetDynamicData_RenderThread进行数据构造。
UImagePlateFrustumComponent
由于始终只是在渲染面向摄像的2D材质,所以不需要VertexFactory。
所以FImagePlateFrustumSceneProxy只是在GetDynamicMeshElements时返回演算的结果。
Drawing Policy
FDepthDrawingPolicy
这个Drawing Policy工作于depth-only通道时,负责将Mesh的opaque和masked的深度信息写出。
通过调用
VertexShader = InMaterialResource.GetShader<TDepthOnlyVS<false> >(VertexFactory->GetType())
DrawingPolicy便找到了对应的shader,并将vertex factory传了进去。
在需要Tessellation的情况下,还会另外获取HullShader和DomainShader
HullShader = InMaterialResource.GetShader<FDepthOnlyHS>(VertexFactory->GetType());
DomainShader = InMaterialResource.GetShader<FDepthOnlyDS>(VertexFactory->GetType());
代码的分支很多,但是作用还是比较明显的。
其中还有SetSharedState和SetMeshRenderState两个函数负责传递参数。
FBasePassDrawingPolicy
这个是在basepass通道时处理Mesh,根据不同的光照类型会有不同的处理
template<typename LightMapPolicyType>
class TBasePassDrawingPolicy : public FBasePassDrawingPolicy
这个类才算是本体的感觉吧。
会根据不同的光照类型获取不同的Base pass的shader,这里的光照类型并不是单纯的编辑器中设置的类型,而是实际在Shader中用于计算的光照模型
enum ELightMapPolicyType
{
LMP_NO_LIGHTMAP,
LMP_PRECOMPUTED_IRRADIANCE_VOLUME_INDIRECT_LIGHTING,
LMP_CACHED_VOLUME_INDIRECT_LIGHTING,
LMP_CACHED_POINT_INDIRECT_LIGHTING,
LMP_SIMPLE_NO_LIGHTMAP,
LMP_SIMPLE_LIGHTMAP_ONLY_LIGHTING,
LMP_SIMPLE_DIRECTIONAL_LIGHT_LIGHTING,
LMP_SIMPLE_STATIONARY_PRECOMPUTED_SHADOW_LIGHTING,
LMP_SIMPLE_STATIONARY_SINGLESAMPLE_SHADOW_LIGHTING,
LMP_SIMPLE_STATIONARY_VOLUMETRICLIGHTMAP_SHADOW_LIGHTING,
LMP_LQ_LIGHTMAP,
LMP_HQ_LIGHTMAP,
LMP_DISTANCE_FIELD_SHADOWS_AND_HQ_LIGHTMAP,
// Mobile specific
LMP_MOBILE_DISTANCE_FIELD_SHADOWS_AND_LQ_LIGHTMAP,
LMP_MOBILE_DISTANCE_FIELD_SHADOWS_LIGHTMAP_AND_CSM,
LMP_MOBILE_DIRECTIONAL_LIGHT_AND_SH_INDIRECT,
LMP_MOBILE_MOVABLE_DIRECTIONAL_LIGHT_AND_SH_INDIRECT,
LMP_MOBILE_MOVABLE_DIRECTIONAL_LIGHT_CSM_AND_SH_INDIRECT,
LMP_MOBILE_DIRECTIONAL_LIGHT_CSM_AND_SH_INDIRECT,
LMP_MOBILE_MOVABLE_DIRECTIONAL_LIGHT,
LMP_MOBILE_MOVABLE_DIRECTIONAL_LIGHT_CSM,
LMP_MOBILE_MOVABLE_DIRECTIONAL_LIGHT_WITH_LIGHTMAP,
LMP_MOBILE_MOVABLE_DIRECTIONAL_LIGHT_CSM_WITH_LIGHTMAP,
// LightMapDensity
LMP_DUMMY
};
GetUniformBasePassShaders负责完成差分,最终通过LightMapPolicyType就会得到不同的basepass的shader,不同的basepass的shader中,使用的vertex factory也就不同了。
DrawingPolicyFactory
这是一组负责生成DrawingPloicy的工厂类,但是他们都没有各自的基类,而是对应自己的功能有稍微有些不同的实现。
在FDepthDrawingPolicyFactory::AddStaticMesh能够看到一个FStaticMesh是如何被注册到FScene中去的。
而这个调用来自FStaticMesh::AddToDrawLists,在其中能够看到FStaticMesh在通过各种DrawingPolicyFactory来进行Shader的关联注册。
而之后,在渲染线程中,FDepthDrawingPolicyFactory::DrawStaticMesh之类的函数就会被调用来进行渲染。
FPrimitiveSceneInfo
对DrawingPolicyFactory的调用最终来自FPrimitiveSceneInfo,这个类与FPrimitiveSceneProxy 是一对一的关系,是最终注册到FScene的数据结构。
在FScene::UpdatePrimitiveTransform_RenderThread中能够看到渲染线程是如何通过FPrimitiveSceneProxy ::GetPrimitiveSceneInfo来更新与FScene的关系的。
C++到Shader的绑定
上面这些类是在C++中负责渲染逻辑的,而为了最终代码与Shader之间能够相互交流需要进行绑定。
绑定帮助宏
UE4中有一组宏来帮助绑定C++类与Shader代码。
FShader绑定
IMPLEMENT_MATERIAL_SHADER_TYPE(TemplatePrefix,ShaderClass,SourceFilename,FunctionName,Frequency)
这个是实际将C++的类与Shader进行绑定的宏。
在引擎中能够看到很多这个宏,例如
IMPLEMENT_MATERIAL_SHADER_TYPE(,FVelocityVS,TEXT("/Engine/Private/VelocityShader.usf"),TEXT("MainVertexShader"),SF_Vertex);
这个宏将FVelocityVS绑定到VelocityShader.usf中,而MainVertexShader是shader端的入口函数。
最后一个ShaderFrequency感觉上更加接近Shader的类型:
enum EShaderFrequency
{
SF_Vertex = 0,
SF_Hull = 1,
SF_Domain = 2,
SF_Pixel = 3,
SF_Geometry = 4,
SF_Compute = 5,
SF_NumFrequencies = 6,
SF_NumBits = 3,
};
第一个参数用于宏的进一步模板化,在上面的BasePass进行绑定的时候就能看到
IMPLEMENT_BASEPASS_LIGHTMAPPED_SHADER_TYPE
这个宏对这个参数的使用。
VertexFactory绑定
IMPLEMENT_VERTEX_FACTORY_TYPE(FactoryClass,ShaderFilename,bUsedWithMaterials,bSupportsStaticLighting,bSupportsDynamicLighting,bPrecisePrevWorldPos,bSupportsPositionOnly)
这个宏将VertexFactory绑定到对应的shader中去
例如
IMPLEMENT_VERTEX_FACTORY_TYPE(FGPUSkinPassthroughVertexFactory, "/Engine/Private/LocalVertexFactory.ush", true, false, true, false, false);
这个绑定使得FGPUSkinPassthroughVertexFactory与LocalVertexFactory.ush进行关联,能够看到有很多不同的VertexFactory绑定到了这里,而且VertexFatory在各个不同的ush里面都有定义。
这是因为前面有提到MeshShader是有很多实例,每一个实例会根据自己的光照类型、数据类型进行不同的绑定。
Shader Plugin
从UE4.17开始,已经可以在插件中自己定义Shader并进行调用了。不过UE4的核心渲染流程依然没有开放,所以想要自定义Shader Model之类的话还是必须对源码进行修改。
详细的操作可以参考[官方文档],不过官方文档的操作没有进行充分的解释,只是教你怎么把引擎插件的LensDistortion插件给拷贝并修改成自己的插件,不过刚好可以方便对Shader绑定进行理解。
基础重载
ShouldCache用于定义是否在指定的平台要编译这个材质。
ModifyCompilationEnvironment用于在特定的平台上添加自己的定义,但是示例中直接就加进了两个自己的定义
OutEnvironment.SetDefine(TEXT("GRID_SUBDIVISION_X"), kGridSubdivisionX);
OutEnvironment.SetDefine(TEXT("GRID_SUBDIVISION_Y"), kGridSubdivisionY);
参数绑定
可以看到FLensDistortionUVGenerationShader继承自FGlobalShader,然后添加了
FShaderParameter PixelUVSize;
FShaderParameter RadialDistortionCoefs;
FShaderParameter TangentialDistortionCoefs;
FShaderParameter DistortedCameraMatrix;
FShaderParameter UndistortedCameraMatrix;
FShaderParameter OutputMultiplyAndAdd;
几个成员,然后在构造中绑定
PixelUVSize.Bind(Initializer.ParameterMap, TEXT("PixelUVSize"));
RadialDistortionCoefs.Bind(Initializer.ParameterMap, TEXT("RadialDistortionCoefs"));
第二个参数是参数在Shader中的名称。
之后就可以通过
SetShaderValue(RHICmdList, ShaderRHI, PixelUVSize, PixelUVSizeValue);
SetShaderValue(RHICmdList, ShaderRHI, DistortedCameraMatrix, CompiledCameraModel.DistortedCameraMatrix);
来进行修改了。