我们在开发中,可能会遇到,需要在unity中拼场景,然后到处模型给美术优化的情况,这种情况下,就需要在Unity中导出模型的操作了
思路
实际这个功能的设计思路也很简单,根据在场景中选择的对象,获取它的mesh数据,然后把数据重新生成文件就好了
实现
1.网格数据类
这个类是用来获取网格各个部分的数据信息的
保存网格名称
private void SaveMeshName(MeshFilter meshFilter, StringBuilder data)
{
data.Append("g ").Append(meshFilter.name).Append("\n");
}
保存顶点数据
private static void SaveVertices(MeshFilter meshFilter, StringBuilder data)
{
foreach (Vector3 ver in meshFilter.sharedMesh.vertices)
{
Vector3 worldPos = meshFilter.transform.TransformPoint(ver);
//因为坐标系的区别,x分量需要反转
data.Append(string.Format("v {0} {1} {2}\n", -worldPos.x, worldPos.y, worldPos.z));
}
data.Append("\n");
}
保存法线数据
private void SaveNormals(MeshFilter meshFilter, StringBuilder data)
{
foreach (Vector3 normal in meshFilter.sharedMesh.normals)
{
Vector3 directionWorld = meshFilter.transform.TransformDirection(normal);
data.Append(string.Format("vn {0} {1} {2}\n", -directionWorld.x, directionWorld.y, directionWorld.z));
}
data.Append("\n");
}
保存uv数据
private void SaveUVs(MeshFilter meshFilter, StringBuilder data)
{
foreach (Vector3 uv in meshFilter.sharedMesh.uv)
{
data.Append(string.Format("vt {0} {1}\n", uv.x, uv.y));
}
}
保存材质数据
private void SaveMaterails(MeshFilter meshFilter, StringBuilder data, Dictionary<string, MaterialData> materialDic)
{
Mesh mesh = meshFilter.sharedMesh;
Material[] materialArray = meshFilter.GetComponent<Renderer>().sharedMaterials;
string materialName = "";
for (int materialIndex = 0; materialIndex < mesh.subMeshCount; materialIndex++)
{
materialName = materialArray[materialIndex].name;
data.Append("\n");
data.Append("usemtl ")
.Append(materialName)
.Append("\n");
data.Append("usemap ")
.Append(materialName)
.Append("\n");
//筛选同名材质,不重复添加
if (!materialDic.ContainsKey(materialName))
{
MaterialData materialData = new MaterialData();
materialData.Name = materialName;
materialData.TextureName =
materialArray[materialIndex].mainTexture ?
AssetDatabase.GetAssetPath(materialArray[materialIndex].mainTexture) : null;
materialDic[materialData.Name] = materialData;
}
//保存三角形数据
int[] triangles = mesh.GetTriangles(materialIndex);
for (int i = 0; i < triangles.Length; i += 3)
{
data.Append(string.Format("f {1}/{1}/{1} {0}/{0}/{0} {2}/{2}/{2}\n",
triangles[i] + 1 + _vertexOffset, triangles[i + 1] + 1 + _normalOffset,
triangles[i + 2] + 1 + _uvOffset));
}
}
_vertexOffset += mesh.vertices.Length;
_normalOffset += mesh.normals.Length;
_uvOffset += mesh.uv.Length;
}
2.导出文件部分功能
导出多个模型为一个文件
public static void ExportObjsToOne(MeshFilter[] mf, string folder, string filename)
{
MeshData data = new MeshData();
using (StreamWriter sw = new StreamWriter(folder + "/" + filename + ".obj"))
{
sw.Write("mtllib ./" + filename + ".mtl\n");
foreach (MeshFilter mesh in mf)
{
data.SaveData(mesh);
sw.Write(data.ToString());
}
}
ExportMaterials(data.GetMaterialDic(), folder, filename);
}
导出单个模型为一个文件
public static void ExportObjToOne(MeshFilter mf, string folder, string filename)
{
MeshData data = new MeshData();
data.SaveData(mf);
using (StreamWriter sw = new StreamWriter(folder + "/" + filename + ".obj"))
{
sw.Write("mtllib ./" + filename + ".mtl\n");
sw.Write(data.ToString());
}
ExportMaterials(data.GetMaterialDic(), folder, filename);
}
保存材质
private static void ExportMaterials(Dictionary<string, MaterialData> materialList, string folder, string filename)
{
using (StreamWriter sw = new StreamWriter(folder + "/" + filename + ".mtl"))
{
foreach (KeyValuePair<string, MaterialData> kvp in materialList)
{
sw.Write("\n");
sw.Write("newmtl {0}\n", kvp.Key);
sw.Write("Ka 0.6 0.6 0.6\n");
sw.Write("Kd 0.6 0.6 0.6\n");
sw.Write("Ks 0.9 0.9 0.9\n");
sw.Write("d 1.0\n");
sw.Write("Ns 0.0\n");
sw.Write("illum 2\n");
if (kvp.Value.TextureName != null)
{
string destinationFile = kvp.Value.TextureName;
int stripIndex = destinationFile.LastIndexOf('/');
if (stripIndex >= 0)
destinationFile = destinationFile.Substring(stripIndex + 1).Trim();
string relativeFile = destinationFile;
destinationFile = folder + "/" + destinationFile;
try
{
File.Copy(kvp.Value.TextureName, destinationFile);
}
catch
{
}
sw.Write("map_Kd {0}", relativeFile);
}
sw.Write("\n\n\n");
}
}
}
2.编辑器菜单部分
这个模式会将父物体和子物体的模型分别导出为单独的模型文件
[MenuItem("BlueToolKit/导出模型/将选中模型分别导出(子物体会拆分导出)")]
private static void ExportAllChild()
{
if (!ExportFile.CreateExportFolder())
return;
Transform[] selection = Selection.GetTransforms(SelectionMode.Editable | SelectionMode.ExcludePrefab);
if (selection.Length == 0)
{
EditorUtility.DisplayDialog("未选中模型", "请选中一个或多个模型", "关闭");
return;
}
int exportCount = 0;
for (int i = 0; i < selection.Length; i++)
{
Component[] meshfilter = selection[i].GetComponentsInChildren(typeof(MeshFilter));
for (int m = 0; m < meshfilter.Length; m++)
{
exportCount++;
ExportFile.ExportObjToOne((MeshFilter)meshfilter[m], ExportFile.EXPORT_FOLDER, selection[i].name + "_" + i + "_" + m);
}
}
if (exportCount > 0)
EditorUtility.DisplayDialog("导出成功", "成功导出 " + exportCount + " 个模型", "关闭");
else
EditorUtility.DisplayDialog("导出失败", "导出模型必须含有Mesh Filter组件", "关闭");
}
这个模式会将所有选中的模型导出为一个模型文件
[MenuItem("BlueToolKit/导出模型/将选中模型导出成一个obj文件")]
private static void ExportToSingleObj()
{
if (!ExportFile.CreateExportFolder())
return;
Transform[] selection = Selection.GetTransforms(SelectionMode.Editable | SelectionMode.ExcludePrefab);
if (selection.Length == 0)
{
EditorUtility.DisplayDialog("未选中模型", "请选中一个或多个模型", "关闭");
return;
}
int exportCount = 0;
ArrayList mfList = new ArrayList();
for (int i = 0; i < selection.Length; i++)
{
Component[] meshfilter = selection[i].GetComponentsInChildren(typeof(MeshFilter));
for (int m = 0; m < meshfilter.Length; m++)
{
exportCount++;
mfList.Add(meshfilter[m]);
}
}
if (exportCount > 0)
{
MeshFilter[] mf = new MeshFilter[mfList.Count];
for (int i = 0; i < mfList.Count; i++)
{
mf[i] = (MeshFilter)mfList[i];
}
string filename = SceneManager.GetActiveScene().name + "_" + exportCount;
int stripIndex = filename.LastIndexOf('/'); //FIXME: Should be Path.PathSeparator
if (stripIndex >= 0)
filename = filename.Substring(stripIndex + 1).Trim();
ExportFile.ExportObjsToOne(mf, ExportFile.EXPORT_FOLDER, filename);
EditorUtility.DisplayDialog("导出成功", "导出模型名称:" + filename, "关闭");
}
else
EditorUtility.DisplayDialog("导出失败", "导出模型必须含有Mesh Filter组件", "关闭");
}
这个模式会将选中物体和子物体的模型导出为一个文件,选择几个模型就到处几个
[MenuItem("BlueToolKit/导出模型/将选中模型分别导出(子物体不拆分导出)")]
private static void ExportParent()
{
if (!ExportFile.CreateExportFolder())
return;
Transform[] selection = Selection.GetTransforms(SelectionMode.Editable | SelectionMode.ExcludePrefab);
if (selection.Length == 0)
{
EditorUtility.DisplayDialog("未选中模型", "请选中一个或多个模型", "关闭");
return;
}
int exportCount = 0;
for (int i = 0; i < selection.Length; i++)
{
Component[] meshfilter = selection[i].GetComponentsInChildren(typeof(MeshFilter));
MeshFilter[] mf = new MeshFilter[meshfilter.Length];
for (int m = 0; m < meshfilter.Length; m++)
{
mf[m] = (MeshFilter)meshfilter[m];
}
exportCount++;
ExportFile.ExportObjsToOne(mf, ExportFile.EXPORT_FOLDER, selection[i].name + "_" + i);
}
if (exportCount > 0)
{
EditorUtility.DisplayDialog("导出成功", "成功导出 " + exportCount + " 个模型", "关闭");
}
else
EditorUtility.DisplayDialog("导出失败", "导出模型必须含有Mesh Filter组件", "关闭");
}
这个小插件在有需求的时候,还是蛮好用的,希望对大家有帮助,完整代码可以到我的工具集里找到
工具收录于我自己写的工具集,内部还有我写的几个小插件,我会慢慢更新,欢迎关注
工具集地址:https://github.com/BlueMonk1107/BlueToolkit