【游戏开发】UE4渲染引擎模块讲解

2021-09-07
3062
0

本文的内容是从渲染引擎的宏观功能上罗列UE4的覆盖面和划分方式,尚不会涉及到具体每个功能模块的实现细节。本文在讨论渲染模块的时候还假设大家均具备这些图形引擎常识:渲染API的功能范畴、如何组织基础的渲染管线、夸平台图形引擎需要基础框架支持的最小集

先从顶层来看一次完整的渲染

给渲染器输入以原始的几何和材质数据,渲染器把几何和材质数据转换为渲染API所支持的数据、渲染状态、Shader及Shader参数并由这些数据组装为一个RenderPipeline,然后执行该RenderPipeline,得到渲染结果后交换到渲染的目标Context上去(如Windows下的一个窗口,Android下的一个View等)。一个3D渲染引擎的核心工作就是组织好这一宏观上的工作流,使其最大化利用目标平台的硬件资源(CPU,GPU,内存,硬盘或闪存等)和特性,使其使用最便利、性能最优,效果最佳。

UE4的渲染系统也不例外,所以我们的渲染功能的识别方式的基于以上基本过程和传统的3D引擎功能划分来做。UE4的模块(Module)和我们将要讨论的渲染功能模块不存在一一对应关系,可能UE4的一两个类即实现一个功能块,或一个UE4的模块(Module)除了包含数个渲染相关的功能。

UE4场景和场景管理(Scene 、SceneManager)

在UE4中不存在传统引擎中的严格一一对应的Scene和SceneManager,它的实现是散落在许多类中。

传统引擎中的Scene一般表达一个渲染用的世界。这个概念在UE4中有两个类和它对应:用于游戏线程中的UWorld类和用于渲染线程中的FScene类.UE4中的中UWorld和FScene有一一对应关系,UWorld用于游戏线程,用于用户的主动操作(如创建、删除世界中的物件等),而FScene则隐藏于渲染线程,由UWorld和世界中的对象被动操作。

在游戏过程中,一般只存在一个UWorld实例(在过渡的时候可能有两个),但在编辑器形态下,一般会存在许多个UWorld对象——一般来说,一个UWorld对象表达一个单独的编辑器窗口。

UE4和其它支持大世界的引擎一样支持游戏场景中的物体动态加载和卸载。但它对于大世界的拆分方式是比较独特的——UE4的场景的划分模式不是基于物件级而是基于子关卡级来做。在UE4中,一个UWorld由一个一直存在的持久关卡(ULevel类)和多个动态加载卸载的子关卡组成。UE4中这种动态加载卸载的子关卡叫做流关卡(StreamingLevel ,ULevelStreaming类),且场景中的具体物件都是放置在关卡或流关卡中而不是直接位于UWorld中。

UE4中的流关卡的加、卸载策略实现是由UWorldComposition类来负责的。这是一个基于视点距离和流关卡卡包围盒的简单的加载策略实现。

用于渲染线程的FScene不具备复杂的场景管理功能,它有一些数组用于各类管理场景可渲染对象和灯光,它有两个Octree结构用于空间的快速查询——一个用于灯光,另一个用于其它的可渲染对象,它还有一个DrawList用于Cache各个渲染Pass的指令。

UE4场景中的物体(SceneObject)

当我们在UE编辑器中往场景里拖一个NPC,或放置一个灯光,一个后处理盒(PostProcess Volume)的时候,我们都是往该关卡中添加了一个AActor子类实例,UE4关卡和流关卡中每一个独立物件由一个AActor及其子类的对象实例来建模表达

AActor及其子类本身并不直接持有渲染所需的数据,AActor基于组合模式设计,可持有数个UActorComponent实例,具体的渲染相关的数据均在UActorComponent及其子类的实例中。

USceneComponent见名知义,它是UActorCompoent子类里可用于场景中的组件基类。USceneComponent有两个主要作用:它包含Transform数据,它可以支持Attachment。

UPrimitiveComponent是USceneComponent的子类,它是所有可渲染组件的基类。它包含一系列的几何数据,而做为附赠品,它同时也可以做为碰撞数据使用。

ULightComponentBase是USceneComponent的另一个子类,它是所有灯光组件的基类。

渲染相关的主要Component类结构层次

UE4要渲染API封装

UE4中的渲染API封装是个独立的模块(Module),他们把它命名为RHI(Render Hardware Interface)。RHI的接口定义上倾向于向最新的渲染API靠近(如DX12和Vulkan),它除了提供渲染API提供的主要接口转发外,还对CommandList,ShaderCache、StateCache和GpuProfiler做了基本的封装。

