源码学习网 首页 编程学园 ue4教程 查看内容

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

2019-7-16 22:09| 发布者: opiye| 查看: 94| 评论: 0

摘要: 先从点下编辑器Play按钮那一刻开始。流程从Engine的tick出发。这下我们走到了FViewport::Draw(boolbShouldPresent/*=true*/)方法,实现是在UnrealClicent.cpp里面。还有一层Draw,是ViewportClicent的Draw,每个FVie ...
腾讯云服务器秒杀

先从点下编辑器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流程。




来源网址:https://zhuanlan.zhihu.com/p/73586731

鲜花

握手

雷人

路过

鸡蛋