前言
最近有人问到写Shader需要注意哪些地方及如何优化,正好笔者也在研究这方面,这里主要针对Unity来说,其它平台或引擎也可以参考,本文主要分如下几个方面来说:Shader的选择,属性和状态的设置,数据类型选择,代码编写,举例,调试
Unity中Shader的选择
Unity中现在可以新建4种Shader,分别是Standard Surface Shader,Unlit Shader,Image Effect Shader,Compute Shader.
- Standard Surface Shader: 主要用于光照,Unity封装好了许多光照模型,如果项目中需要比较多的光照,可以选用这个类型的Shader
- Unlit Shader:就是普通的Vertex&Fragment Shader,可以用CG语言来写,这种Shader封装的比较少,效果,光照都要自己码代码。
- Image Effect Shader:主要用于后期处理,和Unlit Shader一样,唯一有点区别的是,关闭了Cull,ZWrite,打开了ZTest。
- Compute Shader:这个主要是GPU计算,这个不太熟悉,也就不探讨这个了。
笔记的建议是尽量用Unlit Shader来写,一方面封装的少,可以更深入了解底层,一方面可以自己动手实现任何想要的效果。也没有引入好多光照方面的变量,效率上会比Surface Shader高
如果想省时间,又不想了解光照具体实现的,Surface Shader可以帮你快速实现效果。
属性和状态的设置
- 大部分shader都有_Color属性,但如果你并没有使用,那么就应该去掉,避免无谓的计算,其它的自动生成的属性或无用的属性也都应该去掉。
- Alpha Test,Cull,Zwrite,ZTest等能关的都关掉,需要时再打开。
- 排除延迟渲染的pass(通道)
- 关闭渲染附加通道(reduce lights Shader process)或者直接指定渲染
- 添加noforwardadd,只对一个方向光进行逐像素纹理运算,其他所有灯光都强制转换成逐顶点的光照:
CGPROGRAM
#pragma surface surf SimpleLambert exclude_path:prepass noforwardadd
数据类型选择(降低内存,提高运算速度)
- float:完整的32位浮点格式,对应的值有float2,float3,float4,适用于顶点变换,但性能最慢。
- half:简化的16位浮点格式,对应的值有half2,half3,half4,适用于纹理UV坐标,颜色值等,比float大约快两倍。
- fixed:10位定点格式,对应的值有fixed2,fixed3,fixed4,适合颜色,光照,单位向量和其它高性能操作,速度大约比float快4倍。
- 为获得最佳性能,挑选精度尽可能小的浮点格式至关重要。很多台式机 GPU 均完全忽略运算精确,但是它对于大量移动 GPU 的性能具有重大影响
代码编写
- 使用swizzle是非常快的
- 尽量把计算合并成向量计算,记住向量计算和一个float计算那样快!
- shader内置的函数比条件判断和分支的效率要高很多,因为GPU主要是为了计算而不是做判断,因此在GPU编程中,if else ,switch case等条件语句和太复杂的逻辑是不推荐的。相应的,可以用step()等函数进行替换,用阶梯函数的思维来构建条件语句
- 共享UV,使用了MainTexture的UV坐标来代替法线贴图的UV坐标,去掉法线贴图的输入,使用UnpckNormal共享uv。
- 减少处理的光源个数,把其他所有光源当成顶点光源,而在计算像素颜色时只计算一个主平行光作为像素光源
- 使用近似值代替精确值
- 减少或压缩贴图的使用
- 充分利用内置光照模型
- 使用半角向量(half vector)作为视线方向并用于高光计算,因为半角向量是基于逐顶点计算的
- 复杂的数学函数(如 pow,exp,log,cos,sin,tan 等等)会大大增加 GPU 负担,所以一个好的经验法则是,此类运算在每个像素中不得超过一个。考虑在合适时使用查找纹理作为替代选择
- 只计算需要计算的东西:减少无用的顶点,避免过多的顶点计算(如过多的光源),过于复杂的光照计算(复杂的光照模型)避免指令数量太多,减少VS的长度和复杂程度
- 尽量在VS前计算:尽量将运算从FS移到VS,或直接通过脚本设置固定值
举例
- 计算合并成向量计算,向量计算和float一样快:
比如:
修改前:
float x,y;
x=x*a;
y=y*b;
修改后:
float2 v = float2(x,y);
v = v*float2(a,b);
结果:前一种需要2次乘法,而后一种只需要1次。
- 用step替换分支
比如:
修改前:
float4 a;
if(b>1)
{
a.a=1;
}else
{
a.a=0.5;
}
修改后:
float4 a;
float tmp = step(b,1);//if(1>=b)=>1 else =>0;
a=tmp*0.5+(1-tmp);
结果:if else可以被step出来的0或1的乘法代替。
- 使用swizzle
如:
修改前:
float4 a=float4(1,1,1,1);
a.w = 2;
a.z = 3;
修改后:
float4 a=float4(1,1,1,1);
a.wz=float2(2,3);
调试
- 根据Unity自带的性能分析器调试
- FrameDebugger
- glsl-optimizer 优化工具,glsl_optimizer 是一个免费开源的glsl优化器。可以生成GPU无关的shader优化代码
总结
- 由于GPU受限的显存空间及GPU架构上的不同,导致Shader不同的写法对于其执行影响非常大,代码的优化思想也不一样。
- GPU是SIMD的架构,即单指令多数据流架构,也就是说同时执行n个数据和执行一个数据的效率是一样的,所以尽量把并行的计算搬到GPU上。
- GPU是以向量计算为基础设计的,专门对向量运算做了硬件层面的处理,所以执行向量乘法和执行一个float乘法的效率是一样的,并不像CPU那样要多执行几次。
- 避免使用分支或条件判断语句,这种控制语句涉及到一些同步等消耗的操作,大多数这种语句可以用数值的方式替代
- 轻量级Shader主要是内存占用量,贴图使用量,还有数据使用量来优化
- 还可以进行非常多的优化项目,比如 函数内联,死代码删除,常量折叠,常量传递,数学优化等等
本文探讨了Unity Shader的优化方法,包括选择Unlit Shader以提高效率,优化属性设置,合理选取数据类型,避免无用计算,以及使用向量计算和内置函数提高性能。通过实例展示了如何用step函数替换条件判断,利用swizzle优化代码,并提到了使用Unity自带的性能分析器和glsl-optimizer工具进行调试和优化。

8857

被折叠的 条评论
为什么被折叠?