RHI的转发实现在RHICommandList.h文件里,可以看到其实现除了基本的条件判断,大都是直接 转调渲染API实现的RHI子模块里的渲染指令。

UE4具体的实现了以下RHI模块的封装

D3D11RHI ,基于D3D11 Feature的RHI封装

D3D12RHI,基于D3D12 Feature的RHI封装

MetalRHI,基于Metal 1和2的RHI封装

OpenGLGLDrv ,它同时实现了Windows,Linux,Android,Ios,Web等各个平台的Opengl,包含GL3,GL4.x和GLES2,GLES3和H5上的Feature.

EmptyRHI和NullDrv,这两个都是对RHI的空实现

UE4的材质系统

UE4对材质系统的封装可以理解为RenderPipiline输入的所有数据中除了几何体数据之外的所有其它数据。它包括渲染所需要选择的光照模型、光照自身的照射分布函数、材质模型及该材质模型所需要的输入参数,渲染状态数据,为各种顶点格式和渲染分支生成的Shader,以及一个提供给用户编辑态使用的节点图等等。

UE4中可用于渲染的材质分为两种:一种是材质模板(UMaterial),另一个是基于材质模板的材质实例(UMaterialInstance).这两货都是UMaterialInterface的子类。只有UMaterial材质模板带有可编辑的节点图并可拒此生成对应的Shader组合,而UMaterialInstance材质实例则只需要引用UMaterial对应的Shader.UMaterialInstance只能修改材质模板暴露出来的材质参数。

对渲染层来说,一般并不需要区分材质实例和材质模板本身。

FMaterialResourceFMaterail的子类,用于UMaterial的渲染,具体来说FMaterialResource负责为各个渲染API和材质所支持的各种质量等级生成对应的Shader组合。所以,每个UMaterial都会包含多个FMaterialResource。

FMaterialRenderProxyFMaterial用于渲染线程的代理,它可以透过FMaterail和UMaterialInterface访问到Shader、渲染状态,光照模型等所有用户设置好的材质参数。

UE4的材质中光照模型是不可定制的,所以在不魔改源码的前提下,你无法修改其光照模型,比如你想实现一个NPR的Ramp给光照分层时。

UE4中Shader生成

FShader是UE4中所有Shader的基类,它有两个主要的子类

FGlobalShader:全局Shader,会自动注册到全局ShaderCache中

FMaterialShader:用于材质(编辑器)的Shader,所有的后处理、UI、用于模型渲染的Shader都是它的子类。

UE4 Shader生成分两部分,第一部分是把材质编辑器中的节点图编译成HLSL代码,这一部分是通过FHLSLMaterialTranslator来完成的。

UE4 Shader生成的第二部分是把HLSL生成多平台的Shader代码,如Windows上的HLSL,Android上的GLSL,IOS上的MetalShader,简单的流程是这样:

如果是目标平台是HLSL相关的平台,则使用ShaderCompilerCommon模块编译出HLSL AST,再适配到不同的SM Feature上。

如果目标平台是非HLSL相关的平台,则先通过Hlslcc模块(在源码的ThirdParty中)把HLSL编译成基于Mesa自定义的GLSL ByteCode的AST,对该AST再使用GLSLOptimizer进行优化,并把对应的AST通过不同平台的Shader编译后端把Mesa GLSL ByteCode生成不同的Shader源码。

第二部分编译是通过启动ShaderCompilerWorker实用程序并行编译

ShaderCompilerWorker的只是简单封装了一下就转调了IShaderFormat的各个子类的CompileShader,而ShaderFormat则会调用对应的xxxxFrontend(FOpenGLFrontend)进行具体的Shader生成。

基本生成流程如下图

从上面的介绍可以看到UE4的Shader跨平台方案,和U3D一样,使用的字节码的方案,不过一个用的是HLSL BC,一个使用的是Mesa BC。Shader Cross Compile在有了Spir-v之后或者大家都往其迁移是更靠谱的方式——毕竟这是个有强大开源组织在维护、升级和推动的天然跨平台的字节码,而且其Optimizier也在持续维护,要比目前的UE使用的glsloptimizer可维护性会更好。

PSO Caching(Pipeline State Object Caching)

UE4用新的PSO Caching用来替代原来的FShaderCache。原来的FShaderCache实现的是对Shader代码(或二进制的ByteCode)进行Cache.新的PSO Caching则是ShaderCache的超集,它不仅Cache了渲染所用的Shader代码,同时也Cache了渲染状态.PSO Caching的设计在很大程度是贴合了新的渲染API的方向,向Vulkan的Pipeline Cache致敬

