首页 >Unity3D频道 >【Unity3D研究院之游戏开发】 > Unity3D研究院之更换着色器后删除材质残留宏和属性(一百二十五)
2021
04-24

Unity3D研究院之更换着色器后删除材质残留宏和属性(一百二十五)

Unity的材质有个隐患,当美术同学在切换shader的时候会造成上一个shader里的宏或者属性(如贴图)造成残留,未来在打assetbundle的时候就会造成冗余浪费,Unity这种设计可以更好的解决运行期进行shader的切换,但大部分材质是不需要这种需求的,所以需要一个方法来一键删除残留。

Snip20210424_2

首先使用ShaderUtil.GetShaderGlobalKeywords和GetShaderLocalKeywords来获取当前着色器有哪些宏,方法是内部方法所以需要用到反射,然后就可以取当前材质保存了哪些宏进行比较,最终删除残留的宏Material.shaderKeywords

接着就是残留属性的删除,比如残留贴图。使用shader.GetPropertyName获取当前shader有哪些属性,然后和当前材质中保留的进行比较删除残留的属性。

//获取shader中所有的宏
public static bool GetShaderKeywords(Shader target,out string[] global, out string[] local)
{
    try
    {
        MethodInfo globalKeywords = typeof(ShaderUtil).GetMethod("GetShaderGlobalKeywords", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
        global = (string[])globalKeywords.Invoke(null, new object[] { target });
        MethodInfo localKeywords = typeof(ShaderUtil).GetMethod("GetShaderLocalKeywords", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
        local = (string[])localKeywords.Invoke(null, new object[] { target });
        return true;
    }
    catch 
    {
        global = local = null;
        return false;
    }
}

[MenuItem("工具/重置着色器")]
static void Test222()
{
    //这里替换成自己的材质,也可以深度遍历整个工程中的材质
    Material m = AssetDatabase.LoadAssetAtPath<Material>("Assets/aa223.mat");
    if (GetShaderKeywords(m.shader,out var global, out var local))
    {
        HashSet<string> keywords = new HashSet<string>();
        foreach (var g in global)
        {
            keywords.Add(g);
        }
        foreach (var l in local)
        {
            keywords.Add(l);
        }
        //重置keywords
        List<string> resetKeywords = new List<string>(m.shaderKeywords);
        foreach (var item in m.shaderKeywords)
        {
            if (!keywords.Contains(item))
                resetKeywords.Remove(item);
        }
        m.shaderKeywords = resetKeywords.ToArray();
    }
    HashSet<string> property = new HashSet<string>();
    int count = m.shader.GetPropertyCount();
    for (int i = 0; i < count; i++)
    {
        property.Add(m.shader.GetPropertyName(i));
    }

    SerializedObject o = new SerializedObject(m);
    SerializedProperty disabledShaderPasses = o.FindProperty("disabledShaderPasses");
    SerializedProperty SavedProperties = o.FindProperty("m_SavedProperties");
    SerializedProperty TexEnvs = SavedProperties.FindPropertyRelative("m_TexEnvs");
    SerializedProperty Floats = SavedProperties.FindPropertyRelative("m_Floats");
    SerializedProperty Colors = SavedProperties.FindPropertyRelative("m_Colors");
    //对比属性删除残留的属性
    for (int i = disabledShaderPasses.arraySize - 1; i >= 0; i--)
    {
        if (!property.Contains(disabledShaderPasses.GetArrayElementAtIndex(i).displayName))
        {
            disabledShaderPasses.DeleteArrayElementAtIndex(i);
        }
    }
    for (int i = TexEnvs.arraySize - 1; i >=0; i--)
    {
        if (!property.Contains(TexEnvs.GetArrayElementAtIndex(i).displayName))
        {
            TexEnvs.DeleteArrayElementAtIndex(i);
        }
    }
    for (int i = Floats.arraySize - 1; i >= 0; i--)
    {
        if (!property.Contains(Floats.GetArrayElementAtIndex(i).displayName))
        {
            Floats.DeleteArrayElementAtIndex(i);
        }
    }
    for (int i = Colors.arraySize - 1; i >= 0; i--)
    {
        if (!property.Contains(Colors.GetArrayElementAtIndex(i).displayName))
        {
            Colors.DeleteArrayElementAtIndex(i);
        }
    }
    o.ApplyModifiedProperties();

    Debug.Log("Done!");
}

最后目前在项目中已经跑通暂未发现问题,如有疑问欢迎大家补充。

最后编辑:
作者:雨松MOMO
专注移动互联网,Unity3D游戏开发
捐 赠写博客不易,如果您想请我喝一杯星巴克的话?就进来看吧!

Unity3D研究院之更换着色器后删除材质残留宏和属性(一百二十五)》有 4 条评论

  1. uk说:

    GetShaderGlobalKeywords 也会获取到Local的关键字 这是为啥

  2. 充电线说:

    GetPropertyCount和GetPropertyName 在unity2018.4版本中需要从shaderUtil中获取。顺便感谢答主。

  3. 石头说:

    直接使用旧材质的shader重新创建一个材质,然后把旧材质的数据赋值给新的材质,然后保存新的材质。这样创建出来的材质不会有残留信息,因为它的shader压根就没有修改过。

留下一个回复

你的email不会被公开。 必填项已用 * 标注。