unity2018.4.2f1

vs2017

最近项目需求,需要实现动态读物外部obj模型,并加载到场景中,研究了好几天,终于实现了,在此做个记录。

1、首先随便找个.obj模型,带贴图,我的资源截图如下:

.mtl文件是负责记录模型与贴图的对应关系

obj问价与mtl文件均可以用记事本打开,查看内部数据

obj文件截图:

mtllib Tifa.mtl   记录当前obj文件对应的mtl文件的名称(一个mtl文件可以包含多个材质)

v:模型顶点数据

vt:模型顶点纹理坐标数据

vn:模型顶点法线数据

usemtl diss_00.png 表示当前模型分组使用的材质,每一个模型分组以usemtl 开始(一个模型存在有多个子物体的情况)

比如我这个:

mtl文件截图:

newmtl diss_00.png:表示当前的材质名称

illum:照明度(0-10)

kd:当前材质的散射光

ka:当前材质的环境光

ks:当前材质的镜面光

ke:当前材质的散射光

Ns:材质的光亮度

map_Kd:当前材质对应的贴图名称

2、根据模型文本文件分析,编写模型数据结构,直接上代码

ObjPart:obj文件数据结构

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class ObjPart
{/// <summary>/// 材质名称/// </summary>public string strMatName;/// <summary>/// UV坐标数组/// </summary>public List<Vector2> listUV; //vt/// <summary>/// 法线数组/// </summary>public List<Vector3> listNormal;//vn/// <summary>/// 切线数组/// </summary>public List<Vector4> listTangent;/// <summary>/// 顶点数组/// </summary>public List<Vector3> listVertex; //v/// <summary>/// 面数组 面索引/// </summary>public List<int> listTriangle;public ObjPart(){strMatName = "";listUV = new List<Vector2>();listNormal = new List<Vector3>();listVertex = new List<Vector3>();listTriangle = new List<int>();listTangent = new List<Vector4>();}
}

ObjMatItem:mtl文件数据结构

using System.Collections;
using System.Collections.Generic;
using UnityEngine;//mtl数据结构(记录的是材质对应的贴图)
public class ObjMatItem
{/// <summary>/// 材质名称/// </summary>public string strMatName;public int illum;public Vector3 Kd;public Vector3 Ka;public Vector3 Tf;public Vector2 widthHeight;public string map_Kd;public float Ni;public ObjMatItem(){strMatName = "";illum = -1;Kd = new Vector3(0.0f, 0.0f, 0.0f);Ka = new Vector3(0.0f, 0.0f, 0.0f);Tf = new Vector3(0.0f, 0.0f, 0.0f);map_Kd = "";Ni = 1.0f;widthHeight.x = 0.0f;widthHeight.y = 0.0f;}
}

ObjModel:模型数据结构

using System.Collections;
using System.Collections.Generic;
using UnityEngine;/// <summary>
/// 完整模型=网格+材质
/// </summary>
public class ObjModel
{public List<ObjPart> objParts;   //网格public List<ObjMatItem> ObjMats; //材质
}

3、从文本文件中加载obj数据以及mtl数据

