这个月终于腾出手为新项目做了一些Shader调整和优化工作,不知不觉就整出一个功能略多的”肥”Shader。 所以我就在思考这么一个事情:如何维护这个Shader,以及如何在运行时尽可能减少无用的消耗。

举俩栗子:

  • 有的材质球里贴了法线贴图,那么就必须计算切空间,但有的材质球只要读取模型的顶点法线就足够了
  • 有的材质球贴了自发光贴图,但是没贴的材质球完全就可以跳过这个tex2D(就算是一张小的黑色也是浪费啊)

更新: 这个是在知乎上有朋友提到的:

用了Shader feature的shader,跟用到它的material文件放在不同的bundle里打包,由material里的keyword决定的shader变种会丢失。

关于这个现象,我的测试结果是Asset Bundle里正常,但是编辑器下模拟Asset Bundle功能的时候因为要使用Windows/OSX版本的Shader,反而出现了shader_feature丢失的现象… 目前安全起见我也从shader_feature转到了multi_compile上,不过根据贴图情况去控制Keyword的思路没变

在官方文档里找到了答案:Making multiple shader program variants。之前我主要使用的是multi_compile在运行时切换,现在发现shader_feature对应自定义的CustomEditor能很好的完成这个需求。下表为同一个材质球配合不同贴图的参数,以及对应GLES版本的Compiled Code的指令数:

  完整版本 精简版本
  SGPBR SGPBR2
vert 43 43
frag 81 50

Shader部分

#pragma shader_feature _EMISSIONMAP
#if _EMISSIONMAP
sampler2D _EmissionMap;
#endif
fixed4 sgpbr_frag(v2f i) : SV_Target
{
  fixed3 Color = 0;
#if _EMISSIONMAP
  Color += tex2D(_EmissionMap, i.tex).rgb;
#endif
  return fixed4(Color, 1);
}

这里比较好理解,相当于自发光相关的代码都利用_EMISSIONMAP这个宏包起来了。

ps.记得在最后加上CustomEditor "SGPBRInspector"

C#部分

这里参考了官方的Standard的编辑器代码S tandardShaderGUI.cs。其实和其他inspector一样,最核心的几行就是根据某个属性是否贴了贴图,打开或关闭对应的宏…

override public void OnGUI(MaterialEditor materialEditor, MaterialProperty[] properties)
{
  MaterialProperty emissionMap = ShaderGUI.FindProperty("_EmissionMap", props);
  bool emissionEnabled = emissionMap.textureValue != null;

  Material material = materialEditor.target as Material;
  if (emissionEnabled)
      material.EnableKeyword("_EMISSIONMAP");
  else
      material.DisableKeyword("_EMISSIONMAP");
}

简单粗暴,完成~