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 NAME CORRESPONDING 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對應放頂點也是可以的,這樣想想才是對的,都已經脫離固定管線了,本來傳上來的數據各式各樣,系統根據定義名稱來對應本就死板,給我們自己聯系就好.改好后,看這代碼再也沒別扭的地方了.

  

 

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM