Unity 导出obj模型

2 篇文章 1 订阅


前言

obj格式是一种通用的3D模型格式,也是unity支持的模型格式之一。obj具体格式介绍可以去某度看看,有不少。本篇重点是在unity编辑器中运行状态下和非运行状态下将场景中的物体导出为obj。


一、部分细节

1.镜像

也就是坐标手系变换,unity是使用左手坐标系的,而标准obj是右手坐标系,所以unity在导入obj后会自动将obj模型镜像。在导出时笔者也加上了这个功能,不然按默认的导出obj后再放入unity中就会发现两个模型镜像了。其实变换手系原理很简单,如下图所示(图片来源),固定两条轴------把两条轴重叠-------就会发现另外一条轴是相反的,比如把Y轴和Z轴对齐,此时只要把X轴的值取反就可以达到镜像的效果。
请添加图片描述

//写出顶点
for (int i = 0; i < vertices.Length; i++)
{
     Vector3 worldPos = trans.TransformPoint(vertices[i]);
     //顶点镜像
     if (exchangeCoordinate) worldPos.x *= -1;
     sw.Write("v " + worldPos.x + " " + worldPos.y + " " + worldPos.z + "\n");
}
sw.Write("\n");

//写出法线
if(normals.Length == vertices.Length)
{
     hasNormal = true;
     for (int i = 0; i < normals.Length; i++)
     {
           Vector3 worldNormal = trans.TransformDirection(normals[i]);
           //法线镜像
           if (exchangeCoordinate) worldNormal.x *= -1;
           sw.Write("vn " + worldNormal.x + " " + worldNormal.y + " " + worldNormal.z + "\n");
     }
     sw.Write("\n");
}

2.压缩存储

所谓压缩存储,其实就是利用obj三角片面的索引特性将指向相同的内容使用同一个索引,也就是重用。其实unity里面的基本几何体都是没有重用的,如下图,一个正方体应该只有8个点,12个三角面片,但图中显示的却是24个顶点。
请添加图片描述
将cube直接不压缩导出来后确实有24个,但是可以明显看到有不少点坐标是相同的,特别是uv信息,相同的更多,这种方式有个特点,就是顶点有多少个,法线和uv就有多少个(某些可能没有法线或uv的模型除外),从后面的三角面片信息也可以看出来,顶点/法线/UV 的索引都是相同的。
请添加图片描述
请添加图片描述
再看下添加重用后导出的obj数据,明显少了很多数据,此时顶点是真的只有8个了,然后看下三角面片中顶点,法线和uv的索引不尽相同。
请添加图片描述
不过这时如果把这个压缩后的obj再次导入unity就会发现一个神奇的事,如下图,显示的顶点数又变成24了,具体原因可以看下这篇博客
请添加图片描述
要实现压缩存储其实不难,就是先遍历一遍,把相同的数据用一个代替就可以了,这里用字典来存储单一的数据

//保存相同的 顶点/法线/UV 对应的唯一索引
Dictionary<Vector3, int> verticesDic = new Dictionary<Vector3, int>();
Dictionary<Vector3, int> normalDic = new Dictionary<Vector3, int>();
Dictionary<Vector2, int> uvDic = new Dictionary<Vector2, int>();
//计算重复的顶点法线uv
for (int i = 0; i < vertices.Length; i++)
{
   if (!verticesDic.ContainsKey(vertices[i]))
       verticesDic.Add(vertices[i], verticesDic.Count);
   }
}
if(normals.Length  == vertices.Length)
{
   hasNormal = true;
   for (int i = 0; i < normals.Length; i++)
   {
       if (!normalDic.ContainsKey(normals[i]))
       {
           normalDic.Add(normals[i], normalDic.Count);
       }
   }
}

if(uvs.Length == vertices.Length )
{
    hasUV = true;
    for (int i = 0; i < uvs.Length; i++)
    {
       if (!uvDic.ContainsKey(uvs[i]))
        {
            uvDic.Add(uvs[i], uvDic.Count);
        }
    }
}

写出数据部分有点长,可以看下下面的完整代码部分。


二、测试效果

测试模型来源于AssetStore中的unity-chan!

1.编辑器非运行环境

测试脚本

/****************************************************
    文件:ExportObjExample.cs
	作者:TKB
    邮箱: 544726237@qq.com
    日期:2021/7/24 23:42:59
	功能:Nothing
*****************************************************/
using UnityEngine;
using System.IO;

namespace TLib
{
    public class ExportObjExample
    {
#if UNITY_EDITOR
		//将选中的模型及其子物体导出到一个obj中
        [UnityEditor.MenuItem("Tools/导出obj",false)]
        private static void OnClickExportObj()
        {
            GameObject go = UnityEditor.Selection.activeObject as GameObject;
            Exporter.ExportObj(go, Application.dataPath + "/Export/"+ go.name+".obj",true,true);
            UnityEditor.AssetDatabase.Refresh();
        }
        //将选中的物体及其子对象分别导出为obj
        [UnityEditor.MenuItem("Tools/导出objs", false)]
        private static void OnClickExportObj1()
        {
            GameObject go = UnityEditor.Selection.activeObject as GameObject;
            Exporter.ExportObjs(go, Application.dataPath + "/Export");
            UnityEditor.AssetDatabase.Refresh();
        }
#endif
    }
}

