shader整洁之道:规范shader代码、优化shader性能的方法

下面给出一些如何规范shader代码的建议。

1. float、half还是fixed

我们一般使用Cg/HLSL来编写shader代码,有三种数据类型,.float、half和fixed。这些精度将决定计算结果的数值范围。下面给出了这三种精度通常情况下的数值范围。


上面的精度范围并不是绝对正确的,尤其是在不同平台和GPU上,它们实际精度可能和上面给出的范围不一致。

(1)大多数现在桌面GPU会把所有计算都按最高的浮点精度进行计算,也就是说,float、half、fixed在这些平台上实际上是等价的,意味着PC上很难看出因为half和fixed精度而带来的不同。

(2)但在移动平台的GPU上,确实会有不同的精度范围,而且不同精度的浮点值的运算速度也是有所差异。因此应该确保在真正的移动平台上验证我们的shader。

(3)fixed精度实际上只在一些较旧的移动平台上有用,在大多数现在GPU上,它们内部把fixed和half当成同等精度来对待。

尽可能使用精度较低的类型,因为这可以优化shader的性能,这一点在移动平台上尤其重要,从它们大体的值域范围来看,我们可以使用fixed类型来存储颜色和单位矢量,如果要存储更大范围的数据可以选择half类型,最差的情况下再选择使用float。如果我们的目标是移动平台,一定要确保在真实的手机上测试我们的shader,这一点非常重要。

2. 规范语法

DirectX平台对shader的语义优更加严格的要求。这意味着我们要发布到DirectX平台上就需要使用更严格的语法,例如使用和变量类型相匹配的参数数目来对变量进行初始化。

3. 避免不必要的计算

如果毫无节制的在shader(尤其片元着色器)中进行大量的计算,可能很快就会收到unity的错误提示:


出现这些错误信息大多因为我们在shader中进行了过多的计算,使得需要的临时寄存器数目或指令数目超过了当前可支持的数目。不同的shader target、不同的着色器阶段,我们可使用的临时寄存器和指令数目都是不同的。

通常可以通过指定更高等级的shader target来消除这些错误,下表给出了unity目前支持的一些shader target。


由于unity版本的不同,unity支持的shader target种类也不同。

shader model是由微软提出的一套规范,通俗的理解就是他们决定了shader中各个特性的能力。这些特性和能力体现在shader能使用的运算指令数目、寄存器个数等各个方面。shader model等级越高,shader的能力越强大。虽然更高等级的shader target可以让我们使用更大的临时寄存器和运算指令,但一个更好的方法是尽可能减少shader中的运算,或者通过预计算的方式来提供更多数据。

4. 慎用分支和循环语句

编写shader的时候要格外小心这些。

最开始GPU是不支持在顶点着色器和片元着色器中使用流程控制语句的。随着GPU的发展,现在已经可以使用if-else、for和while这种流程控制指令了。但是他们在GPU上的实现和在CPU上有很大不同,大体来说,GPU使用了不同于CPU的技术来实现分支语句,在最坏的情况下,我们花在一个分支语句的时间相当于运行了所有分支语句的时间,因此不鼓励在shader中使用流程控制语句,因为他们会降低GPU的并行处理操作(尽管在现代的GPU上已经有了改进)。

如果在shader中使用了大量的流程控制语句,那么shader性能可能会成倍下降。一个解决方法是,尽量把计算向流水线的上端移动,例如把放在片元着色器中的计算放到顶点着色器中,或者直接在CPU中进行预计算,再把结果传递给shader。当然有时我们不可避免使用分支语句来进行运算,那么有一些建议:

(1)分支判断语句中使用的条件变量最好是常数,即在shader运行过程中不会发生变化。

(2)每个分支中包含的操作指令数尽可能少。

(3)分支的嵌套层数尽可能少。

5. 不要除以0

我们往往在编写shader时会忽略这个问题。

例如在编写shader中写如下代码:

fixed4 frag(v2f i):SV_Target
{
   return fixed4(0.0/0.0,0.0/0.0,0.0/0.0,1.0);
}

这些代码的结果往往是不可预测的,在某些渲染平台上不会造成shader崩溃,即便不会崩溃得到的结果也是不确定的,有些会得到白色(由无限大截取到1.0),有些会得到黑色,但在另一些平台上,shader可能就会直接崩溃。即便在开发游戏的平台上,我们看到的结果可能符合预期,但目标平台上可能会出现问题。

一个解决方法是,对那些除数可能为0的情况,强制截取到非0的范围。有时候我们可能会看到使用if语句来判断除数是否为0的例子。另一个方法是,使用一个很小的浮点值,例如0.000001来保证分母大于0(前提是原始数值是非负数)。

版权声明:本文为CSDN博主「小橙子0」的原创文章,
遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/cgy56191948/article/details/102339651

最新文章