zoukankan      html  css  js  c++  java
  • MD2关键桢动画3D模型加载.

      在看Cg教程中,看到关键桢插值来表示一个动画的物体,例如一个动物拥有站着,奔跑,下跪等动画序列,美工将这些特定的姿态称为一个关键桢。为什么要用关键桢这种来表示了,这个比较容易理解,我们知道我们看的一些卡通动画,都不是每桢来画的,都是准备一些关键的过渡动画,然后,美工人员在根据每二幅之间来补充一些中间的动画,以增加精细的效果。

      MD2模型文件就是存储一些关键桢的动画模型,格式还是很简单的,对比OBJ模型来说,更容易读取,分为几个主要部分,一部分是头文件,里面对相应的数据描述在那,如多个面,多少桢,从那读顶点,读桢都有说明,头文件后就是数据存放位置了。

      我们先来看下头文件的定义,有用的部分我做了注释。

     1 type Md2Header =
     2     struct
     3         val magic:           int         //MD2文件标示   
     4         val version:         int         //MD2版本
     5         val skinWidth:       int         //纹理宽度
     6         val skinHeight:      int         //纹理长度
     7         val frameSize:       int         //桢的大小
     8         val numSkins:        int         //
     9         val numVertices:     int         //多少个顶点(每桢数量相同,数据不同)
    10         val numTexCoords:    int         //多少个纹理顶点(所有桢共用)
    11         val numTriangles:    int         //每桢由多少个三角形组成,所有桢是一样的
    12         val numGlCommands:   int         //用VBO直接放弃
    13         val numFrames:       int         //多少桢
    14         val offsetSkins:     int         //
    15         val offsetTexCoords: int         //从那开始读纹理数据
    16         val offsetTriangles: int         //从那开始读三角形
    17         val offsetFrames:    int         //从那开始读桢数据
    18         val offsetGlCommands:int         //无用
    19         val offsetEnd:       int         //可以用来检查
    20     end    
    MD2 头部格式

      然后就是对MD2模型文件的读取了,对MD2整个解析,不包含着色器代码只有200行,可以说读取与绘制比较容易,需要注意的是,一个MD2模型文件中三角形也就我们要画的面是所有桢共有的,在三角形中包含当前顶点的偏移量。这样在所有桢中,三角形的顶点不一样,但是他的纹理索引与纹理是一样的,每桢要画的三角形的个数也是一样的。所以在模型中,他们可以共有纹理缓冲区与顶点索引缓冲区,而每桢要自己建立顶点缓冲区,因顶点的不同,造成法线也会变,故每桢还需要自己建立法线缓冲区,下面是主要代码。

      1 type Md2Frame(md2Model: Md2Model,count:int) =
      2     let mutable points = Array2D.create 0 0 0.f
      3     let mutable vbo = 0
      4     member val Vectexs = Array.create count Vector3.Zero
      5     member val Name = ""
      6     member this.VBO with get() = vbo
      7     member this.Faces with get() : ArrayList<int[]*int[]> = md2Model.Faces
      8     member this.TexCoords with get():ArrayList<float32*float32> = md2Model.TexCoords
      9     member this.ElementCount with get() = md2Model.ElementCount
     10     member this.DataArray 
     11         with get() = 
     12             if points.Length = 0 then this.CreateData()
     13             points
     14     //MD2中不变的是面的面数.面里的顶点根据桢里保存的不同而不同,而面用的纹理是用的同一数据
     15     member this.CreateData() =
     16         let normals = Array.create this.Vectexs.Length (0.f,Vector3.Zero)
     17         //遍历第一次,生成面法线,记录对应点的共面,共法线信息
     18         this.Faces.ForEach(fun p ->
     19             let vi = fst p
     20             let p1 = this.Vectexs.[vi.[1]] - this.Vectexs.[vi.[0]]
     21             let p2 = this.Vectexs.[vi.[2]] - this.Vectexs.[vi.[0]]
     22             let normal = -Vector3.Cross(p1,p2)
     23             vi |> Array.iter(fun v ->
     24                 let mutable ind,n = normals.[v]
     25                 n <- n + normal
     26                 normals.[v] <- (ind+1.f,n)
     27                 )
     28             )
     29         //平均点的法线信息并且组装N3fV3f
     30         points <- Array2D.init this.ElementCount 6 (fun i j ->
     31             //当前面,当前面的第几个点
     32             let m,n = i/3,i%3
     33             let vi = fst this.Faces.[m]
     34             match j with
     35             | 0|1|2 -> 
     36                 let vn = snd normals.[vi.[n]]/fst normals.[vi.[n]]
     37                 if j = 0 then vn.X elif j = 1 then vn.Y else vn.Z  
     38             | 3|4|5 -> 
     39                 let p = this.Vectexs.[vi.[n]]
     40                 if j = 3 then p.X elif j = 4 then p.Y else p.Z
     41             )        
     42     member this.CreateVBO() =
     43         if vbo = 0 then
     44             vbo <- GL.GenBuffers(1)
     45             GL.BindBuffer(BufferTarget.ArrayBuffer,vbo)
     46             GL.BufferData(BufferTarget.ArrayBuffer,IntPtr (4 * this.ElementCount * 6),this.DataArray,BufferUsageHint.StaticDraw)
     47 
     48 and Md2Model(fileName:string,?texureName:string) =
     49     inherit ModelCommon()
     50     let mutable vbo,ebo = 0,0
     51     member val Header = Md2Header() with get,set
     52     member val Faces = new ArrayList<int[]*int[]>()
     53     member val TexCoords = new ArrayList<float32*float32>()
     54     member val Frames = new ArrayList<Md2Frame>()
     55     member val texID = 0 with get,set  
     56     member val CurrentFrame = 0.f with get,set  
     57     member this.ElementCount with get() = this.Faces.Count * 3
     58     member this.TotalFrames with get() = this.Frames.Count
     59     member this.LoadModel() =  
     60         //加载纹理
     61         if texureName.IsSome then
     62             let dict = Path.GetDirectoryName(fileName)
     63             this.texID <- TexTure.Load(Path.Combine(dict,texureName.Value))
     64         //加载MD2程序
     65         let file = new FileStream(fileName,FileMode.Open, FileAccess.Read)
     66         let binary = new BinaryReader(file)
     67         let size = Marshal.SizeOf(this.Header)
     68         let mutable bytes = Array.create size 0uy
     69         file.Read(bytes, 0, bytes.Length) |> ignore
     70         let allocIntPtr = Marshal.AllocHGlobal(size)
     71         Marshal.Copy(bytes,0,allocIntPtr,size)
     72         this.Header <- Marshal.PtrToStructure(allocIntPtr,typeof<Md2Header>) :?> Md2Header 
     73         //读取纹理数据         
     74         file.Seek(int64 this.Header.offsetTexCoords,SeekOrigin.Begin)|> ignore
     75         let mTexCoords = Array.init this.Header.numTexCoords (fun p -> 
     76             float32 (binary.ReadInt16())/float32 this.Header.skinWidth,
     77             float32 (binary.ReadInt16())/float32 this.Header.skinWidth
     78             )
     79         this.TexCoords.AddRange(mTexCoords)
     80         //读取面数(顶点索引与纹理索引)
     81         file.Seek(int64 this.Header.offsetTriangles,SeekOrigin.Begin)|> ignore
     82         let mtriangles = Array.init this.Header.numTriangles (fun p ->
     83             [|int (binary.ReadInt16());int (binary.ReadInt16());int (binary.ReadInt16())|],
     84             [|int (binary.ReadInt16());int (binary.ReadInt16());int (binary.ReadInt16())|]
     85             )
     86         this.Faces.AddRange(mtriangles)
     87         //读取所有桢
     88         file.Seek(int64 this.Header.offsetFrames,SeekOrigin.Begin)|> ignore
     89         let frames = Array.init this.Header.numFrames (fun p -> 
     90             let frame = Md2Frame(this,this.Header.numVertices)
     91             let scale = Vector3(binary.ReadSingle(),binary.ReadSingle(),binary.ReadSingle())
     92             let translate = Vector3(binary.ReadSingle(),binary.ReadSingle(),binary.ReadSingle())
     93             let name = binary.ReadChars(16)
     94             //这桢的所有点
     95             let vectexs = Array.init this.Header.numVertices (fun t -> 
     96                 let mvertex = [|binary.ReadByte();binary.ReadByte();binary.ReadByte()|]
     97                 let mlightNormalIndex = binary.ReadByte()
     98                 mvertex,mlightNormalIndex
     99                 )
    100             //桢上的点精确化
    101             vectexs |> Array.iteri(fun i v ->
    102                 frame.Vectexs.[i].X <- float32 (fst v).[0] * scale.X + translate.X
    103                 frame.Vectexs.[i].Y <- float32 (fst v).[2] * scale.Z + translate.Z
    104                 frame.Vectexs.[i].Z <- float32 (fst v).[1] * -scale.Y - translate.Y
    105                 )
    106             frame
    107             )
    108         this.Frames.AddRange(frames)
    109         //生成正确的数据
    110         binary.Close()
    111         file.Close()
    112     member this.FrameStep 
    113         with get() =
    114            let currentFrame = int (Math.Floor(float this.CurrentFrame))   
    115            let step = this.CurrentFrame - float32 currentFrame 
    116            currentFrame,step
    117     member this.CreateEBO() =
    118         let len = this.ElementCount - 1
    119         let eboData = [|0..len|]
    120         ebo <- GL.GenBuffers(1)
    121         GL.BindBuffer(BufferTarget.ElementArrayBuffer,ebo)
    122         GL.BufferData(BufferTarget.ElementArrayBuffer,IntPtr (4 * this.ElementCount),eboData,BufferUsageHint.StaticDraw)
    123     member this.CreateVBO() =
    124         let texCoords = Array2D.init this.ElementCount 2 (fun i j ->
    125             //当前面,当前面的第几个点
    126             let m,n = i/3,i%3
    127             let ti = snd this.Faces.[m]
    128             let u,v = this.TexCoords.[ti.[n]]
    129             if j = 0 then u else v
    130             )  
    131         vbo <- GL.GenBuffers(1)
    132         GL.BindBuffer(BufferTarget.ArrayBuffer,vbo)
    133         GL.BufferData(BufferTarget.ArrayBuffer,IntPtr (4 * 2 * this.ElementCount),texCoords,BufferUsageHint.StaticDraw)
    134     member this.Render() =
    135         if vbo = 0 then this.CreateVBO()
    136         if ebo = 0 then this.CreateEBO()  
    137         if this.CurrentFrame >= float32 this.TotalFrames - 1.f then this.CurrentFrame <- 0.f
    138         let currentFrame = this.Frames.[fst this.FrameStep]
    139         let nextFrame = this.Frames.[fst this.FrameStep + 1]
    140         currentFrame.CreateVBO()
    141         nextFrame.CreateVBO() 
    142         //当前桢的法线与顶点
    143         GL.BindBuffer(BufferTarget.ArrayBuffer,currentFrame.VBO)
    144         GL.InterleavedArrays(InterleavedArrayFormat.N3fV3f,0,IntPtr.Zero)
    145         //如果有纹理        
    146         if this.texID > 0 && vbo > 0 then    
    147             GL.BindBuffer(BufferTarget.ArrayBuffer,vbo)                     
    148             GL.ClientActiveTexture(TextureUnit.Texture0)
    149             GL.EnableClientState(ArrayCap.TextureCoordArray) 
    150             GL.TexCoordPointer(2,TexCoordPointerType.Float,0,IntPtr.Zero)   
    151         //下一桢的法线与顶点存放在Texture1与Texture2
    152         GL.BindBuffer(BufferTarget.ArrayBuffer,nextFrame.VBO)   
    153         //下一桢顶点
    154         GL.ClientActiveTexture(TextureUnit.Texture1)
    155         GL.EnableClientState(ArrayCap.TextureCoordArray)
    156         GL.TexCoordPointer(3,TexCoordPointerType.Float,24,IntPtr 12) 
    157         //下一桢法线
    158         GL.ClientActiveTexture(TextureUnit.Texture2)
    159         GL.EnableClientState(ArrayCap.TextureCoordArray)
    160         GL.TexCoordPointer(3,TexCoordPointerType.Float,24,IntPtr.Zero)  
    161         //绘画
    162         GL.BindBuffer(BufferTarget.ElementArrayBuffer,ebo)
    163         GL.DrawElements(BeginMode.Triangles,this.ElementCount,DrawElementsType.UnsignedInt,IntPtr.Zero)
    164         //一定要按顺序执行这几行,不行,会影响后面的代码
    165         GL.ClientActiveTexture(TextureUnit.Texture0)
    166         GL.DisableClientState(ArrayCap.TextureCoordArray)
    167         GL.ClientActiveTexture(TextureUnit.Texture1)
    168         GL.DisableClientState(ArrayCap.TextureCoordArray)
    169         GL.ClientActiveTexture(TextureUnit.Texture2)
    170         GL.DisableClientState(ArrayCap.TextureCoordArray)
    MD2 读取模型。

      一些部分我做了注释,相信看懂不难。这段代码有些长,因为读取与存取缓冲区,绘画全在这里了,介绍一下主要方法实现,为了免去桢与模型中的数据交换,故让他们互相引用,其中F#需要二个类用and来连接,Md2Model的方法LoadModel主要加载纹理,然后根据头文件里的各部分偏移量加载纹理坐标信息,加载三角形面数,加载桢数据,需要注意的量,纹理读取出的是当前像素位置,意思给opengl需要除以对应的长宽,而桢里的数据因为MD2模型生成工具的Z是向上的,Y是从人向屏幕的方向,而Opengl中Z是屏幕向人的方向,Y才是向上的,帮我们需要仔细对应。

      如前面所面,模型自己建立了纹理数组的缓冲区以及顶点索引缓冲区,在Md2Model中用vbo,ebo表示,而在桢里,需要自己建立桢自己的顶点与法线缓冲区,法线生成方法和上遍中OBJ模型中法线生成是一样的,定义一个和顶点一样长的数据,以顶点的下标来表示顶点的法线。

      建立了各个缓冲区,我们需要来画了,根据前面对关键桢的介绍,我想我们需要当前桢与下一桢的数据,在这里面,我们定义一个不断向前走的CurrentFrame,他在等于2.3时,我们知道,他在第二桢与第三桢之间,靠近第二桢多点。在Md2Model里的Renader有具体实现,对当前桢,我们以正常的方式传入,顶点,法线以OpenGL的方式来,但是下一桢的数据如何传了,在这里和上遍中OBJ传入切线的方法比较相似,我们用当前第几份纹理来存取,不用着色器可不容易取来当正确数据用了,分别设点当前纹理,然后存入对应下一桢的顶点与法线到对应的纹理坐标中,这里首先要注意,顶点与法线放在一个数组里,所以设定的时候要注意正确的偏移量,最后注意要执行下面的关闭纹理代码,不然会影响当前与后面执行过程。

      数据传入OpenGL后,我们需要在顶点着色器中执行插值过程,使之看起来连续,一般我们采用线性插值方式,使用的是Cg着色器语言,后面如果没特别指定,默认都是Cg着色器语言,相关如果启用Cg环境,请看上篇文章。  

     1 void v_main(float3 positionA : POSITION,
     2             float3 normalA : NORMAL,
     3             float2 texCoord : TEXCOORD0,
     4             float3 positionB : TEXCOORD1,
     5             float3 normalB : TEXCOORD2,
     6             out float4 oPosition : POSITION,
     7             out float3 objectPos : TEXCOORD0,
     8             out float3 oNormal : TEXCOORD1,
     9             out float2 oTexCoord : TEXCOORD2,
    10             uniform float framstep,
    11             uniform float4x4 mvp)
    12 {
    13     float3 position = lerp(positionA, positionB,framstep);//positionA; 
    14     oPosition = mul(mvp,float4(position,1.0));
    15     oNormal = lerp(normalA, normalB,framstep);//normalA;
    16     oTexCoord = texCoord;
    17     objectPos = position.xyz;
    18 }
    顶点着色器

      整个过程很简单,对当前桢与下一桢做线性插值,传入的不带前缀的参数中,对应的后缀指向当前Opengl传入的数据,如POSITION是当前桢的顶点,Normal是当前桢的法线,而TEXCOORD1与TEXCOORD2分别指定下一桢的顶点与法线。带Out前缀的,除了POSITION后缀有意义,别的后缀都只是用来与片断着色器对应的,没有具体的意义。

      片断着色器和上篇中的一样,就不贴出来了,下面看下效果图。

      主要代码 引用DLL Md2模型文件 和前面一样,其中EDSF前后左右移动,鼠标右键加移动鼠标控制方向,空格上升,空格在SHIFT下降。

      大家组织好对应目录应该就可以看到效果了。

     PS 2013/12/20 16:20.

      在上面的把数据从OpenGL传入着色器中时,模访的是Cg基础教程16课,但是总感觉别扭,把法线顶点分别放入纹理这种方式,就和前面把切线放入本来颜色位置一样,感觉不爽,虽然功能是实现了,但是代码总感觉阅读时容易出乱子,后查找得这个API(glvertexattribpointer),在GLSL里,着色器根据传入的attribut来对每个顶点附加数据,glsl里有的,没道理cg里没有,查找在http://3dgep.com/?p=2665中如下:

    Cg defines the following default semantics and the default generic attribute ID’s that are bound to the semantic.

    BINDING SEMANTICS NAMECORRESPONDING DATA
    POSITION, ATTR0 Input Vertex, Generic Attribute 0
    BLENDWEIGHT, ATTR1 Input vertex weight, Generic Attribute 1
    NORMAL, ATTR2 Input normal, Generic Attribute 2
    DIFFUSE, COLOR0, ATTR3 Input primary color, Generic Attribute 3
    SPECULAR, COLOR1, ATTR4 Input secondary color, Generic Attribute 4
    TESSFACTOR, FOGCOORD, ATTR5 Input fog coordinate, Generic Attribute 5
    PSIZE, ATTR6 Input point size, Generic Attribute 6
    BLENDINDICES, ATTR7 Generic Attribute 7
    TEXCOORD0-TEXCOORD7, ATTR8-ATTR15 Input texture coordinates (texcoord0-texcoord7), Generic Attributes 8-15
    TANGENT, ATTR14 Generic Attribute 14
    BINORMAL, ATTR15 Generic Attribute 15

    Don’t worry if this concept of semantics doesn’t make sense yet. I will go into more detail about semantics when I show how we send the vertex data to the shader program. I will just define a few macros that are used to refer to these predefined generic attributes.

      根据上面描述,把原来里面绘制二桢数据传值部分改为:

     1     member this.Render() =
     2         if vbo = 0 then this.CreateVBO()
     3         if ebo = 0 then this.CreateEBO()  
     4         if this.CurrentFrame >= float32 this.TotalFrames - 1.f then this.CurrentFrame <- 0.f
     5         let currentFrame = this.Frames.[fst this.FrameStep]
     6         let nextFrame = this.Frames.[fst this.FrameStep + 1]
     7         currentFrame.CreateVBO()
     8         nextFrame.CreateVBO() 
     9         //当前桢的法线与顶点
    10         GL.BindBuffer(BufferTarget.ArrayBuffer,currentFrame.VBO)
    11         GL.VertexAttribPointer(0,3,VertexAttribPointerType.Float,false,24,IntPtr 12)
    12         GL.EnableVertexAttribArray(0)
    13         GL.VertexAttribPointer(2,3,VertexAttribPointerType.Float,false,24,IntPtr.Zero)
    14         GL.EnableVertexAttribArray(2)
    15         //如果有纹理        
    16         if this.texID > 0 && vbo > 0 then    
    17             GL.BindBuffer(BufferTarget.ArrayBuffer,vbo)   
    18             GL.VertexAttribPointer(8,2,VertexAttribPointerType.Float,false,0,IntPtr.Zero)
    19             GL.EnableVertexAttribArray(8)
    20         //下一桢的法线与顶点存放在Texture1与Texture2
    21         GL.BindBuffer(BufferTarget.ArrayBuffer,nextFrame.VBO)   
    22         GL.VertexAttribPointer(9,3,VertexAttribPointerType.Float,false,24,IntPtr 12)
    23         GL.EnableVertexAttribArray(9)
    24         GL.VertexAttribPointer(10,3,VertexAttribPointerType.Float,false,24,IntPtr.Zero)
    25         GL.EnableVertexAttribArray(10)
    26         //绘画
    27         GL.BindBuffer(BufferTarget.ElementArrayBuffer,ebo)
    28         GL.DrawElements(BeginMode.Triangles,this.ElementCount,DrawElementsType.UnsignedInt,IntPtr.Zero)
    新版 绘画动画

      着色器部分改为:

     1 void v_main(float3 positionA : ATTR0,
     2             float3 normalA : ATTR3,
     3             float2 texCoord : ATTR8,
     4             float3 positionB :  ATTR9,
     5             float3 normalB : ATTR10,
     6             out float4 oPosition : POSITION,
     7             out float3 objectPos : TEXCOORD0,
     8             out float3 oNormal : TEXCOORD1,
     9             out float2 oTexCoord : TEXCOORD2,
    10             uniform float framstep,
    11             uniform float4x4 mvp)
    12 {
    13     float3 position = lerp(positionA, positionB,framstep);//positionA; 
    14     oPosition = mul(mvp,float4(position,1.0));
    15     oNormal = lerp(normalA, normalB,framstep);//normalA;
    16     oTexCoord = texCoord;
    17     objectPos = position.xyz;
    18 }
    新版 着色器

      可以看到,完美运行,这部分附件就不放了,大家直接复制到原来的代码上就好了,其中,代码里的glvertexattribpointer给的序号与Opengl脱离顶点,法线等对应关系上,上面写的好像0对应顶点一样,实际我的代码开始也是根据对应关系来写的,但是根据实际测试,1放顶点,只要着色器ATTR1对应放顶点也是可以的,这样想想才是对的,都已经脱离固定管线了,本来传上来的数据各式各样,系统根据定义名称来对应本就死板,给我们自己联系就好.改好后,看这代码再也没别扭的地方了.

      

     

      

  • 相关阅读:
    如何更专业的使用Chrome开发者工具
    Javascript中的Object对象
    【leetcode】 Remove Duplicates from Sorted List
    Windows上x86程序正常但x64程序崩溃问题
    Microsoft source-code annotation language (SAL) 相关
    Visual Studio 2013 编译CEF步骤
    C++中调用Python脚本
    MFCButton Memory leak(内存泄露问题)
    快速排序
    插入排序
  • 原文地址:https://www.cnblogs.com/zhouxin/p/3483151.html
Copyright © 2011-2022 走看看