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对应放顶点也是可以的,这样想想才是对的,都已经脱离固定管线了,本来传上来的数据各式各样,系统根据定义名称来对应本就死板,给我们自己联系就好.改好后,看这代码再也没别扭的地方了.

  

 

  

posted @ 2013-12-20 02:39  天天不在  阅读(3669)  评论(1编辑  收藏  举报