介绍

Unity 在很多情况下会使用内置的 Default-Material 材质,材质同时也会引用内置的 Standard Shader,但是 AssetBundle 中的资源如果有这个材质的引用,那么不同的 AssetBundle 中会有这个材质的多份副本。导致的问题包括资源重复、包体变大、Shader 重复加载编译。

环境

  • Unity 5.6.6f2

问题

  1. FBX 勾选 Import Materials 会在 Materials 目录下生成使用 Standard Shader 的材质,需要手动修改为需要使用的 Shader
  2. FBX 未勾选 Import Materials 会在内嵌的 Renderer 上面使用 Unity 内置的 Default-Material 材质,并且使用了内置的 Standard Shader,这里并不会使用手动放到项目内的 Standard Shader

方案

  1. 反向查找项目内 Standard Shader 的引用,将所有找到材质的 Shader 修改为实际需要的,或者指定为 Mobile/Diffuse 之类较为节省的 Shader。可以使用脚本批量处理。
  2. 使用脚本将未勾选 Import Materials 的 FBX 中的材质置空

由于 Unity 只会使用导入到项目内的缓存,因此设置 OnPostprocessModel 勾子后重新导入触发修改,将材质置空。

注意事项

缓存依赖

但是如果开启 Unity Cache Server,那么资源重新导入时无法触发 OnPostprocessModel,就不会正确修改资源。

How do I work with Asset dependencies?

It is also easy to use AssetPostprocessor to introduce dependencies. For example you might use data from a text file next to the Asset to add additional components to the imported GameObjects. This is not supported in the Cache Server. If you want to use the Cache Server, you have to remove dependency on other Assets in the project folder. Since the Cache Server doesn’t know anything about the dependency in your postprocessor, it does not know that anything has changed, and thus uses an old cached version of the Asset.

猜测的可行方案

首先声明,这是根据原理推理出来的可行方案,并未实践过。

如果确实需要使用 Unity Cache Server 并使用 OnPostprocessModel 修改资源,可以考虑使用 meta 文件中的 userData 字段,修改完成资源后通过 AssetImporter.userData API 将相关的信息写入到 userData 中,这样 Unity Cache Server 就会缓存正确的资源。

优化

考虑到材质修改只影响打包,可以只禁用打包机的 Unity Cache Server 选项。 注意:不同的 DCC 会有不同的模型文件扩展名,因此 .fbx .obj .3ds 等等都需要处理。

更好的做法

如果可以升级 Unity 版本,那么可以使用新版本中指定的材质映射功能,将 FBX 内置的材质映射为指定外部的材质。

实测 Unity 2017.4.29f1 中已有此功能

代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
[MenuItem("Tools/Reimport All Model")]
public static void ReimportAllModel()
{
    var assetPaths = AssetDatabase.GetAllAssetPaths();
    Array.Sort(assetPaths);
    Debug.LogWarning(string.Format("Total assets count: {0}", assetPaths.Length));
    int processedCount = 0;

    foreach (string assetPath in assetPaths)
    {
        string normalizedAssetPath = assetPath.ToLower();
        if (!normalizedAssetPath.EndsWith(".fbx") &&
            !normalizedAssetPath.EndsWith(".obj") &&
            !normalizedAssetPath.EndsWith(".3ds"))
        {
            continue;
        }

        var modelImporter = AssetImporter.GetAtPath(assetPath) as ModelImporter;
        if (modelImporter == null || modelImporter.importMaterials)
        {
            continue;
        }

        AssetDatabase.ImportAsset(assetPath, ImportAssetOptions.ImportRecursive | ImportAssetOptions.ForceUpdate);
        Debug.Log(assetPath, AssetDatabase.LoadMainAssetAtPath(assetPath));
        processedCount++;
    }

    Debug.LogWarning(string.Format("Total processed model count: {0}", processedCount));
}

private void OnPostprocessModel(GameObject model)
{
    var modelImporter = assetImporter as ModelImporter;
    if (modelImporter == null || modelImporter.importMaterials)
    {
        return;
    }

    var renderers = model.GetComponentsInChildren<Renderer>();
    if (renderers == null)
    {
        return;
    }

    foreach (var renderer in renderers)
    {
        if (renderer == null)
        {
            continue;
        }

        renderer.sharedMaterials = new Material[0];
    }
}

参考资料