既可以导出MeshRendererer(右边方块组成的)也可以导出SkinnedMeshRenderer(左边)。眼尖的同学可能看到了导出来的chan脸上的腮红有点问题,显示效果也有差距,这其实是因为chan使用的shader是自定义的,不是标准材质。
请添加图片描述
请添加图片描述

2.编辑器运行环境

测试代码

using UnityEngine;
using TLib;

public class RunTimeExport : MonoBehaviour
{
    public GameObject go;

    // Update is called once per frame
    void Update()
    {
        if (Input.GetKeyUp(KeyCode.A))
        {
            if (go != null)
            {
                Exporter.ExportObj(go, Application.dataPath + "/Export/" + go.name + ".obj");
            }
        }
    }
}

注意,运行时如果是带动画的需要先把动画脚本禁用掉,不然一些节点位置可能会发生错乱
禁用动画:
请添加图片描述
导出的效果截图:
请添加图片描述
不禁用动画时,可以看到脸部节点已经错乱了:
请添加图片描述

局限性

  • 只支持unity标准材质,或者漫反射颜色与贴图属性名跟标准材质相同的自定义材质
  • 材质只有漫反射颜色、透明度和漫反射贴图导出
  • 上面提到的带动画导出时节点位置可能会错乱
  • 导出的模型名请别用中文,目前导出的mtl文件的名字将会与模型名保持一致,如果mtl文件名含有中文,不少3D软件无法识别材质信息,包括unity,可以看下笔者的另一篇博客

完整代码

/****************************************************
    文件:Exporter.cs
	作者:TKB
    邮箱: 544726237@qq.com
    日期:2021/7/24 23:9:12
	功能:导出obj(如果有贴图,仅在编辑器模式下才支持)
*****************************************************/

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using UnityEngine;

