1、OBJFile类
用于存储读取obj
文件的g、v、f
g
对应gameobject.name
v
对应mesh.vertices
f
对应mesh.triangles
注意unity
对于mesh
点索引的起始点是0
不是1
using System.Collections.Generic;
using UnityEngine;
public class OBJFile
{
public string GroupName { get; set; } = ""; //g
public List<Vector3> PointList { get; set; } = new List<Vector3>(); //v
public List<int> FaceList { get; set; } = new List<int>(); //f
}
2、AnalyseFile函数
用于读取obj
文件的文本内容并取出其中的g、v、f
存入OBJFile
类
fileText
对应obj
文件的文本内容
internal static OBJFile AnalyseFile(string fileText)
{
//创建obj类
OBJFile objFile = new OBJFile();
//按行切割obj文本
string[] fileLines = fileText.Split('\n');
foreach (string fileLine in fileLines)
{
//组
if (fileLine.Contains("g "))
{
string[] groupLine = fileLine.Split(' ');
objFile.GroupName = groupLine[1];
}
//点
else if (fileLine.Contains("v "))
{
string[] vertexLine = fileLine.Split(' ');
Vector3 vertex = new Vector3(float.Parse(vertexLine[1]), float.Parse(vertexLine[2]), float.Parse(vertexLine[3]));
objFile.PointList.Add(vertex);
}
//面
else if (fileLine.Contains("f "))
{
string[] faceLine = fileLine.Split(' ');
objFile.FaceList.Add(int.Parse(faceLine[1]) - 1);//-1是因为unity里顶点索引是从0开始,不是1开始
objFile.FaceList.Add(int.Parse(faceLine[2]) - 1);//文本f 1 2 3 转化到unity要成为f [0] [1] [2]
objFile.FaceList.Add(int.Parse(faceLine[3]) - 1);
}
}
return objFile;
}
3、LoadOBJModel函数
用于读取制定路径的obj
文件并在unity
场景中生成对应物体
path
对应obj
文件路径
public static void LoadOBJModel(string path)
{
//obj文件路径
//string filePath = @"C:\Users\username\Desktop\测试用.obj";
string filePath = path;
//获取obj文件文本
string fileText = ReadFile(filePath);
//解析obj字符串
OBJFile objFile = AnalyseFile(fileText);
//创建网格
Mesh mesh = new Mesh();
mesh.name = objFile.GroupName;
mesh.vertices = objFile.PointList.ToArray();
mesh.triangles = objFile.FaceList.ToArray();
mesh.RecalculateNormals();//从三角形和顶点重新计算网格的法线。
//创建游戏物体
GameObject gameObject = new GameObject(objFile.GroupName);
MeshFilter meshFilter = gameObject.AddComponent<MeshFilter>();
MeshRenderer meshRenderer = gameObject.AddComponent<MeshRenderer>();
meshFilter.mesh = mesh;
meshRenderer.material = new Material(Shader.Find("Standard"));
}
=========================================================================================================
4、OpenFileName类
用于声明windows
资源管理器dll
public class OpenFileName
{
public int structSize = 0;
public IntPtr dlgOwner = IntPtr.Zero;
public IntPtr instance = IntPtr.Zero;
public String filter = null;
public String customFilter = null;
public int maxCustFilter = 0;
public int filterIndex = 0;
public String file = null;
public int maxFile = 0;
public String fileTitle = null;
public int maxFileTitle = 0;
public String initialDir = null;
public String title = null;
public int flags = 0;
public short fileOffset = 0;
public short fileExtension = 0;
public String defExt = null;
public IntPtr custData = IntPtr.Zero;
public IntPtr hook = IntPtr.Zero;
public String templateName = null;
public IntPtr reservedPtr = IntPtr.Zero;
public int reservedInt = 0;
public int flagsEx = 0;
}
public class WindowDLL
{
[DllImport("Comdlg32.dll", SetLastError = true, ThrowOnUnmappableChar = true, CharSet = CharSet.Auto)]
public static extern bool GetOpenFileName([In, Out] OpenFileName ofn);//外部dll的方法
public static bool GetOpenFileName1([In, Out] OpenFileName ofn)//调用外部dll方法
{
return GetOpenFileName(ofn);
}
}
5、添加按钮事件调用资源管理器
public void Button_OnClick()
{
OpenFileName ofn = new OpenFileName();//实例化一个文件对象
ofn.structSize = Marshal.SizeOf(ofn);//结构大小
ofn.filter = "All Files\0*.*\0\0";//文件类型过滤器
ofn.file = new string(new char[512]);//文件路径
ofn.maxFile = ofn.file.Length;//文件路径长度
ofn.fileTitle = new string(new char[128]);//文件标题
ofn.maxFileTitle = ofn.fileTitle.Length;//文件标题长度
ofn.initialDir = Application.dataPath;//默认路径
ofn.title = "Open OBJ Model";//对象标题
ofn.defExt = "OBJ";//显示文件的类型
//注意 下面这个字段不一定要全选 但是 0x00000008 项不要缺少
ofn.flags = 0x00080000 | 0x00001000 | 0x00000800 | 0x00000200 | 0x00000008;
//OFN_EXPLORER|OFN_FILEMUSTEXIST|OFN_PATHMUSTEXIST| OFN_ALLOWMULTISELECT|OFN_NOCHANGEDIR
//如果调用外部WindowDLL能访问到该文件
if (WindowDLL.GetOpenFileName(ofn))
{
//加载OBJ文件
ImportOBJUtil.LoadOBJModel(ofn.file);
Debug.Log("所选文件路径为:" + ofn.file);
}
else
{
Debug.Log("无法访问到文件:" + ofn.file);
}
}
6、效果
该方法只针对博主另一篇博客的Revit
插件生成的OBJ
模型格式
https://blog.csdn.net/qq_28907595/article/details/84838419
7、问题
上面的效果展示中可以看出有些模型出现“黑面”的情况,目前认为最大的可能性是因为mesh.RecalculateNormal()这个方法重新计算法线的时候计算错误导致的
查明“黑面”原因为 两个朝向不同的面如果有重叠部分就会导致黑面
灵感来源于
https://blog.csdn.net/liu_if_else/article/details/73294579
感谢该博主
首先给这个“黑面”一个特写
我们创建一个简单的Mesh
using UnityEngine;
public class DoubleMesh : MonoBehaviour
{
public MeshFilter meshFilter;
// Use this for initialization
void Start()
{
Vector3[] v3s = new Vector3[3];
Mesh mesh = new Mesh();
v3s[0] = new Vector3(0, 0, 0);
v3s[1] = new Vector3(0, 1, 0);
v3s[2] = new Vector3(1, 0, 0);
mesh.vertices = v3s;
int[] index = new int[3];
index[0] = 0;
index[1] = 1;
index[2] = 2;
mesh.triangles = index;
mesh.RecalculateBounds();
mesh.RecalculateNormals();
meshFilter.mesh = mesh;
}
}
如果不懂面的朝向请用左手定则然后看大拇指朝向
然后我们在代码里面再添加一个三角网格
using UnityEngine;
public class DoubleMesh : MonoBehaviour
{
public MeshFilter meshFilter;
// Use this for initialization
void Start()
{
Vector3[] v3s = new Vector3[4];
Mesh mesh = new Mesh();
v3s[0] = new Vector3(0, 0, 0);
v3s[1] = new Vector3(0, 1, 0);
v3s[2] = new Vector3(1, 0, 0);
v3s[3] = new Vector3(1, 1, 0);
mesh.vertices = v3s;
int[] index = new int[6];
index[0] = 0;
index[1] = 1;
index[2] = 2;
index[3] = 0;
index[4] = 1;
index[5] = 3;
mesh.triangles = index;
mesh.RecalculateBounds();
mesh.RecalculateNormals();
meshFilter.mesh = mesh;
}
}
//index[3] = 0;
//index[4] = 1;
//index[5] = 3;
index[3] = 3;
index[4] = 1;
index[5] = 0;