Unity中修改材质shader,属性数据未清除的问题

问题

在Unity中我们可以为材质赋予要使用的贴图,例如为“Material”材质赋予“512”和“1024”两张贴图:

Unity中修改材质shader,属性数据未清除的问题_第1张图片
为材质赋予两张贴图
Unity中修改材质shader,属性数据未清除的问题_第2张图片
512贴图
Unity中修改材质shader,属性数据未清除的问题_第3张图片
1024贴图
材质对贴图的引用关系是保存在“.mat”文件中的:
Unity中修改材质shader,属性数据未清除的问题_第4张图片
材质对贴图的引用
当我们改变材质使用的shader时,Unity会自动的将 同名属性的值拷贝过来。例如我们将“Material”材质的shader改为“Unlit/Texture”,“_MainTex”属性会被自动拷贝:
Unity中修改材质shader,属性数据未清除的问题_第5张图片
_MainTex属性被自动拷贝了
但这个时候,我们再次查看“.mat”文件,发现之前shader的其他属性仍然被保留了:
Unity中修改材质shader,属性数据未清除的问题_第6张图片
从我自身的角度理解,Unity这么做的原因是出于方便,假设有天你把材质的shader改了,然后过了一天又想改回来了,此时你会发现,哇塞,之前设置的属性值竟然都还在。

So,会有什么问题

此时最容易让人担心的就是既然“.mat”文件中保留了一些不用的属性,那么资源打包时会将这些不用的东西打包进去嘛?让我们来测试下。新建一个Cube,使用“Material”材质,此时“Material”材质使用的shader为“Unlit/Texture”,但是“.mat”文件中保留了用不到的一张“1024”贴图。我们将Cube放到“Resources”文件夹下,接着打包成apk,查看apk中的资源,发现“1024”贴图并没有被打包:

Unity中修改材质shader,属性数据未清除的问题_第7张图片
1024纹理贴图没有被打包
运行时使用 Resources.Load来加载,发现内存中确实只加载了“512”贴图,如下:
Unity中修改材质shader,属性数据未清除的问题_第8张图片
使用Resources.Load加载Cube
将资源放在Resources文件夹下看来没什么问题,尽管“.mat”文件中保留了未使用的属性数据,但在打包和使用过程中这些属性数据都不会被用到。
接着来看看AssetBundle。首先我们将Cube制作成单独的AB,两张纹理贴图不作为独立的AB。我们使用未压缩的格式打包AB,这样对文件大小可以看的更清晰。打出来的Cube的AB文件的大小为302KB,显然没有包含大小为1MB的“1024”纹理贴图:
Cube AB
运行时加载Cube AB,发现确实没有加载“1024”贴图,如下:
Unity中修改材质shader,属性数据未清除的问题_第9张图片
加载Cube AB
最后,我们将两张纹理贴图作为独立的AB进行打包,此时我们发现,尽管Cube的材质没有使用到“1024”纹理贴图,但是它却被认为是依赖项:
Unity中修改材质shader,属性数据未清除的问题_第10张图片
“1024”纹理贴图被认为是Cube的依赖项
接着我们在运行时加载Cube,由于“1024”纹理贴图被认为是依赖项,因此在处理依赖项时会去加载“1024”纹理贴图的AB,最终在内存中的结果如图:
Unity中修改材质shader,属性数据未清除的问题_第11张图片
加载Cube AB

中期总结

现在我们来总结下:在材质从shader A改为shader B后,“.mat”文件中会保留A中拥有的但在B中未使用到的属性数据。无论是使用Resources还是AB处理资源,这对资源的打包和加载都没有影响。但是如果未使用到的纹理贴图被作为独立的AB来处理,则该纹理贴图会作为依赖项存在。

解决方案

那么如何解决未使用的纹理贴图作为依赖项存在呢?我采取的方法就是使用脚本动态清理材质用不到的纹理贴图的引用。具体方案为

  1. 收集材质当前使用到的所有纹理贴图
  2. 逐行读取“.mat”文件
  3. 如果是纹理贴图对应的行,则判断该纹理贴图是否被材质用到
  4. 用到则保留,没有用到则guid归零
    代码如下:
/******************************************************************************
 * DESCRIPTION: 清理材质未使用的纹理贴图的引用
 * 
 *     Copyright (c) 2018, 谭伟俊 (TanWeijun)
 *     All rights reserved
 * 
 * COMPANY:
 * CREATED: 2018.05.17, 15:13, CST
*******************************************************************************/

using System.IO;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using UnityEngine;
using UnityEditor;

namespace Game
{
    public class CleanMaterial
    {
        [MenuItem("Assets/Tools/Clean Material")]
        public static void Clean()
        {
            Material[] materials = Selection.GetFiltered(SelectionMode.Assets | SelectionMode.DeepAssets);
            foreach (var material in materials)
            {
                CleanOneMaterial(material);
            }
        }

        private static bool CleanOneMaterial(Material _material)
        {
            // 收集材质使用到的所有纹理贴图
            HashSet textureGUIDs = CollectTextureGUIDs(_material);

            string materialPathName = Path.GetFullPath(AssetDatabase.GetAssetPath(_material));
            
            StringBuilder strBuilder = new StringBuilder();
            using (StreamReader reader = new StreamReader(materialPathName))
            {
                Regex regex = new Regex(@"\s+guid:\s+(\w+),");
                string line = reader.ReadLine();
                while (null != line)
                {
                    if (line.Contains("m_Texture:"))
                    {
                        // 包含纹理贴图引用的行,使用正则表达式获取纹理贴图的guid
                        Match match = regex.Match(line);
                        if (match.Success)
                        {
                            string textureGUID = match.Groups[1].Value;
                            if (textureGUIDs.Contains(textureGUID))
                            {
                                strBuilder.AppendLine(line);
                            }
                            else
                            {
                                // 材质没有用到纹理贴图,guid赋值为0来清除引用关系
                                strBuilder.AppendLine(line.Substring(0, line.IndexOf("fileID:") + 7) + " 0}");
                            }
                        }
                        else
                        {
                            strBuilder.AppendLine(line);
                        }
                    }
                    else
                    {
                        strBuilder.AppendLine(line);
                    }
                    
                    line = reader.ReadLine();
                }
            }

            using (StreamWriter writer = new StreamWriter(materialPathName))
            {
                writer.Write(strBuilder.ToString());
            }

            return true;
        }

        private static HashSet CollectTextureGUIDs(Material _material)
        {
            HashSet textureGUIDs = new HashSet();
            for (int i = 0; i < ShaderUtil.GetPropertyCount(_material.shader); ++i)
            {
                if (ShaderUtil.ShaderPropertyType.TexEnv == ShaderUtil.GetPropertyType(_material.shader, i))
                {
                    Texture texture = _material.GetTexture(ShaderUtil.GetPropertyName(_material.shader, i));
                    if (null == texture)
                    {
                        continue;
                    }
                    
                    string textureGUID = AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(texture));
                    if (!textureGUIDs.Contains(textureGUID))
                    {
                        textureGUIDs.Add(textureGUID);
                    }
                }
            }

            return textureGUIDs;
        }
    }
}

如有错误,请不吝赐教。

本文固定链接: https://www.jianshu.com/p/35cdb6fcfe6f
转载请注明: EnigmaJJ 2018年05月17日 于 发表

你可能感兴趣的:(Unity中修改材质shader,属性数据未清除的问题)