ObjMesh:负责从.obj文件中加载数据

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;/// <summary>
/// 负责从.obj文件中加载数据
/// </summary>
public class ObjMesh
{/// <summary>/// UV坐标列表/// </summary>private List<Vector3> uvArrayList;/// <summary>/// 法线列表/// </summary>private List<Vector3> normalArrayList;/// <summary>/// 顶点列表/// </summary>private List<Vector3> vertexArrayList;public List<ObjPart> listObjParts;public List<ObjMatItem> listObjMats;public string _strMatPath { get; set; }public string _strObjPath { get; set; }public string _strObjName { get; set; }/// <summary>/// 构造函数    /// </summary>public ObjMesh(){//初始化列表uvArrayList = new List<Vector3>();normalArrayList = new List<Vector3>();vertexArrayList = new List<Vector3>();listObjParts = new List<ObjPart>();listObjMats = new List<ObjMatItem>();_strMatPath = _strObjName = "";}/// <summary>/// 从一个文本化后的.obj文件中加载模型/// </summary>public ObjMesh LoadFromObj(string strObjPath){_strObjPath = strObjPath;_strObjName = strObjPath;_strObjName = _strObjName.Replace("/", "");//读取内容if (!File.Exists(strObjPath)) return null;StreamReader reader = new StreamReader(strObjPath, System.Text.Encoding.Default);string objText = reader.ReadToEnd();reader.Close();if (objText.Length <= 0)return null;//v这一行在3dsMax中导出的.obj文件//  前面是两个空格后面是一个空格objText = objText.Replace("  ", " ");//将文本化后的obj文件内容按行分割string[] allLines = objText.Split('\n');foreach (string line in allLines){//将每一行按空格分割char[] charsToTrim = { ' ' };string[] chars = line.TrimEnd('\r').TrimStart(' ').Split(charsToTrim, StringSplitOptions.RemoveEmptyEntries);if (chars.Length <= 0){continue;}//根据第一个字符来判断数据的类型switch (chars[0]){case "mtllib":_strMatPath = _strObjPath.Substring(0, _strObjPath.LastIndexOf('/') + 1) + chars[1];break;case "v"://处理顶点this.vertexArrayList.Add(new Vector3(-(ConvertToFloat(chars[1])),ConvertToFloat(chars[2]),ConvertToFloat(chars[3])));break;case "vn"://处理法线this.normalArrayList.Add(new Vector3(-ConvertToFloat(chars[1]),ConvertToFloat(chars[2]),ConvertToFloat(chars[3])));break;case "vt"://处理UVthis.uvArrayList.Add(new Vector3(ConvertToFloat(chars[1]),ConvertToFloat(chars[2])));break;case "usemtl":ObjPart objPart = new ObjPart();objPart.strMatName = chars[1];//材质名称listObjParts.Add(objPart);break;case "f"://处理面GetTriangleList(chars);break;}}//获取mtl文件路径// string mtlFilePath = strObjPath.Replace(".obj", ".mtl");if (_strMatPath != ""){LoadMat(_strMatPath);}return this;}private void LoadMat(string strmtlPath){//从mtl文件中加载材质listObjMats = ObjMaterial.Instance.LoadFormMtl(strmtlPath);}/// <summary>/// 获取面列表./// </summary>/// <param name="chars">Chars.</param>/// private List<Vector3> indexVectorList = new List<Vector3>();private Vector3 indexVector = new Vector3(0, 0);private void GetTriangleList(string[] chars){indexVectorList.Clear();for (int i = 1; i < chars.Length; ++i){//将每一行按照空格分割后从第一个元素开始//按照/继续分割可依次获得顶点索引、法线索引和UV索引string[] indexs = chars[i].Split('/');Vector3 vertex = (Vector3)vertexArrayList[ConvertToInt(indexs[0]) - 1];listObjParts[listObjParts.Count - 1].listVertex.Add(vertex);indexVector = new Vector3(0, 0);//UV索引if (indexs.Length > 1){if (indexs[1] != "")indexVector.y = ConvertToInt(indexs[1]);}//法线索引if (indexs.Length > 2){if (indexs[2] != "")indexVector.z = ConvertToInt(indexs[2]);}//给UV数组赋值if (uvArrayList.Count > 0 && indexVector.y > 0.01){Vector3 tVec = (Vector3)uvArrayList[(int)indexVector.y - 1];listObjParts[listObjParts.Count - 1].listUV.Add(new Vector2(tVec.x, tVec.y));}//给法线数组赋值if (normalArrayList.Count > 0 && indexVector.z > 0.01){Vector3 nVec = (Vector3)normalArrayList[(int)indexVector.z - 1];listObjParts[listObjParts.Count - 1].listNormal.Add(nVec);}//将索引向量加入列表中indexVectorList.Add(indexVector);}//面索引int nCount = listObjParts[listObjParts.Count - 1].listVertex.Count - indexVectorList.Count; //nCount==0 3 6 9 indexVectorList.Count=3 三角形面//Debug.Log(indexVectorList.Count);for (int j = 1; j < indexVectorList.Count - 1; ++j){//按照0,1,2这样的方式来组成面          listObjParts[listObjParts.Count - 1].listTriangle.Add(nCount);listObjParts[listObjParts.Count - 1].listTriangle.Add(nCount + j);listObjParts[listObjParts.Count - 1].listTriangle.Add(nCount + j + 1);}}/// <summary>/// 将一个字符串转换为浮点类型/// </summary>/// <param name="s">待转换的字符串</param>/// <returns></returns>private float ConvertToFloat(string s){//return (float)System.Convert.ToDouble(s,CultureInfo.InvariantCulture);float fValue = 0.0f;try{fValue = (float)Convert.ToDouble(s);}catch (Exception ex){Debug.LogError("数据[" + s + "]转换失败! " + ex.Message);}return fValue;}/// <summary>/// 将一个字符串转化为整型 /// </summary>/// <returns>待转换的字符串</returns>/// <param name="s"></param>private int ConvertToInt(string s){//return System.Convert.ToInt32(s, CultureInfo.InvariantCulture);int nValue = 0;try{nValue = Convert.ToInt32(s);}catch (Exception ex){Debug.LogError("数据[" + s + "]转换失败! " + ex.Message);}return nValue;}
}

