在我们前面绘制一个屋,我们可以看到,需要每个立方体一个一个的自己来推并且还要处理位置信息.代码量大并且要时间.现在我们通过加载模型文件的方法来生成模型文件,比较流行的3D模型文件有OBJ,FBX,dae等,其中OBJ模式只包含静态的模型,相对FBX这种来说,比较简单,刚好给我们用来学习之用.

  对比我们之前用代码来一个一个建模型,用模型文件OBJ的不同就是在OBJ里包含了我们需要的顶点,法线,以及纹理坐标以及顶点组成面索引.去掉了我们用代码建模最要时的过程.用模型文件我们要做的仅仅是读出里面的信息,然后组织供OpenGL调用.

  不同的模型文件有不同的信息组织格式,相对于FBX这种二进制并且没公布格式的文件来说,OBJ模型文本结构对于我们来说更易读并且容易理解,网上也有不少大神对OBJ模型中出现的文本做了详细的解说并提供相应的加载模型方法.

OBJ模型文件的结构、导入与渲染Ⅰ OBJ模型文件的结构、导入与渲染Ⅱ

  在上面二篇文章中以及文章中的链接,有对OBJ模型比较详细的解说以及加载,与原文章加载稍有不同的是,我们解析相应数据按照OBJ模型的定义来定义结构.

  在OBJ模型中主要分二块,一块是模型组成文件,包含顶点,法线,纹理坐标,面,组的信息,另一块是模型文件所需的材质信息与对应纹理所需图片.

  我们分别定义第一块的数据结构如下:VertexAttribute,ObjFace,ObjGroup.第二块ObjMaterialItem,ObjMaterial.其中模型定义为ObjModel.代码如下:

 type ArrayList<'T> = System.Collections.Generic.List<'T>

 type ObjMaterialItem() =
