fwalker 发表于 2023-1-2 16:46

一种帮助优化GPU渲染的算法

这个算法是干什么的

这个算法可以为大量物体渲染优化提供理论指导,可以指导编写GPU驱动的渲染管线。如果你正在编写游戏引擎,或者从事渲染管线编写、优化方面的工作,请继续阅读。
文章为纯理论,没有具体实现,也没有推导公式或推导过程。
问题引入

从简单的场景看渲染

让我们从只有一个光源和一个模型的场景开始研究,画出一张图像,然后分析它是如何画出来的。



Kizuna AI模型和一个点光源

我们可以从非常多的视角来分析这个问题,但是在这里我们只关注GPU在绘制这张图像时使用了哪些信息,更具体点说我们只关注“绘制”这个动作使用了哪些信息。
为了简化问题,将整个场景简化为只有物体,而所有场景相关的信息都存在于物体中。
假设绘制这个模型只有一次绘制调用,那么在本次绘制调用中,只有两个信息被使用,分别是:模型、光源。
复杂场景

让我们思考一下更复杂的场景,比如两个点光源和两个模型



一个红色光源和一个绿色光源

绘制这张图像需要两次绘制调用,第一次绘制调用使用了三个信息:模型1、光源1、光源2,第二次绘制调用也使用了三个信息:模型2、光源1、光源2。
思考过后得到以下推论:光源越多,绘制调用使用的信息越多。模型越多,绘制调用次数越多。
推论

虽然我们可以对所有的场景给出这种推论,但是我们也不懂这种推论有什么用。所以直接给结论:游戏引擎里的Shader,可以处理绘制调用中的所有情况。对于每一个情况的绘制调用,游戏引擎都至少有一个Shader来处理。才能正常的渲染出画面。
这种道理每一个写渲染器的人都会懂的,但我还有一个重要的结论:渲染游戏画面的代码要么在产生这种绘制调用,要么在产生绘制调用合集(合批、实例化)。所以研究这个问题有助于我们优化渲染过程,从最大程度上优化渲染,甚至有助于研究从GPU上发出渲染指令,也就是所谓的GPU Driven Pipeline。
研究一般的场景

让我们从更一般的场景开始研究。这个场景中有天空光照,平行光,一个相机,和两个模型。


为了方便理解,我画一张图片,展示两次绘制调用所需的信息:


然后考虑有2个相机的情况,下图是绘制调用所需的信息:


相机的数量增加了1个,绘制调用的次数从2次变成了4次。引入相机会使绘制调用增加。
我这里有一种算法可以求出绘制调用所包含的信息。接下来将会介绍一下这个算法。
算法

输入、输出

这个算法的输入有:场景、场景元素间的关系。算法的输出是绘制调用所需的信息。
例如:对于上文中2个相机、1个天空光、2个模型、1个平行光的场景,输入除了场景本身外、还有场景元素间的关系。我认为元素之间只有两种关系,将其命名为+(加)和×(乘)。例子里元素间的关系表示如下:
{{相机×模型}, {天空光+模型}, {平行光+模型}}
计算没有顺序。计算的输出如下图。+代表将前面的元素附加到后面的元素,×代表将两个元素排列组合



和上一张图片一样

可以从计算结果中得出一部分结论:在不做优化的情况下,渲染这一帧需要4次绘制调用(绘制了2个相机),并且每次绘制调用需要如上图所示的信息。优化建议:如果上图中的模型2和模型1是一样的,则可以使用批处理一次处理两个原始绘制调用。
延迟渲染

上文中的计算结果是针对前向渲染的。如果需要使用这个算法来计算延迟渲染,则需要修改输入中的关系,使其和延迟渲染相适应。
我们假设GBuffer是和相机一一绑定的,因此不再引入新元素。
针对延迟渲染的输入如下:
2个相机、1个天空光、2个模型、1个平行光的场景。{相机×模型}, {平行光×相机}, {天空光×相机}
计算结果如下图:


从计算结果得知,使用延迟渲染渲染一帧需要6次绘制调用。当然你可以使用不同的方法绘制,将关系更改如下。
{相机×模型}, {{平行光+相机}, {天空光+相机}}
计算结果如下图


此时需要4次绘制调用来完成一帧。同时,一次绘制调用需要同时处理平行光和天空光。
光源剔除的情况




一个大的点光源和一个小的点光源

考虑2个点光源、2个模型、天空光照、1个相机的情况。从左至右给模型编号1、2号。1号模型接受1个点光源,2号模型接受2个点光源。
{{点光源+模型}, {天空光+模型}, {相机×模型}}
加入了限制条件(但是没有直接在符号上体现),计算结果如下图



一次调用处理两个光源

此时需要两个绘制调用来完成一帧。在处理这两个绘制调用时,可以使用同一个Shader,也可以使用两个不同的Shader,这取决于程序员的代码能力或是性能基准测试。
你也可以通过检查计算结果,找出存在高性能代价的光照。
镜子或传送门

只需加入{镜子×模型},这个有很多种组合可以满足(你可能需要虚空镜子),结果是每个镜子都会让绘制调用增加很多。
光照探针(组)

使用普通的相机更新光照探针。在数据视角处理光照探针时就像处理普通的光照那样。
优化渲染

这个算法的计算结果是你优化渲染的基础。你可以设计一个算法,自动的将渲染进行合批和实例化,或是手动分析数据,选出最有效的处理方法,提高CPU或是GPU的处理速度。
引入效果

通常来说游戏里会渲染一些粒子、贴花、动画纹理、描边之类的特效,同样可以通过这个算法进行计算。只要你编写的Shader足够多或是足够复杂,可以处理出现的每一种数据组合,那么一定不会出现渲染错误。如果出现了某一种数据组合,而没有适当的Shader对其进行处理,那就要考虑一下是否会出现渲染错误。
举个例子,某两个技能特效都会给模型附加上动态的火焰,但是Shader没办法同时处理这两个火焰,所以只会有一个特效被播放。解决办法是编写一个能同时处理这两个特效的Shader(但确实有难度,一般不会处理)。
另一个例子:许多游戏引擎单个物体所能接受的光照被限制了,这是出于性能考虑的。
另一个例子:有些游戏的镜子不会反射粒子效果,因为它的粒子效果只会在主相机渲染一次。
转向GPU Driven Pipeline

从数据的角度来看,GPU驱动的渲染管线和普通的渲染管线并没有什么不同。但是使用GPU驱动的渲染管线必然有些限制,这样才能最大程度的发挥显卡的性能。在有了以上算法之后,我认为GPU驱动的渲染管线可以在以下方面介入渲染:

[*]收集渲染信息,将场景中的元素联系起来
[*]对收集到的渲染信息进行分组
[*]使用收集到的渲染信息发起绘制调用
[*]使用通用Shader处理不可合批的绘制调用
GPU驱动的渲染管线还会处理网格簇的情况,但是已经不在文章的研究范围内了。如果将网格簇当成普通的模型的话,这个算法可以显示渲染每个模型所需的信息,渲染时需要考虑处理这些信息。
给算法起个名字吧

因为算法还没实现,没有起名的想法,也不知道起什么名字好。
页: [1]
查看完整版本: 一种帮助优化GPU渲染的算法