UE5渲染 您所在的位置:网站首页 ens渲染是什么 UE5渲染

UE5渲染

2023-03-20 03:32| 来源: 网络整理| 查看: 265

前言:前面章节分析MeshDrawPipeline时,在Render函数里调用RenderPrePass之前会先执行一些更新:GPUScene、InstanceCullingManager、UpdatePhysicsField、PrepareDistanceFieldScene更新。本章进行分析GPUScene,而这个是UE实现合批渲染的关键。

1、GPUScene是什么

顾名思义,GPU场景,是CPU端渲染数据的镜像!其实就是全局的一些UniformBuffer并进行更新维护,在后续执行各个RenderPass的时候可以直接按PrimitiveId去索引使用,主要包含数据:

Primitive数据:每个Primitive的Shader参数列表,存储了每个Primitive渲染需要的Shader参数Instance数据:对于能进行Auto Instance的物体,存储了每个Instance渲染需要的Shader参数Payload数据:给Instance使用的,SceneData.ush里解析的数据更新FInstanceSceneData的某些属性BVH数据:场景空间结构,用于CullLightmap数据:场景光照贴图

内容挺多的,本章对合批绘制进行分析,涉及Primitive、Instance、Payload。

Primitive:C++的数据类型FPrimitiveUniformShaderParameters(PrimitiveUniformShaderParameters.h)对应Shader的数据FPrimitiveSceneData(SceneData.ush);

Instance:C++的数据类型FInstanceSceneShaderData(InstanceUniformShaderParameters.h)对应Shader的数据FInstanceSceneData(SceneData.ush);

Payload:C++数据类型FPackedBatch、FPackedItem(InstanceCullingLoadBalancer.h)对应Shader的数据类型FPackedInstanceBatch、FPackedInstanceBatchItem(InstanceCullingLoadBalancer.ush)

2、GPUScene数据更新// 函数调用关系 void FDeferredShadingSceneRenderer::Render(FRDGBuilder& GraphBuilder) { void FGPUScene::Update(FRDGBuilder& GraphBuilder, FScene& Scene, FRDGExternalAccessQueue& ExternalAccessQueue) { void FGPUScene::UpdateInternal(FRDGBuilder& GraphBuilder, FScene& Scene, FRDGExternalAccessQueue& ExternalAccessQueue) } }

主要逻辑在FGPUScene::UpdateInternal,我们看看怎么实现的。

调用/Engine/Private/GPUScene/GPUSceneDataManagement.usf,通过ComputeShader去更新InstanceSceneData里的PrimitiveId,这部分更新的是Primitive里的数据只有Id变了,所以特殊处理。

接下来,更新剩下需要全量更新的数据,使用模板FUploadDataSourceAdapterScenePrimitives调用UploadGeneral()。

初始化5类Buffer的上传任务TaskContext

后面代码都是在对这个TaskContext进行初始化,然后启动任务,进行数据的上传。

2.1、并行更新Primitive数据,将需要更新的PrimitiveCopy到Upload Buffer里,后续通过ComputeShader进行显存的上传然后更新到目标Buffer里;

2.2、同理InstanceSceneData以及InstancePayloadData数据的处理。

2.3、同理InstanceBVHUploader,LightmapUploader。

2.4、最后都会调用每个Uploader的End()方法进行GPU显存的更新。

总结:数据的上传都一样,将需要更新的数据Copy到对应的UploadBuffer对应的位置里,然后通过对应的ComputeShader进行更新到目标Buffer,在函数FRDGAsyncScatterUploadBuffer::End()里实现,截图:

3、GPUScene数据的解析应用

数据的上传更新,是为了使用,那么它们是怎么应用的,分析一下。

3.1、FPrimitiveSceneData