namespace TLib
{
    public class Exporter
    {
        //保存同名次数
        static Dictionary<string, int> meshNameCountDic = new Dictionary<string, int>();
        #region 导出obj
        /// <summary>
        /// 导出GameObject及其子对象为一个obj
        /// </summary>
        /// <param name="go">要导出的GameObject</param>
        /// <param name="outputPath">导出的obj完整路径,如 Application.dataPath+"/temp.obj"</param>
        /// <param name="exchangeCoordinate">是否要变换坐标系,从左手坐标系(unity)变换到右手坐标系(标准obj),默认为true</param>
        /// <param name="compress">是否要压缩存储,默认为true</param>
        public static void ExportObj(GameObject go, string outputPath, bool exchangeCoordinate = true,bool compress = true)
        {
            if (!go) return;
            meshNameCountDic.Clear();
            if (!Directory.Exists(Path.GetDirectoryName(outputPath)))
            {
                Directory.CreateDirectory(Path.GetDirectoryName(outputPath));
            }
            if (File.Exists(outputPath))
            {
                try
                {
                    File.Delete(outputPath);
                    Debug.LogWarning("该路径已存在同名文件,已删除!" + outputPath);
                }
                catch (Exception e)
                {
                    Debug.LogError(e + "该路径已存在同名文件并且删除失败!" + outputPath);
                    return;
                }
            }

            MeshFilter[] meshFilters = go.GetComponentsInChildren<MeshFilter>();
            SkinnedMeshRenderer[] skinnedMeshRenderers = go.GetComponentsInChildren<SkinnedMeshRenderer>();
            int meshCount = meshFilters.Length + skinnedMeshRenderers.Length;
            List<string> exportedMatList = new List<string>();//保存已经导出的材质名字 
            FileStream meshFS=null;
            StreamWriter meshSW=null;
            try
            {
                meshFS = new FileStream(outputPath, FileMode.Create, FileAccess.Write, FileShare.ReadWrite);
                meshSW = new StreamWriter(meshFS, Encoding.UTF8);

                string matPath = Path.Combine(Path.GetDirectoryName(outputPath), Path.GetFileNameWithoutExtension(outputPath) + ".mtl");
                StringBuilder sb = new StringBuilder();

                int currentIndex = 0;
                int currentNormalIndex = 0;
                int currentUVIndex = 0;
                meshSW.Write("#Export by TLib from Unity3D\n");
                meshSW.Write("#Time : "+DateTime.Now+"\n");
                meshSW.Write("\nmtllib " + Path.GetFileNameWithoutExtension(outputPath) + ".mtl\n\n");
                for (int i = 0; i < meshFilters.Length; i++)
                {
                    Mesh mesh;
                    Material[] mats;
#if UNITY_EDITOR
                    mesh = meshFilters[i].sharedMesh;
                    mats = meshFilters[i].gameObject.GetComponent<MeshRenderer>().sharedMaterials;
#else
                    mesh = meshFilters[i].mesh;
                    mats = meshFilters[i].gameObject.GetComponent<MeshRenderer>().materials;
#endif
                    for (int j = 0; j < mats.Length; j++)
                    {
                        //某些材质没有设置或者丢失
                        if (mats[j] == null)
                        {
                            Material mat = new Material(Shader.Find("Standard"));
                            mat.name = mesh.name + "_" + i + "_" + j;
                            mats[j] = mat;
                        }
                        if (exportedMatList.Contains(mats[j].name)) continue;
                        ExportMaterialToObj(mats[j], sb, Path.GetDirectoryName(outputPath));
                        exportedMatList.Add(mats[j].name);
                    }
#if UNITY_EDITOR
                    UnityEditor.EditorUtility.DisplayProgressBar("导出Obj", mesh.name + ":" + i + "/" + meshCount, i * 1.0f / meshCount);
#endif
                    ExportMeshToObj(meshFilters[i].transform, mesh, meshSW, mats, ref currentIndex, ref currentNormalIndex, ref currentUVIndex, exchangeCoordinate,compress);

                }
                for (int i = 0; i < skinnedMeshRenderers.Length; i++)
                {
                    Mesh mesh;
                    Material[] mats;
#if UNITY_EDITOR
                    mesh = skinnedMeshRenderers[i].sharedMesh;
                    mats = skinnedMeshRenderers[i].sharedMaterials;
#else
                    mesh = meshFilters[i].mesh;
                    mats = meshFilters[i].materials;
#endif
                    
                    for (int j = 0; j < mats.Length; j++)
                    {
                        //某些材质没有设置或者丢失
                        if (mats[j] == null)
                        {
                            Material mat = new Material(Shader.Find("Standard"));
                            mat.name = mesh.name + "_" + i + "_" + j;
                            mats[j] = mat;
                        }
                        if (exportedMatList.Contains(mats[j].name)) continue;
                        ExportMaterialToObj(mats[j], sb, Path.GetDirectoryName(outputPath));
                        exportedMatList.Add(mats[j].name);
                    }
#if UNITY_EDITOR
                    UnityEditor.EditorUtility.DisplayProgressBar("导出Obj", mesh.name + ":" + (i+meshFilters.Length) + "/" + meshCount, i * 1.0f / meshCount);
#endif
                    ExportMeshToObj(skinnedMeshRenderers[i].transform, mesh, meshSW, mats, ref currentIndex, ref currentNormalIndex, ref currentUVIndex, exchangeCoordinate,compress);
                }
                meshSW.Close();
                meshFS.Close();
                File.WriteAllText(matPath, sb.ToString());
                
            }
            catch (Exception e)
            {
                Debug.LogError(e);
                if (meshSW!=null) meshSW.Close();
                if (meshFS != null) meshFS.Close();
            }
            finally
            {
                exportedMatList.Clear();
                UnityEditor.EditorUtility.ClearProgressBar();
            }
            
        }

        /// <summary>
        /// 导出Transform及其子对象为一个obj
        /// </summary>
        /// <param name="trans">待导出的Transform</param>
        /// <param name="outputPath">导出的obj完整路径,如 Application.dataPath+"/temp.obj"</param>
        /// <param name="exchangeCoordinate">是否要变换坐标系,从左手坐标系(unity)变换到右手坐标系(标准obj),默认为true</param>
        /// <param name="compress">是否要压缩存储,默认为true</param>
        public static void ExportObj(Transform trans, string outputPath, bool exchangeCoordinate = true,bool compress = true)
        {
            ExportObj(trans.gameObject, outputPath, exchangeCoordinate,compress);
        }

