{"id":620,"date":"2012-12-17T21:50:22","date_gmt":"2012-12-17T21:50:22","guid":{"rendered":""},"modified":"2013-03-09T10:58:53","modified_gmt":"2013-03-09T02:58:53","slug":"620","status":"publish","type":"post","link":"https:\/\/1vr.cn\/?p=620","title":{"rendered":"\u8f93\u51faUnity\u7684\u573a\u666f\u6587\u4ef6\u4e3aObj\u6a21\u578b\u6587\u4ef6"},"content":{"rendered":"<p>\u5728Unity\u4e2d\u5efa\u7acb\u7684\u573a\u666f\u6587\u4ef6,\u6446\u653e\u7684\u6a21\u578b\u53ef\u4ee5\u7528\u4e0b\u5217\u811a\u672c\u5bfc\u51fa\u4e3aObj\u6a21\u578b\u6587\u4ef6,\u53ef\u4ee5\u4fdd\u6301\u573a\u666f\u4e2d\u6a21\u578b\u7684\u4f4d\u7f6e,\u8d34\u56fe\u706f\u4fe1\u606f.<\/p>\n<p>\u5c06\u811a\u672c\u547d\u540d\u4e3aObjExporter.cs,\u653e\u5728\u9879\u76ee\u7684Editor\u76ee\u5f55\u4e0b,\u9009\u62e9\u4f60\u8981\u5bfc\u51fa\u7684\u6a21\u578b\u7269\u4f53(\u53ef\u4ee5\u6709\u82e5\u5e72\u5b50\u7269\u4f53),\u5728\u7f16\u8f91\u5668\u83dc\u5355\u4e2d\u627e\u5230Custom\u7684\u8f93\u51fa\u9009\u9879,\u80fd\u770b\u5230\u4e09\u4e2a\u9009\u9879,\u5206\u522b\u662f&#8221;\u8f93\u51fa\u6240\u6709\u7f51\u683c\u5230\u5355\u4e00\u7684Obj\u6587\u4ef6&#8221;,&#8221;\u8f93\u51fa\u9009\u62e9\u7684\u7f51\u683c\u5408\u4f53\u5230\u4e00\u4e2aObj\u6587\u4ef6&#8221;,&#8221;\u8f93\u51fa\u6bcf\u4e2a\u9009\u62e9\u7684\u6a21\u578b\u5230\u5355\u4e00Obj\u6587\u4ef6&#8221;,\u4e00\u822c\u5e38\u7528\u7b2c\u4e8c\u9879.\u6210\u529f\u8f93\u51fa\u540e\u5c06\u5f39\u51fa\u5bf9\u8bdd\u6846,\u6a21\u578b\u548c\u5173\u8054\u7684\u8d34\u56fe\u5c06\u653e\u5230\u9879\u76ee\u7684\u6839\u76ee\u5f55\u7684&#8221;ExportedObj&#8221;\u6587\u4ef6\u5939\u4e2d.<br \/>\n<!--more--><br \/>\nEditorObjExporter.cs\u811a\u672c:<\/p>\n<p><code>using UnityEngine;<br \/>\nusing UnityEditor;<br \/>\nusing System.Collections;<br \/>\nusing System.Collections.Generic;<br \/>\nusing System.IO;<br \/>\nusing System.Text;<br \/>\nusing System;<\/p>\n<p>struct ObjMaterial<br \/>\n{<br \/>\npublic string name;<br \/>\npublic string textureName;<br \/>\n}<\/p>\n<p>public class EditorObjExporter : ScriptableObject<br \/>\n{<br \/>\nprivate static int vertexOffset = 0;<br \/>\nprivate static int normalOffset = 0;<br \/>\nprivate static int uvOffset = 0;<\/p>\n<p>\/\/User should probably be able to change this. It is currently left as an excercise for<br \/>\n\/\/the reader.<br \/>\nprivate static string targetFolder = \"ExportedObj\";<\/p>\n<p>private static string MeshToString(MeshFilter mf, Dictionary&lt;string, ObjMaterial&gt; materialList)<br \/>\n{<br \/>\nMesh m = mf.sharedMesh;<br \/>\nMaterial[] mats = mf.renderer.sharedMaterials;<\/p>\n<p>StringBuilder sb = new StringBuilder();<\/p>\n<p>sb.Append(\"g \").Append(mf.name).Append(\"<br \/>\n\");<br \/>\nforeach(Vector3 lv in m.vertices)<br \/>\n{<br \/>\nVector3 wv = mf.transform.TransformPoint(lv);<\/p>\n<p>\/\/This is sort of ugly - inverting x-component since we're in<br \/>\n\/\/a different coordinate system than \"everyone\" is \"used to\".<br \/>\nsb.Append(string.Format(\"v {0} {1} {2}<br \/>\n\",-wv.x,wv.y,wv.z));<br \/>\n}<br \/>\nsb.Append(\"<br \/>\n\");<\/p>\n<p>foreach(Vector3 lv in m.normals)<br \/>\n{<br \/>\nVector3 wv = mf.transform.TransformDirection(lv);<\/p>\n<p>sb.Append(string.Format(\"vn {0} {1} {2}<br \/>\n\",-wv.x,wv.y,wv.z));<br \/>\n}<br \/>\nsb.Append(\"<br \/>\n\");<\/p>\n<p>foreach(Vector3 v in m.uv)<br \/>\n{<br \/>\nsb.Append(string.Format(\"vt {0} {1}<br \/>\n\",v.x,v.y));<br \/>\n}<\/p>\n<p>for (int material=0; material &lt; m.subMeshCount; material ++) {<br \/>\nsb.Append(\"<br \/>\n\");<br \/>\nsb.Append(\"usemtl \").Append(mats[material].name).Append(\"<br \/>\n\");<br \/>\nsb.Append(\"usemap \").Append(mats[material].name).Append(\"<br \/>\n\");<\/p>\n<p>\/\/See if this material is already in the materiallist.<br \/>\ntry<br \/>\n{<br \/>\nObjMaterial objMaterial = new ObjMaterial();<\/p>\n<p>objMaterial.name = mats[material].name;<\/p>\n<p>if (mats[material].mainTexture)<br \/>\nobjMaterial.textureName = EditorUtility.GetAssetPath(mats[material].mainTexture);<br \/>\nelse<br \/>\nobjMaterial.textureName = null;<\/p>\n<p>materialList.Add(objMaterial.name, objMaterial);<br \/>\n}<br \/>\ncatch (ArgumentException)<br \/>\n{<br \/>\n\/\/Already in the dictionary<br \/>\n}<\/p>\n<p>int[] triangles = m.GetTriangles(material);<br \/>\nfor (int i=0;i&lt;triangles.Length;i+=3)<br \/>\n{<br \/>\n\/\/Because we inverted the x-component, we also needed to alter the triangle winding.<br \/>\nsb.Append(string.Format(\"f {1}\/{1}\/{1} {0}\/{0}\/{0} {2}\/{2}\/{2}<br \/>\n\",<br \/>\ntriangles[i]+1 + vertexOffset, triangles[i+1]+1 + normalOffset, triangles[i+2]+1 + uvOffset));<br \/>\n}<br \/>\n}<\/p>\n<p>vertexOffset += m.vertices.Length;<br \/>\nnormalOffset += m.normals.Length;<br \/>\nuvOffset += m.uv.Length;<\/p>\n<p>return sb.ToString();<br \/>\n}<\/p>\n<p>private static void Clear()<br \/>\n{<br \/>\nvertexOffset = 0;<br \/>\nnormalOffset = 0;<br \/>\nuvOffset = 0;<br \/>\n}<\/p>\n<p>private static Dictionary&lt;string, ObjMaterial&gt; PrepareFileWrite()<br \/>\n{<br \/>\nClear();<\/p>\n<p>return new Dictionary&lt;string, ObjMaterial&gt;();<br \/>\n}<\/p>\n<p>private static void MaterialsToFile(Dictionary&lt;string, ObjMaterial&gt; materialList, string folder, string filename)<br \/>\n{<br \/>\nusing (StreamWriter sw = new StreamWriter(folder + \"\/\" + filename + \".mtl\"))<br \/>\n{<br \/>\nforeach( KeyValuePair&lt;string, ObjMaterial&gt; kvp in materialList )<br \/>\n{<br \/>\nsw.Write(\"<br \/>\n\");<br \/>\nsw.Write(\"newmtl {0}<br \/>\n\", kvp.Key);<br \/>\nsw.Write(\"Ka\u00a0\u00a00.6 0.6 0.6<br \/>\n\");<br \/>\nsw.Write(\"Kd\u00a0\u00a00.6 0.6 0.6<br \/>\n\");<br \/>\nsw.Write(\"Ks\u00a0\u00a00.9 0.9 0.9<br \/>\n\");<br \/>\nsw.Write(\"d\u00a0\u00a01.0<br \/>\n\");<br \/>\nsw.Write(\"Ns\u00a0\u00a00.0<br \/>\n\");<br \/>\nsw.Write(\"illum 2<br \/>\n\");<\/p>\n<p>if (kvp.Value.textureName != null)<br \/>\n{<br \/>\nstring destinationFile = kvp.Value.textureName;<\/p>\n<p>int stripIndex = destinationFile.LastIndexOf('\/');\/\/FIXME: Should be Path.PathSeparator;<\/p>\n<p>if (stripIndex &gt;= 0)<br \/>\ndestinationFile = destinationFile.Substring(stripIndex + 1).Trim();<\/p>\n<p>string relativeFile = destinationFile;<\/p>\n<p>destinationFile = folder + \"\/\" + destinationFile;<\/p>\n<p>Debug.Log(\"Copying texture from \" + kvp.Value.textureName + \" to \" + destinationFile);<\/p>\n<p>try<br \/>\n{<br \/>\n\/\/Copy the source file<br \/>\nFile.Copy(kvp.Value.textureName, destinationFile);<br \/>\n}<br \/>\ncatch<br \/>\n{<\/p>\n<p>}<\/p>\n<p>sw.Write(\"map_Kd {0}\", relativeFile);<br \/>\n}<\/p>\n<p>sw.Write(\"<\/code><\/p>\n<p>&#8220;);<br \/>\n}<br \/>\n}<br \/>\n}<\/p>\n<p>private static void MeshToFile(MeshFilter mf, string folder, string filename)<br \/>\n{<br \/>\nDictionary&lt;string, ObjMaterial&gt; materialList = PrepareFileWrite();<\/p>\n<p>using (StreamWriter sw = new StreamWriter(folder +&#8221;\/&#8221; + filename + &#8220;.obj&#8221;))<br \/>\n{<br \/>\nsw.Write(&#8220;mtllib .\/&#8221; + filename + &#8220;.mtl<br \/>\n&#8220;);<\/p>\n<p>sw.Write(MeshToString(mf, materialList));<br \/>\n}<\/p>\n<p>MaterialsToFile(materialList, folder, filename);<br \/>\n}<\/p>\n<p>private static void MeshesToFile(MeshFilter[] mf, string folder, string filename)<br \/>\n{<br \/>\nDictionary&lt;string, ObjMaterial&gt; materialList = PrepareFileWrite();<\/p>\n<p>using (StreamWriter sw = new StreamWriter(folder +&#8221;\/&#8221; + filename + &#8220;.obj&#8221;))<br \/>\n{<br \/>\nsw.Write(&#8220;mtllib .\/&#8221; + filename + &#8220;.mtl<br \/>\n&#8220;);<\/p>\n<p>for (int i = 0; i &lt; mf.Length; i++)<br \/>\n{<br \/>\nsw.Write(MeshToString(mf[i], materialList));<br \/>\n}<br \/>\n}<\/p>\n<p>MaterialsToFile(materialList, folder, filename);<br \/>\n}<\/p>\n<p>private static bool CreateTargetFolder()<br \/>\n{<br \/>\ntry<br \/>\n{<br \/>\nSystem.IO.Directory.CreateDirectory(targetFolder);<br \/>\n}<br \/>\ncatch<br \/>\n{<br \/>\nEditorUtility.DisplayDialog(&#8220;Error!&#8221;, &#8220;Failed to create target folder!&#8221;, &#8220;&#8221;);<br \/>\nreturn false;<br \/>\n}<\/p>\n<p>return true;<br \/>\n}<\/p>\n<p>[MenuItem (&#8220;Custom\/Export\/Export all MeshFilters in selection to separate OBJs&#8221;)]<br \/>\nstatic void ExportSelectionToSeparate()<br \/>\n{<br \/>\nif (!CreateTargetFolder())<br \/>\nreturn;<\/p>\n<p>Transform[] selection = Selection.GetTransforms(SelectionMode.Editable | SelectionMode.ExcludePrefab);<\/p>\n<p>if (selection.Length == 0)<br \/>\n{<br \/>\nEditorUtility.DisplayDialog(&#8220;No source object selected!&#8221;, &#8220;Please select one or more target objects&#8221;, &#8220;&#8221;);<br \/>\nreturn;<br \/>\n}<\/p>\n<p>int exportedObjects = 0;<\/p>\n<p>for (int i = 0; i &lt; selection.Length; i++)<br \/>\n{<br \/>\nComponent[] meshfilter = selection[i].GetComponentsInChildren(typeof(MeshFilter));<\/p>\n<p>for (int m = 0; m &lt; meshfilter.Length; m++)<br \/>\n{<br \/>\nexportedObjects++;<br \/>\nMeshToFile((MeshFilter)meshfilter[m], targetFolder, selection[i].name + &#8220;_&#8221; + i + &#8220;_&#8221; + m);<br \/>\n}<br \/>\n}<\/p>\n<p>if (exportedObjects &gt; 0)<br \/>\nEditorUtility.DisplayDialog(&#8220;Objects exported&#8221;, &#8220;Exported &#8221; + exportedObjects + &#8221; objects&#8221;, &#8220;&#8221;);<br \/>\nelse<br \/>\nEditorUtility.DisplayDialog(&#8220;Objects not exported&#8221;, &#8220;Make sure at least some of your selected objects have mesh filters!&#8221;, &#8220;&#8221;);<br \/>\n}<\/p>\n<p>[MenuItem (&#8220;Custom\/Export\/Export whole selection to single OBJ&#8221;)]<br \/>\nstatic void ExportWholeSelectionToSingle()<br \/>\n{<br \/>\nif (!CreateTargetFolder())<br \/>\nreturn;<\/p>\n<p>Transform[] selection = Selection.GetTransforms(SelectionMode.Editable | SelectionMode.ExcludePrefab);<\/p>\n<p>if (selection.Length == 0)<br \/>\n{<br \/>\nEditorUtility.DisplayDialog(&#8220;No source object selected!&#8221;, &#8220;Please select one or more target objects&#8221;, &#8220;&#8221;);<br \/>\nreturn;<br \/>\n}<\/p>\n<p>int exportedObjects = 0;<\/p>\n<p>ArrayList mfList = new ArrayList();<\/p>\n<p>for (int i = 0; i &lt; selection.Length; i++)<br \/>\n{<br \/>\nComponent[] meshfilter = selection[i].GetComponentsInChildren(typeof(MeshFilter));<\/p>\n<p>for (int m = 0; m &lt; meshfilter.Length; m++)<br \/>\n{<br \/>\nexportedObjects++;<br \/>\nmfList.Add(meshfilter[m]);<br \/>\n}<br \/>\n}<\/p>\n<p>if (exportedObjects &gt; 0)<br \/>\n{<br \/>\nMeshFilter[] mf = new MeshFilter[mfList.Count];<\/p>\n<p>for (int i = 0; i &lt; mfList.Count; i++)<br \/>\n{<br \/>\nmf[i] = (MeshFilter)mfList[i];<br \/>\n}<\/p>\n<p>string filename = EditorApplication.currentScene + &#8220;_&#8221; + exportedObjects;<\/p>\n<p>int stripIndex = filename.LastIndexOf(&#8216;\/&#8217;);\/\/FIXME: Should be Path.PathSeparator<\/p>\n<p>if (stripIndex &gt;= 0)<br \/>\nfilename = filename.Substring(stripIndex + 1).Trim();<\/p>\n<p>MeshesToFile(mf, targetFolder, filename);<\/p>\n<p>EditorUtility.DisplayDialog(&#8220;Objects exported&#8221;, &#8220;Exported &#8221; + exportedObjects + &#8221; objects to &#8221; + filename, &#8220;&#8221;);<br \/>\n}<br \/>\nelse<br \/>\nEditorUtility.DisplayDialog(&#8220;Objects not exported&#8221;, &#8220;Make sure at least some of your selected objects have mesh filters!&#8221;, &#8220;&#8221;);<br \/>\n}<\/p>\n<p>[MenuItem (&#8220;Custom\/Export\/Export each selected to single OBJ&#8221;)]<br \/>\nstatic void ExportEachSelectionToSingle()<br \/>\n{<br \/>\nif (!CreateTargetFolder())<br \/>\nreturn;<\/p>\n<p>Transform[] selection = Selection.GetTransforms(SelectionMode.Editable | SelectionMode.ExcludePrefab);<\/p>\n<p>if (selection.Length == 0)<br \/>\n{<br \/>\nEditorUtility.DisplayDialog(&#8220;No source object selected!&#8221;, &#8220;Please select one or more target objects&#8221;, &#8220;&#8221;);<br \/>\nreturn;<br \/>\n}<\/p>\n<p>int exportedObjects = 0;<\/p>\n<p>for (int i = 0; i &lt; selection.Length; i++)<br \/>\n{<br \/>\nComponent[] meshfilter = selection[i].GetComponentsInChildren(typeof(MeshFilter));<\/p>\n<p>MeshFilter[] mf = new MeshFilter[meshfilter.Length];<\/p>\n<p>for (int m = 0; m &lt; meshfilter.Length; m++)<br \/>\n{<br \/>\nexportedObjects++;<br \/>\nmf[m] = (MeshFilter)meshfilter[m];<br \/>\n}<\/p>\n<p>MeshesToFile(mf, targetFolder, selection[i].name + &#8220;_&#8221; + i);<br \/>\n}<\/p>\n<p>if (exportedObjects &gt; 0)<br \/>\n{<br \/>\nEditorUtility.DisplayDialog(&#8220;Objects exported&#8221;, &#8220;Exported &#8221; + exportedObjects + &#8221; objects&#8221;, &#8220;&#8221;);<br \/>\n}<br \/>\nelse<br \/>\nEditorUtility.DisplayDialog(&#8220;Objects not exported&#8221;, &#8220;Make sure at least some of your selected objects have mesh filters!&#8221;, &#8220;&#8221;);<br \/>\n}<\/p>\n<p>}<\/p>\n<p>\u539f\u6587:<a href=\"http:\/\/wiki.unity3d.com\/index.php\/ObjExporter\" target=\"_blank\" rel=\"external\">http:\/\/wiki.unity3d.com\/index.php\/ObjExporter<\/a><br \/>\n\u7531\u5c0f\u53ef\u7ffb\u8bd1,\u8f6c\u8f7d\u8bf7\u6ce8\u660e\u6765\u81ea1vr.cn<\/p>\n","protected":false},"excerpt":{"rendered":"<p>\u5728Unity\u4e2d\u5efa\u7acb\u7684\u573a\u666f\u6587\u4ef6,\u6446\u653e\u7684\u6a21\u578b\u53ef\u4ee5\u7528\u4e0b\u5217\u811a\u672c\u5bfc\u51fa\u4e3aObj\u6a21\u578b\u6587\u4ef6,\u53ef\u4ee5\u4fdd\u6301\u573a\u666f\u4e2d\u6a21\u578b\u7684\u4f4d\u7f6e,\u8d34\u56fe\u706f\u4fe1 &hellip; <a href=\"https:\/\/1vr.cn\/?p=620\" class=\"more-link\">Continue reading <span class=\"screen-reader-text\">\u8f93\u51faUnity\u7684\u573a\u666f\u6587\u4ef6\u4e3aObj\u6a21\u578b\u6587\u4ef6<\/span><\/a><\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-620","post","type-post","status-publish","format-standard","hentry","without-featured-image"],"_links":{"self":[{"href":"https:\/\/1vr.cn\/index.php?rest_route=\/wp\/v2\/posts\/620","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/1vr.cn\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/1vr.cn\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/1vr.cn\/index.php?rest_route=\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/1vr.cn\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=620"}],"version-history":[{"count":3,"href":"https:\/\/1vr.cn\/index.php?rest_route=\/wp\/v2\/posts\/620\/revisions"}],"predecessor-version":[{"id":1342,"href":"https:\/\/1vr.cn\/index.php?rest_route=\/wp\/v2\/posts\/620\/revisions\/1342"}],"wp:attachment":[{"href":"https:\/\/1vr.cn\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=620"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/1vr.cn\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=620"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/1vr.cn\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=620"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}