member val Name = "" with get,set
member val Ambient = [|.f;.f;.f;.f|] with get,set
member val Diffuse = [|.f;.f;.f;.f|] with get,set
member val Specular = [|.f;.f;.f;.f|] with get,set
member val Shiness = .f with get,set
member val DiffuseMap = "" with get,set
member val SpecularMap = "" with get,set
member val BumpMap = "" with get,set
member val DiffuseID = with get,set
member val SpecularID = with get,set
member val BumpID = with get,set type ObjMaterial() =
member val Name = "" with get,set
member val Items = new ArrayList<ObjMaterialItem>() with get,set
member val currentItem = new ObjMaterialItem() with get,set type VertexAttribute() =
let strToInt str =
let (ok,f) = System.Int32.TryParse(str)
if ok then f else -
member val Position= Vector3.Zero with get,set
member val Texcoord=Vector2.Zero with get,set
member val Normal= Vector3.Zero with get,set
member val PositionIndex = - with get,set
member val TexcoordIndex = - with get,set
member val NormalIndex = - with get,set
//各个值的索引信息
member this.SetValue(line:string) =
let ls = line.Split('/')
match ls.Length with
| ->
this.PositionIndex <- strToInt ls.[]
| ->
this.PositionIndex <- strToInt ls.[]
this.TexcoordIndex <- strToInt ls.[]
| ->
this.PositionIndex <- strToInt ls.[]
this.NormalIndex <- strToInt ls.[]
if not (ls.[] = "" || ls.[] = null) then
this.TexcoordIndex <- strToInt ls.[]
| _ -> ()
//组织格式用T2fV3f/N3fV3f/T2fN3fV3f/V3f成float32[]
member this.PointArray
with get() =
let mutable ps = Array.create 0.0f
if this.TexcoordIndex > then ps <- Array.append ps [|this.Texcoord.X;1.0f - this.Texcoord.Y|]
if this.NormalIndex > then ps <- Array.append ps [|this.Normal.X;this.Normal.Y;this.Normal.Z|]
if this.PositionIndex > then ps <- Array.append ps [|this.Position.X;this.Position.Y;this.Position.Z|]
ps type ObjFace() =
let mutable vectexs = [||] : VertexAttribute array
//每个面的顶点,一个是三角形,如果是矩形,为了兼容性,应该化为成二个三角形.
member this.Vectexs
with get() =
let mutable result = vectexs.[..]
if vectexs.Length = then
let newvxs = [|vectexs.[];vectexs.[]|]
result <- Array.append result newvxs
result
//在读取文件时,得到当前面包含的顶点索引信息.(此时对应顶点只有索引,没有真实数据)
member this.AddVectex (line:string) =
let ls = line.TrimEnd(' ').Split(' ')
let vs =
ls |> Array.map(fun p ->
let va = new VertexAttribute()
va.SetValue(p)
va)
vectexs <- vs
member this.VertexCount with get() = this.Vectexs.Length type ObjGroup() =
//得到数组里所有面的对应所有顶点属性
let mutable vectexs = new ArrayList<VertexAttribute>()
let mutable points = Array2D.create .f
let mutable vbo,ebo = ,
member val Faces = new ArrayList<ObjFace>() with get,set
member val Mtllib = "" with get,set
member val Usemtl = "" with get,set
member val Name = "" with get,set
member val Material = new ObjMaterialItem() with get,set
member val IsHaveMaterial = false with get,set
member val Path = "" with get,set
member this.VBO with get() = vbo
member this.EBO with get() = ebo
//读取文件,读取当前group里的面的信息,并且会在读面信息时读取到这个面所有顶点索引
member this.AddFace (line:string) =
let face = new ObjFace()
face.AddVectex(line)
this.Faces.Add(face)
vectexs.AddRange(face.Vectexs)
//组织一个规则二维数组,一维表示每面上的每个顶点,二维表示每个顶点是如何组织,包含法向量,纹理坐标不
member this.DataArray
with get() =
if points.Length < then
let length1 = vectexs.Count
if length1 > then
let length2 = vectexs.[].PointArray.Length
if length2 > then
points <- Array2D.init length1 length2 (fun i j -> vectexs.[i].PointArray.[j])
points
member this.CreateVBO() =
if this.ElementLength > then
vbo <- GL.GenBuffers()
GL.BindBuffer(BufferTarget.ArrayBuffer,vbo)
GL.BufferData(BufferTarget.ArrayBuffer,IntPtr ( *this.ElementLength*this.VectorLength ),this.DataArray,BufferUsageHint.StaticDraw)
let len = this.ElementLength -
let eboData = [|..len|]
ebo <- GL.GenBuffers()
GL.BindBuffer(BufferTarget.ElementArrayBuffer,ebo)
GL.BufferData(BufferTarget.ElementArrayBuffer,IntPtr ( * this.ElementLength),eboData,BufferUsageHint.StaticDraw)
if this.IsHaveMaterial then
let kdPath = Path.Combine(this.Path,this.Material.DiffuseMap)
if File.Exists kdPath then
this.Material.DiffuseID <- TexTure.Load(kdPath)
member this.DrawVBO() =
if this.VBO > && this.EBO > then
GL.BindBuffer(BufferTarget.ArrayBuffer,this.VBO)
GL.BindBuffer(BufferTarget.ElementArrayBuffer,this.EBO)
if this.IsHaveMaterial then
GL.Enable(EnableCap.Texture2D)
GL.BindTexture(TextureTarget.Texture2D,this.Material.DiffuseID)
GL.InterleavedArrays(this.InterFormat,,IntPtr.Zero)
GL.DrawElements(BeginMode.Triangles,this.ElementLength,DrawElementsType.UnsignedInt,IntPtr.Zero)
GL.Disable(EnableCap.Texture2D)
//多少个顶点
member this.ElementLength with get() = Array2D.length1 this.DataArray
//顶点组织形式长度T2fV3f/N3fV3f/T2fN3fV3f/V3f
member this.VectorLength with get() = Array2D.length2 this.DataArray
//顶点组织形式
member this.InterFormat
with get()=
let mutable result = InterleavedArrayFormat.T2fN3fV3f
if this.VectorLength = then result <- InterleavedArrayFormat.V3f
if this.VectorLength = then result <- InterleavedArrayFormat.T2fV3f
if this.VectorLength = then result <- InterleavedArrayFormat.N3fV3f
result type ObjModel(fileName:string) =
let mutable groupName = "default"
let mutable groups = [] : ObjGroup list
let addGroup group = groups <- (group :: groups)
//得到每行数组去掉标识符后的数据如 v 1.0 2.0 3.0 -> 1.0 2.0 3.0
let getLineValue (line:string) =
let fs = line.Split(' ')
let len = fs.Length -
if fs.Length > then (fs.[..len] |> Array.filter (fun p -> p <> null && p<> " " && p <> ""))
else [|line|]
//数组转化成float32
let strToFloat str =
let (ok,f) = System.Single.TryParse(str)
if ok then f else System.Single.NaN
let mutable group = ObjGroup()
let mutable mtllib = ""
member val Positions = new ArrayList<Vector3>() with get,set
member val Normals = new ArrayList<Vector3>() with get,set
member val Texcoords = new ArrayList<Vector2>() with get,set
member val Materials = new ArrayList<ObjMaterial>() with get,set
member this.Path
with get() = System.IO.Path.GetDirectoryName(fileName)
member this.GetLineFloatArray (line:string) =
let fs = getLineValue(line)
fs |> Array.map (fun p -> strToFloat p)
member this.GetLineValue (line:string,?sep) =
let dsep = defaultArg sep " "
let fs = getLineValue(line)
String.concat dsep fs
member this.CurrentGroup
with get() =
let bExist = groups |> List.exists(fun p -> p.Name = groupName)
if not bExist then
let objGroup = new ObjGroup()
objGroup.Name <- groupName
objGroup.Mtllib <- mtllib
addGroup objGroup
group <- groups |> List.find(fun p -> p.Name = groupName)
group
member this.Groups
with get() =
groups
//主要有二步,首先读取文件信息,然后把顶点,法线,纹理坐标根据索引来赋值
member this.LoadObjModel(?bCreateVBO) =
let bCreate = defaultArg bCreateVBO false
let file = new StreamReader(fileName)
let mutable beforeFace = false
let (|StartsWith|) suffix (s:string) = s.TrimStart(' ','\t').StartsWith(suffix,StringComparison.OrdinalIgnoreCase)
//首先读取文件信息,此时顶点只有索引信息.
while not file.EndOfStream do
let str = file.ReadLine()
match str with
| StartsWith "mtllib " true ->
mtllib <- this.GetLineValue(str)
//#region 读纹理
let material = new ObjMaterial()
material.Name <- mtllib
let mtlFile = new StreamReader(Path.Combine(this.Path,mtllib))
while not mtlFile.EndOfStream do
let str = mtlFile.ReadLine()
match str with
| null -> ()
| StartsWith "newmtl " true ->
material.currentItem <- new ObjMaterialItem()
material.currentItem.Name <- this.GetLineValue(str)
material.Items.Add(material.currentItem)
| StartsWith "ka " true -> material.currentItem.Ambient <- this.GetLineFloatArray(str)
| StartsWith "kd " true -> material.currentItem.Diffuse <- this.GetLineFloatArray(str)
| StartsWith "ks " true -> material.currentItem.Specular <- this.GetLineFloatArray(str)
| StartsWith "map_Kd " true -> material.currentItem.DiffuseMap <- this.GetLineValue(str)
| StartsWith "map_Ks " true -> material.currentItem.SpecularMap <- this.GetLineValue(str)
| StartsWith "map_bump " true -> material.currentItem.BumpMap <- this.GetLineValue(str)
| StartsWith "Ns " true ->
let ns = this.GetLineFloatArray(str).[]
material.currentItem.Shiness <- ns * 0.128f
| _ -> ()
mtlFile.Close()
this.Materials.Add(material)
//#endregion
| null -> ()
| StartsWith "usemtl " true -> this.CurrentGroup.Usemtl <- this.GetLineValue(str)
| StartsWith "g " true ->
groupName <- this.GetLineValue(str)
beforeFace <- false
| StartsWith "vn " true ->
let fs = this.GetLineFloatArray(str)
this.Normals.Add(Vector3(fs.[],fs.[],fs.[]))
| StartsWith "vt " true ->
let fs = this.GetLineFloatArray(str)
this.Texcoords.Add(Vector2(fs.[],fs.[]))
| StartsWith "v " true ->
let fs = this.GetLineFloatArray(str)
this.Positions.Add(Vector3(fs.[],fs.[],fs.[]))
| StartsWith "f " true ->
if beforeFace then
group.AddFace(this.GetLineValue(str))
else
this.CurrentGroup.AddFace(this.GetLineValue(str))
beforeFace <- true
| _ -> printfn "%s" ("---------"+str)
file.Close()
//根据索引信息来给对应的顶点,法线,纹理坐标赋值
groups |>List.iter (fun p ->
p.Faces.ForEach(fun face ->
face.Vectexs |> Array.iter(fun vect ->
if vect.PositionIndex > then vect.Position <-this.Positions.[vect.PositionIndex-]
if vect.TexcoordIndex > then vect.Texcoord <- this.Texcoords.[vect.TexcoordIndex-]
if vect.NormalIndex > then vect.Normal <- this.Normals.[vect.NormalIndex-]
)
)
let mater = this.Materials.Find(fun m -> m.Name = p.Mtllib)
if box(mater) <> null then
let mitem = mater.Items.Find(fun i -> i.Name = p.Usemtl)
if box(mitem) <> null then
p.Material <- mitem
p.Path <- this.Path
p.IsHaveMaterial <- true
)
//释放空间
this.Positions.Clear()
this.Normals.Clear()
this.Texcoords.Clear()
if bCreate then this.CreateVbo()
//生成VBO信息
member this.CreateVbo() =
this.Groups |> List.iter (fun p -> p.CreateVBO())
member this.DrawVbo() =
this.Groups |> List.iter (fun p -> p.DrawVBO())

  其中ObjMode主要是加载文件,主要方法在LoadObjModel里,这个方法主要有二个主要作用.

  一是在file与file.close这节,主要是读取OBJ文件里所有的信息,当读到mtllib时,会尝试打开关联的材质文件,然后读取材质里的信息,根据每读一个newmtl,来添加一个ObjMaterialItem.然后就是读到g就会生成一个group,然后读到usemtl与f(面)时,分别为前面生成的group,来分别对应group当前所用材质以及添加f(面)信息到group中,f(面)一般包含3个顶点(三角形)与四个顶点(方形)的v/vt/vn(可能只包含v,也可能全包含)的顶点索引信息.而f中vn(法向量),v(顶点),vt(纹理向量)中索引指向全局的对应值,就是说,当f中索引v可能已经到100了,而这时,我们读到的顶点数据可能只有10个.

  Face中通过读到的如下结构,v,v/vt,v//vn,v/vt/vn这四种结构,然后通过AddVectex里分别解析成对应的VertexAttribute结构.在VertexAttribute中,记住属性PointArray,这个把上面的v,v/vt,v//vn,v/vt/vn这四种结构按照顺序会组装成一个float[],里的数据分别对应Opengl中的InterleavedArrayFormat中的V3f,T2fV3f,N3fV3f,T2N3fV3f.与后面在Group里组装VBO要用到.(前面Opengl绘制我们的小屋(一)球体,立方体绘制有讲解)其类还有一个作用,如果检查到4个顶点,则分成六个顶点,索引如果为1,2,3,4,分成1,2,3,4,1,3,意思就是一个方形分成二个三角形,保持逆时针顺序不变,一是为了只生成一个VBO,二是为了兼容性.

  二是把对应的VertexAttribute里的v/vt/vn的索引,变成ObjMode里所读到的对应v/vt/vn里的真实数据.为什么分成二步做,上面其实有说,f中的v/vt/vn的索引值是全局的.这个索引可能大于你读到的相关索引数据.并且把对应group里用到的材质关联上去.

  上面的完成后,下面的才能开始,VertexAttribute中的PointArray就能组装到对应值.Group里的DataArray根据其中的Face中的VertexAttribute中的PointArray来组装数据生成VBO,PointArray的组装是一个规则二维数组[x,y],x等于Group里的顶点个数,y就是V3f/T2fV3f/N3fV3f/T2fN3fV3f所对应的数据长度,分别是3,5,6,8.创建VBO与显示VBO也是group来完成的,在OBJ里,就是根据每组数据来绘制显示的数据.

  创建VBO与绘制的代码因为有了上面数据的组装,所以显示的很简单,其中还是注意GL.InterleavedArrays(this.InterFormat,0,IntPtr.Zero)这句使用,这句能帮我们节省很多代码,会自动根据InterleavedArrayFormat来给我们关闭打开相应状态,自动给对应顶点结构如VectorPointer,TexcoordPointer,NormalPointer赋值.

  在材质方面,我只对我们最平常的贴图map_Kd做了处理,还有对应的是法线纹理会在后面说明.

  在网上下载了一些OBJ模型,然后用这个来加载,开始会发现纹理是上下反的,在网上查找了下,有种说法,纹理是用窗口坐标系,而Opengl是用的笛卡尔坐标系.对这种说法我表示怀疑,但是又不知从何解释,不过把纹理坐标经过y经过变换1-y后表示确实显示正常.

  通过这次OBJ模型的加载,也解决了长久以来我心中的一个疑问,我以前老是在想,如果一个顶点,有几个纹理坐标或者几个法向量,那是如何用VBO的,原来就是通过最简单,最粗暴的方法复制几分数据来处理的.

  代码全是通过F#写的,以前也没说F#的东东,因为我自己也是在摸索,通过这个模型加载,我发现有些东东可以说下.大家可以发现,在F#里,ObjGroup里的顶点数组,法线数组,面数组相关数据量大的全是用的ArrayList<'T>这个结构,这个我们可以看到定义type ArrayList<'T> = System.Collections.Generic.List<'T>,就是C#的List<T>,大家可能会问这和F#中的List,Array有什么不同?以及为什么不用这二个数据结构,下面是我的实践.

  从这次来看,F#的array为了函数式不变性,在需要一点一点添加上万元素时,很坑爹.因为每次添加一个元素,就相当于重新生成一个数组.而F#中的List也不同于C#中的List(本质是个数组).当时打开一个3M的文件,加载需要我20S,主要是因为ReadObjFile里读ObjGroup里.我用表示多面元素用的F#中的array,导致每添加一个元素就需要重新生成.然后根据元素对应索引找到对应的值,这个都需要十秒左右,主要是因为我在ReadObjFile后,读到的点,法线等数据全是用F#的List保存,而在后面根据下标来得到对应的数据是,这就是个大杯具.

  如果要求又能快速添加,又能快速根据下标找元素,应该还是用到C#中包装数组的List结构.上面提到的一些操作换成C#中的list,总共原来30S的时间到现在不到2S的时间,不能不说,坑爹啊.

  不过我能肯定的是,在objgroup中的DataArray,这个是用的F#的Array2D,里面数据是超大量的.但是这个不会有前面说的问题,因为在组织这个Array2D时,我们已知其中这个二维数组的长度,和各个对应元素值.

