UE4渲染管线与性能分析系列——CPU篇 - 知乎

06/19 12:24
阅读数 1.4K
虚幻引擎提供了很多功能,它们也有不同的性能特性。为了达到性能的要求会需要对游戏素材和代码进行优化。因此需要能够知道性能花费在何处。

最近把RTR的延时渲染管线摸了一遍,想做一个关于渲染管线和游戏性能的总结。(只针对于延时渲染)

计划分为3个部分(GPU的比较复杂所以分了两块):

  • CPU篇
  • Geometry Rendering (GPU)
  • Lighting and Post processing(GPU)
本系列相对较为基础,适合没有图形学基础的同学。

CPU——central processing unit(中央处理器)

首先,我们要知道CPU在实时渲染中扮演了怎样的角色。

在渲染之前,我们要确定好我们要渲染的是什么,我们要渲染的具体有哪些对象,我们要渲染的对象的位置变化是怎样的,这些信息都要经过复杂的计算得出,并且整理好一份渲染列表给到我们的gpu进行光栅化和像素处理。

那么执行这一步操作的就是CPU。

游戏线程 Game Thread

CPU会在内存中得到数据,计算场景中所有对象的逻辑与变换。例如:

  • 动画
  • 模型与对象的位置
  • 计算物理特效
  • 对象的生成与销毁,隐藏与显示
  • AI

这些都属于cpu中进行的游戏线程。

控制台:stat unit 调用渲染延时数据

其中“game”就是游戏线程所产生的延时,根据这个数据可以粗略的判断,该帧的性能瓶颈位于cpu还是gpu。

控制台:stat game 游戏线程各环节调用次数和延时

此图标可以找到引起cpu受限的游戏代码(如蓝图、光线投射、物理、AI、内存分配等)


分析工具-Session Frontend

Window菜单栏的Developer Tools部分选择Session Frontend,然后选择Profiler。

控制台输入“stat startfile”,间隔几秒钟后输入“stat stopfile”,来得到这段时间的数据

点击load,导入ue4stat数据 路径saved/Profiling/UnrealStats

使用该分析器可以查看游戏线程中花费性能最多的逻辑与运算是那一个环节,如下图

找到问题之后可以对相关逻辑的代码或者蓝图进行优化。


剔除 Culling

经过游戏线程的计算,得到了所有对象的位置,但是我们并不需要场景中所有的对象都要得到渲染,我们要剔除掉对画面没有实际帮助对象,只渲染我们能看的到的对象,只把这些对象提交后环节,这一环节叫做culling剔除。这一环节大部分的工作在cpu中完成。

在ue4中,culling会使用4种混合方案来实现:

1.Distance Culling 距离剔除

根据距离摄像机的远近进行剔除,在物体属性下,可以进行自定义控制数值

同时也可以使用cull distance volume进行对区域内物体整体控制


2.Frustum Culling 视锥剔除

根据摄像机所捕获的范围进行剔除,由摄像机的视角和远近裁切平面决定。


3.Precomputed Visibillty 预计算可预见性

对场景内各个位置观察物体的情况进行预计算,储存在场景当中,当摄像机移动到场景中某个位置,就会调用该处的保存好的信息,直接得到需要渲染的对象,而不需要实时计算。

开启方法:world setting-Precomputed Visibillty 勾选开启

加一个Precomputed Visibillty Volume 框柱场景中需要计算的部分


加入完成后构建一下灯光,打开show-visualize-precomputed visibillty cells,可以看到被划分好的网格机构,每个网格当中储存了可见性信息。


4.Occlusion Culling 遮挡剔除

遮挡剔除会实时计算每个物体在摄像机前的可见性,剔除掉那些被完全挡住的对象,剔除都以对象为单位,而不是像素或者多边形,所以occlusion culling是消耗最大的,所以会放到culling的最后一个环节,尽量让它少计算一些对象。

控制台输入 stat initviews 可以查看场景中具体的剔除情况。


