诚信为本:市场永远在变,诚信永远不变。

摩杰资讯

当前位置: 首页 > 摩杰资讯

摩杰资讯

发布时间:2024-05-20 19:41:00点击量:

总结一下最近看到的shader性能优化策略:
首先介绍下一个shader优化工具:glsl-optimizer

通过之前文章的学习,我们应该已经了解到 GPU 是流式处理器,其中的顶点着色单元会每顶点执行一次,而片元着色单元会每片元执行一次。可以想象,每绘制一帧画面,顶点着色器与片元着色器的代码少则执行成百上千次,多则执行几万甚至几百万次。

因此,如果能恰当地减少顶点着色器与片元着色器每次执行所做的工作量,性能将得到可观的改善。经常涉及的简单技巧主要包括着色计算的位置优化以及着色计算的代码优化两方面,具体内容如下:

与着色计算相关的任务有 3 个可能的执行位置: CPU、顶点着色器及片元着色器。从获得更高画面质量考虑,很多开发人员会把大量的着色计算相关代码放在片元着色器中。但在不影响画面质量或可以略微牺牲一点画面质量的情况下,可以考虑将相关代码的位置做一些变化,以换取性能的提升,主要包括如下两点。

  • 每当把计算任务安排到片元着色器中时,都应该考量一下:若将这个计算任务安排到顶点着色器中,画面质量会不会有影响,若有影响,在不在可以接受的范围内。如果条件允许,则应该将相应的计算任务安排到顶点着色器中进行。因为顶点着色器的执行频率远低于片元着色器,这样做一般可以获得较为明显的性能提升。
  • 每当把计算任务安排到顶点着色器中时,要首先判断此计算任务是对于每个顶点单独计算、结果不同,还是所有顶点共享一个相同计算结果。如果是所有顶点共享一个相同计算结果的情况,则应该将此计算任务交由CPU 执行,然后由宿主程序将计算结果作为一致变量传入顶点着色器供使用。

注: 在有些情况下,对于某个复杂计算的某部分是所有顶点共享的,那么,就应该把这部分计算提取出来交由 CPU 一次完成,然后将计算结果作为一致变量传入顶点着色器供使用。对于一个 3D 模型而言,顶点少则数百上千,多则几万、几十万甚至上百万,这样做可以降低计算负载,获得显著的性能提升。

编写着色器代码时,开发人员应该特别小心,这是由于着色器代码的执行频率很高,因此应该尽量优化这部分代码,使其运算量以及复杂度降到最低,这样会提高着色程序的执行效率。具体需要注意以下几点。

  • 编写着色代码时常常会进行一些计算,计算时要尽量通过代数方法简化计算,这样可以最大限度地提高运算速度。例如, p =sqrt(2 * (X+1))可以改写成 1.414 * sqrt(X+1)等。

  • 编写着色代码时常会对向量进行操作,例如,将向量归一化,求两个向量的点积、叉积等。对于这些操作,着色器提供了强有力的支持,内置了很多内建函数,方便开发人员使用。例如,归一化向量运算函数 normalize、求点积运算函数 dot、求向量折射函数 refract 等。这些函数大部分都是硬件实现的,开发时可以直接利用,能够降低代码的复杂度,优化计算速度。

  • 编写着色代码时经常会对矩阵进行操作,例如,将两个矩阵进行相乘运算,对于此类操作,着色器同样也提供了强有力的支持,内置了很多运算及函数,方便开发人员使用。例如,两个矩阵相乘直接使用“ * ”即可完成,这样无论从编码复杂度还是运算速度来看,效果都很不错。

