unity 纹理压缩格式‘_Unity中修改材质shader,属性数据未清除

dc544b792d59d7fa2d1012f18be07c3b.png

相关内容最早发布于我的简书,由于反馈有帮助的童鞋挺多的,再加上Unity迭代更新了许多版本,因此对文章内容进行了整理与更新,希望可以对大家有所帮助。

本文基于Unity2019.3.0f1进行测试。

缘起

在Unity中,可以为材质赋予要使用的纹理贴图。在如下示例中,为"Standard"材质指定了一张Albedo贴图以及Occlusion贴图:

96dbc98214815a905b23a9b2214571f2.png

材质对贴图的引用关系是保存在".mat"文件中的,如图所示:

3c1bba807d45212e803fb1fc31a463da.png

将材质的shader改为"Unlit/Texture"之后,会发现"_MainTex"属性已经有贴图了,这张贴图就是之前为"Standard"设置的:

14b60a431e8c4498813cbdf82872e189.png

再次查看".mat"文件,会发现除了"Unlit/Texture"中定义的新属性外,之前"Standard"中的属性并没有被清除,依然被保留了下来:

9d868eb2527cb785dd8503ca27e1e44e.png

所以这就是为什么将shader改为"Unlit/Texture"之后,"_MainTex"属性就已经有贴图的原因。

因此我们得出结论,Unity在改变材质的shader时不会清除之前的属性,只会新增缺失的属性。

以我个人的想法,Unity这样做是出于便利性。假设在手抖的情况下或者出于其他原因,我们改变了材质的shader,然后我们又想改回之前的shader,此时之前设置的所有属性仍旧存在,你也许会感叹一声,哇塞,太智能了。

问题

既然Unity不会清除旧的属性,只会增加新的属性,那么就会产生一个令人担心的问题:资源打包时这些不用的内容会打包进去吗?

测试 - Resources目录

新建一个cube放在场景中,并将cube作为预制体放入Resources文件夹,cube使用的材质的shader为"Unlit/Texture",该材质中引用着用不到的"_OcclusionMap":

cb583adc1fd159605abee17bd90a266d.png

其中_MainTex引用的纹理贴图是"512.tga",而_OcclusionMap引用的纹理贴图为"1024.tga":

d71601579819065f1ef205ac7382f5e7.png

接着开始打包,打包后打开日志查看,发现没有用到的"1024.tga"贴图没有被打包,只打包了用到的"512.tga"贴图:

fa03808f6d6d34c8777d8ee90047747c.png

运行打包后的工程,连接profiler查看,发现内存中也确实只加载了"512.tga":

0e197110ad7dc8c0f498fd0e0779b86e.png

结论:因此,将资源放在Resources文件夹下没有什么问题,尽管材质中保留了不会被使用的属性数据,但在打包和运行时这些属性数据都不会产生额外的开销。

测试 - AssetBundle

接着对使用AssetBundle的方式进行测试,我们分两种情况来测试:1. 将纹理贴图和资源一起打在一个ab包中;2. 将纹理贴图作为独立的ab

首先来测试情况1,将cube作为一个独立的ab,两张纹理贴图不作为独立的ab,使用未压缩的格式打包ab,这样对文件大小可以看的更清晰。经过打包的cube的ab文件大小为235kb,显然没有包含大小为0.7MB的"1024.tga"纹理贴图:

3c76e21298808bc3cecfd92789caf2ee.png

运行时从ab中加载并实例化cube,连接profiler查看,发现内存中只加载了"512.tga":

20eb117472b63fcbe8cb360a738a553c.png

结论:因此,未使用的属性数据不会被打包到资源所在的ab中。

接着来测试情况2,将cube作为一个独立的ab,同时两张纹理贴图也作为独立的ab。打包后查看cube所在ab的manifest,可以发现尽管材质没有使用到"1024.tga",但该纹理贴图却被认为是依赖项:

fa46d570b4d1a9a6f77dbb351cc3656f.png

运行时从ab中加载并实例化cube,由于"1024.tga"是依赖项,所以在处理依赖项时ab会被加载进来,但是真正的资源不会被加载:

261f1f21b4b700aa8d902f73555b10b5.png

结论:因此,当属性数据作为独立的ab时,尽管没有被使用,但还是会被认为是依赖项,但是资源不会被加载到内存中。

测试总结

现在来总结下测试结果:无论是使用Resources还是AssetBundle,对资源的打包和加载都没有影响。但是如果未使用到的纹理贴图被作为独立的ab来处理,则该纹理贴图会作为依赖项存在。

解决方案

通过脚本来清理材质未使用的纹理贴图,具体方案为:

  1. 收集材质当前使用到的所有纹理贴图
  2. 逐行读取".mat"文件
  3. 如果是纹理贴图对应的行,则判断该纹理贴图是否被材质用到
  4. 用到则保留,没有用到则清除

代码如下:

/******************************************************************************
 * DESCRIPTION: 清理材质未使用的纹理贴图的引用
 * 
 *     Copyright (c) 2020, 谭伟俊 (TanWeijun)
 *     All rights reserved
 * 
 * COMPANY:
 * CREATED: 2020.03.01, 15:20, CST
*******************************************************************************/

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

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

        private static bool CleanOneMaterial(Material _material)
        {
            // 收集材质使用到的所有纹理贴图
            HashSet<string> 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<string> CollectTextureGUIDs(Material _material)
        {
            HashSet<string> textureGUIDs = new HashSet<string>();
            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;
        }
    }
}

也可以通过我的github来访问和查看代码,Happy Coding! :)

本文固定链接: EnigmaJJ:Unity中修改材质shader,属性数据未清除

转载请注明: EnigmaJJ 2020年03月01日 于 知乎 发表

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值