ObjMaterial:负责从.mtl中加载数据

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;/// <summary>
/// 负责从.mtl中加载数据
/// </summary>
public class ObjMaterial
{/// <summary>/// 当前实例/// </summary>private static ObjMaterial instance=new ObjMaterial();public static ObjMaterial Instance{get{if (instance == null)instance = new ObjMaterial();//GameObject.FindObjectOfType<ObjMaterial>(); return instance;}}/// <summary>/// 从一个文本化后的mtl文件加载一组材质/// </summary>/// <param name="mtlText">文本化的mtl文件</param>/// <param name="texturePath">贴图文件夹路径</param>public List<ObjMatItem> LoadFormMtl(string strMtlText){List<ObjMatItem> listObjMats = new List<ObjMatItem>();DirectoryInfo mtlParent=Directory.GetParent(Settings.ObjPath);if (!File.Exists(mtlParent + "\\" + strMtlText)) return null;Stream mtlStream = new FileStream(mtlParent+"\\"+ strMtlText,FileMode.Open);//mtl文件与obj文件处于同一级目录下StreamReader reader = new StreamReader(mtlStream);string mtlText = reader.ReadToEnd();reader.Close();if (mtlText == "")return listObjMats;//将文本化后的内容按行分割string[] allLines = mtlText.Split('\n');foreach (string line in allLines){//按照空格分割每一行的内容string[] chars = line.TrimEnd('\r').TrimStart(' ').Split(' ');switch (chars[0]){case "newmtl"://处理材质名ObjMatItem matItem = new ObjMatItem();matItem.strMatName = chars[1];listObjMats.Add(matItem);//根据贴图创建材质球break;case "Ka":listObjMats[listObjMats.Count - 1].Ka = new Vector3(ConvertToFloat(chars[1]),ConvertToFloat(chars[2]),ConvertToFloat(chars[3]));break;case "Kd"://处理漫反射listObjMats[listObjMats.Count - 1].Kd = new Vector3(ConvertToFloat(chars[1]),ConvertToFloat(chars[2]),ConvertToFloat(chars[3]));break;case "Ks"://暂时仅考虑漫反射break;case "Ke"://Todobreak;case "Tf"://处理漫反射listObjMats[listObjMats.Count - 1].Tf = new Vector3(ConvertToFloat(chars[1]),ConvertToFloat(chars[2]),ConvertToFloat(chars[3]));break;case "Ni":listObjMats[listObjMats.Count - 1].Ni = ConvertToFloat(chars[1]);break;case "e"://Todobreak;case "illum":listObjMats[listObjMats.Count - 1].illum = Convert.ToInt32(chars[1]);break;case "map_Ka"://暂时仅考虑漫反射break;case "map_Kd"://处理漫反射贴图string textureName = chars[1].Substring(chars[1].LastIndexOf("\\") + 1, chars[1].Length - chars[1].LastIndexOf("\\") - 1);listObjMats[listObjMats.Count - 1].map_Kd = textureName;break;case "map_Ks"://暂时仅考虑漫反射break;}}GameObject.Find("Canvas").GetComponent<LoadModel>().CreatMaterial(listObjMats);return listObjMats;}/// <summary>/// 将一个字符串转换为浮点类型/// </summary>/// <param name="s">待转换的字符串</param>/// <returns></returns>private float ConvertToFloat(string s){return System.Convert.ToSingle(s);}}

Settings:记录数据

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class Settings
{public static string ObjPath=null;           //模型路径public static Dictionary<string, Material> ModelMaterialList = new Dictionary<string, Material>();//材质球存储 key:材质名称 value:材质球
}

4、开始使用获得的文件数据,在场景中加载模型

LoadModel:创建模型(该脚本挂载到canvas上,由按钮触发LoadModelEvent函数 开始创建模型)

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;
using System;
using System.IO;public class LoadModel : MonoBehaviour
{private List<ObjMesh> listObjModel = new List<ObjMesh>();private List<ObjMatItem> listObjMats = new List<ObjMatItem>();private List<ObjPart> listObjParts = new List<ObjPart>();private GameObject goParent;public GameObject _gameObj;    //模型父节点private bool isDone = false;    //true:材质球全部创建完成后 给模型贴材质//按钮事件处理public void LoadModelEvent(){string strObjPath = "C:\\Users\\123\\Desktop\\obj__001\\123\\飞马\\飞马 .obj";Settings.ObjPath = strObjPath;ObjMesh objInstace = new ObjMesh();objInstace = objInstace.LoadFromObj(strObjPath);if (objInstace == null) return;listObjModel.Add(objInstace);listObjParts = objInstace.listObjParts;//模型Debug.Log("Parts:" + listObjParts.Count);listObjMats = objInstace.listObjMats;  //材质if(listObjMats!=null)Debug.Log("Mats:" + listObjMats.Count);string strGameName = strObjPath;strGameName = strGameName.Replace("/", "");string[] names = strGameName.Split('\\');strGameName = names[names.Length - 1];goParent = new GameObject(strGameName); //模型资源名称StartCoroutine(WaitLoadMaterialTexture());}IEnumerator WaitLoadMaterialTexture(){while (!isDone&& listObjMats!=null) //等待材质创建完成{yield return null;}//计算网格int i = 0;foreach (ObjPart part in listObjParts){++i;Mesh mesh = new Mesh();mesh.vertices = part.listVertex.ToArray();//顶点//mesh.triangles = part.listTriangle.ToArray();mesh.triangles= ResetTriangles(part.listTriangle.ToArray());//修改三角形面 翻转三角形面if (part.listUV.Count > 0){mesh.uv = part.listUV.ToArray();}if (part.listNormal.Count > 0){mesh.normals = part.listNormal.ToArray();}mesh.tangents = part.listTangent.ToArray(); //切线mesh.RecalculateBounds();// mesh.RecalculateNormals(); //法线//生成物体GameObject go = new GameObject(part.strMatName + i.ToString());// ==go.AddComponent<ObjDestroy>();MeshFilter meshFilter = go.AddComponent<MeshFilter>();meshFilter.mesh = mesh;//parts里面存储的有材质名称 根据材质名称生成材质球MeshRenderer render = go.AddComponent<MeshRenderer>();RenderAddMaterials(go, part);go.transform.SetParent(goParent.transform);}goParent.transform.SetParent(_gameObj.transform);}//翻转法线private Vector3[] FlipNormals(Vector3[] normals){Vector3[] res=new Vector3[normals.Length];for (int i = 0; i < normals.Length; i++){normals[i] = -normals[i];}return normals;}//翻转三角形面片private int[] ResetTriangles(int[] triangles){for (int i = 0; i < triangles.Length; i+=3){int t = triangles[i];triangles[i] = triangles[i + 2];triangles[i + 2] = t;}return triangles;}private void RenderAddMaterials(GameObject go, ObjPart part){//给模型添加材质球if (Settings.ModelMaterialList.ContainsKey(part.strMatName)){go.GetComponent<MeshRenderer>().material = Settings.ModelMaterialList[part.strMatName];}}//创建所有需要的材质球(mtl文件与obj文件读取完成后,调用)public void CreatMaterial(List<ObjMatItem> matList){int nowIndex = 0;foreach (ObjMatItem item in matList){++nowIndex;string textureName = item.map_Kd;//贴图名称string texturePath = Directory.GetParent(Settings.ObjPath).FullName + "\\" + textureName;StartCoroutine(LoadTexture(item, nowIndex, matList.Count));}}/// <summary>/// 加载贴图   /// </summary>IEnumerator LoadTexture(ObjMatItem item, int nowIndex, int maxIndex){string texturePath = Directory.GetParent(Settings.ObjPath).FullName + "\\" + item.map_Kd;  //图片路径string matName = item.strMatName;//材质名称if (!File.Exists(texturePath)) { yield break; } //终止协成UnityWebRequest request = new UnityWebRequest(texturePath);DownloadHandlerTexture tex = new DownloadHandlerTexture(true);request.downloadHandler = tex;yield return request.SendWebRequest();if (!request.isNetworkError){Texture2D texture = tex.texture;       //下载的东西不用时要清空 否则内存占用会越来越多TODOMaterial material = new Material(Shader.Find("Standard"));material.mainTexture = texture;Settings.ModelMaterialList.Add(matName, material);if (nowIndex == maxIndex){isDone = true;}}else{Debug.LogError("load texture failed!");}}
}

5、实现过程中遇到的困难

首先脚本数据结构以及文件读取都是从网上找的,然后结合自己的实际需求稍作修改。

比如mtl文件、obj文件以及贴图都在同一路径,所以读取路径拼接方式全部都改了;

模型材质加载是自己加上去的,根据mtl文件创建对应的材质球,存储起来,模型的子节点根据材质名称选择贴哪个材质;

其中最大的问题是模型网格面反了,导致贴上贴图后模型看起来是透明的,问了老大,说可能是法线反了,改了没效果,就百度下mesh的知识,看了下网格加载部分的代码,注意到加载顶点以及法线时vector3的第一个数据取反了,如下图所示:

所以面反了,根据这个将mesh中的triangles三角形面翻转,最后完美实现加载。

有想法的可以留言,时间长了我估计有可能记不清了,就不能都回复了

unity动态加载obj文件相关推荐