在编写完整的着色器代码过程中,除了要考虑执行计算任务的位置、计算量等,对于一些内置函数的使用、方法的声明、变量的使用等也要时刻注意,出于性能方面的考虑,一般应该注意以下几点。

  • 进行计算的时候,尽量使用系统自带的内置函数,因为开发人员自己开发的计算方法在大多数情况下不如系统的内建函数高效,例如,可以使用系统自带的常见函数、几何函数、向量关系函数等。
  • 在编写着色代码的过程中,有时候需要进行一些简单的计算,对于这些重复性较低,而且计算的内容较为简单的操作,开发过程中不应该将这种计算封装成函数,直接用代码计算即可。 这样能大大减少 GPU 函数调用与返回的消耗(这方面是目前 GPU 的弱项),下面给出的两段代码 说明了这个问题。
 
  • 如果通过调整片元颜色的 alpha 值结合混合操作就可以达到想要的效果,就要尽量少使用discard函数舍弃当前片元,因为discard 是一个非常消耗资源的操作,会大大降低渲染的 FPS。
  • 如果进行计算之前已经对当前需要使用的向量进行归一化,那么,在使用时就不要再去求该向量的模长,因为该向量的模长就是 1。
  • 编写着色代码时要选择合适的精度,精度要和选择的纹理图质量相吻合,例如,如果选择的纹理图是低质量的 16 位或者 8 位的颜色模式,则可以考虑使用 lowp float 精度。
    浮点数精度相关:
    float:最高精度,通常32位
    half:中等精度,通常16位,-60000到60000,
    fixed:最低精度,通常11位,-2.0到2.0,1/256的精度。
    尽量使用低精度。对于 color 和 unit length vectors,使用fixed,其他情况,根据取值范围尽量使用 half,实在不够则使用 float 。
  • 编写完片元着色器后要检查其代码的长度,如果太长(一般超过 25 行),则需要考虑是否能将一些计算转移到顶点着色器。
  • 由于片元着色器是在 GPU 中执行, GPU 与 CPU 在硬件结构上有很大的差异,其执行分支语句的效率非常低。故在 GPU 中需要执行分支语句时往往需要想办法用内建的函数计算出因子,再结合数学表达式来替代分支操作,具体情况如以下代码段所示。
 

其中,着色器代码巧妙地使用内建函数避免了很多分支操作,将分支操作化解为顺序执行的函数运算。这是一种着色器编程常用的开发技巧,有需要时也可尽量采用。

选择纹理图时,出于性能的考虑,需要注意以下几点。

  • 不要使用特别大的纹理图,一般小于或等于 256×256 为适合。同时图片的尺寸要为 2 的 n次方,这样不仅能保证所有的设备都识别该纹理,而且计算速度也是最快的。
  • 在游戏或者可视化应用中,总是会遇到许多非常小的纹理,一种比较好的办法是把这些纹理组合成一个比较大的纹理。这样驱动程序在加载纹理的时候,仅仅需要加载一次就可以了。例如,开发人体模型,可以将一个人的头发、脸、眼睛等组合为一个纹理。
  • 使用压缩纹理。压缩纹理比非压缩纹理具有更快的运算速度和更小的存储空间要求,而且很容易使用图形硬件纹理缓冲。因此,这样能够显著地提高渲染性能,特别适合 3D 应用中纹理数量较大的场合。笔者的经验是能采用压缩纹理时一定要采用,很多大厂商的游戏中都是如此。
  • 在纹理图的选择上,如果使用的颜色模式为 16 位即可达到可以接受的效果,就尽量不要使用 32 位的,也就是说尽量使用颜色模式小的纹理图。

使用纹理图时,不能拿到一幅纹理图就直接使用,需要对纹理图做一些必要的处理后再使用。这样就能很好地避免资源的浪费,出于性能考虑,一般应该注意以下几点。

  • 对于大场景贴图而言,进行纹理采样时要尽量使用 mipmap,虽然这样相对而言会占用一些内存,但是,这样会提高纹理采样的效率,同时会得到更好的画面效果。
  • 进行纹理采样时,若最近点采样就能满足视觉效果的要求,就不要使用线性采样。由于线性采样算法比最近点采样算法复杂很多,其执行效率要比最近点采样低不少。
  • 对于需要将较小的纹理图平铺到一个较大平面上的情况,不应该采用一堆小的纹理矩形组合的方式,而应该采用一个大的纹理矩形结合 GL_REPEAT 采样方式来解决。

平台注册入口