Unity实验室之Shader优化

作者: koo叔 分类: Unity3D 发布时间: 2018-03-20 15:08 编辑

前言

最近有人问到写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计算,这个不太熟悉,也就不探讨这个了。<br/>
    笔记的建议是尽量用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一样快:<br/>
    比如:<br/>

修改前:

    float x,y;
    x=x*a;
    y=y*b;

修改后:

    float2 v = float2(x,y);
    v = v*float2(a,b);

结果:前一种需要2次乘法,而后一种只需要1次。

  • 用step替换分支
    比如:<br/>

修改前:

    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
    如:<br/>

修改前:

    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主要是内存占用量,贴图使用量,还有数据使用量来优化
  • 还可以进行非常多的优化项目,比如 函数内联,死代码删除,常量折叠,常量传递,数学优化等等

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!

发表评论

你的email不会被公开。必填项已用*标注

更多阅读
标签云