虚幻引擎UE渲染框架

4

主题

5

帖子

13

积分

新手上路

Rank: 1

积分
13
发表于 2022-9-20 09:41:35 | 显示全部楼层


本文整体介绍虚幻引擎UE的渲染框架。
以通俗易懂的方式描述,不喜欢上代码,个人风格就是能用文字和图片介绍清楚就不上代码,希望对新手友好、对代码犯困者友好。也不喜欢把文章写的太长,万字长文自己看着都犯困,尽量精简。
此外,读者最好有基本的图形学基础。当然没有也没关系,遇到陌生名称如延时渲染、pass百度了解下就行。

目录

  • 渲染引擎流程
  • UE多线程渲染
  • UE渲染流程架构
  • 渲染依赖图
  • 总结




渲染引擎流程

开始介绍渲染架构之前,先了解下整个渲染引擎需要做的事情。
简单来说,渲染就是将一个虚拟场景,以摄像机视角,渲染出屏幕图像的过程。如图1。



图1 渲染展示

虚拟场景一般是以某数学形式表示的三维表面。虚拟摄像机为场景取景。光源产生的光线会与环境中的物体交互作用并反射,到达虚拟摄像机从而产生画面。 渲染结果如图中右下角。
对于引擎来说,要完成整个过程,大致需要经历以下步骤。



图2 渲染引擎流程

资源加载:加载本地模型、材质等资源
场景搭建:将场景中的物体以特定的数据结构组织起来
视锥裁剪:剔除掉不在当前视角范围内的物体
可见性判定:剔除当前视角下被遮挡的物体
渲染管线:描述如何渲染、设置渲染状态、分配GPU资源
渲染指令:生成GPU执行的指令
GPU:真正开始渲染



多线程渲染

谈到UE渲染框架,不得不从多线程开始。当然,这里不会谈论UE的所有线程模块,也不会谈及线程的线程池、线程管理、任务图等。
这里只讨论与渲染最相关的三个线程:游戏线程、渲染线程、RHI线程。
游戏线程:也叫引擎线程、主线程,核心线程,负责整个引擎以及游戏的逻辑、运行。
渲染线程:渲染的流程的主要线程,负责整个渲染资源收集、状态设置、生成渲染指令等。
RHI线程:(Render Hardware Interface)线程将渲染指令转成指定图形API,并提交给GPU。



图3 线程关系

三个线程的关系如图。将三个线程整合到图2中,对应的流程如下。



图4 线程功能划分

其中游戏线程负责资源加载合场景搭建,渲染线程负责剔除、渲染管线,RHI负责生成指令提交GPU渲染。
三个线程都是在LaunchEngineLoop.cpp的PreInitPostStartupScreen中创建、开启。这个接口有1500+行,还只是预初始化相关操作,感兴趣的读者可以自行阅读。



图5 引擎接口

线程通信
基于分层式的设计,不同层级完成不同任务,需要做到相互独立、低耦合,虚幻采样的是命令方式。不同层级之间通过指令交互,如图6。



图6 通信流程

数据更新
多线程存在一个棘手的问题就是数据竞争。发生数据竞争时可能导致不可预期的结果,甚至使程序崩溃,而且这种BUG通常是偶现的,很难排查。
UE对数据竞争的解决方案就是一对一服务。不同线程拥有单独数据,每份数据明确修改者和修改对象,同一线程使用固定的数据。
那么,这必然存在一个问题:不同线程要使用同一份数据怎么办?比如,游戏线程和渲染线程都需要使用场景中的物体网格等。
答案就是复制!每个线程拥有一份!这样达到数据隔离的目的。当然,这种方式针对的是动态数据,如骨骼网格。静态资源只需要一份,保证所有线程都使用完才释放即可。
因此,不同线程中出现了一些不同名字,但是代表同一事物的命名。如下



图7 命名对应关系

再回过头看UE的多线程渲染机制,事实上,多线程并非多个渲染线程,渲染线程从始至终只有一个。这里的多线程指的是游戏线程、渲染线程、RHI线程同时存在,并行处理整个渲染过程。每个线程负责不同的任务而已。
此外,三个线程的处理速度并不一致,如图8,渲染线程在游戏线程的一两帧后操作。如果游戏线程跑的太快,游戏线程会在每个Tick事件的末尾阻塞,直到渲染线程赶上一到两帧的差距,才继续处理下一帧。



图8 不同线程处理速度



渲染流程架构

正式进入渲染管线核心部分的介绍。UE的渲染管线的大框架采用的是延时渲染管线,但又不是纯粹的延时渲染管线,参杂了一些其他的逻辑,如前向渲染、全局光照、LPV等等。



图9 整体架构图

UE渲染整体架构如图9。每个view是一趟独立渲染流水线。整体流程相对简单。
首先针对一个场景,找出所有需要渲染的view,经过模型网格绑定、剔除、等系列处理。随后将view拆分为多个pass进行渲染。
整体的代码在FDeferredShadingSceneRenderer::Render中。整个接口1400+行,感兴趣的可自行阅读。



图10 渲染接口代码

真正复杂的是过程的具体实现。下面将详细介绍整个过程。