        /// <summary>
        /// 导出GameObject及其子对象为多个obj,每个mesh对应一个obj
        /// </summary>
        /// <param name="go">要导出的GameObject</param>
        /// <param name="outputDir">将obj导出到哪个文件夹</param>
        /// <param name="exchangeCoordinate">是否要变换坐标系,从左手坐标系(unity)变换到右手坐标系(标准obj),默认为true</param>
        /// <param name="compress">是否要压缩存储,默认为true</param>
        public static void ExportObjs(GameObject go, string outputDir, bool exchangeCoordinate = true, bool compress = true)
        {
            if (!Directory.Exists(outputDir)) Directory.CreateDirectory(outputDir);
            MeshFilter[] meshFilters = go.GetComponentsInChildren<MeshFilter>();
            SkinnedMeshRenderer[] skinnedMeshRenderers = go.GetComponentsInChildren<SkinnedMeshRenderer>();
            int meshCount = meshFilters.Length + skinnedMeshRenderers.Length;
            Dictionary<string, int> meshNameDic = new Dictionary<string, int>();
            int currentIndex = 0;
            int currentNormalIndex = 0;
            int currentUVIndex = 0;
            for (int i = 0; i < meshFilters.Length; i++)
            {
                try
                {
                    string name = meshFilters[i].gameObject.name;
                    if (meshNameDic.ContainsKey(name))
                    {
                        meshNameDic[name]++;
                        name += meshNameDic[name];
                    }
                    else meshNameDic.Add(name, 0);
                    string objPath = Path.Combine(outputDir, name + ".obj");
                    FileStream meshFS = new FileStream(objPath, FileMode.Create, FileAccess.Write, FileShare.ReadWrite);
                    StreamWriter meshSW = new StreamWriter(meshFS, Encoding.UTF8);

                    string matPath = Path.Combine(outputDir, name + ".mtl");
                    StringBuilder sb = new StringBuilder();

                    meshNameCountDic.Clear();
                    meshSW.Write("# Export by TLib\n# " + DateTime.Now.ToString("yyyy-MM-dd hh-mm-ss") + "\n");
                    meshSW.Write("\nmtllib " + name + ".mtl\n\n");

                    Mesh mesh;
                    Material[] mats;
#if UNITY_EDITOR
                    mesh = meshFilters[i].sharedMesh;
                    mats = meshFilters[i].gameObject.GetComponent<MeshRenderer>().sharedMaterials;
#else
                mesh = meshFilters[i].mesh;
                mats = meshFilters[i].gameObject.GetComponent<MeshRenderer>().materials;
#endif
                    List<string> exportedMatList = new List<string>();//保存已经导出的材质名字 
                    for (int j = 0; j < mats.Length; j++)
                    {
                        //某些材质没有设置或者丢失
                        if (mats[j] == null)
                        {
                            Material mat = new Material(Shader.Find("Standard"));
                            mat.name = mesh.name + "_" + i + "_" + j;
                            mats[j] = mat;
                        }
                        if (exportedMatList.Contains(mats[j].name)) continue;
                        ExportMaterialToObj(mats[j], sb, Path.GetDirectoryName(objPath));
                        exportedMatList.Add(mats[j].name);
                    }
#if UNITY_EDITOR
                    UnityEditor.EditorUtility.DisplayProgressBar("导出Obj", mesh.name + ":" + i + "/" + meshCount, i * 1.0f / meshCount);
#endif
                    //分别导出obj时,每导出一个obj都要重置这些索引变量
                    currentIndex = 0;
                    currentNormalIndex = 0;
                    currentUVIndex = 0;

                    ExportMeshToObj(meshFilters[i].transform, mesh, meshSW, mats, ref currentIndex, ref currentNormalIndex, ref currentUVIndex, exchangeCoordinate,compress);
                    File.WriteAllText(matPath, sb.ToString());
                    meshSW.Close();
                    meshFS.Close();
                }
                catch (Exception e)
                {
                    Debug.Log(e);
                }
            }
            for (int i = 0; i < skinnedMeshRenderers.Length; i++)
            {
                try
                {
                    string name = skinnedMeshRenderers[i].gameObject.name;
                    if (meshNameDic.ContainsKey(name))
                    {
                        name += meshNameDic[name];
                        meshNameDic[name]++;
                    }
                    else meshNameDic.Add(name, 1);
                    string objPath = Path.Combine(outputDir, name + ".obj");
                    FileStream meshFS = new FileStream(objPath, FileMode.Create, FileAccess.Write, FileShare.ReadWrite);
                    StreamWriter meshSW = new StreamWriter(meshFS, Encoding.UTF8);

                    string matPath = Path.Combine(outputDir, name + ".mtl");
                    StringBuilder sb = new StringBuilder();
                    meshNameCountDic.Clear();
                    meshSW.Write("# Export by TLib\n# " + DateTime.Now.ToString("yyyy-MM-dd hh-mm-ss") + "\n");
                    meshSW.Write("\nmtllib " + name + ".mtl\n\n");

                    Mesh mesh;
                    Material[] mats;
#if UNITY_EDITOR
                    mesh = skinnedMeshRenderers[i].sharedMesh;
                    mats = skinnedMeshRenderers[i].sharedMaterials;
#else
                mesh = meshFilters[i].mesh;
                mats = meshFilters[i].
materials;
#endif
                    List<string> exportedMatList = new List<string>();//保存已经导出的材质名字 
                    for (int j = 0; j < mats.Length; j++)
                    {
                        //某些材质没有设置或者丢失
                        if (mats[j] == null)
                        {
                            Material mat = new Material(Shader.Find("Standard"));
                            mat.name = mesh.name + "_" + i + "_" + j;
                            mats[j] = mat;
                        }
                        if (exportedMatList.Contains(mats[j].name)) continue;
                        ExportMaterialToObj(mats[j], sb, Path.GetDirectoryName(objPath));
                        exportedMatList.Add(mats[j].name);
                    }
#if UNITY_EDITOR
                    UnityEditor.EditorUtility.DisplayProgressBar("导出Obj", mesh.name + ":" + (i + meshFilters.Length) + "/" + meshCount, i * 1.0f / meshCount);
#endif
                    //分别导出obj时,每导出一个obj都要重置这些索引变量
                    currentIndex = 0;
                    currentNormalIndex = 0;
                    currentUVIndex = 0;

                    ExportMeshToObj(skinnedMeshRenderers[i].transform, mesh, meshSW, mats, ref currentIndex, ref currentNormalIndex, ref currentUVIndex, exchangeCoordinate,compress);
                    File.WriteAllText(matPath, sb.ToString());
                    meshSW.Close();
                    meshFS.Close();
                }
                catch (Exception e)
                {
                    Debug.Log(e);
                }
            }
            UnityEditor.EditorUtility.ClearProgressBar();
        }

