Unity读取并加载大模型(OBJ格式)

时间:2024-04-04 13:39:48

本篇博客介绍如何在Unity中加载3Dmax导出的obj模型文件,为了简化流程,我们除了顶点和三角面之外只选择导出法线信息。

Unity读取并加载大模型(OBJ格式)          Unity读取并加载大模型(OBJ格式)

导出之后我们得到这样的一个文件

Unity读取并加载大模型(OBJ格式)      Unity读取并加载大模型(OBJ格式)          Unity读取并加载大模型(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);
        }
    }

如此便可以加载比较大的模型。