Shader in AssetBundle

关于Resources和AssetBundle优劣之前已经提过很多次了(参考官方教程The Resources folder ),正好最近@张迪在做框架AssetBundle部分的优化,特此整理一下两个特常见的坑及对应解决办法。之前在关于Unity中的资源管理,你可能遇到这些问题里有有人提到过这个问题:

Q6: 请问粒子特效的Shader是否不能使用依赖打包? 我们对Shader的模型和特效使用了依赖打包,运行的时候发现模型显示是正常的,但是粒子特效使用的Shader就不能正常运行,特效显示不正常。而在编辑器中,我们看到Material中的Shader是存在的。这时候如果重新手动给这个Material指定同样的Shader,这个粒子特效就能正常显示,请问这是什么原因引起的?

这里主要分享如何在编辑器里模拟AssetBundle时处理Shader(之后我们会分享『如何从框架层面实现0冗余』相关经验)

在我们的框架里是这么区分资源的:

  • Editor模式
    • 开发时直接使用UnityEditor.AssetDatabase.LoadAssetAtPath加载
    • 也支持模拟真机行为,使用打包出来的AssetBundle文件
  • Player模式:真机使用StreamingAssets下自带的或热更新下载的AssetBundle文件

在开发过程中,Editor模拟模式下会有一个非常麻烦的情况:Android或iOS模式下加载出来的材质球都是紫色的。官方支持里有提到这个问题Shaders Are Pink When Loaded From An AssetBundle ,论坛里也有相关讨论Shaders and asset bundles 。根据我的理解,问题就出在:打包到AssetBundle里的Shader是移动平台版本的,但是编辑器需要的是Windows/OSX版本的。

找到原因之后,我们使用了以下解决方案:

  • 在打包AssetBundle之后,针对所有shader重新打包一个对应编辑器版本的AssetBundle
  • 载入AssetBundle时,如果是shader则载入Windows或OSX版本,同时其他资源载入对应Android或iOS版本
  • 载入GameObject时,编辑器里会有额外处理:找到所有材质球,并在shader AssetBundle里找到同名shader进行替换(因为原材质球是对应的移动版本的shader)

在第一步中,除了需要打包项目内本身所有Shader之外,还需要打包builtin shaders,便于后面几步查找; 第三步利用了反射来修改所有材质球

System.Type materialType = typeof(Material);
Component[] comps = go.GetComponentsInChildren<Component>();
foreach (Component comp in comps)
{
    System.Type t = comp.GetType();

    var fields = t.GetFields();
    foreach (var field in fields)
    {
        if (field.FieldType == materialsType)
        {
            Material mat = field.GetValue(comp) as Material;
            if (mat != null)
            {
                mat = FixMaterial(mat);
                if (mat != null)
                    field.SetValue(comp, mat);
            }
        }
    }
}

这样的最大好处在于不需要额外的操作:还有一个解决方案是每个项目里解压一份builtin shader,然后强制使用项目内的版本而不是Resources/unity_builtin_extra下的版本。相比之下我们的解决方案只是在打包编辑器版本shader时解压出来然后及时删除,同时这样替换的比较干净(包括脚本里的public Material lineMaterial;这种也能被处理到),不需要任何人工操作。当然代价就是打包的时候比较慢,同时载入的时候使用了反射来查找所有需要替换shader的材质球也有一定的性能损失。

update

知乎评论区提出了一个解决方案:

这个问题,直接把编辑器环境变成opengl es2.0,具体内容在player setting pc的设置 other settings 去掉“Auto Graphics API for Windows”勾,在里面添加opengl es2.0,并且放到第一位。

我测试了下Windows下确实可以,但是OSX下没有GLES2/3。