OpenGL OBJ模型加载.
在我們前面繪制一個屋,我們可以看到,需要每個立方體一個一個的自己來推并且還要處理位置信息.代碼量大并且要時間.現在我們通過加載模型文件的方法來生成模型文件,比較流行的3D模型文件有OBJ,FBX,dae等,其中OBJ模式只包含靜態的模型,相對FBX這種來說,比較簡單,剛好給我們用來學習之用.
對比我們之前用代碼來一個一個建模型,用模型文件OBJ的不同就是在OBJ里包含了我們需要的頂點,法線,以及紋理坐標以及頂點組成面索引.去掉了我們用代碼建模最要時的過程.用模型文件我們要做的僅僅是讀出里面的信息,然后組織供OpenGL調用.
不同的模型文件有不同的信息組織格式,相對于FBX這種二進制并且沒公布格式的文件來說,OBJ模型文本結構對于我們來說更易讀并且容易理解,網上也有不少大神對OBJ模型中出現的文本做了詳細的解說并提供相應的加載模型方法.
OBJ模型文件的結構、導入與渲染Ⅰ?OBJ模型文件的結構、導入與渲染Ⅱ
在上面二篇文章中以及文章中的鏈接,有對OBJ模型比較詳細的解說以及加載,與原文章加載稍有不同的是,我們解析相應數據按照OBJ模型的定義來定義結構.
在OBJ模型中主要分二塊,一塊是模型組成文件,包含頂點,法線,紋理坐標,面,組的信息,另一塊是模型文件所需的材質信息與對應紋理所需圖片.
我們分別定義第一塊的數據結構如下:VertexAttribute,ObjFace,ObjGroup.第二塊ObjMaterialItem,ObjMaterial.其中模型定義為ObjModel.代碼如下:
1 type ArrayList<'T> = System.Collections.Generic.List<'T> 2 3 type ObjMaterialItem() = 4 member val Name = "" with get,set 5 member val Ambient = [|0.f;0.f;0.f;0.f|] with get,set 6 member val Diffuse = [|0.f;0.f;0.f;0.f|] with get,set 7 member val Specular = [|0.f;0.f;0.f;0.f|] with get,set 8 member val Shiness = 0.f with get,set 9 member val DiffuseMap = "" with get,set 10 member val SpecularMap = "" with get,set 11 member val BumpMap = "" with get,set 12 member val DiffuseID = 0 with get,set 13 member val SpecularID = 0 with get,set 14 member val BumpID = 0 with get,set 15 16 type ObjMaterial() = 17 member val Name = "" with get,set 18 member val Items = new ArrayList<ObjMaterialItem>() with get,set 19 member val currentItem = new ObjMaterialItem() with get,set 20 21 type VertexAttribute() = 22 let strToInt str = 23 let (ok,f) = System.Int32.TryParse(str) 24 if ok then f else -1 25 member val Position= Vector3.Zero with get,set 26 member val Texcoord=Vector2.Zero with get,set 27 member val Normal= Vector3.Zero with get,set 28 member val PositionIndex = -1 with get,set 29 member val TexcoordIndex = -1 with get,set 30 member val NormalIndex = -1 with get,set 31 //各個值的索引信息 32 member this.SetValue(line:string) = 33 let ls = line.Split('/') 34 match ls.Length with 35 | 1 -> 36 this.PositionIndex <- strToInt ls.[0] 37 | 2 -> 38 this.PositionIndex <- strToInt ls.[0] 39 this.TexcoordIndex <- strToInt ls.[1] 40 | 3 -> 41 this.PositionIndex <- strToInt ls.[0] 42 this.NormalIndex <- strToInt ls.[2] 43 if not (ls.[1] = "" || ls.[1] = null) then 44 this.TexcoordIndex <- strToInt ls.[1] 45 | _ -> () 46 //組織格式用T2fV3f/N3fV3f/T2fN3fV3f/V3f成float32[] 47 member this.PointArray 48 with get() = 49 let mutable ps = Array.create 0 0.0f 50 if this.TexcoordIndex > 0 then ps <- Array.append ps [|this.Texcoord.X;1.0f - this.Texcoord.Y|] 51 if this.NormalIndex > 0 then ps <- Array.append ps [|this.Normal.X;this.Normal.Y;this.Normal.Z|] 52 if this.PositionIndex > 0 then ps <- Array.append ps [|this.Position.X;this.Position.Y;this.Position.Z|] 53 ps 54 55 type ObjFace() = 56 let mutable vectexs = [||] : VertexAttribute array 57 //每個面的頂點,一個是三角形,如果是矩形,為了兼容性,應該化為成二個三角形. 58 member this.Vectexs 59 with get() = 60 let mutable result = vectexs.[0..] 61 if vectexs.Length = 4 then 62 let newvxs = [|vectexs.[0];vectexs.[2]|] 63 result <- Array.append result newvxs 64 result 65 //在讀取文件時,得到當前面包含的頂點索引信息.(此時對應頂點只有索引,沒有真實數據) 66 member this.AddVectex (line:string) = 67 let ls = line.TrimEnd(' ').Split(' ') 68 let vs = 69 ls |> Array.map(fun p -> 70 let va = new VertexAttribute() 71 va.SetValue(p) 72 va) 73 vectexs <- vs 74 member this.VertexCount with get() = this.Vectexs.Length 75 76 type ObjGroup() = 77 //得到數組里所有面的對應所有頂點屬性 78 let mutable vectexs = new ArrayList<VertexAttribute>() 79 let mutable points = Array2D.create 0 0 0.f 80 let mutable vbo,ebo = 0,0 81 member val Faces = new ArrayList<ObjFace>() with get,set 82 member val Mtllib = "" with get,set 83 member val Usemtl = "" with get,set 84 member val Name = "" with get,set 85 member val Material = new ObjMaterialItem() with get,set 86 member val IsHaveMaterial = false with get,set 87 member val Path = "" with get,set 88 member this.VBO with get() = vbo 89 member this.EBO with get() = ebo 90 //讀取文件,讀取當前group里的面的信息,并且會在讀面信息時讀取到這個面所有頂點索引 91 member this.AddFace (line:string) = 92 let face = new ObjFace() 93 face.AddVectex(line) 94 this.Faces.Add(face) 95 vectexs.AddRange(face.Vectexs) 96 //組織一個規則二維數組,一維表示每面上的每個頂點,二維表示每個頂點是如何組織,包含法向量,紋理坐標不 97 member this.DataArray 98 with get() = 99 if points.Length < 1 then 100 let length1 = vectexs.Count 101 if length1 > 0 then 102 let length2 = vectexs.[0].PointArray.Length 103 if length2 > 0 then 104 points <- Array2D.init length1 length2 (fun i j -> vectexs.[i].PointArray.[j]) 105 points 106 member this.CreateVBO() = 107 if this.ElementLength > 0 then 108 vbo <- GL.GenBuffers(1) 109 GL.BindBuffer(BufferTarget.ArrayBuffer,vbo) 110 GL.BufferData(BufferTarget.ArrayBuffer,IntPtr (4 *this.ElementLength*this.VectorLength ),this.DataArray,BufferUsageHint.StaticDraw) 111 let len = this.ElementLength - 1 112 let eboData = [|0..len|] 113 ebo <- GL.GenBuffers(1) 114 GL.BindBuffer(BufferTarget.ElementArrayBuffer,ebo) 115 GL.BufferData(BufferTarget.ElementArrayBuffer,IntPtr (4 * this.ElementLength),eboData,BufferUsageHint.StaticDraw) 116 if this.IsHaveMaterial then 117 let kdPath = Path.Combine(this.Path,this.Material.DiffuseMap) 118 if File.Exists kdPath then 119 this.Material.DiffuseID <- TexTure.Load(kdPath) 120 member this.DrawVBO() = 121 if this.VBO >0 && this.EBO >0 then 122 GL.BindBuffer(BufferTarget.ArrayBuffer,this.VBO) 123 GL.BindBuffer(BufferTarget.ElementArrayBuffer,this.EBO) 124 if this.IsHaveMaterial then 125 GL.Enable(EnableCap.Texture2D) 126 GL.BindTexture(TextureTarget.Texture2D,this.Material.DiffuseID) 127 GL.InterleavedArrays(this.InterFormat,0,IntPtr.Zero) 128 GL.DrawElements(BeginMode.Triangles,this.ElementLength,DrawElementsType.UnsignedInt,IntPtr.Zero) 129 GL.Disable(EnableCap.Texture2D) 130 //多少個頂點 131 member this.ElementLength with get() = Array2D.length1 this.DataArray 132 //頂點組織形式長度T2fV3f/N3fV3f/T2fN3fV3f/V3f 133 member this.VectorLength with get() = Array2D.length2 this.DataArray 134 //頂點組織形式 135 member this.InterFormat 136 with get()= 137 let mutable result = InterleavedArrayFormat.T2fN3fV3f 138 if this.VectorLength = 3 then result <- InterleavedArrayFormat.V3f 139 if this.VectorLength = 5 then result <- InterleavedArrayFormat.T2fV3f 140 if this.VectorLength = 6 then result <- InterleavedArrayFormat.N3fV3f 141 result 142 143 type ObjModel(fileName:string) = 144 let mutable groupName = "default" 145 let mutable groups = [] : ObjGroup list 146 let addGroup group = groups <- (group :: groups) 147 //得到每行數組去掉標識符后的數據如 v 1.0 2.0 3.0 -> 1.0 2.0 3.0 148 let getLineValue (line:string) = 149 let fs = line.Split(' ') 150 let len = fs.Length - 1 151 if fs.Length > 1 then (fs.[1..len] |> Array.filter (fun p -> p <> null && p<> " " && p <> "")) 152 else [|line|] 153 //數組轉化成float32 154 let strToFloat str = 155 let (ok,f) = System.Single.TryParse(str) 156 if ok then f else System.Single.NaN 157 let mutable group = ObjGroup() 158 let mutable mtllib = "" 159 member val Positions = new ArrayList<Vector3>() with get,set 160 member val Normals = new ArrayList<Vector3>() with get,set 161 member val Texcoords = new ArrayList<Vector2>() with get,set 162 member val Materials = new ArrayList<ObjMaterial>() with get,set 163 member this.Path 164 with get() = System.IO.Path.GetDirectoryName(fileName) 165 member this.GetLineFloatArray (line:string) = 166 let fs = getLineValue(line) 167 fs |> Array.map (fun p -> strToFloat p) 168 member this.GetLineValue (line:string,?sep) = 169 let dsep = defaultArg sep " " 170 let fs = getLineValue(line) 171 String.concat dsep fs 172 member this.CurrentGroup 173 with get() = 174 let bExist = groups |> List.exists(fun p -> p.Name = groupName) 175 if not bExist then 176 let objGroup = new ObjGroup() 177 objGroup.Name <- groupName 178 objGroup.Mtllib <- mtllib 179 addGroup objGroup 180 group <- groups |> List.find(fun p -> p.Name = groupName) 181 group 182 member this.Groups 183 with get() = 184 groups 185 //主要有二步,首先讀取文件信息,然后把頂點,法線,紋理坐標根據索引來賦值 186 member this.LoadObjModel(?bCreateVBO) = 187 let bCreate = defaultArg bCreateVBO false 188 let file = new StreamReader(fileName) 189 let mutable beforeFace = false 190 let (|StartsWith|) suffix (s:string) = s.TrimStart(' ','\t').StartsWith(suffix,StringComparison.OrdinalIgnoreCase) 191 //首先讀取文件信息,此時頂點只有索引信息. 192 while not file.EndOfStream do 193 let str = file.ReadLine() 194 match str with 195 | StartsWith "mtllib " true -> 196 mtllib <- this.GetLineValue(str) 197 //#region 讀紋理 198 let material = new ObjMaterial() 199 material.Name <- mtllib 200 let mtlFile = new StreamReader(Path.Combine(this.Path,mtllib)) 201 while not mtlFile.EndOfStream do 202 let str = mtlFile.ReadLine() 203 match str with 204 | null -> () 205 | StartsWith "newmtl " true -> 206 material.currentItem <- new ObjMaterialItem() 207 material.currentItem.Name <- this.GetLineValue(str) 208 material.Items.Add(material.currentItem) 209 | StartsWith "ka " true -> material.currentItem.Ambient <- this.GetLineFloatArray(str) 210 | StartsWith "kd " true -> material.currentItem.Diffuse <- this.GetLineFloatArray(str) 211 | StartsWith "ks " true -> material.currentItem.Specular <- this.GetLineFloatArray(str) 212 | StartsWith "map_Kd " true -> material.currentItem.DiffuseMap <- this.GetLineValue(str) 213 | StartsWith "map_Ks " true -> material.currentItem.SpecularMap <- this.GetLineValue(str) 214 | StartsWith "map_bump " true -> material.currentItem.BumpMap <- this.GetLineValue(str) 215 | StartsWith "Ns " true -> 216 let ns = this.GetLineFloatArray(str).[0] 217 material.currentItem.Shiness <- ns * 0.128f 218 | _ -> () 219 mtlFile.Close() 220 this.Materials.Add(material) 221 //#endregion 222 | null -> () 223 | StartsWith "usemtl " true -> this.CurrentGroup.Usemtl <- this.GetLineValue(str) 224 | StartsWith "g " true -> 225 groupName <- this.GetLineValue(str) 226 beforeFace <- false 227 | StartsWith "vn " true -> 228 let fs = this.GetLineFloatArray(str) 229 this.Normals.Add(Vector3(fs.[0],fs.[1],fs.[2])) 230 | StartsWith "vt " true -> 231 let fs = this.GetLineFloatArray(str) 232 this.Texcoords.Add(Vector2(fs.[0],fs.[1])) 233 | StartsWith "v " true -> 234 let fs = this.GetLineFloatArray(str) 235 this.Positions.Add(Vector3(fs.[0],fs.[1],fs.[2])) 236 | StartsWith "f " true -> 237 if beforeFace then 238 group.AddFace(this.GetLineValue(str)) 239 else 240 this.CurrentGroup.AddFace(this.GetLineValue(str)) 241 beforeFace <- true 242 | _ -> printfn "%s" ("---------"+str) 243 file.Close() 244 //根據索引信息來給對應的頂點,法線,紋理坐標賦值 245 groups |>List.iter (fun p -> 246 p.Faces.ForEach(fun face -> 247 face.Vectexs |> Array.iter(fun vect -> 248 if vect.PositionIndex > 0 then vect.Position <-this.Positions.[vect.PositionIndex-1] 249 if vect.TexcoordIndex > 0 then vect.Texcoord <- this.Texcoords.[vect.TexcoordIndex-1] 250 if vect.NormalIndex > 0 then vect.Normal <- this.Normals.[vect.NormalIndex-1] 251 ) 252 ) 253 let mater = this.Materials.Find(fun m -> m.Name = p.Mtllib) 254 if box(mater) <> null then 255 let mitem = mater.Items.Find(fun i -> i.Name = p.Usemtl) 256 if box(mitem) <> null then 257 p.Material <- mitem 258 p.Path <- this.Path 259 p.IsHaveMaterial <- true 260 ) 261 //釋放空間 262 this.Positions.Clear() 263 this.Normals.Clear() 264 this.Texcoords.Clear() 265 if bCreate then this.CreateVbo() 266 //生成VBO信息 267 member this.CreateVbo() = 268 this.Groups |> List.iter (fun p -> p.CreateVBO()) 269 member this.DrawVbo() = 270 this.Groups |> List.iter (fun p -> p.DrawVBO()) View Code其中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下降。
轉載于:https://www.cnblogs.com/zhouxin/p/3453809.html
總結
以上是生活随笔為你收集整理的OpenGL OBJ模型加载.的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 网络资源汇总
- 下一篇: oracle 12 问题:需要 Orac