zoukankan      html  css  js  c++  java
  • 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(' ','	').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时,我们已知其中这个二维数组的长度,和各个对应元素值.

    下面给出效果图:

    image

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

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

  • 相关阅读:
    处理在finally中出现的异常(Java)
    【转】alt和title属性的区别及应用
    IE6下兼容CSS属性minheight的解决办法
    javascript中判断字符串是否以指定字符串开始或结尾
    IE6兼容改造中的反思
    字符操作函数
    魔术公式
    抽象类和纯虚函数
    悬空指针
    重载
  • 原文地址:https://www.cnblogs.com/zhouxin/p/3453809.html
Copyright © 2011-2022 走看看