UE4里GPUScene相关的梳理。
1.引擎传输Primitive数据到GPU的方式
什么是PrimitiveData?
PrimitiveData是UPrimitiveComponent级别的几何数据,包括LocalToWorldMatrix,WorldPosition,AABB,LightMap等数据。
传统方式
每个Primitive一个UniformBuffer,每次DrawCall绑定。
1 2 3 4 5 6 7 8 9 10 11
| GameThread
→ FPrimitiveSceneProxy
→ CreatePrimitiveUniformBufferImmediate()
→ RHICreateUniformBuffer
→ Draw 时 SetUniformBuffer
→ GPU
|
每一帧每一个Primitve需要,创建,更新,绑定。适合小场景。
每一帧上传:DrawCall * PrimitiveData
如果场景全部是动态物体,这种是合理的。但是现实有大量静态物体的数据,也需要每帧上传。
GPUScene的方式
为了减少Cpu per-draw state的绑定成本,UE4.23引入了GPUScene。
所有的Primitive数据,打包进一个大的StructuredBuffer,GPU里通过PrimitiveId获取。
然后每一帧不变的静态物体的数据就保持不变,只更新变化的静态物体以及动态物体。
动态物体也分为每个视图(不是全部更新),视图会提前收集到需要更新的动态物体。
每一帧上传:变化的Static + View * View收集的Dynamic
2.GPUScene的工作流程
GPUScene是一个大的功能,它是Primitive基础数据的容器,有一些功能比如DistanceField、LightMap、InstanceCulling这些,有自己的更新逻辑。
如距离场数据的更新,以前在这里写过
https://ahappyfun.github.io/2023/09/04/UE4-HISM%E5%89%94%E9%99%A4%E6%9B%B4%E6%96%B0DFShadow/
GPUScene基础的更新流程
标脏 → 收集 Dirty Primitive → 构建 PrimitiveShaderData → 批量写入 StructuredBuffer → Shader 通过 PrimitiveId 读取
更新标记Dirty路径:GameThread——>RenderThread
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| UPrimitiveComponent::MarkRenderTransformDirty() UPrimitiveComponent::MarkRenderStateDirty()
——>
FScene::UpdatePrimitiveTransform() FScene::UpdatePrimitiveSceneInfo()
——>
Scene->GPUScene.MarkPrimitiveDirty(PrimitiveSceneInfo);
——>
DirtyPrimitiveSceneInfos.Add(PrimitiveSceneInfo);
Dirty标记完成。不立刻更新,后面统一更新。
|
GPU更新流程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| FSceneRender::Render() FSceneRenderer::UpdateGPUScene()
——>
统计Dirty数量 NumDirtyPrimitives = GPUScene.DirtyPrimitiveSceneInfos.Num();
——>
为上传准备CPU Staging buffer TArray<FPrimitiveSceneShaderData> PrimitiveUploadData;
——>
构建每个 Dirty Primitive 的 ShaderData for (PrimitiveSceneInfo : DirtyList) { BuildPrimitiveShaderData(...) }
——>
写入上传数据
——>
批量更新Buffer到GPU RHICmdList.UpdateBuffer( PrimitiveBuffer, OffsetInBytes, SizeInBytes, UploadDataPtr );
——>
GPU里StructuredBuffer已经更新,shader里获取新的。 PrimitiveSceneData = PrimitiveSceneDataBuffer[PrimitiveId];
|