Obj格式解析以及在Unity3D下导入测试

Posted HONT blog

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Obj格式解析以及在Unity3D下导入测试相关的知识,希望对你有一定的参考价值。

目前基本实现了导入

 

四边面模型:

技术分享

 

全三角面模型(测试单一材质,所以重新分了下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
Cube(Obj)

 

常用类型:

#开头表示注释

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 = float.Parse(splitInfo[1]), Y = float.Parse(splitInfo[2]), Z = float.Parse(splitInfo[3]) });
                }
                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 "))
                {
                    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 = int.Parse(face1[0]), TextureIndex = int.Parse(face1[1]), NormalIndex = int.Parse(face1[2]) };
                    face.Points[1] = new FacePoint() { VertexIndex = int.Parse(face2[0]), TextureIndex = int.Parse(face2[1]), NormalIndex = int.Parse(face2[2]) };
                    face.Points[2] = new FacePoint() { VertexIndex = int.Parse(face3[0]), TextureIndex = int.Parse(face3[1]), NormalIndex = int.Parse(face3[2]) };
                    face.Points[3] = isQuad ? new FacePoint() { VertexIndex = int.Parse(face4[0]), TextureIndex = int.Parse(face4[1]), NormalIndex = int.Parse(face4[2]) } : default(FacePoint);
                    face.IsQuad = isQuad;

                    faceList.Add(face);
                }
            }

            VertexArr = vertexList.ToArray();
            VertexNormalArr = vertexNormalList.ToArray();
            VertexTextureArr = vertexTextureList.ToArray();
            FaceArr = faceList.ToArray();
        }
    }
}
ObjFormatAnalyzer

 

工厂类,目前只有输出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 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 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 Vector2(uv.X, uv.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 Vector2(uv.X, uv.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));

                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 Vector2(uv.X, uv.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));

                    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 Vector2(uv.X, uv.Y));
                }

                j += 3;
            }

            mesh.vertices = vertexList.ToArray();
            mesh.uv = uvList.ToArray();
            mesh.triangles = triangles;

            meshFilter.mesh = mesh;
            meshRenderer.material = new Material(Shader.Find("Standard"));

            return go;
        }
    }
}
ObjFormatAnalyzerFactory

 

 

测试使用脚本:

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;
    }
}

 

然后挂载运行即可。

目前可能还有点小问题,因为没有实现材质的绑定,所以只能支持单一材质。而且如果含有其他比如曲线之类的复杂数据可能出错。

以上是关于Obj格式解析以及在Unity3D下导入测试的主要内容,如果未能解决你的问题,请参考以下文章

unity怎么打开模型文件

如何导入 3D 场景(.obj 文件和 .mtl 文件)

解析波前 obj 文件格式

unity人物模型可以导入到ue5吗

如何把UNITY3D输出的.html格式导入到网站里?

unity3d怎么导入3d模型