        /// <summary>
        /// 导出Transform及其子对象为多个obj,每个mesh对应一个obj
        /// </summary>
        /// <param name="trans">待导出的Transform</param>
        /// <param name="outputDir">将obj导出到哪个文件夹</param>
        /// <param name="exchangeCoordinate">是否要变换坐标系,从左手坐标系(unity)变换到右手坐标系(标准obj),默认为true</param>
        /// <param name="compress">是否要压缩存储,默认为true</param>
        public static void ExportObjs(Transform trans, string outputDir, bool exchangeCoordinate = true,bool compress = true)
        {
            ExportObjs(trans.gameObject, outputDir, exchangeCoordinate,compress);
        }

        /// <summary>
        /// 将mesh数据导出obj,用指定的StreamWrite写出
        /// </summary>
        /// <param name="trans">mesh对应的Transform,用于将顶点转换到世界空间</param>
        /// <param name="mesh">待导出的mesh</param>
        /// <param name="sw">输出流,使用这个输出流导出obj</param>
        /// <param name="materialName">这个mesh对应的材质名</param>
        /// <param name="currentIndex">到这个mesh为止前面导出了多少个顶点,用于索引偏移</param>
        /// <param name="exchangeCoordinate">是否要变换坐标系,从左手坐标系(unity)变换到右手坐标系(标准obj)</param>
        private static void ExportMeshToObj(Transform trans, Mesh mesh, StreamWriter sw, Material[] materials, ref int currentIndex, ref int currentNormalIndex, ref int currentUVIndex, bool exchangeCoordinate,bool compress)
        {
            Vector3[] vertices = mesh.vertices;
            Vector3[] normals = mesh.normals;
            Vector2[] uvs = mesh.uv;

            bool hasNormal = false;
            bool hasUV = false;

            //mesh名字处理
            string name = mesh.name;
            if (meshNameCountDic.ContainsKey(name))
            {
                string tempName = name;
                name = name + "_" + meshNameCountDic[tempName];
                meshNameCountDic[tempName]++;
            }
            else
            {
                meshNameCountDic.Add(name, 1);
            }

            if (compress)
            {
                //保存相同的 顶点/法线/UV 对应的唯一索引
                Dictionary<Vector3, int> verticesDic = new Dictionary<Vector3, int>();
                Dictionary<Vector3, int> normalDic = new Dictionary<Vector3, int>();
                Dictionary<Vector2, int> uvDic = new Dictionary<Vector2, int>();
                //计算重复的顶点法线uv
                for (int i = 0; i < vertices.Length; i++)
                {
                    if (!verticesDic.ContainsKey(vertices[i]))
                    {
                        verticesDic.Add(vertices[i], verticesDic.Count);
                    }
                }
                if(normals.Length  == vertices.Length)
                {
                    hasNormal = true;
                    for (int i = 0; i < normals.Length; i++)
                    {
                        if (!normalDic.ContainsKey(normals[i]))
                        {
                            normalDic.Add(normals[i], normalDic.Count);
                        }
                    }
                }

                if(uvs.Length == vertices.Length )
                {
                    hasUV = true;
                    for (int i = 0; i < uvs.Length; i++)
                    {
                        if (!uvDic.ContainsKey(uvs[i]))
                        {
                            uvDic.Add(uvs[i], uvDic.Count);
                        }
                    }
                }

                //将不重复的顶点法线uv写出到obj
                foreach (Vector3 item in verticesDic.Keys)
                {
                    //变换到世界空间
                    Vector3 worldPos = trans.TransformPoint(item);
                    //如果需要变换手系
                    if (exchangeCoordinate) worldPos.x *= -1;
                    //写出
                    sw.Write("v " + worldPos.x + " " + worldPos.y + " " + worldPos.z + "\n");
                }
                sw.Write("\n");
                if(hasNormal)
                {
                    foreach (Vector3 item in normalDic.Keys)
                    {
                        //变换到世界空间
                        Vector3 worldNormal = trans.TransformDirection(item);
                        //如果需要变换手系
                        if (exchangeCoordinate) worldNormal.x *= -1;
                        //写出
                        sw.Write("vn " + worldNormal.x + " " + worldNormal.y + " " + worldNormal.z + "\n");
                    }
                    sw.Write("\n");
                }
                if (hasUV)
                {
                    foreach (Vector2 item in uvDic.Keys)
                    {
                        sw.Write("vt " + item.x + " " + item.y + " 0.0\n");
                    }
                    sw.Write("\n");
                }
                for (int k = 0; k < mesh.subMeshCount; k++)
                {
                    
                    if (mesh.subMeshCount == 1)
                    {
                        sw.Write("\ng " + name + "\n");
                    }
                    else
                    {
                        sw.Write("\ng " + name + "_" + k + "\n");
                    }
                    sw.Write("usemtl " + materials[k].name + "\n");
                    int[] tris = mesh.GetIndices(k);
                    for (int i = 0; i < tris.Length / 3; i++)
                    {
                        int verticesIndex1 = 0, verticesIndex2 = 0, verticesIndex3 = 0, normalIndex1 = 0, normalIndex2 = 0, normalIndex3 = 0, uvIndex1 = 0, uvIndex2 = 0, uvIndex3 = 0;
                        try
                        {
                            Vector3 curPos1 = vertices[tris[i * 3]];
                            Vector3 curPos2 = vertices[tris[i * 3 + 1]];
                            Vector3 curPos3 = vertices[tris[i * 3 + 2]];

                            verticesIndex1 = verticesDic[curPos1];
                            verticesIndex2 = verticesDic[curPos2];
                            verticesIndex3 = verticesDic[curPos3];
                        }
                        catch(Exception e)
                        {
                            Debug.Log("缺少顶点重用信息,索引为:" + tris[i * 3] + "\n" + e);
                        }
                        if (hasNormal)
                        {
                            try
                            {
                                Vector3 curNormal1 = normals[tris[i * 3]];
                                Vector3 curNormal2 = normals[tris[i * 3 + 1]];
                                Vector3 curNormal3 = normals[tris[i * 3 + 2]];

                                normalIndex1 = normalDic[curNormal1];
                                normalIndex2 = normalDic[curNormal2];
                                normalIndex3 = normalDic[curNormal3];
                            }
                            catch (Exception e)
                            {
                                Debug.Log("缺少法线重用信息,索引为:" + tris[i * 3] + "\n" + e);
                            }
                        }
                        if (hasUV)
                        {
                            try
                            {
                                Vector2 curUv1 = uvs[tris[i * 3]];
                                Vector2 curUv2 = uvs[tris[i * 3 + 1]];
                                Vector2 curUv3 = uvs[tris[i * 3 + 2]];

                                uvIndex1 = uvDic[curUv1];
                                uvIndex2 = uvDic[curUv2];
                                uvIndex3 = uvDic[curUv3];
                            }
                            catch(Exception e)
                            {
                                Debug.Log("缺少UV重用信息,索引为:" + tris[i * 3] + "\n" + e);
                            }
                        }
                        

                        if (exchangeCoordinate)
                        {
                            //既有法线也有uv
                            if (hasNormal && hasUV)
                            {
                                sw.Write("f " + (verticesIndex2 + 1 + currentIndex) + "/" + (uvIndex2 + 1 + currentUVIndex) + "/" + (normalIndex2 + 1 + currentNormalIndex));
                                sw.Write(" " + (verticesIndex1 + 1 + currentIndex) + "/" + (uvIndex1 + 1 + currentUVIndex) + "/" + (normalIndex1 + 1 + currentNormalIndex));
                                sw.Write(" " + (verticesIndex3 + 1 + currentIndex) + "/" + (uvIndex3 + 1 + currentUVIndex) + "/" + (normalIndex3 + 1 + currentNormalIndex) + "\n");
                            }
                            //有uv没法线
                            else if(!hasNormal && hasUV)
                            {
                                sw.Write("f " + (verticesIndex2 + 1 + currentIndex) + "/" + (uvIndex2 + 1 + currentUVIndex));
                                sw.Write(" " + (verticesIndex1 + 1 + currentIndex) + "/" + (uvIndex1 + 1 + currentUVIndex));
                                sw.Write(" " + (verticesIndex3 + 1 + currentIndex) + "/" + (uvIndex3 + 1 + currentUVIndex) + "\n");
                            }
                            //有法线没uv
                            else if(hasNormal && !hasUV)
                            {
                                sw.Write("f " + (verticesIndex2 + 1 + currentIndex) + "//" + (normalIndex2 + 1 + currentNormalIndex));
                                sw.Write(" " + (verticesIndex1 + 1 + currentIndex) + "//" + (normalIndex1 + 1 + currentNormalIndex));
                                sw.Write(" " + (verticesIndex3 + 1 + currentIndex) + "//" + (normalIndex3 + 1 + currentNormalIndex) + "\n");
                            }
                            //既没法线也没uv
                            else if(!hasNormal && !hasUV)
                            {
                                sw.Write("f " + (verticesIndex2 + 1 + currentIndex));
                                sw.Write(" " + (verticesIndex1 + 1 + currentIndex));
                                sw.Write(" " + (verticesIndex3 + 1 + currentIndex) + "\n");
                            }
                        }
                        else
                        {
                            //既有法线也有uv
                            if (hasNormal && hasUV)
                            {
                                sw.Write("f " + (verticesIndex1 + 1 + currentIndex) + "/" + (uvIndex1 + 1 + currentUVIndex) + "/" + (normalIndex1 + 1 + currentNormalIndex));
                                sw.Write(" " + (verticesIndex2 + 1 + currentIndex) + "/" + (uvIndex2 + 1 + currentUVIndex) + "/" + (normalIndex2 + 1 + currentNormalIndex));
                                sw.Write(" " + (verticesIndex3 + 1 + currentIndex) + "/" + (uvIndex3 + 1 + currentUVIndex) + "/" + (normalIndex3 + 1 + currentNormalIndex) + "\n");
                            }
                            //有uv没法线
                            else if (!hasNormal && hasUV)
                            {
                                sw.Write("f " + (verticesIndex1 + 1 + currentIndex) + "/" + (uvIndex1 + 1 + currentUVIndex));
                                sw.Write(" " + (verticesIndex2 + 1 + currentIndex) + "/" + (uvIndex2 + 1 + currentUVIndex));
                                sw.Write(" " + (verticesIndex3 + 1 + currentIndex) + "/" + (uvIndex3 + 1 + currentUVIndex) + "\n");
                            }
                            //有法线没uv
                            else if (hasNormal && !hasUV)
                            {
                                sw.Write("f " + (verticesIndex1 + 1 + currentIndex) + "//" + (normalIndex1 + 1 + currentNormalIndex));
                                sw.Write(" " + (verticesIndex2 + 1 + currentIndex) + "//" + (normalIndex2 + 1 + currentNormalIndex));
                                sw.Write(" " + (verticesIndex3 + 1 + currentIndex) + "//" + (normalIndex3 + 1 + currentNormalIndex) + "\n");
                            }
                            //既没法线也没uv
                            else if (!hasNormal && !hasUV)
                            {
                                sw.Write("f " + (verticesIndex1 + 1 + currentIndex));
                                sw.Write(" " + (verticesIndex2 + 1 + currentIndex));
                                sw.Write(" " + (verticesIndex3 + 1 + currentIndex) + "\n");
                            }
                        }
                    }
                }
                sw.Write("\n");
                currentIndex += verticesDic.Count;
                currentNormalIndex += normalDic.Count;
                currentUVIndex += uvDic.Count;
                verticesDic.Clear();
                normalDic.Clear();
                uvDic.Clear();
            }
            else
            {
                for (int i = 0; i < vertices.Length; i++)
                {
                    Vector3 worldPos = trans.TransformPoint(vertices[i]);
                    //顶点镜像
                    if (exchangeCoordinate) worldPos.x *= -1;
                    sw.Write("v " + worldPos.x + " " + worldPos.y + " " + worldPos.z + "\n");
                }
                sw.Write("\n");
                if(normals.Length == vertices.Length)
                {
                    hasNormal = true;
                    for (int i = 0; i < normals.Length; i++)
                    {
                        Vector3 worldNormal = trans.TransformDirection(normals[i]);
                        //法线镜像
                        if (exchangeCoordinate) worldNormal.x *= -1;
                        sw.Write("vn " + worldNormal.x + " " + worldNormal.y + " " + worldNormal.z + "\n");
                    }
                    sw.Write("\n");
                }
                if(uvs.Length == vertices.Length)
                {
                    hasUV = true;
                    for (int i = 0; i < uvs.Length; i++)
                    {
                        sw.Write("vt " + uvs[i].x + " " + uvs[i].y + "\n");
                    }
                    sw.Write("\n");
                }
                

                for (int k = 0; k < mesh.subMeshCount; k++)
                {
                    if (mesh.subMeshCount == 1)
                    {
                        sw.Write("\ng " + mesh.name + "\n");
                    }
                    else
                    {
                        sw.Write("\ng " + mesh.name + "_" + k + "\n");
                    }
                    sw.Write("usemtl " + materials[k].name + "\n");
                    int[] tris = mesh.GetIndices(k);
                    for (int i = 0; i < tris.Length / 3; i++)
                    {
                        if (exchangeCoordinate)
                        {
                            if(hasNormal && hasUV)
                            {
                                sw.Write("f " + (tris[i * 3 + 1] + 1 + currentIndex) + "/" + (tris[i * 3 + 1] + 1 + currentUVIndex) + "/" + (tris[i * 3 + 1] + 1 + currentNormalIndex));
                                sw.Write(" " + (tris[i * 3] + 1 + currentIndex) + "/" + (tris[i * 3] + 1 + currentUVIndex) + "/" + (tris[i * 3] + 1 + currentNormalIndex));
                                sw.Write(" " + (tris[i * 3 + 2] + 1 + currentIndex) + "/" + (tris[i * 3 + 2] + 1 + currentUVIndex) + "/" + (tris[i * 3 + 2] + 1 + currentNormalIndex) + "\n");
                            }
                            else if(!hasNormal && hasUV)
                            {
                                sw.Write("f " + (tris[i * 3 + 1] + 1 + currentIndex) + "/" + (tris[i * 3 + 1] + 1 + currentUVIndex));
                                sw.Write(" " + (tris[i * 3] + 1 + currentIndex) + "/" + (tris[i * 3] + 1 + currentUVIndex));
                                sw.Write(" " + (tris[i * 3 + 2] + 1 + currentIndex) + "/" + (tris[i * 3 + 2] + 1 + currentUVIndex) + "\n");
                            }
                            else if(hasNormal && !hasUV)
                            {
                                sw.Write("f " + (tris[i * 3 + 1] + 1 + currentIndex) + "//" + (tris[i * 3 + 1] + 1 + currentNormalIndex));
                                sw.Write(" " + (tris[i * 3] + 1 + currentIndex) + "//" + (tris[i * 3] + 1 + currentNormalIndex));
                                sw.Write(" " + (tris[i * 3 + 2] + 1 + currentIndex) + "//" + (tris[i * 3 + 2] + 1 + currentNormalIndex) + "\n");
                            }
                            else if(!hasNormal && !hasUV)
                            {
                                sw.Write("f " + (tris[i * 3 + 1] + 1 + currentIndex));
                                sw.Write(" " + (tris[i * 3] + 1 + currentIndex));
                                sw.Write(" " + (tris[i * 3 + 2] + 1 + currentIndex) + "\n");
                            }
                        }
                        else
                        {
                            if (hasNormal && hasUV)
                            {
                                sw.Write("f " + (tris[i * 3] + 1 + currentIndex) + "/" + (tris[i * 3] + 1 + currentUVIndex) + "/" + (tris[i * 3] + 1 + currentNormalIndex));
                                sw.Write(" " + (tris[i * 3 + 1] + 1 + currentIndex) + "/" + (tris[i * 3 + 1] + 1 + currentUVIndex) + "/" + (tris[i * 3 + 1] + 1 + currentNormalIndex));
                                sw.Write(" " + (tris[i * 3 + 2] + 1 + currentIndex) + "/" + (tris[i * 3 + 2] + 1 + currentUVIndex) + "/" + (tris[i * 3 + 2] + 1 + currentNormalIndex) + "\n");
                            }
                            else if (!hasNormal && hasUV)
                            {
                                sw.Write("f " + (tris[i * 3] + 1 + currentIndex) + "/" + (tris[i * 3] + 1 + currentUVIndex));
                                sw.Write(" " + (tris[i * 3 + 1] + 1 + currentIndex) + "/" + (tris[i * 3 + 1] + 1 + currentUVIndex));
                                sw.Write(" " + (tris[i * 3 + 2] + 1 + currentIndex) + "/" + (tris[i * 3 + 2] + 1 + currentUVIndex) + "\n");
                            }
                            else if (hasNormal && !hasUV)
                            {
                                sw.Write("f " + (tris[i * 3] + 1 + currentIndex) + "//" + (tris[i * 3] + 1 + currentNormalIndex));
                                sw.Write(" " + (tris[i * 3 + 1] + 1 + currentIndex) + "//" + (tris[i * 3 + 1] + 1 + currentNormalIndex));
                                sw.Write(" " + (tris[i * 3 + 2] + 1 + currentIndex) + "//" + (tris[i * 3 + 2] + 1 + currentNormalIndex) + "\n");
                            }
                            else if (!hasNormal && !hasUV)
                            {
                                sw.Write("f " + (tris[i * 3] + 1 + currentIndex));
                                sw.Write(" " + (tris[i * 3 + 1] + 1 + currentIndex));
                                sw.Write(" " + (tris[i * 3 + 2] + 1 + currentIndex) + "\n");
                            }
                        }
                    }
                }
                currentIndex += vertices.Length;
                currentNormalIndex += normals.Length;
                currentUVIndex += uvs.Length;
            }
        }

        /// <summary>
        /// 写出材质信息,贴图仅在编辑器模式可用
        /// </summary>
        /// <param name="mat">要写出的材质</param>
        /// <param name="sb">StringBuilder,用于写出材质</param>
        /// <param name="outputDir">如果有贴图,需要将贴图复制到obj的生成路径上</param>
        private static void ExportMaterialToObj(Material mat, StringBuilder sb, string outputDir)
        {
            sb.Append("\nnewmtl " + mat.name + "\n");
            //漫反射颜色
            sb.Append("Kd " + mat.color.r + " " + mat.color.g + " " + mat.color.b + "\n");
            sb.Append("d " + mat.color.a + "\n"); //透明度
            if (mat.mainTexture)
            {
#if UNITY_EDITOR
                string path = UnityEditor.AssetDatabase.GetAssetPath(mat.mainTexture);
                sb.Append("map_Kd " + Path.GetFileName(path) + "\n");
                File.Copy(path, Path.Combine(outputDir, Path.GetFileName(path)), true);
#endif
            }
        }
        #endregion
    }
}
  • 6
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值