Unity-Shader读书笔记(15)
Unity-Shader读书笔记(15)
这边文章大多是讲Unity中的渲染优化技术。
影响性能的因素
书中将影响性能的因素分成了三个方面:
(1)CPU 在CPU方面过多的Draw Call、复杂的脚本和物理模拟都是消耗性能的原因。所谓Draw Call就是CPU在每次通知GPU进行渲染之前,都需要提前准备好顶点数据(如位置、法线、颜色和纹理坐标等),然后调用一系列API把它们放到GPU可以访问到的指定位置,最后调用一个绘制指令。而在我们调用绘制指令的时候,就会产生一个Draw Call。如果一帧中Draw Call数量的过多会导致CPU在调用Draw Call上花费许多的时间。 (2)GPU 在GPU方面则为过多的顶点/片元(过多的片元可能是由于分辨率或是overdraw造成的)和过多的逐顶点/逐片元计算。 (3)带宽 在带宽方面,项目中使用了没有尺寸大但是没有压缩的纹理,或者存在分辨率过高的帧缓存。
书中提及的优化技术
(1)CPU优化,使用批处理技术来减少Draw Call的数目
(2)GPU优化。在减少需要处理的顶点数目方面,书中介绍了优化几何体、使用LOD(Level of Detail)和使用遮挡剔除(Occlusion Culling)技术。在减少需要处理的片元数目方面,书中介绍了控制绘制顺序、警惕透明物体和减少实时光照。在减少计算复杂度方面,书中介绍了Shader的LOD(Level of Detail)技术和Shader代码方面的优化。
(3)节省内存带宽。减少纹理大小和利用分辨率缩放。
减少Draw Call数目
我们主要使用的方法就是批处理(batching)技术。为了把一个对象渲染到屏幕上,CPU需要检查哪些光源影响了该物体,绑定Shader并设置它的参数,再把渲染命令发给GPU。如果三角形数量过多的情况(比如一千个三角形)下,我们渲染一千个三角形每个按照单独的网格进行处理小号会大于我们将渲染一个存在一千个三角形面片的网格。
而物体进行批处理也是需要条件的。我们只能使用同一材质的物体才能进行批处理。这是因为对于使用同一材质的物体,它们之间的不同仅在于顶点数据的差别。所以我们就可以将顶点数据合并在一起再发送给GPU。
Unity中支持两种批处理的方式:一种是动态批处理,另一种是静态批处理。
动态批处理.
动态批处理的原理是Unity每一帧把可以进行批处理的模型进行合并,然后再把合并后的数据传递给GPU。动态处理的好处在于一切都是Unity自动帮我们完成的,我们不需要做任何的操作。而且经过处理的物体仍然可以移动(Unity每帧都会进行一次合并,所以移动了也没有问题)。
虽然Unity会自动帮我们处理,但是只有满足条件的物体才会进行这样的操作。而相关条件Unity官方有给出说明:Dynamic batching - Unity 手册(这里我只给出了其中一个版本的说明,大家要设置为各位对应的Unity的版本说明)。
静态批处理
静态批处理适用于任何大小的几何模型。它的原理是只在运行开始阶段就把需要进行静态批处理的模型合并到一个新的网格中。而这些模型就不可以在运行阶段进行移动,即使我们使用脚本来改变也不可以。由于它只需要进行一次合并,所以它比动态批处理更加高效。可是它也需要占用更多的内存来存储合并后的几何结果。这是因为如果静态批处理前一些物体共享了相同的网络,那么在内存中每一个物体都会对应一个该网络的复制品,即一个网格变成多个网格给GPU。可是如果这类使用同一网络的对象很多,则又会变成一个性能瓶颈。比如在一个使用一千个相同树模型的森林使用静态批处理,那么这就会造成多使用一千倍的内存后果。这时候就会造成严重的内存影响。这时候解决的方法就是要么忍受,要么不使用动态批处理,或者自己写方法做优化。(这篇文章中也有讲到静态批处理的内容Static batching - Unity 手册)如果我们要用静态批处理只需要将模型中Inspector的static属性勾起就可以了。
共享材质
无论是动态批处理还是静态批处理都要求我们使用共享材质。但不同的模型之间总会需要有不同的渲染属性,例如使用不同的纹理、颜色等。这时候我们就需要一些策略来尽可能地合并材质。
如果两个材质之间只有使用纹理不同,我们可以把这些纹理合并到一张更大的纹理中。这种纹理被称为图集。一旦使用了同一张纹理,我们就可以使用同一个材质,再使用不同的采样坐标对纹理进行采样就可以了。
但有时,除了纹理不同外,不同的物体在材质上还有一些微小的参数变化,例如,颜色不同、某些浮点属性不同。但是,不管是动态批处理还是静态批处理,它们的前提都是要使用同一个材质。是同一个,而不是使用了同一种Shader的材质,也就是说它们指向的材质必须是同一个实体。这意味着,只要我们调整了参数,就会影响到所有使用这个材质的对象。那么想要微小的调整怎么办呢?一种常用的方法就是使用网格的顶点数据(最常见的就是顶点颜色数据)来存储这些参数。
前面说过,经过批处理后的物体会被处理成更大的VBO发送给GPU,VBO中的数据可以作为输入传递给顶点着色器,因此,我们可以巧妙地对VBO 中的数据进行控制,从而达到不同效果的目的。一个例子是,森林场景中所有的树使用了同一种材质,我们希望它们可以通过批处理来减少draw call,但不同树的颜色可能不同。这时,我们可以利用网格的顶点的颜色数据来调整。
需要注意的是,如果我们需要在脚本中访问共享材质,应该使用Renderer.sharedMaterial来保证修改的是和其他物体共享的材质,但这意味着修改会应用到所有使用该材质的物体上。另一个类似的API是Renderer.material,如果使用Renderer.material来修改材质,Unity 会创建一个该材质的复制品,从而破坏批处理在该物体上的应用,这可能并不是我们希望看到的。
Ps:书中讲得太完备了,所以我直接记录下书中的内容了。
批处理的注意事项
尽可能选择静态批处理,但得时刻小心对内存的消耗,并且记住经过静态批处理的物体不可以再被移动。
如果无法进行静态批处理,而要使用动态批处理的话,那么请小心上面提到的各种条件限制。例如,尽可能让这样的物体少并且尽可能让这些物体包含少量的顶点属性和顶点数目。
对于游戏中的小道具,例如可以捡拾的金币等,可以使用动态批处理。
对于包含动画的这类物体,我们无法全部使用静态批处理,但其中如果有不动的部分,可以把这部分标识成“Static”。
由于批处理需要把多个模型变换到世界空间下再合并它们。因此,如果 shader 中存在一些基于模型空间下的坐标的运算,那么往往会得到错误的结果。一个解决方法是在shader中使用DisableBatching标签来强制使用该Shader的材质不会被批处理。还有一个注意事项是,使用半透明材质的物体通常需要使用严格的从后往前的绘制顺序来保证透明混合的正确性。对于这些物体,Unity 会首先保证它们的绘制顺序,再尝试对它们进行批处理。这意味着,当绘制顺序无法满足时,批处理无法在这些物体上被成功应用。
减少需要处理的顶点数目
优化几何体
有时候三维软件上显示的模型顶点数目和三角面片数会小于Unity渲染统计窗口的模型顶点数目和三角面片数。它们都是正确的数目,只是它们所在的观察角度不同。三维软件是站在人类的角度去看待的,即组成几何体的每一个点就是单独的一个点。在GPU看来,有时需要把一个顶点拆分成两个或者更多的顶点。而GPU这样做的原因有两个:一个是为了分离纹理坐标(uv splits),另一个是为了产生平滑的边界(smoothing splits)。它们的本质都是因为对于GPU来说,顶点的每一个属性和顶点之间必须是一对一的关系。而分离纹理坐标,是因为建模时一个顶点的纹理坐标有多个。例如,对于一个立方体,它的6个面之间虽然使用了一些相同的顶点,但在不同面上,同一个顶点的纹理坐标可能并不相同。对于GPU来说,这是不可理解的,因此,它必须把这个顶点拆分成多个具有不同纹理坐标的顶点。而平滑边界也是类似的,不同的是,此时一个顶点可能会对应多个法线信息或切线信息。这通常是因为我们要决定一个边是一条硬边(hard edge)还是一条平滑边(smooth edge)。
对于GPU来说,它本质上只关心有多少个顶点。因此,尽可能减少顶点的数目其实才是我们真正需要关心的事情。几何体优化要移除不必要的硬边以及纹理衔接,避免边界平滑和纹理分离。
模型的LOD技术
LOD(level of detail)技术的原理是当一个物体离摄像机很远时,模型上的很多细节是无法被看到的。因此当对象离摄像机很远的时候,我们就可以减少模型的面片数目,从而提高性能。
在Unity中,我们可以直接使用LOD Group组件来实现这个效果。
遮挡剔除技术
关于这个Unity有介绍:Occlusion Culling 窗口 - Unity 手册和遮挡剔除 - Unity 手册。
减少需要处理的片元数目
这部分的优化重点在于减少overdraw(指同一个片元被绘制了多次)。
透明物体
透明物体这里指的是使用关闭深度写入进行透明渲染的物体。正因为关闭了深度写入,所以如果我们要得到正确的结果,我们就要从后往前渲染。而一开始我们是从前往后,这就导致了这样的透明物体一定会造成overdraw。所以要尽量少使用这样的透明物体。
减少实时光照和阴影
这一点自然不必多说明,这本来就是很消耗性能的操作。
减少计算复杂度
Shader的LOD技术
Unity中有提及Shader中使用LOD:ShaderLab:为子着色器指定 LOD 值 - Unity 手册。
PS:这篇文章基本都是照抄原文了。这些优化建议,都是要在实践中才能得到。而我本来就很少用Shader,所以基本都记录下来了。剩下的还有代码优化,在我找资料的时候,我就找到很多种说法。随着时间段的不同大家各有一套说辞。但我可以肯定的是,所谓的优化都是根据具体的项目来。而我实践实在是太少了,并且我还懒得去比较他们的说辞。所以这个我就不写了。如果你想知道原文,我建议直接买书。其实你也可以去网上搜一下,网上可多的是像我一样照抄原文的人。如果我有一定的实践或许就不用照抄原文了吧,可惜工作上用不到,自己用根本不需要优化。