本文介绍一种快捷清理Unity材质球上关联的废弃贴图引用的方法,全程无痛方便,推荐大家在自己项目中做一次全局清理。
材质球的无用贴图残留是这样产生的:
第一步,创建一个带有多张贴图材质球资源
第二步,直接切换成一个使用更少贴图数量的shader,看上去这个材质球似乎清理了多余的贴图引用,但是实际上它这个机制隐藏了资源引用
第三步,选择材质球资源,点击Select Dependencies,显示所有依赖,果然原有的两张贴图资源还被引用着
由上面的重现步骤来看,我们日常的美术制作流程是非常容易引入上述的冗余问题的,材质球shader的切换,并不会清理掉多余贴图槽位上的资源引用,导致资源被冗余包含到发布包中,白白浪费内存,浪费加载带宽。
所以我们就非常有必要对项目中的所有材质文件做一次全局大扫除,达到优化资源的目的。我们仅仅需要一个简单的批处理脚本,该脚本会清理掉材质球上所有没有被shader引用到的贴图资源引用。
下面给出核心代码,使用了一个小技巧来保证材质资源引用正确,希望对大家有用
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System;
using UnityEngine;
using UnityEditor;
static class MaterialCleaner {
public static bool ClearMaterialAsset( Material m ) {
if ( m == null ) {
return false;
}
var path = AssetDatabase.GetAssetPath( m );
if ( String.IsNullOrEmpty( path ) ) {
return false;
}
var deps = AssetDatabase.GetDependencies( new String[] { path } );
var deps_textures = deps.Where( s => IsTextureAsset( s ) ).ToList();
var used_textures = new HashSet<String>();
var shader = m.shader;
var newMat = new Material( shader );
var c = ShaderUtil.GetPropertyCount( shader );
for ( int i = 0; i < c; ++i ) {
var type = ShaderUtil.GetPropertyType( shader, i );
var name = ShaderUtil.GetPropertyName( shader, i );
var value = m.GetProperty( i );
switch ( type ) {
case ShaderUtil.ShaderPropertyType.Color: {
newMat.SetColor( name, m.GetColor( name ) );
}
break;
case ShaderUtil.ShaderPropertyType.Float: {
newMat.SetFloat( name, m.GetFloat( name ) );
}
break;
case ShaderUtil.ShaderPropertyType.Range: {
newMat.SetFloat( name, ( float )value );
}
break;
case ShaderUtil.ShaderPropertyType.TexEnv: {
newMat.SetTexture( name, ( Texture )value );
newMat.SetTextureOffset( name, m.GetTextureOffset( name ) );
newMat.SetTextureScale( name, m.GetTextureScale( name ) );
var tpath = AssetDatabase.GetAssetPath( ( Texture )value );
if ( !String.IsNullOrEmpty( tpath ) ) {
used_textures.Add( tpath );
}
}
break;
case ShaderUtil.ShaderPropertyType.Vector: {
newMat.SetVector( name, ( Vector4 )value );
}
break;
}
}
bool rebuild = false;
if ( used_textures.Count != deps_textures.Count ) {
for ( int i = 0; i < deps_textures.Count; ++i ) {
var _fn = deps_textures[ i ];
if ( !used_textures.Contains( _fn ) ) {
rebuild = true;
UnityEngine.Debug.LogWarning( String.Format( "unused texture: {0}", _fn ) );
}
}
}
if ( !rebuild ) {
if ( newMat != null ) {
UnityEngine.Object.DestroyImmediate( newMat );
}
return false;
}
String basePath;
String fn;
String ext;
SplitFullFilename( path, out fn, out ext, out basePath );
var tempAssetPath = String.Format( "{0}{1}_temp.{2}", basePath, fn, ext );
var _test = AssetDatabase.LoadAllAssetsAtPath( tempAssetPath );
if ( _test != null ) {
AssetDatabase.DeleteAsset( tempAssetPath );
}
// create a new material to replace it latter
AssetDatabase.CreateAsset( newMat, tempAssetPath );
Resources.UnloadAsset( newMat );
var tempAssetDataPath = String.Format( "{0}{1}_datatemp.bytes", basePath, fn, ext );
if ( File.Exists( tempAssetPath ) ) {
// rename it to .bytes
File.Copy( tempAssetPath, tempAssetDataPath, true );
// delete temp material
AssetDatabase.DeleteAsset( tempAssetPath );
if ( File.Exists( tempAssetDataPath ) ) {
// delete original material
File.Delete( path );
// replace original material with .bytes file
File.Copy( tempAssetDataPath, path, true );
// remove bytes file
File.Delete( tempAssetDataPath );
AssetDatabase.Refresh();
// make sure the temp file has been removed correctly
if ( File.Exists( tempAssetDataPath ) ) {
UnityEngine.Debug.Log( String.Format( "AssetDatabase.DeleteAsset failed: {0}", tempAssetDataPath ) );
File.Delete( tempAssetDataPath );
AssetDatabase.Refresh();
return true;
}
}
}
return false;
}
static void SplitFilename( String qualifiedName, out String outBasename, out String outPath ) {
String path = qualifiedName.Replace( '\\', '/' );
int i = path.LastIndexOf( '/' );
if ( i == -1 ) {
outPath = String.Empty;
outBasename = qualifiedName;
} else {
outBasename = path.Substring( i + 1, path.Length - i - 1 );
outPath = path.Substring( 0, i + 1 );
}
}
static void SplitBaseFilename( String fullName, out String outBasename, out String outExtention ) {
int i = fullName.LastIndexOf( '.' );
if ( i == -1 ) {
outExtention = String.Empty;
outBasename = fullName;
} else {
outExtention = fullName.Substring( i + 1 );
outBasename = fullName.Substring( 0, i );
}
}
static void SplitFullFilename( String qualifiedName, out String outBasename, out String outExtention, out String outPath ) {
String fullName = String.Empty;
SplitFilename( qualifiedName, out fullName, out outPath );
SplitBaseFilename( fullName, out outBasename, out outExtention );
}
static object GetProperty( this Material material, int index ) {
var name = ShaderUtil.GetPropertyName( material.shader, index );
var type = ShaderUtil.GetPropertyType( material.shader, index );
switch ( type ) {
case ShaderUtil.ShaderPropertyType.Color:
return material.GetColor( name );
case ShaderUtil.ShaderPropertyType.Vector:
return material.GetVector( name );
case ShaderUtil.ShaderPropertyType.Range:
case ShaderUtil.ShaderPropertyType.Float:
return material.GetFloat( name );
case ShaderUtil.ShaderPropertyType.TexEnv:
return material.GetTexture( name );
}
return null;
}
static bool IsTextureAsset( String assetPath ) {
var ext = Path.GetExtension( assetPath ).ToLower();
return ext == ".png" ||
ext == ".tga" ||
ext == ".jpg" ||
ext == ".bmp" ||
ext == ".psd" ||
ext == ".dds" ||
ext == ".exr";
}
}
本文提供了一种有效清理Unity材质球上废弃贴图引用的方法,通过执行批处理脚本,解决因切换shader导致的无用资源引用问题,以优化项目资源并减少内存浪费。
11万+

被折叠的 条评论
为什么被折叠?



