找回密码
 立即注册
查看: 295|回复: 4

UE4笔记-渲染流程源码分析(引擎Tick开始)

[复制链接]
发表于 2022-9-1 08:02 | 显示全部楼层 |阅读模式
先从点下编辑器Play按钮那一刻开始。
流程从Engine的tick出发。


这下我们走到了 FViewport::Draw( boolbShouldPresent/*= true */)方法,实现是在UnrealClicent.cpp里面。


还有一层Draw,是ViewportClicent的Draw,每个FViewport持有一个ViewportClicent,后续可以继续分析其关系。


往下看下去,来到一个比较重点的代码
FSceneViewFamilyContext ViewFamily(FSceneViewFamily::ConstructionValues(  
InViewport,MyWorld->Scene,EngineShowFlags) .SetRealtimeUpdate(true));
这里是构建一个场景视图成员,用于渲染用,其实里面更多的是一些数据记录,比较重要的是所持有的FSceneInterface的子类FScene实例,FScene可以理解成UWorld在渲染模块下的映射,也就是渲染模块不直接关心UWorld上的Actor,而是关心FScene上的数据。




再看看FSceneViewFamily和FSceneViewFamilyContext






到目前为止,FSceneViewFamily里面只是包含了一些视口数据,但是实际渲染需要的ViewMatrix和ProjectionMatrix并没有任何求解地方,所以接下来肯定是有地方计算的,接着往下看,来到了APlayerController的CalcSceneView方法,获得对应的FSceneView。
FSceneView* View = LocalPlayer->CalcSceneView(&ViewFamily, ViewLocation, ViewRotation, InViewport, &GameViewDrawer, PassType);


FSceneView里面包含了渲染的基础矩阵ViewProjectionMatrix,那么这个是怎么得出来的?继续往里面走。
这里创建FSceneViewInitOptions的局部变量,查看其包含了求解VPMatrix的要素,但是这个时候是没有初始化过的Matrix,后面肯定是有地方处理了。其实就是下一句CalcSceneViewInitOptions




经过这个方法后,相应的旋转矩阵已经处理完成,进里面再看,其实是在ULocalPlayer::GetProjectionData里面获取到ViewOrigin和ViewRotationMatrix,本人觉得这个名字有问题。这名字不是说投影数据吗?为什么连旋转和位移数据也在里面求解...


同时可以看到,通过GetViewPoint(ViewInfo, StereoPass),获得了视口的位移和旋转。再往下看,实际是通过PlayerController->GetPlayerViewPoint(/*out*/OutViewInfo.Location, /*out*/OutViewInfo.Rotation)获取。这里代码有点重复了。。。


基本上到这里,渲染用的vp数据有了,再往下看,是根据不同的显示模式,记录不同参数,比如线框模式、漫反射模式等。同时FSceneViewFamilyContext持有FSceneView实例。


当这些数据都准备好后,开始通知渲染模块,渲染当前FSceneViewFamily。之前说了FSceneViewFamily持有FSceneView实例,所以相关的渲染数据也就一并传递过去了。
看看对应的函数GetRendererModule().BeginRenderingViewFamily(SceneCanvas,&ViewFamily);实现


World->SendAllEndOfFrameUpdates();还没有了解清楚,应该是更新proxy信息,确保proxy信息是最新的。然后到创建FSceneRenderer。






不难看出,通过不同的渲染路径,会创建出不同的渲染实例
一个是延迟渲染,PC上基本走这个,性价比高。
一个是移动平台的渲染。

接下来会进行相关的cameracapture和平面反射的内容更新,最后调用
ENQUEUE_UNIQUE_RENDER_COMMAND_ONEPARAMETER(
FDrawSceneCommand,
FSceneRenderer*,SceneRenderer,SceneRenderer,
{
RenderViewFamily_RenderThread(RHICmdList, SceneRenderer);
FlushPendingDeleteRHIResources_RenderThread();
});
为渲染线程添加RenderViewFamily_RenderThread(RHICmdList, SceneRenderer);任务。


进入static void RenderViewFamily_RenderThread(FRHICommandListImmediate& RHICmdList, FSceneRenderer* SceneRenderer)方法,查看其实现,可以看到其实际是调用了SceneRender的Render方法。先进入FDeferredShadingSceneRenderer进行分析。


首先是initview->PrevisibilityFrameSetup,如果是TemporalAA,则进行TemporalAA处理,大概算法如下,TemporalAA两种操作,一种就是downsample(看资料说是,但是我觉得这是SSAA吧...),一种就是偏移多帧混合。常规电影帧率不高却流畅,就是大多数用了这个。UE看起来就是用偏移多帧混合。




然后到ComputeViewVisibility,里面主要实现了FrustumCull。
UE使用的是并行是锥体裁剪,之前看文章说比起线性八叉树要快,具体算法后续再深入。






为了做视椎体裁剪,肯定是要网格位置,体积等信息,这里UE通过FScene获取所有的动态物体的FPrimitiveSceneInfo,进而获取proxy,然后通过GetDynamicMeshElements,构建图形接口需要的Vertex信息。
其中FPrimitiveSceneInfo和proxy是渲染用的数据,但其实通过UWorld的Actor的Component去处理生成。
那么目前,UWrold的Actor与渲染线程的桥梁就搭建起来了,后续再分析Acotr的Component和proxy是怎么桥接。






InitView流程大概就到这里,基本上完成了渲染动态网格数据的收集。
接下来是Prepass,就是EaelyZPass,顾名思义,先渲染深度,为后续进行深度剔除,减少OD,用并行方式进行处理。






最终通过创建FDrawVisibleMeshCommandsAnyThreadTask,doTask方法为SubmitMeshDrawCommandsRange,实际SubmitMeshDrawCommandsRange就是向RHI提供相关的图形接口的渲染数据了。在这里还有动态遮挡剔除处理,用的是HZB算法做剔除,就是用上一帧的Zbuffer来判断,具体算法已经有了解,不深入。








Prepass完成后,到RenderBasepass,也是通过并行处理渲染。渲染过程与上述基本一致,只是相关的渲染状态不一样,prepass部分只渲染深度,这部分进行GBUFFER处理,具体后续再分析。
RenderBasePass(RHICmdList, BasePassDepthStencilAccess, ForwardScreenSpaceShadowMask.GetReference(), bDoParallelBasePass, bRenderLightmapDensity);







后续还有透明物体渲染、光照渲染部分,都大同小异了,就不想分析了,用RenderDoc可以查看流程。
目前分析了渲染层的流程,后续再继续分析UWorld下如何生成对应渲染proxy,渲染线程在什么时候组装对应的proxy,shader流程。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
发表于 2022-9-1 08:11 | 显示全部楼层
已拜读,很棒很棒!
发表于 2022-9-1 08:14 | 显示全部楼层
截图是真心看不清
发表于 2022-9-1 08:17 | 显示全部楼层
已拜读,很厉害呀
发表于 2022-9-1 08:21 | 显示全部楼层
FrustumCull 没看出具体是怎么剔除的, 看上去只是记录了要剔除的个数
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-11-15 02:20 , Processed in 0.117480 second(s), 26 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表