前言
模型的打包与加载,比普通类型的资源要复杂一些,涉及到模型身上引用到的图片、材质球、fbx文件的处理。而材质球的打包也不是打包的本体,而是先将材质球转成json的形式进行打包,之后加载时,再解析json中的数据重新生成材质球。这样做的目的,一来可能是为了减小包体大小,或者便于处理依赖,也可以使材质球信息更方便查看和管理。
打包
初始资源
初始资源有四个,分别是materials, prefab,texture,fbx
解析Material成json
打开debug模式,查看材质球,可以看到有三个属性。
因此可以建立与材质球对应的类
材质球结构类如下:
// 材质球结构类
public class MatData
{
public string shader = "";
public List<Attritubute> attributes = new List<Attritubute>();
}
public class Attritubute
{
public Tuple<string, MatShaderPropertyType, MatVector> attri;
public Tuple<string, MatShaderPropertyType, MatTex> texAttri;
public Tuple<string, MatShaderPropertyType, double> floatAttri;
}
public struct MatVector
{
public double x;
public double y;
public double z;
public double w;
public void Set(double x,double y,double z , double w)
{
this.x = x;
this.y = y;
this.z = z;
this.w = w;
}
}
public struct MatTex
{
public string texName;
public string bundleDir;
public double x;
public double y;
public double z;
public double w;
public byte[] data;
}
public enum MatShaderPropertyType
{
Color,
Vector,
Float,
Range,
TexEnv,
}
再新建一个解析类,用将material转换成对应结构。
材质球解析类如下:
//材质球解析类
public static MatData MatToJson(Material mat)
{
MatData data = new MatData();
Shader shader = mat.shader;
data.shader = shader.name;
int propertyCount = ShaderUtil.GetPropertyCount(shader);
for (int i = 0; i < propertyCount; i++)
{
Attritubute attr = new Attritubute();
string propertyName = ShaderUtil.GetPropertyName(shader, i);
MatShaderPropertyType type = (MatShaderPropertyType) ShaderUtil.GetPropertyType(shader, i);
switch (type)
{
case MatShaderPropertyType.Color:
Color color = mat.GetColor(propertyName);
attr.attri = new Tuple<string, MatShaderPropertyType, MatVector>(propertyName, type, new MatVector()
{
x = color.r, y = color.g, z = color.b, w = color.a
});
break;
case MatShaderPropertyType.Float:
case MatShaderPropertyType.Range:
double f = mat.GetFloat(propertyName);
attr.floatAttri = new Tuple<string, MatShaderPropertyType, double>(propertyName,type,f);
break;
case MatShaderPropertyType.Vector:
Vector4 v4 = mat.GetVector(propertyName);
attr.attri = new Tuple<string, MatShaderPropertyType, MatVector>(propertyName,type,new MatVector()
{
x = v4.x,y= v4.y,z=v4.z,w = v4.w
});
break;
case MatShaderPropertyType.TexEnv:
Texture t2d = mat.GetTexture(propertyName);
string assetPath = AssetDatabase.GetAssetPath(t2d);
attr.texAttri = new Tuple<string, MatShaderPropertyType, MatTex>(propertyName,type,new MatTex()
{
texName = t2d.name,
bundleDir = assetPath
});
break;
}
data.attributes.Add(attr);
}
return data;
}
将结构解析完成后,得到材质球信息类,再调用LitJson,传入信息类,得到json文本,如下:
public static void DoMatAndPng(string rootName, string path)
{
string assetPath = TCommon.RemovePathPrefix(path);
Material m = AssetDatabase.LoadAssetAtPath<Material>(assetPath);
//转换材质球为json
MatData data = MatTool.MatToJson(m);
string jsonData = JsonMapper.ToJson(data);
StringBuilder sb = new StringBuilder();
JsonWriter write = new JsonWriter(sb);
JsonMapper.ToJson(data, write);
int idx = path.LastIndexOf('\\');
string paraPath = path.Substring(0, idx);
paraPath = paraPath + "\\mat.json";
File.WriteAllText(paraPath, sb.ToString());
}
打包json与png
再对其进行打包即可。
同时也可以拿到材质球上的texture,再对texture进行打包。代码如下:
//对mat_json文件标记ab
AssetImporter importer = AssetImporter.GetAtPath(TCommon.RemovePathPrefix(paraPath));
importer.assetBundleName = $"{rootName}_mat";
importer.assetBundleVariant = TCommon.ABSUFFIX;
//对png标记
AssetImporter importerTex = AssetImporter.GetAtPath(data.attributes[0].texAttri.Item3.bundleDir);
importerTex.assetBundleName = $"{rootName}_png";
importerTex.assetBundleVariant = TCommon.ABSUFFIX;
打包fbx预制体
处理fbx模型文件,模型文件并不直接打包,可新建预制体,进行实例化后,对prefab进行打包。这样可以间接剔除掉除fbx外的其他资源被打包。不会导致冗余。
public static void DoFbx(string rootName, string fbxPath)
{
GameObject fbxGo = AssetDatabase.LoadAssetAtPath<GameObject>(TCommon.RemovePathPrefix(fbxPath));
GameObject instanGo = GameObject.Instantiate(fbxGo);
string prefabPath = fbxPath.Substring(0, fbxPath.LastIndexOf('\\'));
prefabPath = $"{prefabPath}\\{rootName}.prefab";
prefabPath = prefabPath.Replace("\\", "/");
Object newPrefab = PrefabUtility.CreateEmptyPrefab(TCommon.RemovePathPrefix(prefabPath));
GameObject finalPrefab = PrefabUtility.ReplacePrefab(instanGo, newPrefab);
string finalPath = AssetDatabase.GetAssetPath(finalPrefab);
AssetImporter importer = AssetImporter.GetAtPath(finalPath);
importer.assetBundleName = $"{rootName}_base";
importer.assetBundleVariant = TCommon.ABSUFFIX;
}
加载
模型加载的思路:从assetbundle中加载出fbx所在的prefab、json、png。
通过json解析,组装出材质球。材质球赋值png和对应的shader,prefab上的meshRenderer组件再赋值材质球。代码如下:
public GameObject LoadModel(string modelName)
{
//加载图片
string pngPath = $"{Application.streamingAssetsPath}/model/{modelName}_png{TCommon.ABSUFFIX_AFTER}";
string fbxPath = $"{Application.streamingAssetsPath}/model/{modelName}_base{TCommon.ABSUFFIX_AFTER}";
AssetBundle ab_png = AssetBundle.LoadFromFile(TCommon.RemovePathPrefix(pngPath));
Texture2D png = ab_png.LoadAsset<Texture2D>("city02_beauty");
//加载材质球json
string jsonPath = $"{Application.streamingAssetsPath}/model/{modelName}_mat{TCommon.ABSUFFIX_AFTER}";
AssetBundle ab_json = AssetBundle.LoadFromFile(jsonPath);
TextAsset textAsset = ab_json.LoadAsset<TextAsset>("mat");
MatData matData = new MatData();
matData = JsonMapper.ToObject<MatData>(textAsset.text);
Shader shader = new Shader();
shader = Shader.Find(matData.shader);
Material mat = new Material( shader);
mat.mainTexture = png;
Debug.Log("aa:"+matData.shader);
//加载fbx
AssetBundle fbx_ab = AssetBundle.LoadFromFile(TCommon.RemovePathPrefix(fbxPath));
GameObject prefab = fbx_ab.LoadAsset<GameObject>(modelName);
GameObject model = GameObject.Instantiate(prefab);
MeshRenderer render = model.GetComponent<MeshRenderer>();
render.material = mat;
return model;
}