|
先从点下编辑器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流程。 |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|