本篇博客介绍如何在Unity中加载3Dmax导出的obj模型文件,为了简化流程,我们除了顶点和三角面之外只选择导出法线信息。
导出之后我们得到这样的一个文件
其中v代表顶点信息,vn代表法线信息,f代表三角索引信息。我们在c#脚本中分别读取这些信息并绘制网格。(ReadInfoObj方法放在多线程中可以避免读取文件时Unity卡顿,路径改为绝对路径)
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using UnityEngine;
public class loadObj : MonoBehaviour
{
private List<Vector3> TVerts = new List<Vector3>();
private List<Vector3> TNormals = new List<Vector3>();
private List<int> TTriangles = new List<int>();
private List<int> TNormalIndexs = new List<int>();
private List<Mesh> meshs = new List<Mesh>();
private bool readFinsh;
void Start()
{
ReadInfoObj(Application.streamingAssetsPath + "/1.obj");
}
private void Update()
{
if (readFinsh)
{
readFinsh = false;
try
{
//ClipMesh();
ConstructMesh();
CreatGameObject();
}
catch (Exception e)
{
Debug.Log(e);
}
}
}
void ReadInfoObj(string path)
{
readFinsh = false;
if (!File.Exists(path)) return;
string[] lines = File.ReadAllLines(path);
foreach (string line in lines)
{
//解析内嵌材质信息
string[] unit = System.Text.RegularExpressions.Regex.Split(line, "\\s+", System.Text.RegularExpressions.RegexOptions.IgnoreCase);
switch (unit[0])
{
case "v":
TVerts.Add(new Vector3(float.Parse(unit[1]), float.Parse(unit[2]), float.Parse(unit[3])));
break;
case "vn":
TNormals.Add(new Vector3(float.Parse(unit[1]), float.Parse(unit[2]), float.Parse(unit[3])));
break;
case "f":
TTriangles.Add(int.Parse(unit[1].Split('/')[0]) - 1);
TNormalIndexs.Add(int.Parse(unit[1].Split('/')[2]) - 1);
TTriangles.Add(int.Parse(unit[2].Split('/')[0]) - 1);
TNormalIndexs.Add(int.Parse(unit[2].Split('/')[2]) - 1);
TTriangles.Add(int.Parse(unit[3].Split('/')[0]) - 1);
TNormalIndexs.Add(int.Parse(unit[3].Split('/')[2]) - 1);
break;
default:
break;
}
}
readFinsh = true;
}
void ConstructMesh() {
List<int> triangles = new List<int>(TTriangles);
List<Vector3> verts = new List<Vector3>(TVerts);
List<Vector3> normals = new List<Vector3>(TNormals);
Mesh mesh = new Mesh();
mesh.name = "obj";
mesh.vertices = verts.ToArray();
mesh.normals = normals.ToArray();
mesh.triangles = triangles.ToArray();
meshs.Add(mesh);
}
void CreatGameObject()
{
Material mat = new Material(Shader.Find("Standard"));
for (int i = 0; i < meshs.Count; i++)
{
GameObject obj = new GameObject("obj " + i.ToString());
obj.transform.SetParent(transform, false);
MeshFilter meshFilter = obj.AddComponent<MeshFilter>();
meshFilter.mesh = meshs[i];
MeshRenderer meshRender = obj.AddComponent<MeshRenderer>();
meshRender.material = mat;
}
}
}
当模型顶点数大于65000时上面的方法会报错,需要我们在代码中分割一下模型,由于原来的三角索引是无序的,无法作为分割模型的标准,因此需要重新排序。同时相应的调整法线和顶点信息。
void ClipMesh()
{
int fragNum = TTriangles.Count;
int meshNum = fragNum % 60000 > 0 ? fragNum / 60000 + 1 : fragNum / 60000;
List<int> triangles = new List<int>();
List<Vector3> verts = new List<Vector3>();
List<Vector3> normals = new List<Vector3>();
for (int i = 0; i < meshNum; i++)
{
triangles.Clear();
verts.Clear();
normals.Clear();
int num = i == (meshNum - 1) ? (fragNum) % 60000 : 60000;
for (int j = 0; j < num; j++)
{
int n = 60000 * i + j;
triangles.Add(j);
if (!verts.Contains(TVerts[TTriangles[n]]))
{
}
verts.Add(TVerts[TTriangles[n]]);
normals.Add(TNormals[TNormalIndexs[n]]);
}
Mesh mesh = new Mesh();
mesh.name = "obj" + i.ToString();
mesh.vertices = verts.ToArray();
mesh.normals = normals.ToArray();
mesh.triangles = triangles.ToArray();
meshs.Add(mesh);
}
}
如此便可以加载比较大的模型。