![dc544b792d59d7fa2d1012f18be07c3b.png](https://i-blog.csdnimg.cn/blog_migrate/14455b70041d47dd218a975cc709f5a1.jpeg)
相关内容最早发布于我的简书,由于反馈有帮助的童鞋挺多的,再加上Unity迭代更新了许多版本,因此对文章内容进行了整理与更新,希望可以对大家有所帮助。
本文基于Unity2019.3.0f1进行测试。
缘起
在Unity中,可以为材质赋予要使用的纹理贴图。在如下示例中,为"Standard"材质指定了一张Albedo贴图以及Occlusion贴图:
![96dbc98214815a905b23a9b2214571f2.png](https://i-blog.csdnimg.cn/blog_migrate/490c8b32ff237b6efaf216298bc93455.png)
材质对贴图的引用关系是保存在".mat"文件中的,如图所示:
![3c1bba807d45212e803fb1fc31a463da.png](https://i-blog.csdnimg.cn/blog_migrate/a0d3fa2aba9605b14e82b13fe084638f.png)
将材质的shader改为"Unlit/Texture"之后,会发现"_MainTex"属性已经有贴图了,这张贴图就是之前为"Standard"设置的:
![14b60a431e8c4498813cbdf82872e189.png](https://i-blog.csdnimg.cn/blog_migrate/96e9cf93d0323834dd70957672993a72.png)
再次查看".mat"文件,会发现除了"Unlit/Texture"中定义的新属性外,之前"Standard"中的属性并没有被清除,依然被保留了下来:
![9d868eb2527cb785dd8503ca27e1e44e.png](https://i-blog.csdnimg.cn/blog_migrate/7e23f2d15b96ac6799d84c2629fd1898.jpeg)
所以这就是为什么将shader改为"Unlit/Texture"之后,"_MainTex"属性就已经有贴图的原因。
因此我们得出结论,Unity在改变材质的shader时不会清除之前的属性,只会新增缺失的属性。
以我个人的想法,Unity这样做是出于便利性。假设在手抖的情况下或者出于其他原因,我们改变了材质的shader,然后我们又想改回之前的shader,此时之前设置的所有属性仍旧存在,你也许会感叹一声,哇塞,太智能了。
问题
既然Unity不会清除旧的属性,只会增加新的属性,那么就会产生一个令人担心的问题:资源打包时这些不用的内容会打包进去吗?
测试 - Resources目录
新建一个cube放在场景中,并将cube作为预制体放入Resources文件夹,cube使用的材质的shader为"Unlit/Texture",该材质中引用着用不到的"_OcclusionMap":
![cb583adc1fd159605abee17bd90a266d.png](https://i-blog.csdnimg.cn/blog_migrate/e2d6e1ddd7bc60593e11d5e6a0c270a4.png)
其中_MainTex引用的纹理贴图是"512.tga",而_OcclusionMap引用的纹理贴图为"1024.tga":
![d71601579819065f1ef205ac7382f5e7.png](https://i-blog.csdnimg.cn/blog_migrate/b185dc3b3a2a60f46c57787c9ddb5c65.png)
接着开始打包,打包后打开日志查看,发现没有用到的"1024.tga"贴图没有被打包,只打包了用到的"512.tga"贴图:
![fa03808f6d6d34c8777d8ee90047747c.png](https://i-blog.csdnimg.cn/blog_migrate/5cee00f79b3121318f5f0e7aca80bbc7.png)
运行打包后的工程,连接profiler查看,发现内存中也确实只加载了"512.tga":
![0e197110ad7dc8c0f498fd0e0779b86e.png](https://i-blog.csdnimg.cn/blog_migrate/b639334e66cad0d25d542e1bad9066f1.png)
结论:因此,将资源放在Resources文件夹下没有什么问题,尽管材质中保留了不会被使用的属性数据,但在打包和运行时这些属性数据都不会产生额外的开销。
测试 - AssetBundle
接着对使用AssetBundle的方式进行测试,我们分两种情况来测试:1. 将纹理贴图和资源一起打在一个ab包中;2. 将纹理贴图作为独立的ab
首先来测试情况1,将cube作为一个独立的ab,两张纹理贴图不作为独立的ab,使用未压缩的格式打包ab,这样对文件大小可以看的更清晰。经过打包的cube的ab文件大小为235kb,显然没有包含大小为0.7MB的"1024.tga"纹理贴图:
![3c76e21298808bc3cecfd92789caf2ee.png](https://i-blog.csdnimg.cn/blog_migrate/45280cf1c6ce7d827176aa018ba16d74.png)
运行时从ab中加载并实例化cube,连接profiler查看,发现内存中只加载了"512.tga":
![20eb117472b63fcbe8cb360a738a553c.png](https://i-blog.csdnimg.cn/blog_migrate/c87d680f3fafdd40fc75a66ef87605d8.png)
结论:因此,未使用的属性数据不会被打包到资源所在的ab中。
接着来测试情况2,将cube作为一个独立的ab,同时两张纹理贴图也作为独立的ab。打包后查看cube所在ab的manifest,可以发现尽管材质没有使用到"1024.tga",但该纹理贴图却被认为是依赖项:
![fa46d570b4d1a9a6f77dbb351cc3656f.png](https://i-blog.csdnimg.cn/blog_migrate/a07fce04bc9b3eafad1924f4d281e3da.png)
运行时从ab中加载并实例化cube,由于"1024.tga"是依赖项,所以在处理依赖项时ab会被加载进来,但是真正的资源不会被加载:
![261f1f21b4b700aa8d902f73555b10b5.png](https://i-blog.csdnimg.cn/blog_migrate/fa42d90b7b49d44b3dfe2cb10f8e7a90.png)
结论:因此,当属性数据作为独立的ab时,尽管没有被使用,但还是会被认为是依赖项,但是资源不会被加载到内存中。
测试总结
现在来总结下测试结果:无论是使用Resources还是AssetBundle,对资源的打包和加载都没有影响。但是如果未使用到的纹理贴图被作为独立的ab来处理,则该纹理贴图会作为依赖项存在。
解决方案
通过脚本来清理材质未使用的纹理贴图,具体方案为:
- 收集材质当前使用到的所有纹理贴图
- 逐行读取".mat"文件
- 如果是纹理贴图对应的行,则判断该纹理贴图是否被材质用到
- 用到则保留,没有用到则清除
代码如下:
/******************************************************************************
* 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日 于 知乎 发表