unity .obj文件的導出

.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 || mf.GetComponent<Renderer>() == null)
            {
                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("\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("\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 {2}/{2}/{2} {1}/{1}/{1} {0}/{0}/{0}\n",
                        triangles[i] + 1 + StartIndex, triangles[i + 1] + 1 + StartIndex, triangles[i + 2] + 1 + StartIndex));
                }
            }

            StartIndex += numVertices;
            return sb.ToString();
        }
    }
}

最後,選擇我們需要導出的gameobject,選擇Tool -> Export即可

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章