当前位置: 首页 > article >正文

关于Obj文件格式介绍与Unity加载Obj文件代码参考

        以下是一个典型的obj文件内容:

# 这是一个 OBJ 文件的示例
v 0.0 0.0 0.0
v 1.0 0.0 0.0
v 1.0 1.0 0.0
v 0.0 1.0 0.0
v 0.0 0.0 1.0
v 1.0 0.0 1.0
v 1.0 1.0 1.0
v 0.0 1.0 1.0

vt 0.0 0.0
vt 1.0 0.0
vt 1.0 1.0
vt 0.0 1.0

vn 0.0 0.0 -1.0
vn 0.0 0.0 1.0
vn 0.0 -1.0 0.0
vn 0.0 1.0 0.0
vn -1.0 0.0 0.0
vn 1.0 0.0 0.0

f 1/1/1 2/2/2 3/3/3
f 1/1/1 3/3/3 4/4/4
f 5/1/2 6/2/2 7/3/2
f 5/1/2 7/3/2 8/4/2
f 1/1/1 5/1/2 6/2/2
f 1/1/1 6/2/2 2/2/2
f 2/2/2 6/2/2 7/3/2
f 2/2/2 7/3/2 3/3/3
f 3/3/3 7/3/2 8/4/2
f 3/3/3 8/4/2 4/4/4
f 4/4/4 8/4/2 5/1/2
f 4/4/4 5/1/2 1/1/1

        v开头的行表示顶点坐标

        vt开头的行表示uv坐标

        vn开头的行表示法线

        f开头的行表示三种索引,用斜杠分隔开,顶点/UV/法线,每个f开头的对应三组,每组的第一个整数是顶点索引,第二个是UV索引,第三组是法线索引,以第三个f开头的行为例,这个面的顶点索引是5、6、7,UV索引是1、2、3,法线索引是2、2、2。

-------------------------------重要的分割线----------------------------------------------------------------

        这里必须强调的是,obj文件的索引是从1开始的,不是0!!!!!!

-------------------------------重要的分割线----------------------------------------------------------------

        当然obj文件还包含一些其它内容,暂不做介绍。

        以下是一个Unity发布WebGL后加载obj文件的参考:

using System;
using System.Collections.Generic;
using System.IO;
using UnityEngine;

public class ObjModelLoadManager : MonoBehaviour
{
	public static ObjModelLoadManager instance;

	void Awake()
	{
		instance = this;
	}

	[SerializeField]
	Transform modelRoot;
	//
	[SerializeField]
	Material matObj;

	double offsetX = 0;
	double offsetY = 0;
	double offsetZ = 0;
	float scale = 1;
	Color matColor = Color.gray;
	bool addMode = true;

	void Start()
	{
		SceneLoader.instance.AddActBeforeActiveNewScene(delegate { ClearObjModel(); });
	}

	public void SetLoadObjModelGlobalParam(string json)
	{
		if (json == null || json.Length == 0)
		{
			Debug.Log("EngineLog:TunnelGlobalParameter is empty.");
			return;
		}

		LoadObjModelGlobalParam param = JsonUtility.FromJson<LoadObjModelGlobalParam>(json);
		if (param == null)
		{
			Debug.Log("EngineLog:Parse TunnelGlobalParameter failed.");
			return;
		}

		offsetX = param.offsetX;
		offsetY = param.offsetY;
		offsetZ = param.offsetZ;
		scale = param.scale;
		addMode = param.addMode;
		SetLoadObjModelColor(param.htmlColor);
	}

	public void SetLoadObjModelColor(string htmlColor)
	{
		if (ColorUtility.TryParseHtmlString(htmlColor, out Color color))
		{
			matColor = color;
		}
	}

	public void LoadObjModel(string json)
	{
		LoadObjModelInfo objLoadInfo = JsonUtility.FromJson<LoadObjModelInfo>(json);
		if (objLoadInfo == null)
		{
			Debug.Log("EngineLog:Parse ObjLoadInfo failed.");
			return;
		}
		Color color = matColor;
		LoadFun.instance.LoadBuffer(objLoadInfo.url, delegate (byte[] buf) { OnLoadedBuf(buf, objLoadInfo.id, color); });
	}

