Obj格式解析以及在Unity3D下导入测试
目前基本实现了导入,注意只能打开含有单个模型的obj文件
四边面模型:
全三角面模型(测试单一材质,自动分了下UV):
这里介绍下obj格式:
obj格式是waveFront推出的一种3D模型格式,可以存放静态模型以及一些诸如曲线的附加信息。
其格式以文本形式存放,所以解析起来比较方便,它的大体格式如下:
# WaveFront *.obj file (generated by CINEMA 4D) mtllib ./test.mtl v -100.00000000000000 -100.00000000000000 -100.00000000000000 v -100.00000000000000 100.00000000000000 -100.00000000000000 v 100.00000000000000 -100.00000000000000 -100.00000000000000 v 100.00000000000000 100.00000000000000 -100.00000000000000 v 100.00000000000000 -100.00000000000000 100.00000000000000 v 100.00000000000000 100.00000000000000 100.00000000000000 v -100.00000000000000 -100.00000000000000 100.00000000000000 v -100.00000000000000 100.00000000000000 100.00000000000000 # 8 vertices vn 0.00000000000000 0.00000000000000 -1.00000000000000 vn 1.00000000000000 0.00000000000000 0.00000000000000 vn 0.00000000000000 0.00000000000000 1.00000000000000 vn -1.00000000000000 0.00000000000000 0.00000000000000 vn 0.00000000000000 1.00000000000000 0.00000000000000 vn 0.00000000000000 -1.00000000000000 0.00000000000000 # 6 normals vt 0.00000000000000 -1.00000000000000 0.00000000000000 vt 0.00000000000000 -0.00000000000000 0.00000000000000 vt 1.00000000000000 -0.00000000000000 0.00000000000000 vt 1.00000000000000 -1.00000000000000 0.00000000000000 # 4 texture coordinates o Cube usemtl default f 1/4/1 2/3/1 4/2/1 3/1/1 f 3/4/2 4/3/2 6/2/2 5/1/2 f 5/4/3 6/3/3 8/2/3 7/1/3 f 7/4/4 8/3/4 2/2/4 1/1/4 f 2/4/5 8/3/5 6/2/5 4/1/5 f 7/4/6 1/3/6 3/2/6 5/1/6
常用类型:
#开头表示注释
v表示顶点
vn表示法线,可以共用法线
vt表示uv坐标
f表示一个面,比如参数1/4/1,表示顶点索引/UV索引/法线索引
下面贴一下工具类的代码:
using System; using System.Linq; using System.Collections; using System.Collections.Generic; namespace Hont { public class ObjFormatAnalyzer { public struct Vector { public float X; public float Y; public float Z; } public struct FacePoint { public int VertexIndex; public int TextureIndex; public int NormalIndex; } public struct Face { public FacePoint[] Points; public bool IsQuad; } public Vector[] VertexArr; public Vector[] VertexNormalArr; public Vector[] VertexTextureArr; public Face[] FaceArr; public void Analyze(string content) { content = content.Replace('\r', ' ').Replace('\t', ' '); var lines = content.Split('\n'); var vertexList = new List<Vector>(); var vertexNormalList = new List<Vector>(); var vertexTextureList = new List<Vector>(); var faceList = new List<Face>(); for (int i = 0; i < lines.Length; i++) { var currentLine = lines[i]; if (currentLine.Contains("#") || currentLine.Length == 0) { continue; } if (currentLine.Contains("v ")) { var splitInfo = currentLine.Split(" ".ToCharArray(), StringSplitOptions.RemoveEmptyEntries); vertexList.Add(new Vector() { X = float.Parse(splitInfo[1]), Y = float.Parse(splitInfo[2]), Z = float.Parse(splitInfo[3]) }); } else if (currentLine.Contains("vt ")) { var splitInfo = currentLine.Split(" ".ToCharArray(), StringSplitOptions.RemoveEmptyEntries); vertexTextureList.Add(new Vector() { X = splitInfo.Length > 1 ? float.Parse(splitInfo[1]) : 0, Y = splitInfo.Length > 2 ? float.Parse(splitInfo[2]) : 0, Z = splitInfo.Length > 3 ? float.Parse(splitInfo[3]) : 0 }); } else if (currentLine.Contains("vn ")) { var splitInfo = currentLine.Split(" ".ToCharArray(), StringSplitOptions.RemoveEmptyEntries); vertexNormalList.Add(new Vector() { X = float.Parse(splitInfo[1]), Y = float.Parse(splitInfo[2]), Z = float.Parse(splitInfo[3]) }); } else if (currentLine.Contains("f ")) { Func<string, int> tryParse = (inArg) => { var outValue = -1; return int.TryParse(inArg, out outValue) ? outValue : 0; }; var splitInfo = currentLine.Split(" ".ToCharArray(), StringSplitOptions.RemoveEmptyEntries); var isQuad = splitInfo.Length > 4; var face1 = splitInfo[1].Split('/'); var face2 = splitInfo[2].Split('/'); var face3 = splitInfo[3].Split('/'); var face4 = isQuad ? splitInfo[4].Split('/') : null; var face = new Face(); face.Points = new FacePoint[4]; face.Points[0] = new FacePoint() { VertexIndex = tryParse(face1[0]), TextureIndex = tryParse(face1[1]), NormalIndex = tryParse(face1[2]) }; face.Points[1] = new FacePoint() { VertexIndex = tryParse(face2[0]), TextureIndex = tryParse(face2[1]), NormalIndex = tryParse(face2[2]) }; face.Points[2] = new FacePoint() { VertexIndex = tryParse(face3[0]), TextureIndex = tryParse(face3[1]), NormalIndex = tryParse(face3[2]) }; face.Points[3] = isQuad ? new FacePoint() { VertexIndex = tryParse(face4[0]), TextureIndex = tryParse(face4[1]), NormalIndex = tryParse(face4[2]) } : default(FacePoint); face.IsQuad = isQuad; faceList.Add(face); } } VertexArr = vertexList.ToArray(); VertexNormalArr = vertexNormalList.ToArray(); VertexTextureArr = vertexTextureList.ToArray(); FaceArr = faceList.ToArray(); } } }
工厂类ObjFormatAnalyzerFactory.cs,目前只有输出GameObject:
using UnityEngine; using System.IO; using System.Linq; using System.Collections; using System.Collections.Generic; namespace Hont { public static class ObjFormatAnalyzerFactory { public static GameObject AnalyzeToGameObject(string objFilePath) { if (!File.Exists(objFilePath)) return null; var objFormatAnalyzer = new ObjFormatAnalyzer(); objFormatAnalyzer.Analyze(File.ReadAllText(objFilePath)); var go = new GameObject(); var meshRenderer = go.AddComponent<MeshRenderer>(); var meshFilter = go.AddComponent<MeshFilter>(); var mesh = new Mesh(); var sourceVertexArr = objFormatAnalyzer.VertexArr; var sourceUVArr = objFormatAnalyzer.VertexTextureArr; var sourceNormArr = objFormatAnalyzer.VertexNormalArr; var faceArr = objFormatAnalyzer.FaceArr; var notQuadFaceArr = objFormatAnalyzer.FaceArr.Where(m => !m.IsQuad).ToArray(); var quadFaceArr = objFormatAnalyzer.FaceArr.Where(m => m.IsQuad).ToArray(); var vertexList = new List<Vector3>(); var uvList = new List<Vector2>(); var normList = new List<Vector3>(); var triangles = new int[notQuadFaceArr.Length * 3 + quadFaceArr.Length * 6]; for (int i = 0, j = 0; i < faceArr.Length; i++) { var currentFace = faceArr[i]; triangles[j] = j; triangles[j + 1] = j + 1; triangles[j + 2] = j + 2; var vec = sourceVertexArr[currentFace.Points[0].VertexIndex - 1]; vertexList.Add(new Vector3(vec.X, vec.Y, vec.Z)); var uv = sourceUVArr[currentFace.Points[0].TextureIndex - 1]; uvList.Add(new Vector3(uv.X, uv.Y, uv.Z)); var norm = sourceNormArr[currentFace.Points[0].NormalIndex - 1]; normList.Add(new Vector2(norm.X, norm.Y)); vec = sourceVertexArr[currentFace.Points[1].VertexIndex - 1]; vertexList.Add(new Vector3(vec.X, vec.Y, vec.Z)); uv = sourceUVArr[currentFace.Points[1].TextureIndex - 1]; uvList.Add(new Vector3(uv.X, uv.Y, uv.Z)); norm = sourceNormArr[currentFace.Points[1].NormalIndex - 1]; normList.Add(new Vector2(norm.X, norm.Y)); vec = sourceVertexArr[currentFace.Points[2].VertexIndex - 1]; vertexList.Add(new Vector3(vec.X, vec.Y, vec.Z)); uv = sourceUVArr[currentFace.Points[2].TextureIndex - 1]; uvList.Add(new Vector2(uv.X, uv.Y)); norm = sourceNormArr[currentFace.Points[2].NormalIndex - 1]; normList.Add(new Vector2(norm.X, norm.Y)); if (currentFace.IsQuad) { triangles[j + 3] = j + 3; triangles[j + 4] = j + 4; triangles[j + 5] = j + 5; j += 3; vec = sourceVertexArr[currentFace.Points[0].VertexIndex - 1]; vertexList.Add(new Vector3(vec.X, vec.Y, vec.Z)); uv = sourceUVArr[currentFace.Points[0].TextureIndex - 1]; uvList.Add(new Vector3(uv.X, uv.Y, uv.Z)); norm = sourceNormArr[currentFace.Points[0].NormalIndex - 1]; normList.Add(new Vector2(norm.X, norm.Y)); vec = sourceVertexArr[currentFace.Points[2].VertexIndex - 1]; vertexList.Add(new Vector3(vec.X, vec.Y, vec.Z)); uv = sourceUVArr[currentFace.Points[2].TextureIndex - 1]; uvList.Add(new Vector3(uv.X, uv.Y, uv.Z)); norm = sourceNormArr[currentFace.Points[2].NormalIndex - 1]; normList.Add(new Vector2(norm.X, norm.Y)); vec = sourceVertexArr[currentFace.Points[3].VertexIndex - 1]; vertexList.Add(new Vector3(vec.X, vec.Y, vec.Z)); uv = sourceUVArr[currentFace.Points[3].TextureIndex - 1]; uvList.Add(new Vector3(uv.X, uv.Y, uv.Z)); norm = sourceNormArr[currentFace.Points[3].NormalIndex - 1]; normList.Add(new Vector2(norm.X, norm.Y)); } j += 3; } mesh.vertices = vertexList.ToArray(); mesh.uv = uvList.ToArray(); mesh.normals = normList.ToArray();//加入法线信息传递 mesh.triangles = triangles; mesh.RecalculateBounds(); meshFilter.mesh = mesh; meshRenderer.material = new Material(Shader.Find("Standard")); //自行根据渲染管线修改,用默认管线则不用修改 return go; } } }
测试使用脚本:
public class ObjFormatAnalyzerTest : MonoBehaviour { void Start() { var go = ObjFormatAnalyzerFactory.AnalyzeToGameObject(@"D:\TestObj\centaur.obj"); WWW www = new WWW("file:/D:/TestObj/texture.jpg"); while (!www.isDone) { } go.GetComponent<MeshRenderer>().material.mainTexture = www.texture; } }
然后挂载运行即可。
目前可能还有点小问题,因为没有实现材质的绑定,所以只能支持单一材质。