介绍

Android 平台上使用的 ETC1 并不包含 Alpha 通道,因此如果想要平衡体积与质量,可以尝试将纹理拆分为两份:不包含 Alpha 通道的与包含 Alpha 通道的,然后在运行时通过 Shader 将两个纹理合并起来。

需求

  1. 支持处理场景、Prefab 中的材质
  2. 收集使用到的 Shader 以便后续改写处理
  3. 增加 Alpha 通道材质
  4. 自动替换材质的 Shader 并设置纹理字段

准备工作

需要将使用到的 Shader 编辑通道分离版本,即同时读取两张纹理显示。要注意的是需要尽量保持纹理属性名字一致,这样可以减少特殊情况处理。

处理思路

获取 Shader

 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
private static void GetShadersInGameObject(GameObject go, List<string> list)
{
    var renderers = go.GetComponentsInChildren<Renderer>(true);

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

        foreach (var material in renderer.sharedMaterials)
        {
            if (material == null || material.shader == null)
            {
                continue;
            }

            if (!list.Contains(material.shader.name))
            {
                list.Add(material.shader.name);
            }
        }
    }
}

分离 Alpha 纹理

这里要注意,分离出来 Alpha 纹理需要继承原纹理的导入选项。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
var originTextureImporter = AssetImporter.GetAtPath(path) as TextureImporter;
var settings = originTextureImporter.GetPlatformTextureSettings("Android");
settings.textureCompression = TextureImporterCompression.Compressed;
originTextureImporter.SetPlatformTextureSettings(settings);
originTextureImporter.SaveAndReimport();

AssetDatabase.ImportAsset(alphaPath);
var textureImporter = AssetImporter.GetAtPath(alphaPath) as TextureImporter;
textureImporter.mipmapEnabled = false;
textureImporter.textureType = TextureImporterType.Default;
textureImporter.SetPlatformTextureSettings(settings);
textureImporter.SaveAndReimport();

遍历方法

由于需要修改 Renderer 的材质属性,因此按照 Renderer 逐个处理其材质即可。

 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
57
58
59
60
61
62
63
64
65
66
67
68
69
private static void ProcessScene()
{
    var gos = SceneManager.GetActiveScene().GetRootGameObjects();

    foreach (var go in gos)
    {
        Traverse(go.transform, ProcessTransform);
    }

    AssetDatabase.SaveAssets();
}

private static void Traverse(Transform t, System.Action<Transform> action)
{
    action.Invoke(t);

    for (int i = 0; i < t.childCount; i++)
    {
        Traverse(t.GetChild(i), action);
    }
}

private static void ProcessTransform(Transform t)
{
    var noRuntimeRenderer = t.GetComponent<NoRuntimeRenderer>();
    if (noRuntimeRenderer != null)
    {
        return;
    }

    var renderers = t.GetComponents<Renderer>();
    foreach (var renderer in renderers)
    {
        if (renderer == null)
        {
            Debug.LogError("Renderer error: " + t.name, t);
            continue;
        }

        ProcessRenderer(renderer);
    }
}

private static void ProcessRenderer(Renderer renderer)
{
    if (renderer.sharedMaterials == null || renderer.sharedMaterials.Length == 0)
    {
        Debug.LogError("Renderer has no material: " + renderer.name, renderer);
        return;
    }

    foreach (var material in renderer.sharedMaterials)
    {
        if (material == null || material.shader == null)
        {
            Debug.LogError("Material error: " + renderer.name, renderer);
            continue;
        }

        switch (material.shader.name)
        {
            case "CustomShader":
                ProcessMaterial(material, "_MainTex", renderer);
                break;
            default:
                break;
        }
    }
}

替换 Shader

 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
private static void ProcessMaterial(Material material, string mainTexName, Renderer renderer)
{
    var texture = material.GetTexture(mainTexName) as Texture2D;
    var alphaTexture = ProcessTexture(texture);

    string dir = Path.GetDirectoryName(material.shader.name);
    string name = Path.GetFileName(material.shader.name);
    string alphaShaderName = dir + "_Alpha" + "/" + name;

    Texture mainTexture = material.GetTexture(mainTexName);
    Vector2 scale = material.GetTextureScale(mainTexName);
    Vector2 offset = material.GetTextureOffset(mainTexName);

    material.shader = Shader.Find(alphaShaderName);

    SetTexture(material, "_MainTex", mainTexture, scale, offset);
    SetTexture(material, "_AlphaTex", alphaTexture, scale, offset);

    EditorUtility.SetDirty(material);
}

private static void SetTexture(Material material, string propertyName,
    Texture texture, Vector2 scale, Vector2 offset)
{
    material.SetTexture(propertyName, texture);
    material.SetTextureScale(propertyName, scale);
    material.SetTextureOffset(propertyName, offset);
}