	void OnLoadedBuf(byte[] buf, string id, Color color)
	{
		if (!addMode)
		{
			ClearObjModel();
		}

		MemoryStream mStream = new(buf);
		StreamReader sr = new(mStream);

		List<Vector3> listVert = new() { Vector3.zero };
		List<Vector2> listUV = new() { Vector2.zero };
		List<Vector3> listNormal = new() { Vector3.up };
		List<int> listTriangle = new();

		string line;
		while ((line = sr.ReadLine()) != null)
		{
			if (line.StartsWith("v "))
			{
				AddVertex(line);
			}
			else if (line.StartsWith("vt "))
			{
				AddUV(line);
			}
			else if (line.StartsWith("vn "))
			{
				AddNormal(line);
			}
			else if (line.StartsWith("f "))
			{
				AddTriangles(line);
			}
		}

		Mesh mesh = new Mesh();
		mesh.name = id;
		mesh.vertices = listVert.ToArray();
		mesh.uv = listUV.ToArray();
		mesh.normals = listNormal.ToArray();
		mesh.triangles = listTriangle.ToArray();

		GameObject obj = new(id);
		obj.transform.SetParent(modelRoot);
		//
		obj.layer = LayerMask.NameToLayer("SelObj");
		MeshFilter filter = obj.AddComponent<MeshFilter>();
		filter.mesh = mesh;
		MeshRenderer meshRender = obj.AddComponent<MeshRenderer>();
		Material material = Instantiate(matObj);
		material.color = color;
		meshRender.material = material;
		obj.AddComponent<MeshCollider>();
		SelObj selObj = obj.AddComponent<SelObj>();
		selObj.id = id;
		selObj.SetShowName(id);
		SelObjManager.instance.AddSelObj(selObj);

		void AddVertex(string line)
		{
			bool scaleEnabled = !Mathf.Approximately(scale, 1);
			string[] parts = line.Substring(2).Split(' ', StringSplitOptions.RemoveEmptyEntries);

			if (parts.Length >= 3 &&
				double.TryParse(parts[0], out double x) &&
				double.TryParse(parts[1], out double y) &&
				double.TryParse(parts[2], out double z))
			{
				if (scaleEnabled)
				{
					x *= scale;
					y *= scale;
					z *= -scale;
				}
				x += offsetX;
				y += offsetY;
				z += offsetZ;
				listVert.Add(new Vector3((float)x, (float)y, (float)z));
			}
		}

		void AddUV(string line)
		{
			string[] parts = line.Substring(3).Split(' ', StringSplitOptions.RemoveEmptyEntries);

			if (parts.Length >= 2 &&
				float.TryParse(parts[0], out float u) &&
				float.TryParse(parts[1], out float v))
			{
				listUV.Add(new Vector2(u, v));
			}
		}

		void AddNormal(string line)
		{
			string[] parts = line.Substring(3).Split(' ', StringSplitOptions.RemoveEmptyEntries);

			if (parts.Length >= 3 &&
				float.TryParse(parts[0], out float x) &&
				float.TryParse(parts[1], out float y) &&
				float.TryParse(parts[2], out float z))
			{
				listNormal.Add(new Vector3(x, y, z));
			}
		}

		void AddTriangles(string line)
		{
			string[] parts = line.Substring(2).Split(' ', StringSplitOptions.RemoveEmptyEntries);

			foreach (var part in parts)
			{
				string[] indices = part.Split('/'); // 每个part可能是形如 "v/vt/vn"

				if (indices.Length >= 1 && int.TryParse(indices[0], out int vertexIndex))
				{
					listTriangle.Add(vertexIndex);
				}
			}
		}
	}

	public void ClearObjModel()
	{
		//清理原来的模型。
		for (int i = 0; i < modelRoot.childCount; i++)
		{
			Destroy(modelRoot.GetChild(i).gameObject);
		}
	}
}