PSO Caching会把渲染状态、顶点声明、Primitive类型、RenderTarget像素格式等等的数据保存到文件中,或是从文件中加载它们——因为这些渲染状态数字类数据大都是一系列的Int或枚举类数据,空间大小占用有限,故没有做额外的处理。

但PSO Caching中不会直接保存Shader代码(不管是源代码或是编译好的二进制Shader),也不保存Shader的路径,它保存的是Shader路径的SHA1 Hash做为Shader唯一的索引,真正的Shader是由FShaderCodeLibrary管理。

Unreal Lightmass

Lightmass模块是UE4的烘焙渲染器,是一个单独的可执行文件。它工作在CPU上而非GPU上,配合UE自带的Swarm Agent和Swarm Coordinate实用程序,Lightmass还能实现分布式的烘焙。除了光照图,还烘焙阴影、AO,BentNormal、可见性、Mesh距离场等等。

Lightmass是一个基于简化版本的Photon mapping实现的渲染器,虽然有大量的示例证明它能渲染的和离线渲染器一样好。但我这半吊子离线民科还是强烈的感受到,实际上它的实现传统的离线渲染器相距甚远,实现的思路并没有考虑到离线渲染所关注的最大问题:海量资源的管理调度。

相对于暴力Path Tracing来说,Lightmass是有偏的。

相对于Jensen的提出Photon Mapping时的构想来说,Lightmass的实现是个阉割版。它失去了重构Caustic的可能;它不区分镜面和非镜面反射,Photon Trace的结果只有一份Photon Mapping;它内部的材质模型是单一的;它有实现诸如蒙特卡罗,Irrandiance Cachiing,Final Gather 等Photon Mapping的优化手法,但是许多实现都不是非常完善。

Lightmass中的光子被存在八叉树中,Jensen实现是基于数组的KD-Tree。

一整个场景中参与投影的Mesh在Lightmass里会被组织为一个硕大的Mesh,再使用KD-Tree对其进行划分以加速Ray Cast求交。UE4有实现一套自己的Mesh KD-Tree构建,但它默认的并不使用自己的这份Kd-tree,而是用的Intel的Embree库来表达场景快速求交。

Lightmass对几何体的光栅化是基于平底三角形拆分进行光栅化。

UE4烘焙的基本工作流程

 

 

Render Path

UE4目前支持3种渲染路径:移动端简单的Forward Render Path,PC/主机端的Deffered Render Path和Forward 。在FrameGraph尚未完善情况下,每种渲染器对应的渲染Pass是人肉组装的。

UE4用于组织渲染路径实现h/cpp文件的后缀均为Renderer,每个Renderer均由数个渲染Pass顺序级联而成,对于具体每个Pass的渲染实现和某个单独的功能性的渲染实现,UE4中的h/cpp文件后缀则是Rendering,如FogRendering,DepthRendering等等

Renderer的基类是FSceneRenderer,当你想实现一个自己的渲染路径时,它的核心接口只有一个:

虽然该接口名为Render,但实际上它的工作不止是渲染,它同时负责了更新部分数据、裁剪、遮挡剔除,组织渲染指令等工作。这就容易在和GPU进行渲染指令同步时出现断供和积压的情况,为了应对渲染线程在干这些脏活累活时的所带来的额外延迟,UE4一方面尽可能的把一些耗时大且可并行的任务扔到线程里去实现(如可见性计算,生成渲染指令),另外一方面还专门针对GPU同步的提交实现了一个RHI线程。

UE4渲染数据和命令组织、可视性计算是通过InitViews函数实现的,这不是FSceneRenderer的接口,但是FSceneRenderer的两个子类均有一个一样功能实现。

FDeferredShadingSceneRenderer用于其它非移动平台。它内部的实现是同时集成了延迟渲染和Forward 。在4.23开发版本中还看到有实现单独的Deffered Cluster Pass。关于Deffered Shading Renderer具体的渲染过程,每个Pass的功能分析,网上资料较为详实可观了。

UE4中用于移动端的渲染器叫FMobileSceneRenderer,它实现了一个传统的Forward Renderer。MobileRendeer最核心的Pass是MobileBasePass ,它的具体实现分两部分,CPU端的数据组织,Shader及其Permutation管理在MobileBasePassRendering.h/cpp中,Shader本身的实现则在MobileBasePassVertexShader.usfMobileBasePassPixelShader.usf中。