Culling Performance Implications 性能影响

  • 尽量开启距离剔除,不要让模型在遮挡剔除部分堆积,遮挡剔除很费。
  • 场景超过一万个对象,会有影响,主要是cpu的瓶颈造成的。
  • 大型开放世界,遮挡会很少,所以会很卡。
  • 粒子对象同样可以被遮挡剔除掉。
  • 保持模型在合理的大小,太过细碎会增加绘制次数,太过庞大,会只看到一点的时候也全部渲染,因为剔除是按照对象进行的,而不是像素或者多边形。


Draw Call 绘制调用

当进行完culling后,cpu把渲染名单提交于gpu,并向gpu下达渲染的指令。

那么还需要确定渲染的顺序问题和分组问题。

为了防止渲染多余的像素,我们要知道物体的前后顺序,不去渲染那些被遮挡的像素,在光栅化之前,会有一个early z pass,来定义物体前后关系,避免重复绘制像素。

在渲染中,会把具有相同的属性的多边形分为一组,进行渲染,那么这一组多边形会称为一个drawcall。比如,场景中的有几个对象就会有几个drawcall,对象上附带的几个材质也会分为几个drawcall。

左图绘制调用次数5,右图绘制调用次数6


drawcall的次数是影响性能的关键因素,且造成性能瓶颈的主要是CPU,因为GPU的渲染速度会很快,会快于cpu提交drawcall的速度,如果场景中drawcall的次数过于多,就会造成cpu过载。

控制台:stat SceneRendering 调出绘制调用次数

一般来说2000-3000次比较合理,超过10000就会出现问题了

在手机端的话可能会更加的低一些,可能也就是几百最多了

通常情况下,drawcall也比多边形数具有更大影响。

控制drawcall次数的方法

  • 减少物体数量(静态/动态网格体、网格体粒子)
  • 缩短可视距离(如:场景捕捉 Actor 或每个物体上的距离)
  • 调整画面(将画面拉得更远、使移动物体不在同一个画面中)
  • 不使用 SceneCaptureActor(须重新渲染场景、调低帧率、或只在需要时进行更新)
  • 不使用分屏(分屏比单屏的 CPU 受限更大,需对可延展性设置进行自定义或将内容设为更加主动)
  • 减少每次绘制调用的元素(将接受更复杂像素着色器的材质进行组合或单纯地减少材质数量,将纹理组合为少数几块较大的纹理 - 只在减少材质数量时才使用元素较少的 LOD 模型)
  • 禁用网格体上自定义深度或阴影投射的功能
  • 将光源设为不投射阴影,或拥有更紧凑的边界体(视锥、衰减半径)
  • 为了减少绘制调用,尽量使用大模型,但是 大模型也会带来问题:对遮挡剔除不好,光照贴图精度不高,对碰撞检测不好,对内存也不太好。所以模型的大小要在权衡各个方面后,在一个合理的大小。
  • Hlod 在远距离上用一个模型替换一组模型,降低绘制调用。右上角可以打开hlod编辑器,ue生成集群,然后自动创建网格体,替换模型。

对模型进行合理的合并也可以降低绘制调用次数。合并注意事项:

  • ①合并使用较多且面数较低的模型
  • ②合并相同区域的模型
  • ③合并相同材质的模型
  • ④碰撞体没有或者简单的可以合并
  • ⑤比较小或者只接受动态光照的模型
  • ⑥远处的模型


OK,绘制调用结束之后,CPU的工作也基本完成了,下面就是把信息和指令提交给GPU,在GPU中完成光栅化和最后的画面合成。当然GPU才是实时渲染中的主角!我会在专栏更新后面两篇文章GPU相关内容(着色器,光照等)

欢迎大家关注专栏我的“我又懂了”,里面会有你们喜欢的那种东西!


我又懂了zhuanlan.zhihu.com图标

enjoy it~

展开阅读全文
打赏
0
0 收藏
分享
加载中
更多评论
打赏
0 评论
0 收藏
0
分享
OSCHINA
登录后可查看更多优质内容
返回顶部
顶部