图11 渲染过程详解


  • 发起渲染:游戏线程在Tick时,会创建场景渲染器,并向渲染线程发送绘制场景指令,会进入渲染模块的调用
  • 更新图元信息:通过UpdateAllPrimitiveSceneInfos接口,更新所有图元的信息并同步到GPU
  • 初始化准备工作:初始化View尺寸 、分配渲染纹理
  • 初始化View:初始化View中包含计算可见性和收集图元信息
    首先需要取拿到所有需要渲染的网格
    静态网格收集在PreVisibilityFrameSetup接口中
    动态网格收集在ComputeViewVisibility中
    除了收集动态网格,该接口还负责视锥裁剪、遮挡剔除

  • 各Pass渲染
    Pre-Pass:提前深度测试,渲染不透明物体的深度
    BasePass:延迟渲染的几何pass,渲染不透明物体的几何信息
    Lighting Pass:光照pass,非常复杂的一趟pass,包含各种光源的间接阴影、天空光、AO、透明体积光照等等
    Translucency:渲染半透明物体
    PostProcessing:后处理阶段,渲染各种屏幕效果



渲染依赖图

最后再介绍下基于渲染依赖图RDG的渲染优化。

  • 概念
可以看到渲染代码中随处可见的GraphBuilder



图12 接口



图13 接口



图14 接口

GraphBuilder即是依赖性渲染图(RDG),用于渲染管线的整帧优化,减少渲染指令的数量。
RDG最初是2017年的GDC中,寒霜实现并应用的技术。将上层渲染逻辑和下层资源隔离,进一步的解耦、优化,从而可以多线程和并行渲染。

  • 过程
渲染依赖图系统,运行在pass建立之后。并非立即执行pass,而是延迟到整个帧已记录到依赖性图表数据结构之中后再执行。当完成了对所有pass的收集之后,会执行各类裁剪和优化,可以自动裁减无用的Pass,再按照依赖性的排序顺序对图表进行编译和执行。
具体步骤如下。



图15 RDG过程

收集Pass:即建立阶段,收集Pass的输入纹理、输出纹理、依赖资源等等,转为RDG Pass
编译Pass:优化Pass和资源,计算资源生命周期,创建GPU资源
执行Pass:按照Setup的顺序执行,执行未被剔除的Pass
整个RDG的核心是依赖图(Dependency Graph),可以实现自动异步计算。
帧图依赖关系如图



图16 渲染依赖图


  • 意义
渲染依赖图基本上是现在引擎的标配,通过增加一层调度系统,管理和再使用内存,或执行布局。达到优化内存和减少渲染指令的目的。



总结

本文介绍了虚幻引擎UE的整个渲染框架和基本流程,同时也介绍了渲染优化技术RDG。UE渲染体系复杂而庞大,做了很多优化,为了渲染效果和性能,牺牲了一些灵活性。
本文并未介绍具体技术实现细节,目的是搭好一个框架,能帮助读者有目的性、针对性的深入了解UE渲染,希望勾起读者对研究UE源码的兴趣。


  • 唠嗑
我又回来了,哈哈哈,有三四个月未更文了,中间有不少读者催更过,已经积累了巨大的愧疚感。该来的总会来的。近期求职季,不少读者催面试题的答案版,准备考虑花时间整理下,欢迎有整理过的读者捐献,哈哈哈。
近期Game 104开课了,专门介绍和实现游戏引擎,感兴趣的读者可以试试。

我是五尘,公号 {游戏君五尘},欢迎围观,下期见~
原文链接:虚幻引擎UE渲染架构

参考文献
[1] https://github.com/EpicGames/Signup
[2] https://docs.unrealengine.com/4.27/en-US/
[3] https://www.cnblogs.com/timlly/p/13512787.html
[4]https://epicgames.ent.box.com/s/ul1h44ozs0t2850ug0hrohlzm53kxwrz

原创不易,未经允许,禁止转载、抄袭
回复

举报 使用道具

4

主题

7

帖子

16

积分

新手上路

Rank: 1

积分
16
发表于 2022-9-20 09:42:11 | 显示全部楼层
支持
回复

举报 使用道具

6

主题

11

帖子

23

积分

新手上路

Rank: 1

积分
23
发表于 2022-9-20 09:43:11 | 显示全部楼层
谢谢分享!
回复

举报 使用道具

2

主题

7

帖子

13

积分

新手上路

Rank: 1

积分
13
发表于 2022-9-20 09:43:49 | 显示全部楼层
写的真好,关注一波[爱]
回复

举报 使用道具

2

主题

7

帖子

12

积分

新手上路

Rank: 1

积分
12
发表于 2022-9-20 09:44:21 | 显示全部楼层
PreVisibilityFrameSetup执行的是一些渲染参数的初始化,不包含静态网格的处理,ComputeViewVisibility处理所有类型的网格(静态网格和动态网格)
回复

举报 使用道具

4

主题

9

帖子

19

积分

新手上路

Rank: 1

积分
19
发表于 2022-9-20 09:44:34 | 显示全部楼层
RDG 可以插入指定的Pass后面吗,比如自己写的一个pass,插入到Scene里面的BasePass后面或者ShadowDepth后面
回复

举报 使用道具

1

主题

3

帖子

4

积分

新手上路

Rank: 1

积分
4
发表于 2022-9-20 09:45:27 | 显示全部楼层
RDG是对所有pass的统一优化,不是指一个pass
回复

举报 使用道具

您需要登录后才可以回帖 登录 | 立即注册
快速回复 返回顶部 返回列表