利用shader_feature打造最小版本Shader

Author Avatar
Kanglai Qian 9月 25, 2016

这个月终于腾出手为新项目做了一些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的思路没变

更新2: 这个问题在新版本Unity已解决,见ShaderVariantCollection解决shader_feature丢失

解决思路

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

完整版本 精简版本
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");
}

简单粗暴,完成~