Shader——移动平台上的优化

以下摘录自《Unity 3D ShaderLab开发实战详解》,第34章。(2015年第2版)
本章的大部分内容都是从 Unity 的官方论坛和文档收集的,在此只是简单地分享给读者。

移动平台的特点

相对于 PC 来说,移动平台上不论是内存还是运算速度,都相差了至少一个数量级,而且移动平台一般使用的是电源电池,而不是插线板,因此,其最大负荷是有限的,这就使我们不得不考虑如何最大化地优化 Shader 中的代码,使其提高运行效率。

一些指令的运算速度概念

首先,对于现代的 PC 显卡,它们已经足够“智能”,能够识别出一个 x*x*x*x 为 pow(x,4) ,并对应地做优化,比如有华为两个 x*=x , x*=x ,这样就可以把运算从4个减少为3个。但是,对于移动平台来说,目前还无法做到这一点,因此,你可能不得不使用 pow 指令,或者在 Shader 的代码中手动做些数学运算上的优化。
其次,在现代的 PC 显卡上,数学运算指令是非常快的,你基本不用考虑几条数学运算指令的代价,但是,移动平台上的情况却不是这样。 pow 、 sin 、 cos 这些数学函数在移动平台上的运算速度是很慢的。因此,如果可能,要尽量避免使用它们,把这些计算移到 vertex 函数中,或者使用一张小的贴图,通过查表来代替一些复杂的数学运算。除此之外,大家要注意, noise() 这个在标准 Cg 函数库中的函数,在绝大多数主流平台上都未被实现,因此,大家不要在这个函数上打什么注意。
对于 tex2D/texCUBE 指令,在移动平台上,一个读取贴图的指令和一个普通的数学运算指令消耗大概相差至少一个数量级;在 PC 平台上,这个差别更大,大概在两个数量级以上,这是因为在 PC 平台上,数学运算指令比 tex2D 的速度快了更多。
对于 IOS/PowerVR 来说,同样是 tex2D ,效率却可能很不同,这是因为对于 UV 在进入到 fragment 函数前就能确定的, PowerVR 可以提前读取,这样指令的执行效率就和在 fragment 中执行完全不一样了。
(转注:此外,尽量避免使用条件和循环语句)

几何复杂度的考量

顶点数量可以成为一个影响渲染效率的重要阈值,在 iOS 、 iPad2 上,一个参考值是每一帧渲染的物体,也就是当前视口内的顶点总数不能超过 100K 个,因为这是一个 iOS 底层驱动默认的顶点数据缓冲区的大小,超过这个数字,很可能会导致底层驱动做一些昂贵 split 操作。

贴图的问题

移动平台上可以使用的最大贴图是一个和平台以及版本相关的问题,不过这里有一个大概的数据,读者可以借鉴一下。
此外,对于贴图,其大小应该尽量是2的幂次方,因为所有的计算和存储最终都要以2的幂次方为单位进行的。尽管平台可能支持非2幂次方的贴图,而且可能支持得很好,但是,它存储和查找的效率总不会超过其最接近的2的幂次方贴图。
在各种移动平台上,能支持的最大贴图是多少?实际开发时应该使用多大的贴图呢?平台能支持的最大贴图和硬件密切相关,即时是同为 Android 平台,因为硬件的不同,所支持的最大贴图也是不一样的。这里有一组数据,读者可自己对比一下, PowerVR SGX540 所支持的最大贴图为2048, Tegra 2 为2048, Adreno 200 为4096, Mali 400MP 为 4096.
那么 GPU 所支持的最大贴图是不是最佳的贴图尺寸呢?对于这个问题,为了应用在平台间移植的可能性,建议以1024位标杆,最大不要超过2048。在 iOS 游戏“无尽之剑”内,场景内的角色模型大概使用了3000个顶点,角色的贴图却高达2048,但是,对于移动应用来说,3000个顶点的模型怎么能叫低模呢?这是因为在“无尽之剑”的游戏内,任何时刻最多只会出现两个角色,所以,才可能把角色的顶点数做得这么多。
对于贴图的 mipmaps 选项,如果读者的游戏是 3D,记得一定要打开 mipmaps 选项(转注: UI 一定要关掉)。如果关闭了该选项,这在 iPad 上只会导致大概 3ms 的性能损失,但是,在三星的 Tegra 上,结果却有可能是当机的。
所有的贴图类型在被导入到 Untiy 中时,都会被处理为 Unity 所支持的格式,任何贴图文件的原始尺寸和类型与最终发布时的尺寸和类型完全无关,这意味着可以使用自己方便的格式和尺寸大小来保存贴图文件,而在发布应用时使用一个平台相关的大小和类型。
ETC 、 DXT 和 PVRTC 都是硬件压缩,若果硬件不支持,而你又使用了它们,那么将在贴图被加载时由 CPU 来解压缩。
ETC 是在 Android 平台上被硬件所普遍支持的一种压缩格式,推荐在 Android 上使用 ETC 的压缩格式,但是 ETC 不支持 Alpha 通道。因此,当你的贴图含有 Alpha 通道时, Unity 认为在贴图大小、质量一次渲染速度之间的平衡格式是 RGBA-16bit (转注:做通道分离)。不过对于硬件 Tegra ,更好是 DXT5 的贴图压缩方式。
如果你的目标是 iOS 或者 PowerVR ,好的选择当然是 PVRTC 的贴图压缩格式。