// SceneData.ush struct FPrimitiveSceneData { uint Flags; // TODO: Use 16 bits? int InstanceSceneDataOffset; // Link to the range of instances that belong to this primitive int NumInstanceSceneDataEntries; int PersistentPrimitiveIndex; uint SingleCaptureIndex; // TODO: Use 16 bits? 8 bits? float3 TilePosition; uint PrimitiveComponentId; // TODO: Refactor to use PersistentPrimitiveIndex, ENGINE USE ONLY - will be removed FLWCMatrix LocalToWorld; FLWCInverseMatrix WorldToLocal; FLWCMatrix PreviousLocalToWorld; FLWCInverseMatrix PreviousWorldToLocal; float3 InvNonUniformScale; float ObjectBoundsX; FLWCVector3 ObjectWorldPosition; FLWCVector3 ActorWorldPosition; float ObjectRadius; uint LightmapUVIndex; // TODO: Use 16 bits? // TODO: Move into associated array that disappears if static lighting is disabled float3 ObjectOrientation; // TODO: More efficient representation? uint LightmapDataIndex; // TODO: Use 16 bits? // TODO: Move into associated array that disappears if static lighting is disabled float4 NonUniformScale; float3 PreSkinnedLocalBoundsMin; uint NaniteResourceID; float3 PreSkinnedLocalBoundsMax; uint NaniteHierarchyOffset; float3 LocalObjectBoundsMin; float ObjectBoundsY; float3 LocalObjectBoundsMax; float ObjectBoundsZ; uint InstancePayloadDataOffset; uint InstancePayloadDataStride; // TODO: Use 16 bits? 8 bits? float3 InstanceLocalBoundsCenter; float3 InstanceLocalBoundsExtent; float3 WireframeColor; // TODO: Should refactor out all editor data into a separate buffer float3 LevelColor; // TODO: Should refactor out all editor data into a separate buffer uint PackedNaniteFlags; float2 InstanceDrawDistanceMinMaxSquared; float InstanceWPODisableDistanceSquared; uint NaniteRayTracingDataOffset; float3 Unused; float BoundsScale; float4 CustomPrimitiveData[NUM_CUSTOM_PRIMITIVE_DATA]; // TODO: Move to associated array to shrink primitive data and pack cachelines more effectively };

以上是Primitive包含的字段,这里包含了渲染一个物体的全部数据。普通地渲染一个物体,就可以从这里需要的数据,然后进行Shader计算,如下BasePassPixelShader.usf的部分截图:

3.2、FInstanceSceneData

// SceneData.ush struct FInstanceSceneData { FLWCMatrix LocalToWorld; FLWCMatrix PrevLocalToWorld; FLWCInverseMatrix WorldToLocal; float4 NonUniformScale; float3 InvNonUniformScale; float DeterminantSign; float3 LocalBoundsCenter; uint PrimitiveId; uint RelativeId; uint PayloadDataOffset; float3 LocalBoundsExtent; uint LastUpdateSceneFrameNumber; uint NaniteRuntimeResourceID; uint NaniteHierarchyOffset; #if USES_PER_INSTANCE_RANDOM || USE_DITHERED_LOD_TRANSITION float RandomID; #endif #if ENABLE_PER_INSTANCE_CUSTOM_DATA uint CustomDataOffset; uint CustomDataCount; #endif #if 1 //NEEDS_LIGHTMAP_COORDINATE // TODO: Fix Me float4 LightMapAndShadowMapUVBias; #endif bool ValidInstance; uint Flags; #if USE_EDITOR_SHADERS FInstanceSceneEditorData EditorData; #endif };

这个Per Instance的数据结构,即进行Instance渲染的物体,其中有个PrimitiveId字段,也是Per Instance的,渲染的时候从Per Instance里去数据计算,基础数据还可以通过PrimitiveId取Primitive数据。

3.3、Payload数据,对应Shader文件是InstanceCullingLoadBalancer.ush,后续分析InstanceCulling再深入这里。

4、MeshDrawCommand合批的补充

经过可见性相关性,进行SetupMeshPass之后,对每个Pass调用DispatchPassSetup,然后创建FMeshDrawCommandPassSetupTask任务,这个Task任务通过MeshPassProcessor生成一个个MeshDrawCommand,这里的MeshDrawCommand还没有合批的;

生成完成后会进行排序,为的是让相同的Command能连续存放在一起:

InstaceCullingContext.cpp

之后进行Instance合批:

InstaceCullingContext.cpp

合批的计算方法是,通过对比MeshDrawCommand的 StateBucketId 是否相等:

合批渲染分析到这里了。

总结:GPUScene维护了这个场景的必要数据,以提供Primitive Buffer、Instance Buffer用于渲染着色,以及BVH用于GPU Culling。

优点:

从C++层面,这些数据都是面向数据,缓存命中友好;像大部分StaticMesh,上传到Primitive Buffer后,后续相关物体DrawCall时,只需绑定使用即可;提供了Instance支持,减少DrawCall;后续光追特性也需要场景数据(就算不在可见范围的物体也有GI影响的),提供了基础;

下一篇分析一下 (出现了多次的) InstanceCulling的实现。



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有