#region JsonClass

[Serializable]
public class LoadObjModelGlobalParam
{
	public double offsetX = 0;
	public double offsetY = 0;
	public double offsetZ = 0;
	public float scale = 1;
	public string htmlColor = "#888888";
	public bool addMode = true;
}

[Serializable]
public class LoadObjModelInfo
{
	public string url;
	public string id;
}
#endregion

        其中的offset系列参数是考虑到模型可能距离坐标原点较远,坐标值可能很大,所以用double来解析每个坐标值,然后用户可以整体偏移模型的值,让模型处于坐标原点附近,这时候double转成float精度好很多,scale参数是为了改变模型的大小,这里在使用中是为了调整单位,比如这个obj文件是基于厘米单位的,但是Unity中是米为单位,这时需要把scale设置为0.01,颜色用string表示的htmlColor主要是为了页面使用方便,其值类似"#ff8800"。

        下面的代码中,每个列表都首先添加了一个值,然后再添加obj文件中的内容。

List<Vector3> listVert = new() { Vector3.zero };
List<Vector2> listUV = new() { Vector2.zero };
List<Vector3> listNormal = new() { Vector3.up };

        这么做是因为obj文件的索引从1开始,不是从0开始,这个添加的值就是为了占据0这个索引位置,让可使用的内容从1开始,这是个投机取巧的办法,你当然可以不这么做,而是把obj的索引的值每个都减1,这样结果是一样的,只是个人觉得这样运算量比较大。 

        这里SetLoadObjModelColor和LoadObjModel方法经常会配合使用,就是先设置一个颜色,然后加载一个模型,这样这个加载的模型就使用了这个颜色,在代码编写时应该注意的是LoadFun.instance.LoadBuffer方法是一个异步操作,考虑到连续交替执行SetLoadObjModelColor和LoadObjModel方法的时候,在模型文件加载完成并设置颜色的时候,可能SetLoadObjModelColor已经被执行了好几次,模型获得的颜色可能是最后一次的颜色,这里每次vi加载模型的时候都是用一个临时变量先获取matColor,然后把这个临时变量传递给LoadFun.instance.LoadBuffer方法里面的委托,而不是在加载完成之后再去获取matColor。

        这个原理是什么呢,我也说不清楚,编程多了,有直觉,哈哈。


http://www.kler.cn/a/323197.html

相关文章:

  • Redis五大基本类型——String字符串命令详解(命令用法详解+思维导图详解)
  • 2024-11-16-机器学习方法:无监督学习(1) 聚类(上)
  • .Net Core根据文件名称自动注入服务
  • # ubuntu 安装的pycharm不能输入中文的解决方法
  • 计算机网络 (3)计算机网络的性能
  • 【数据库取证】快速从服务器镜像文件中获取后台隐藏数据
  • 阿里云k8s发布vue项目
  • 防砸安全鞋这样挑,舒适又安心!
  • 用矩阵和统计报告估计polynomial线性回归的系数python
  • 直线模组降噪攻略
  • 【开源免费】基于SpringBoot+Vue.JS技术交流分享平台(JAVA毕业设计)
  • 16 Midjourney从零到商用·实战篇:产品工业设计
  • 2024AI做PPT软件如何重塑演示文稿的创作
  • C语言VS实用调试技巧
  • 华为LTC流程架构分享
  • 一天认识一个硬件之硬盘
  • 【代码模板】Python Decorator / 装饰器
  • 828华为云征文 | 华为云X实例部署Docker应用的性能评测优化与实践指南
  • Facebook对现代社交互动的影响
  • 【串口收发不定长数据】使用中断的方式—以AT32为例
  • 最近职场中的两点感悟与思考
  • C语言 | Leetcode C语言题解之第433题最小基因变化
  • CentOS 系统中设置宝塔面板开机自启
  • 【习题】应用开发安全
  • OpenCV视频I/O(2)视频采集类VideoCapture之检索视频流的各种属性函数get()的使用
  • WinForm程序嵌入Web网页