版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
.obj文件后缀,是一种3D模型文件格式,一种文本文件。支持多边形模型(三个点以上的面),纪录法线和贴图坐标。并不记录动画、材质特性、贴图路径、动力学及粒子等信息。
主要的用途在于将场景的Mesh信息导出给服务器做碰撞检测(服务器拿到.obj文件后具体如何实现的就暂时不是很懂啦),unity也可以识别出.obj文件,我们将导出的.obj文件放入unity中,效果如下
首先来看一下一个简单的.obj文件的内容
#type mesh
#Cube
#-------
g Cube
v -53.00000 -2.00000 -62.00000
v 66.00000 -2.00000 -62.00000
v -53.00000 1.00000 -62.00000
v 66.00000 1.00000 -62.00000
v 66.00000 -2.00000 -62.00000
v 66.00000 -2.00000 49.50000
v 66.00000 1.00000 -62.00000
v 66.00000 1.00000 50.50000
v 66.00000 -2.00000 49.50000
v -53.00000 -2.00000 49.50000
v 66.00000 1.00000 50.50000
v -53.00000 1.00000 50.50000
v -53.00000 -2.00000 49.50000
v -53.00000 -2.00000 -62.00000
v -53.00000 1.00000 50.50000
v -53.00000 1.00000 -62.00000
v -53.00000 1.00000 -62.00000
v 66.00000 1.00000 -62.00000
v -53.00000 1.00000 50.50000
v 66.00000 1.00000 50.50000
v -53.00000 -2.00000 49.50000
v 66.00000 -2.00000 49.50000
v -53.00000 -2.00000 -62.00000
v 66.00000 -2.00000 -62.00000
vn 0 0 1
vn 0 0 1
vn 0 0 1
vn 0 0 1
vn -1 0 0
vn -1 0 0
vn -1 0 0
vn -1 0 0
vn 0 0.3162278 -0.9486833
vn 0 0.3162278 -0.9486833
vn 0 0.3162278 -0.9486833
vn 0 0.3162278 -0.9486833
vn 1 0 0
vn 1 0 0
vn 1 0 0
vn 1 0 0
vn 0 -1 0
vn 0 -1 0
vn 0 -1 0
vn 0 -1 0
vn 0 1 0
vn 0 1 0
vn 0 1 0
vn 0 1 0
vt 53 -2
vt -66 -2
vt 53 1
vt -66 1
vt 62 -2
vt -49.5 -2
vt 62 1
vt -50.5 1
vt 66 13.75591
vt -53 13.75591
vt 66 16.91819
vt -53 16.91819
vt 49.5 -2
vt -62 -2
vt 50.5 1
vt -62 1
vt -53 62
vt 66 62
vt -53 -50.5
vt 66 -50.5
vt 53 -49.5
vt -66 -49.5
vt 53 62
vt -66 62
usemtl ProBuilderDefault
usemap ProBuilderDefault
f 1/1/1 2/2/2 3/3/3
f 2/2/2 4/4/4 3/3/3
f 5/5/5 6/6/6 7/7/7
f 6/6/6 8/8/8 7/7/7
f 9/9/9 10/10/10 11/11/11
f 10/10/10 12/12/12 11/11/11
f 13/13/13 14/14/14 15/15/15
f 14/14/14 16/16/16 15/15/15
f 17/17/17 18/18/18 19/19/19
f 18/18/18 20/20/20 19/19/19
f 21/21/21 22/22/22 23/23/23
f 22/22/22 24/24/24 23/23/23
其中几个关键字的含义为
# |
开头表示注释 |
v | 表示顶点 |
vn | 表示法线,可以共用法线 |
vt | 表示uv坐标 |
f | 表示一个面,比如参数1/4/1,表示顶点索引/UV索引/法线索引 |
usemtl | 材质名称 (Material name) |
通过这些位置信息就可以构建出模型的框架了。
至于如何导出.obj文件,可以参考文档:http://wiki.unity3d.com/index.php/ExportOBJ ,可以根据自己项目的一些需求进行一些小的修改,例如我们的只导出static物体,一个多gameobject的场景要合并导出与拆分导出等等。
using UnityEngine;
using UnityEditor;
using System.Collections;
using System.IO;
using System.Text;
using Base.Framework.Tools;
public class ObjExporter : ScriptableObject
{
[MenuItem("Tools/Export/Export OBJ")]
static void DoExportWSubmeshes()
{
Export(true);
}
[MenuItem("Tools/Export/Export OBJ (No Submeshes)")]
static void DoExportWOSubmeshes()
{
Export(false);
}
static void Export(bool isSubmeshes)
{
if (Selection.gameObjects == null)
{
Debug.Log("Didn't Export Any Meshes; Nothing was selected!");
return;
}
string fileName = EditorUtility.SaveFilePanel("Export .obj file", "", Selection.gameObjects[0].name, "obj");
PrefabExportToObj.DoExport(Selection.gameObjects[0], fileName, isSubmeshes);
}
[MenuItem("Tools/Export/Split Export OBJ")]
static void SplitExport()
{
if (Selection.gameObjects == null)
{
Debug.Log("Didn't Export Any Meshes; Nothing was selected!");
return;
}
string filePath = EditorUtility.SaveFolderPanel("Export .obj file", "", Selection.gameObjects[0].name);
PrefabExportToObj.SplitExport(Selection.gameObjects[0], filePath, true);
}
[MenuItem("Tools/Export/Export All OBJ")]
static void AllExport()
{
if (Selection.gameObjects == null)
{
Debug.Log("Didn't Export Any Meshes; Nothing was selected!");
return;
}
string filePath = EditorUtility.SaveFolderPanel("Export .obj file", "", Selection.gameObjects[0].name);
PrefabExportToObj.SplitExport(Selection.gameObjects[0], filePath, true);
PrefabExportToObj.DoExport(Selection.gameObjects[0], filePath + "/" + Selection.gameObjects[0].name + ".obj", true);
}
}
using System;
using System.IO;
using System.Text;
using UnityEngine;
namespace Base.Framework.Tools
{
public class PrefabExportToObj
{
static StringBuilder filesString;
static int fileCount;
public static void StartRecordFiles(string msg)
{
filesString = new StringBuilder();
filesString.Append("#");
filesString.Append(msg);
filesString.Append("\n");
fileCount = 0;
}
public static void ExportRecordFiles(string path)
{
if(filesString!=null&& filesString.Length > 0)
{
WriteToFile(filesString.ToString(), path);
}
filesString.Length = 0;
filesString = null;
}
public static void SplitExport(GameObject target, string path, bool makeSubmeshes)
{
PrefabExportToObj.StartRecordFiles("GameObject:" + target.name);
Split(target, path);
PrefabExportToObj.ExportRecordFiles(path + "/dependencies.txt");
}
static void Split(GameObject go, string path)
{
if (go.isStatic && go.activeSelf && go.activeInHierarchy && go.GetComponent<MeshFilter>() != null)
{
DoExport(go, path + "/" + fileCount + ".obj", true);
fileCount++;
}
for (int i = 0; i < go.transform.childCount; i++)
{
//Split(go.transform.GetChild(i).gameObject, path + "/" + go.transform.GetChild(i).gameObject.name);
Split(go.transform.GetChild(i).gameObject, path);
}
}
/// <param name="target"></param>
/// <param name="path">x/y/z.obj</param>
/// <param name="makeSubmeshes"></param>
public static void DoExport(GameObject target, string path, bool makeSubmeshes)
{
if (target == null)
{
Debug.Log("Didn't Export Any Meshes; Nothing was selected!");
return;
}
string meshName = target.name;
ObjExporterScript.Start();
StringBuilder meshString = new StringBuilder();
Transform t = target.transform;
meshString.Append($"#type mesh\n");
//Vector3 originalPosition = t.position;
//t.position = Vector3.zero;
if (!makeSubmeshes)
{
meshString.Append("g ").Append(t.name).Append("\n");
}
meshString.Append(processTransform(t, makeSubmeshes));
if (filesString != null)
{
filesString.Append(Path.GetFileName(path));
filesString.Append("\n");
}
WriteToFile(meshString.ToString(), path);
//t.position = originalPosition;
ObjExporterScript.End();
Debug.Log("Exported Mesh: " + path);
}
static string processTransform(Transform t, bool makeSubmeshes)
{
StringBuilder meshString = new StringBuilder();
meshString.Append("#" + t.name
+ "\n#-------"
+ "\n");
if (t.gameObject.isStatic)
{
if (makeSubmeshes)
{
meshString.Append("g ").Append(t.name).Append("\n");
}
MeshFilter mf = t.GetComponent<MeshFilter>();
if (mf)
{
meshString.Append(ObjExporterScript.MeshToString(mf, t));
}
}
for (int i = 0; i < t.childCount; i++)
{
Transform tc = t.GetChild(i);
if (tc.gameObject.isStatic && tc.gameObject.activeSelf && tc.gameObject.activeInHierarchy)
{
meshString.Append(processTransform(tc, makeSubmeshes));
}
}
return meshString.ToString();
}
static void WriteToFile(string s, string filename)
{
string path = Path.GetDirectoryName(filename);
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
}
using (StreamWriter sw = new StreamWriter(filename))
{
sw.Write(s);
}
}
}
public class ObjExporterScript
{
private static int StartIndex = 0;
public static void Start()
{
StartIndex = 0;
}
public static void End()
{
StartIndex = 0;
}
public static string MeshToString(MeshFilter mf, Transform t)
{
Vector3 s = t.localScale;
Vector3 p = t.localPosition;
Quaternion r = t.localRotation;
int numVertices = 0;
Mesh m = mf.sharedMesh;
if (!m)
{
return "####Error####";
}
Material[] mats = mf.GetComponent<Renderer>().sharedMaterials;
StringBuilder sb = new StringBuilder();
foreach (Vector3 vv in m.vertices)
{
Vector3 v = t.TransformPoint(vv);
numVertices++;
sb.Append(string.Format("v {0:f5} {1:f5} {2:f5}\n", v.x, v.y, -v.z));
//sb.Append(string.Format("v {0:f5} {1:f5} {2:f5}\n", v.x, v.y, v.z));
}
sb.Append("\n");
foreach (Vector3 nn in m.normals)
{
Vector3 v = r * nn;
sb.Append(string.Format("vn {0} {1} {2}\n", -v.x, -v.y, v.z));
//sb.Append(string.Format("vn {0} {1} {2}\n", v.x, v.y, v.z));
}
sb.Append("\n");
foreach (Vector3 v in m.uv)
{
sb.Append(string.Format("vt {0} {1}\n", v.x, v.y));
}
for (int material = 0; material < m.subMeshCount; material++)
{
sb.Append("\n");
sb.Append("usemtl ").Append(mats[material].name).Append("\n");
sb.Append("usemap ").Append(mats[material].name).Append("\n");
int[] triangles = m.GetTriangles(material);
for (int i = 0; i < triangles.Length; i += 3)
{
sb.Append(string.Format("f {0}/{0}/{0} {1}/{1}/{1} {2}/{2}/{2}\n",
triangles[i] + 1 + StartIndex, triangles[i + 1] + 1 + StartIndex, triangles[i + 2] + 1 + StartIndex));
}
}
StartIndex += numVertices;
return sb.ToString();
}
}
}
最后,选择我们需要导出的gameobject,选择Tool -> Export即可