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

关于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.0vt 0.0 0.0
vt 1.0 0.0
vt 1.0 1.0
vt 0.0 1.0vn 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.0f 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已经被执行了好几次,模型获得的颜色可能是最后一次的颜色,这里每次加载模型的时候都是用一个临时变量先获取matColor,然后把这个临时变量传递给LoadFun.instance.LoadBuffer方法里面的委托,而不是在加载完成之后再去获取matColor。

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

相关文章:

  • 9.24作业
  • vue实现左侧数据拖拽到右侧区域,且左侧数据保留且左侧数据不能互相拖拽改变顺序
  • 注册中心Eureka
  • 面试-2024年9月13号
  • I2C通信中的当前地址指针(CADDR)工作原理
  • 【韩顺平Java笔记】第3章:变量
  • Spring Boot 配置全流程 总结
  • 【linux 多进程并发】linux进程状态与生命周期各阶段转换,进程状态查看分析,助力高性能优化
  • 51单片机和ARM单片机的区别
  • “领航猿1号” 正式更名为 “AGI舰长”
  • 代码随想录冲冲冲 Day59 图论Part10
  • 数据结构 ——— C语言实现无哨兵位单向不循环链表
  • Linux基础命令lsblk详解
  • vue限定类型上传文件 最简单实践(单个可文件、可图片)
  • Hive数仓操作(五)
  • CSS选择器——伪元素选择器之处理父元素高度及外边距溢出
  • CSS中外联样式表代表的含义
  • Facebook AccountKit 接入的坑点
  • HashMap ConcurrentHashMap
  • If…else
  • JavaScript异步流程控制的前世今生
  • Lucene解析 - 基本概念
  • node-glob通配符
  • 好的网址,关于.net 4.0 ,vs 2010
  • 机器学习 vs. 深度学习
  • 浅谈web中前端模板引擎的使用
  • C# - 为值类型重定义相等性
  • 交换综合实验一
  • #etcd#安装时出错
  • (k8s中)docker netty OOM问题记录
  • (MIT博士)林达华老师-概率模型与计算机视觉”
  • (二)c52学习之旅-简单了解单片机
  • (二)Pytorch快速搭建神经网络模型实现气温预测回归(代码+详细注解)
  • (六) ES6 新特性 —— 迭代器(iterator)
  • (七)Knockout 创建自定义绑定
  • (一)appium-desktop定位元素原理
  • (原创)攻击方式学习之(4) - 拒绝服务(DOS/DDOS/DRDOS)
  • (轉貼) 2008 Altera 亞洲創新大賽 台灣學生成果傲視全球 [照片花絮] (SOC) (News)
  • *p=a是把a的值赋给p,p=a是把a的地址赋给p。
  • .htaccess 强制https 单独排除某个目录
  • .JPG图片,各种压缩率下的文件尺寸
  • .mp4格式的视频为何不能通过video标签在chrome浏览器中播放?
  • .net core 依赖注入的基本用发
  • .Net MVC4 上传大文件,并保存表单
  • .NET/C# 在代码中测量代码执行耗时的建议(比较系统性能计数器和系统时间)...
  • .NET开源快速、强大、免费的电子表格组件
  • .net连接oracle数据库
  • .NET微信公众号开发-2.0创建自定义菜单
  • .Net中间语言BeforeFieldInit
  • @Bean注解详解
  • @NestedConfigurationProperty 注解用法
  • @RequestMapping处理请求异常
  • [AndroidStudio]_[初级]_[修改虚拟设备镜像文件的存放位置]
  • [hdu 4552] 怪盗基德的挑战书
  • [LeetCode]-Integer to Roman 阿拉伯数字转罗马数字