下面给出效果图:

下面给出附件:源代码 可执行文件

和前面一样,其中EDSF上下左右移动,鼠标右键加移动鼠标控制方向,空格上升,空格在SHIFT下降。

OpenGL OBJ模型加载.的更多相关文章

  1. 从零开始openGL——三、模型加载及鼠标交互实现

    前言 在上篇文章中,介绍了基本图形的绘制.这篇博客中将介绍模型的加载.绘制以及鼠标交互的实现. 模型加载 模型存储 要实现模型的读取.绘制,我们首先需要知道模型是如何存储在文件中的. 通常模型是由网格 ...

  2. DirectX11 With Windows SDK--19 模型加载:obj格式的读取及使用二进制文件提升读取效率

    前言 一个模型通常是由三个部分组成:网格.纹理.材质.在一开始的时候,我们是通过Geometry类来生成简单几何体的网格.但现在我们需要寻找合适的方式去表述一个复杂的网格,而且包含网格的文件类型多种多 ...

  3. 软件光栅器实现(四、OBJ文件加载)

    本节介绍软件光栅器的OBJ和MTL文件加载,转载请注明出处. 在管线的应用程序阶段,我们需要设置光栅器所渲染的模型数据.这些模型数据包括模型顶点的坐标.纹理.法线和材质等等,可以由我们手动编写,也可以 ...

  4. Wish3D用户必看!模型加载失败原因汇总

    上传到Wish3D的模型加载不出来,作品显示页面漆黑一片,是什么原因? 很有可能是操作过程中的小失误,不妨从以下几点检查.还是不行的请加QQ群(Wish3D交流群3):635725654,@Wish3 ...

  5. 6_1 持久化模型与再次加载_探讨(1)_三种持久化模型加载方式以及import_meta_graph方式加载持久化模型会存在的变量管理命名混淆的问题

    笔者提交到gitHub上的问题描述地址是:https://github.com/tensorflow/tensorflow/issues/20140 三种持久化模型加载方式的一个小结论 加载持久化模型 ...

  6. cesium模型加载-加载fbx格式模型

    整体思路: fbx格式→dae格式→gltf格式→cesium加载gltf格式模型 具体方法: 1. fbx格式→dae格式 工具:3dsMax, 3dsMax插件:OpenCOLLADA, 下载地址 ...

  7. PyTorch模型加载与保存的最佳实践

    一般来说PyTorch有两种保存和读取模型参数的方法.但这篇文章我记录了一种最佳实践,可以在加载模型时避免掉一些问题. 第一种方案是保存整个模型: 1 torch.save(model_object, ...

  8. CC模型加载太慢?一招破解!

    伴随无人机性能的提升,单个项目涉及到的倾斜摄影数据范围不断扩大,模型的数据量越来越大,在同配置机器上的显示速度也相应的越来越慢,那么如何在不升级配置的情况下提升模型的加载速度呢? 01 百GB倾斜摄影 ...

  9. MD2关键桢动画3D模型加载.

    在看Cg教程中,看到关键桢插值来表示一个动画的物体,例如一个动物拥有站着,奔跑,下跪等动画序列,美工将这些特定的姿态称为一个关键桢.为什么要用关键桢这种来表示了,这个比较容易理解,我们知道我们看的一些 ...

随机推荐

  1. 期权、RSU的区别与行权事宜

    科普一下常见的股票.期权.股票增值权.虚拟股票等常见的激励方式,以及在兑换或行权的一些相关问题.股票(Stock): 股票市场也称权益市场,是专门对股票进行公开交易的市场,包括股票的发行和转让,分为一 ...

  2. 【小白的CFD之旅】25 二维还是三维

    小白最近逛图书馆,发现最近关于Fluent的书是越来越多了,而且还发现这些关于Fluent教材中的案例都大同小异.小白接受小牛师兄的建议,找了一本结构比较鲜明的书照着上面的案例就练了起来.不过当练习的 ...

  3. lua -- 在面板中添加多个部件

    function UIBagController:initItemView( ) -- 获取面板 self.panelCenter = tolua.cast(UIHelper:seekWidgetBy ...

  4. transfer learning

    https://github.com/jindongwang/transferlearning ftp://ftp.cs.wisc.edu/machine-learning/shavlik-group ...

  5. 每日英语:Got a Case of the Mondays? Blame the Sunday Blues

    Welcome to Monday morning at the office. Did you have trouble sleeping last night? Was your stomach ...

  6. 如何将 iOS 工程打包速度提升十倍以上

    如何将 iOS 工程打包速度提升十倍以上   过慢的编译速度有非常明显的副作用.一方面,程序员在等待打包的过程中可能会分心,比如刷刷朋友圈,看条新闻等等.这种认知上下文的切换会带来很多隐形的时间浪费. ...

  7. linux管道命令学习(一)

    继续看鸟哥私房菜,看一直很想弄懂的管道命令(pipe).第一次知道管道这个词还是在学django的时候,模板里的过滤器很像这里的管道.管道就是将输出在标准输出中的信息一次次处理最终打印在标准输出中,所 ...

  8. LeetCode: Partition List 解题报告

    Partition List Given a linked list and a value x, partition it such that all nodes less than x come ...

  9. maven项目强制自动更新所有jar包

    选中即可:

  10. ActiveMQ入门实例(转)

    转载自:http://www.cnblogs.com/xwdreamer/archive/2012/02/21/2360818.html 1.下载ActiveMQ 去官方网站下载:http://act ...