接下来的4个章节,将主要讲解Unreal4.23后,最新的渲染架构,如果不了解整体流程,对实现Skeletal Mesh Dynamic Instance 将会十分困难。
下面4个章节内容分布为:
1.Unreal渲染流程基本架构
2.MeshDrawCommand
3.Uniform Buffer、FVertexFactory、FVertexFactoryType
4.FVisibleMeshDrawCommand到RHICommandList
5.Game Thread与Render Thread数据传输
按照惯例,先自淫一下,宣传下自己的书。
![bcc41097c25f2e5e18cd9c5b4c14e3b4.png](https://i-blog.csdnimg.cn/blog_migrate/9fd0f89da1b889a391be573192cded78.jpeg)
从此篇开始,进入了集成Dynamic Instance最复杂的部分,我也是看了很久的代码
并多次修改才把Skeletal Mesh Dynamic Instance功能放到了最合适的地方。
从4.23版本,你必须要重新认识整个渲染流程,最后给我的印象就是:架构很清晰,细节很复杂。这里的细节我不想多谈,真的太复杂了,很难通过文档来说清楚,但架构还是值得一说,有了对渲染架构整体了解,集成Dynamic Instance会有很大帮助。
发送Primitive加入渲染线程命令
我从FScene::AddPrimitive这个函数说起,一旦Mesh走进这个函数,多线程所需要的渲染数据都会被构建出来。
![aa3d8870d001a36e50095b1f714c26be.png](https://i-blog.csdnimg.cn/blog_migrate/5eca45fb450ed144af983a7ed068a2ef.png)
在Game Thread中,Component注册后会通过AddPrimitive添加到Render Thread。FPrimitiveSceneInfo和FPrimitiveSceneProxy包含渲染数据,包括Transform、Bound Box、是否投影等等吧。相关代码如下:
void FScene::AddPrimitive(UPrimitiveComponent* Primitive)
{
…
// Create the primitive's scene proxy.
FPrimitiveSceneProxy* PrimitiveSceneProxy = Primitive->CreateSceneProxy();
Primitive->SceneProxy = PrimitiveSceneProxy;
// Create the primitive scene info.
FPrimitiveSceneInfo* PrimitiveSceneInfo = new FPrimitiveSceneInfo(Primitive, this);
PrimitiveSceneProxy->PrimitiveSceneInfo = PrimitiveSceneInfo;
// Cache the primitive's initial transform.
FMatrix RenderMatrix = Primitive->GetRenderMatrix();
FVector AttachmentRootPosition(0);
AActor* AttachmentRoot = Primitive->GetAttachmentRootActor();
if (AttachmentRoot)
{
AttachmentRootPosition = AttachmentRoot->GetActorLocation();
}
struct FCreateRenderThreadParameters
{
FPrimitiveSceneProxy* PrimitiveSceneProxy;
FMatrix RenderMatrix;
FBoxSphereBounds WorldBounds;
FVector AttachmentRootPosition;
FBoxSphereBounds LocalBounds;
};
FCreateRenderThreadParameters Params =
{
PrimitiveSceneProxy,
RenderMatrix,
Primitive->Bounds,
AttachmentRootPosition,
Primitive->CalcBounds(FTransform::Identity)
};
// Create any RenderThreadResources required and send a command to the rendering thread to add the primitive to the scene.
FScene* Scene = this;
// If this primitive has a simulated previous transform, ensure that the velocity data for the scene representation is correct
TOptional<FTransform> PreviousTransform =
FMotionVectorSimulation::Get().GetPreviousTransform(Primitive);
ENQUEUE_RENDER_COMMAND(AddPrimitiveCommand)(
[Params, Scene, PrimitiveSceneInfo, PreviousTransform](FRHICommandListImmediate& RHICmdList)
{
FPrimitiveSceneProxy* SceneProxy = Params.PrimitiveSceneProxy;
FScopeCycleCounter Context(SceneProxy->GetStatId());
SceneProxy->SetTransform(Params.RenderMatrix, Params.WorldBounds,
Params.LocalBounds, Params.AttachmentRootPosition);
// Create any RenderThreadResources required.
SceneProxy->CreateRenderThreadResources();
Scene->AddPrimitiveSceneInfo_RenderThread(PrimitiveSceneInfo, PreviousTransform);
});
}
这里删除一些无关代码,可以看见为了解除Game Thread和Render Thread的数据耦合, FPrimitiveSceneInfo和FPrimitiveSceneProxy都new出来的,算是一份Component的数据拷贝。
渲染线程中更新Primitive
![ea37984b014bfd9fce89c73cd99f97bd.png](https://i-blog.csdnimg.cn/blog_migrate/de82e21afb3c7753a00d2e087d94415a.png)
简单说明下流程最后的4个关键点:
1. 当初花大量时间来考虑是否也用GPU Scene实现Skeletal Mesh Dynamic Instance,更新GPU Scene需要消耗大量CPU时间和并Compute Shader写入信息,最后还是放弃了。
2. 生成的Primitives数据都记录在下面的变量里面:
/** Packed array of primitives in the scene. */
TArray<FPrimitiveSceneInfo*> Primitives;
/** Packed array of all transforms in the scene. */
TArray<FMatrix> PrimitiveTransforms;
/** Packed array of primitive scene proxies in the scene. */
TArray<FPrimitiveSceneProxy*> PrimitiveSceneProxies;
/** Packed array of primitive bounds. */
TArray<FPrimitiveBounds> PrimitiveBounds;
/** Packed array of primitive flags. */
TArray<FPrimitiveFlagsCompact> PrimitiveFlagsCompact;
/** Packed array of precomputed primitive visibility IDs. */
TArray<FPrimitiveVisibilityId> PrimitiveVisibilityIds;
/** Packed array of primitive occlusion flags. See EOcclusionFlags. */
TArray<uint8> PrimitiveOcclusionFlags;
/** Packed array of primitive occlusion bounds. */
TArray<FBoxSphereBounds> PrimitiveOcclusionBounds;
/** Packed array of primitive components associated with the primitive. */
TArray<FPrimitiveComponentId> PrimitiveComponentIds;
/** Packed array of runtime virtual texture flags. */
TArray<FPrimitiveVirtualTextureFlags> PrimitiveVirtualTextureFlags;
/** Packed array of runtime virtual texture lod info. */
TArray<FPrimitiveVirtualTextureLodInfo> PrimitiveVirtualTextureLod;
为了实现Skeletal Mesh Dynamic Instance,你需要解读这里面所有有关渲染线程使用的变量,来找到最终最合适的集成方法,也时花了大量时间和实践,排除了这些变量相关性。
3. Static Mesh所有属性基本是不变的,它的任何渲染状态和Shader都是不变的,所以会生成MeshDrawCommand并Cache,为渲染做使用。如果存在特殊情况发生改变,它们会从FScene中移除从新添加进来。
4. 找到和这个Mesh有关系的光源,这部分和集成Skeletal Mesh Dynamic Instance有相关性的,具体后面会讲解。
Cache Static Mesh 的 Mesh Draw Command
![e499bb754c67258daa33d7947bcee541.png](https://i-blog.csdnimg.cn/blog_migrate/95a56fd1f2a12dc3103ce616f0313f94.jpeg)
FPrimitiveSceneProxy::DrawStaticElements会得到StaticMeshes,是FMeshBatch类型,这里包含所有LOD级以及Sub Mesh。FMeshBatch类在之前的Unreal版本就有。
然后会遍历所有的StaticMeshes,看是否符合Cache的条件,则生成MeshDrawCommand。