笔记总结-优化(一)
善用 SIMD。单指令多数据流(Single Instruction Multiple Data,SIMD),例如 Intel 的 MMX 或 SSE,以
及 AMD 的 3D Now!指令集,在很多情形下能获得很好的性能,可以并行计算多个单元,且比较适合
用于顶点操作。
•
使用 float 转 long 转换在奔腾系列处理器上速度较慢。如果可以请尽量避免。
•
尽可能避免使用除法。相对于其他大多数指令而言,执行除法指令所需要的时间大约是执行其他指令
所需时间的 2.5 倍或更多。
•
许多数学函数,如 sin、cos、tan、exp、arcsin 等,计算开销较高,使用的时候必须小心。如果可以接
受低精度,那么只需要使用麦克劳林(MacLaurin)或泰勒(Taylor)级数的前几项即可。由于现代
CPU 对内存的访问的代价依然很高,因此使用级数的前几项比使用查找表(Lookup Tables)强得多。
•
条件分支会有一定的开销,Shader 中的条件分支开销尤甚。尽管大多数处理器都有分支预测功能,但
是这意味着只有准确地进行分支预测,才有可能降低计算开销。错误的分支预测对一些体系结构、特
别是对于具有深管线的体系结构来说,计算开销通常会较高。
•
对于经常调用的小函数使用内联(Inline)。
•
在合理的情况下减少浮点精度,比如用 float 代替 double。而当选用 float 型来代替 double 型数据时,
需要在常数末尾加上一个 f。否则,整个表达式就会被强制转换为 double 型;因此,语句 float x
=2.42f;要比 float x = 2.42;执行得更快。
•
尽可能使用低精度数据,让发送到图形管线的数据量更少。
•
虚函数方法、动态转换、(继承)构造,以及按值传递结构体(passing structs by value)都会对效率造
成一定影响。据了解,一帧画面中大约有 40%的时间花费在用于模型管理的虚拟继承层次结构上。
Blinn 提出了一种技术[3],可以避免计算 C++中向量表方面的一部分开销。
•
在顶点格式中使用尽可能少的位。位数足够即可,不需要对所有数据都使用浮点格式(例如对颜
色)。
•
在顶点程序中产生可推导的顶点属性,而不是把他们存储在输入顶点格式中。例如,正切线
(tangent)、法线(normal)和副法线(binormal)通常不需要都存储。给出任意两个,能用 Vertex-program 简
单叉积推导出第三个。这项技术,即为用顶点处理速度去换取顶点传输速率。
•
使用 16 位的索引代替 32 位的索引。16 位索引更容易查找。移动起来更轻量,而且占用的内存更
少。
•
以相对连续的方式访问顶点数据。当访问顶点数据时现代 GPU 可以进行缓存。因为在任意内存层次
中,引用的空间局部性有助于最大化缓存的命中率,这可以减少对带宽的要求
•
考虑使用的光源类型,以及可以考虑是否所有的多边形都需要光照。有时模型只需纹理贴
图,或者在顶点使用纹理,或只需要顶点颜色。那么很多多边形就无需进行光照计算。
•
如果光源是静态的,且照明对象是几何体,那么漫反射光照和环境光可以预先计算并存储在顶点颜
色中。这样做通常被称为烘焙照明(baking lighting)。一个预光照(prelighting)更复杂的形式是使用
辐射度(Radiosity)方法预先计算场景中的漫反射全局光照,而这样的光照可以存储在顶点颜色或光
照贴图(lightmaps)中。
•
控制光源的数量。光源的数量影响几何阶段的性能,更多的光源意味着更少的速度。此外,双面的光
照可以比单面光照更为昂贵。当对光源使用固定功能距离衰减时,根据物体与光源的距离来关闭/打
开光源是有较为有用,且几乎不会被察觉。而距离足够大时,可以关掉光源。
•
一种常见的优化方法是根据光源的距离来进行剔除,只渲染受本地光源影响的对象。
•
另一种减少工作的方法是禁用光源,取而代之的是使用环境贴图(environment map)
•
如果场景拥有大量光源,可以使用延迟着色技术来限制计算量和避免状态的变化
•
善用背面裁剪。对封闭(实心)的物体和无法看到背面的物体(例如,房间内墙的背面)来说,应该
打开背面裁剪开关。这样对于封闭的物体来说,可以将需光栅化处理的三角形数量减少近 50%。但需
要注意的是,虽然背面裁剪可以减少不必要的图元处理,但需要花费一定的计算量来判断图元是否朝
向视点。例如,如果所有的多边形都是正向的,那么背向裁剪计算就会降低几何阶段的处理速度。
•
一种光栅化阶段的优化技术是在特定时期关闭 Z 缓冲(Z-buffering)。例如,在清楚帧缓冲之后,必
须要进行深度测试也可以直接渲染出任何背景图像。如果屏幕上的每个像素保证被某些对象覆盖(如
室内场景,或正在使用背景天空图),则不需要清楚颜色缓冲区。同样,确保只有在需要时才使用混
合模式(blend modes)。
•
值得一提的是,如果在使用 Z 缓冲,在一些系统上使用模板缓冲不需要额外的时间开销。这是因为 8
位的模板缓冲的值是存储为 24 位 z 深度值的同一个 word 中。
•
优先使用原生的纹理和像素格式。即使用显卡内部使用的原生格式,以避免可能会有的从一种格式到
另一种格式的昂贵转换。
•
另一种适用于光栅化阶段的优化技术是进行合适的纹理压缩。如果在送往图形硬件之前已经将纹理压
缩好,那么将它发送到纹理内存中的速度将会非常迅速。压缩纹理的另一个优点是可以提高缓存使用
率,因为经过压缩的纹理会使用更少的内存。
•
另一种有用的相关优化技术是基于物体和观察者之间的距离,使用不同的像素着色器。例如,在场景
中有三个飞碟模型,最接近摄像机的飞碟的可能用详细的凹凸贴图来进行渲染,而另外两个较远的对
象则不需要渲染出细节。此外,对最远的飞碟可以使用简化的镜面高光,或者直接取消高光,来简化
了计算量以及减少采样次数。
•
理解光栅化阶段的行为。为了很好地理解光栅阶段的负荷,可以对深度复杂度进行可视化,所谓的深
度复杂度就是指一个像素被接触的次数。生成深度复杂度图像的一种简单方法就是,使用一种类似于
OpenGL 的 glBlendFunc(GL ONE,GL ONE)调用,且关闭 Z 缓冲。首先,将图像清除成黑色;然后,对
场景中所有的物体,均使用颜色(0,0,1)进行渲染。而混合函数(blend function)设置的效果即是对每
个渲染的图元来说,可以将写入的像素值增加(0,0,1)。那么,深度复杂度为 0 的像素是黑色,而深度
复杂度为 255 的像素为全蓝色(0, 0, 255)。
•
可以通过计数得到通过 Z 缓冲与否的像素的像素的数量,从而确定需进一步优化的地方。使用双通
道的方法对那些通过或没通过 Z 缓冲深度测试的像素进行计数。在第一个通道中,激活 Z 缓冲,并对
那些通过深度测试的像素进行计数。而对那些没有通过深度测试的像素进行计数,可以通过增加模板
缓冲的方式。另一种方法是关闭 Z 缓冲进行渲染来获得深度复杂度,然后从中减去第一个通道的结
果。
•
避免过度归一化(Normalization)。在写 shader 时,对每个步骤的每个矢量都进行归一化的习惯,常常
被调侃为“以归一化为乐(Normalization-Happy)”。这个习惯通常来说其实是不太好的习惯。我们应
该意识到不改变长度的变换(例如标准正交基上的变换)和不依赖矢量长度的计算(例如正方体贴图
的查询)是完全没必要进行归一化后再进行的。
•
考虑使用片元着色器的 LOD 层次细节。虽然片元着色器的层次细节不像顶点着色器的层次细节影响
那么大(由于投射,在远处物体本身的层次细节自然与像素处理有关),但是减少远处着色器的复杂
性和表面的通道数,可以减少片元处理的负载。
•
在不必要的地方禁用三线性过滤。在现代 GPU 结构的片元着色器中计算三线性过滤(Trilinear
filtering),即使不消耗额外的纹理带宽,也要消耗额外的循环。在 mip 级别转换不容易辨别的纹理
上,关掉三线性过滤,可以节省填充率。
•
使用尽可能简单的 Shader 类型。在 Direct3D 和 OpenGL 中,对片元进行着色都有多种方法。举例来
说,在 Direct3D 9 中,可以指定片元着色的使用,随着复杂性和功率的增加,有纹理阶段、像素
shader 版本 1.x、像素 shader 版本 2.x,以及像素 shader 3.0 等。一般而言,应该使用最简单的着色器
版本来创建预期的效果。更简单的着色版本提供了更多的一些隐式编译选项,通常可以用来让它们更
快地被 GPU 驱动程序编译成处理像素的原生代码。
•
减少纹理尺寸。考虑目标分辨率和纹理坐标。如果玩家是不是真的会看到最高级别的 mip 级别,如果
不是,就应该考虑缩减纹理大小。此方法在超载的帧缓冲存储器从非本地存储器(例如系统存储器,《Real-Time Rendering 3rd》 提炼总结
237
通过 AGP 或 PCI Express 总线)强制进行纹理化时会非常有用。一个 NVIDIA 在 2003 年出品的名叫
NVPerfHUD 的工具可以帮助诊断这个问题,其显示了各个堆(heaps)中由驱动所分配的内存量。
•
压缩所有的彩色纹理。应该压缩作为贴花或细节的一切纹理,根据特定纹理 alpha 的需要,选用
DXT1、DXT3 或 DXT5 进行压缩。这个步骤将会减少内存使用,减少纹理带宽需求,并提高纹理缓存
效率。
•
避免没必要的昂贵纹理格式。64 位或 128 位浮点纹理格式,显然要花费更多带宽,仅在不得已时才
可以使用它们。
•
尽可能地在缩小的表面上使用 mipmapping。mipmapping 除了可以通过减少纹理走样改善质量外,还
可以通过把纹理内存访问定位在缩小的纹理上来改善纹理缓存效用。如果发现某个 mipmapping 使表
面看起来很模糊,不要禁用 mipmapping,或增加大的 LOD 级别的基准偏移,而是使用各向异性过滤
(anisotropic filtering),并适当调整每个批次各向异性的级别。
•
减少 alpha 混合。当 alpha 混合的目标混合因子非 0 时,则要求对帧缓冲区进行读取和写入操作,因
此可能消耗双倍的带宽。所以只有在必要时才进行 alpha 混合,并且要防止高深度级别的 alpha 混合
复杂性。
•
尽可能关闭深度写入。深度写入会消耗额外的带宽,应该在多通道的渲染中被禁用(且多通道渲染中
的最终深度已经在深度缓冲区中了)。比如在渲染 alpha 混合效果(例如粒子)时,也比如将物体渲染
进阴影映射时,都应该关闭深度写入。另外,渲染进基于颜色的阴影映射也可以关闭深度读取。
•
避免无关的颜色缓冲区清除。如果每个像素在缓冲区都要被重写,那么就不必清除颜色缓冲区,因为
清除颜色缓冲区的操作会消耗昂贵的带宽。但是,只要是可能就应该清除深度和模板缓冲区,这是因
为许多早期 z 值优化都依赖被清空的深度缓冲区的内容。
•
默认大致上从前向后进行渲染。除了上文提到的片元着色器会从默认大致上从前向后进行渲染这个方
法中受益外,帧缓冲区带宽也会得到类似的好处。早期 z 值硬件优化能去掉无关的帧缓冲区读出和写
入。实际上,没有优化功能的老硬件也会从此方法中受益。因为通不过深度测试的片元越多,需要写
入帧缓冲区的颜色和深度就越少。
主流性能分析工具列举
有很多不错的分析图形加速器和 CPU 使用的的工具,以及性能优化相关的 Profiling 工具,在
这里,将主流的工具进行列举:
•
Adreno Profiler
•
GPA
•
Tegra Graphics Debuger
•
Xcode Profiler
•
Xcode Instruments
•
PIX for Windows (for DirectX)
•
gDEBugger (for OpenGL)
•
NVIDIA’s NVPerfKit suite of tools
•
ATI’s GPU PerfStudio
•
Apple’s OpenGL Profiler《Real-Time Rendering 3rd》 提炼总结
239
•
Linux 上的 Valgrind http://valgrind.org/
•
NVIDIA 出品的 Nsight 系列性能优化套件
•
https://developer.nvidia.com/gameworks-tools-overview
•
CPU 端内循环优化工具 Vtune https://software.intel.com/en-us/intel-vtune-amplifier-xe
•
AQTime - 代码 profilers 工具 https://smartbear.com/product/aqtime-pro/overview/
现今主流游戏引擎提供的 Profiler 有:
•
Unreal Engine 的一列系列 Profiler 工具集 https://docs
origin.unrealengine.com/latest/INT/Engine/Performance/
•
Unity 的 Profiler 和后续新加入的 Frame Debugger
– Unity – Profiler https://docs.unity3d.com/Manual/ProfilerWindow.html
– Unity - Frame Debugger https://docs.unity3d.com/Manual/FrameDebugger.html
节选自毛星云(浅墨)的《Real-Time Rendering 3rd》提炼总结

浙公网安备 33010602011771号