  1. unity动态加载.obj文件相关

    .obj文件加载相关 QA: 要点 .obj文件结构 .mtl文件结构 材质匹配问题 动态加载obj文件 unity资源商城插件:Runtime Obj Importer QA: q: 如何动态加载o ...

  2. Unity动态加载3D模型

    Unity动态加载3D模型 在Unity中创建游戏对象的方法有 3 种: 第一种是将物体模型资源由 Project 视图直接拖曳到 Hierarchy 面板中: 第二种是在 Unity 3D 菜单 G ...

  3. ExtJS4.x动态加载js文件

    动态加载js文件是ext4.x的一个新特性,可以有效的减少浏览器的压力,提高渲染速度.如动态加载自定义组件 1.在js/extjs/ux目录下,建立自定义组件的js文件. 2.编写MyWindow.j ...

  4. JavaScript动态加载js文件

    /********************************************************************** JavaScript动态加载js文件* 说明:* 之前没 ...

  5. linux 下创建并动态加载.so 文件

    最简单的生成, 动态加载.so 文件的例子 //test.cpp #ifndef _TEST_H    #define _TEST_H       #include <iostream> ...

  6. php动态页面加载慢,通过动态加载JS文件提升网站访问速度

    相对与HTML,CSS,javascript是最影响浏览器性能的,因为浏览器在遇到<script>标签时,必须等待js代码下载和执行完毕后再执行后面的内容,因此当页面中js文件过多时,网站 ...

  7. php 动态加载JavaScript文件或者css文件

    1. 动态加载JS文件 第一种方法: test.php <script language="JavaScript" src="test6.php?str=i lov ...

  8. 动态加载JavaScript文件

    目录 配置 无脑方法! 逆袭之道! 一块蛋糕! 结论 源代码 JavaScript文件的动态加载是您必须拥有的非常有用的工具之一.它允许您通过将阻止脚本从加载过程中移出(通常称为"延迟加载& ...

  9. 动态加载js文件是异步的

    动态加载js文件是异步的. 今天调试一个错误,一个js方法各种调不到. 原因是因为所调方法的js文件是动态加载进来的. <script type="text/javascript&qu ...

最新文章

  1. Oracle:ORA-12560和ORA-01031
  2. uniapp怎么引入css_uniapp - css样式设置scoped
  3. Java的重写和重载机制
  4. MongoDB 2.6.4 主从同步
  5. python 的库如何开发_一篇文章入门Python生态系统
  6. python判断语句的复杂度_Python内置方法的时间复杂度(转)
  7. samba在企业网应用
  8. 静态文件之static+url控制系统(萌新笔记)
  9. iOS开发学习之NSFetchedResultsController
  10. 【离散数学】集合论 第三章 集合与关系(8) 关系的闭包运算
  11. C语言改变运行界面的颜色以及清屏功能
  12. ArcGIS无插件加载(无偏移)天地图
  13. python解析mht文件_php解析mht文件转换成html的实例
  14. vue 中使用 vue-amap(高德地图) 【'AMapUI' is not defined 】
  15. 感悟员工和公司之间的情感关系
  16. 数据分析您需要一个现代化的数据仓库
  17. 一篇文章带你读懂批处理命令
  18. 夜明け前より瑠璃色な 攻略
  19. Matlab小课堂3
  20. Java 求最大公约数

热门文章

  1. 这几年爆火的智能物联网(AIoT),到底前景如何?
  2. jenkins调查总结
  3. 地面搜索matlab算法,数学建模中的地面搜索问题
  4. windows服务器ssl证书安装及配置
  5. 人人 突破 权限 相册 查看
  6. mysql outer join报错_千与千寻-MySQL联结join
  7. 分享75个JS特效倒计时,总有一款适合您
  8. Microsoft OLE DB Provider for ODBC Drivers 错误 '80004005'解决方案
  9. OkHttp实现远程调用
  10. 最有效的赚钱方法,只有100元如何赚到10万?