数据类型的使用方式

fixed 或者说 lowp 的精度是8 bit (转注:11 bit ),在 OpenGL ES 2.0 上的拥有最小域为[-2, 2],适合用作颜色和其他单位化的方向矢量。 half 或者说 midiump 的精度为16 bit ,比较适合用作 UV 的坐标或二位的矢量。 float 或者说 highp 的精度依赖于硬件的不同,在24~32 bit 中,比较适合于三维空间的坐标表示,用于进行数学运算的标量。
在移动平台上面,这些数据类型的运算效率,你可以基本上这样考虑, half/mediump 是 float/highp 的两倍, fixed/lowp 又是 half/mediump 的两倍(转注:稍微有些质疑,虽然fixed在精度上并不是half的一半,但是它是定点数,理论上应该比浮点数快很多)。除此之外,请读者尽量避免在这3个数据类型之间进行转换,也避免在 fixed/lowp 上做 swizzle 操作。这种操作都会引起性能和精度的损失。最后,作为例外, Adreno 的硬件对这类精度并不敏感。
(转注:此外,尽量避免使用int)

变量的使用

大多数 GPU 上都会尽量减少从 vertex 函数传递到 fragment 函数的参数数量,把变量包装起来,比如把两个 fixed2 打包到一个 fixed4 。但是在 PowerVR 上,也就是 iOS 的 GPU 上却不是这样。首先, PowerVR 对变量的数量不敏感,其次,如果你的变量是用来读取贴图的 UV 变量,尽量使用独立的 UV 变量,不要使用一个四元数来包装两个二元数。在进入 fragment 函数前,如果可能,确定 UV ,这样 PowerVR 就能提前读取贴图的值,从而避免在 fragment 中读取贴图操作,而在移动平台上, tex2D 是一个消耗性能的操作。

慎用后期效果

首先,不推荐这样做,因为这是一个逐像素的全屏操作。
其次,如果你这样做了,使用小一点的 Render Texture 来做。
最后,如果可能,把最终效果所需要的计算尽量集中到一个 Pass 中完成。

慎用透明效果

首先,不建议使用透明效果,如果可以的话,尽量多添加几个点来表达你的形状,透明就意味着 Unity 要排序,在 GPU 中逐像素地渲染。所以尽量避免透明,如果必须使用的话,记得一定要尽早进行一些可能会导致后续计算被取消的操作,比如尽早进行 Clip 操作。如果可能,使用 Blend 来实现透明效果,但是,即使使用 Blend ,也要尽量避免透明物体的叠加,因为每一个透明物体的渲染都会迫使 Unity 进行排序。

你可能感兴趣的:(Unity,图形学)