UE4的Shader Permutation是基于Pass和预编译宏来组合的,MobileBasePassPixelShader.usf也不利外,它实现了一个mesh着色的基本过程:从光照图、Shadowmap采样和计算、反射球采样、天光、到实时灯光、Fog混合等。在硬件支持的前提下,它还基于Frame Buffer Fetch实现了Alpha Blend。

UE4同时支持硬件遮挡剔除和软件遮挡剔除,只是它的软件遮挡剔除目前仅用于ES2相关的设备。

UE4 Render Path的实现包含在“Renderer”这一模块中。

Instancing

在Batch和Instancing这两项减少DrawCall的技术方案选型上,和U3D不同,UE4选的是Instancing,所以UE4的Batch功能是比较弱的,你在场景中放100个一模一样的Box,在没有Instancing时,它就是100个DrawCall。

UE4目前有三种Mesh相关的Instancing实现:

UInstancedStaticMeshComponent(ISM),静态的模型Instance,即我们在书上和其它引擎中最常见的Instance形式——只有位置相异而mesh和材质均完全相同的物体可以合并成一个Actor,在理想情况下只提交一次DrawCall。ISM好处是DrawCall少,坏处是LOD计算,裁剪和OC等等都是按一个对象来做,往往ISM的Drawcall减少了,但提交渲染的三角形却更多了。

UHierarchicalInstancedStaticMeshComponent(HISM),UE4中的HISM和ISM不同之处是它是基于分层实现。目的是为了满足一个Mesh有大量实例时分区域进行裁剪、计算LOD。UE中的植被就是HISM的子类。看到HISM大量的Console Variable都直接叫Foliagexxx...可以猜到的是HISM大概一开始是为植被系统所准备的。

HISM的实现有两部分,一部分是构建分层(自动化的,每次修改它的instance个数、位置等都会触发)并保存到文件中,另一部分则是基于分层的可见性计算、LOD和渲染组织。

HISM的分层结构是一个KD-Tree,不过它的生成不是基于启发式平衡的结构去做,而是直接用的最长轴做为当前拆分轴,且简单粗暴的从长轴中间分开成左右子树,叶节点所包含的Instance数或三角形数由外部控制。

Dynamic Instance ,UE4在4.22中支持的Dynamic Instance和上述的ISM/HISM虽同为Instancing,但实现上是完全不相关的。ISM/HISM的实现是静态的,需要显示的在场景中对相同物件强制打包到一个PrimitiveComponent下面,属于编辑阶段需要确定的数据组织功能。

而Dynamic Instancing则是在渲染指令组织的时候,发现相同的mesh pipeline state自动组装的。

Dynamic Instance实现的核心是UE4实现了一个GPU Scene——用一个Buffer来存储全场景每个Primitive的Transform等信息,这样在组装出Instance Buffer之后只需用PrimitiveID就可以访问到自己的位置相关数据,从而实现Instance渲染了。

Dynmic Instance在4.22上的移动端没有实现,但其实只要稍微修改一下UE4使用的RWStructureBuffer为RWBuffer,并且把GPU Scene相关的检测和更新修正,就可以在手机端ES3.1 Feature上正常使用。

 

转载声明:本文来源于网络,不作任何商业用途。

免责声明:本文内部分内容来自网络,所涉绘画作品及文字版权与著作权归原作者,若有侵权或异议请联系我们处理。
收藏

全部评论

您还没登录

暂无留言,赶紧抢占沙发
绘学霸是国内专业的CG数字艺术设计线上线下学习平台,在绘学霸有2D绘画、3D模型、影视后期、动画、特效等数字艺术培训课程,也有学习资源下载,还有行业社区交流。学习、交流,来绘学霸就对了。
绘学霸iOS端二维码

IOS下载

绘学霸安卓端二维码

安卓下载

绘学霸微信小程序二维码

小程序

版权声明
本网站所有产品设计、功能及展示形式,均已受版权或产权保护,任何公司及个人不得以任何方式复制部分或全部,违者将依法追究责任,特此声明。
热线电话
18026259035
咨询时间:9:00~21:00
在线客服
联系网站客服
客服微信:18026259035
公司地址
中国·广州
广州市海珠区晓港中马路130号之19
绘学霸客户端(权限暂无,用于CG资源与教程交流分享)
开发者:广州王氏软件科技有限公司 | 应用版本:Android:6.0,IOS:5.1 | App隐私政策> | 应用权限 | 更新时间:2020.1.6