MD5骨骼動畫模型載入(一)

weixin_34391854發表於2013-12-26

  前面我們分析了靜態模型OBJ格式,楨動畫模型MD2,這篇主要分析骨骼動畫MD5的一些概念並且實現。

  混合楨動畫有計算簡單,容易實現等優點,但是在需要比較細緻的效果時,則需要更多的關鍵楨,每楨都新增相同的頂點,如果模型再細分一些,則比較恐怖了。在這基礎上,則發展出了骨骼動畫模型,原理說起來很簡單,比如我們人類,做的各種動作具體都是由幾個關節點來控制,比如你抬腿,你只把你大腿的骨骼調動起來,而大腿的肌肉跟著骨骼向上。由些我們只需要儲存每楨的骨骼變動,然後再上面蒙上表皮。因此大量簡單了頂點儲存,並且,我們能方便的對骨骼實時改動就能新增不同的動畫,但是因為骨骼的改變都是針對父骨骼來的,而蒙皮操作又是針對骷髏節點來做的,這些操作需要大量的運算。

  下面我們來解析MD5骨骼模型中,一些基本的概念與實現,在MD5,除去紋理圖片,有二個比較主要的檔案,一個是字尾為md5mesh的檔案,一個是字尾為md5anim的檔案,二個檔案如他們的字尾名所表達的意思一樣,前者和OBJ模型裡的描述比較類似,主要包含每部分的頂點,面,紋理組成,不同於OBJ模型的的,這些元素是變化的,因此在OBJ模型有一些新的元素,如頂點不是單獨的頂點,而是由一個或多個權重點構成,每個權重點關聯著對應著骨骼節點,這樣骨骼節點的改變能引起權重點的改變,而權重點的改變又引起了頂點的改變,至於為什麼要用到權重點來連線骨骼和頂點,而不是直接用骷髏和頂點關聯,首先拿我們來說,我們身上有些位置並不是只和一個骨骼節點有關,更多是和多個節點有關,這樣能讓動畫更真實,也避免在關節點產生重合和斷裂的現象。

  首先我們來解析md5mesh檔案裡的資訊,在這個檔案裡,主要有二大元素,一個是骨骼節點資訊,一個是多個部位蒙皮資訊,下面我簡化了一個md5mesh,實際肯定不可能這樣,主要是用來說明各節點用的。

  

  在檔案中我都加了註釋,簡單來說,第一行是版本資訊,下面寫的解析也是針對這個10的版本,然後是命令列,骨骼節點數,蒙皮元件數,然後是骨骼節點的具體資訊,在這裡包含每個骨骼的父索引,頂點位置,四元數(包含旋轉資訊).在這裡特別說下,這個骨骼節點的順序暗含他們自己的索引,還有特別一點,在md5mesh檔案中,骨骼的頂點位置與旋轉資訊是針對模型空間的,後面我們會看到在md5anim也有骨骼節點下的頂點位置與旋轉資訊,但是那是針對父骨骼節點座標來的。然後就是蒙皮各個部分的詳細資訊,包含紋理座標位置,頂點數,面的資訊,權重資訊,如前面所說,一個麵包含3個頂點,每個頂點包含多個權重,每個權重關聯一個或多個骨骼資訊。下面根據上面各個部位來定義我們程式碼裡各個類:

 1 type ArrayList<'T> = System.Collections.Generic.List<'T>
 2 let filtLine (line:string) = not (String.IsNullOrEmpty(line)) && line <> "(" && line <> ")" 
 3 let getLineData (line:string) = line.Split(' ','\t','\"') |> Array.filter filtLine
 4 let getFloat str = snd (System.Single.TryParse(str))
 5 let getInt str = snd (System.Int32.TryParse(str))
 6 let getw x y z = 1.0f - x*x - y*y - z*z |> fun p -> if p < 0.f then 0.f else float32 (-Math.Sqrt(float p))
 7 
 8 //--------mesh裡用的結構
 9 type Md5Joint() = 
10     member val Name = "" with get,set
11     member val Index = -2 with get,set
12     //位移
13     member val Position = Vector3.Zero with get,set
14     //旋轉
15     member val Quat = Quaternion.Identity  with get,set       
16     member val ParentIndex = -2 with get,set    
17     member this.SetValue(line:string,ind:int) =
18         let ls = getLineData line// line.Split(' ','\t') |> Array.filter (fun p -> not (String.IsNullOrEmpty(p)))//&& p <> @"\t" && p.Length > 0)
19         let pos,quat =
20             let gf str = snd (System.Single.TryParse(str))
21             let x,y,z,a,b,c = (gf ls.[2]),(gf ls.[3]),(gf ls.[4]),(gf ls.[5]),(gf ls.[6]),(gf ls.[7])
22             let d = getw a b c
23             Vector3(x,y,z),Quaternion(a,b,c,d)
24         this.Name <- ls.[0]
25         this.ParentIndex <- snd (System.Int32.TryParse(ls.[1]))
26         this.Position <- pos
27         this.Quat <- quat
28         this
29 
30 //Vert包含紋理座標,以及關聯的權重,根據權重求頂點
31 type Md5Vert() =
32     member val Index = -1 with get,set
33     member val Texcoord = Vector2.Zero with get,set
34     member val WeightStart = 0 with get,set
35     member val WeightCount = 0 with get,set
36     //經過權重求頂點實際位置
37     member val Position = Vector3.Zero with get,set 
38     member val Normal = Vector3.Zero with get,set       
39     member this.SetValue(line:string) =
40         let ls =getLineData line// line.Split(' ','\t') |> Array.filter (fun p -> not (String.IsNullOrEmpty(p)))
41         let gf str = snd (System.Single.TryParse(str))
42         this.Index <- snd (System.Int32.TryParse(ls.[1]))
43         this.Texcoord <- Vector2(gf ls.[2],gf ls.[3])
44         this.WeightStart <- snd (System.Int32.TryParse(ls.[4]))
45         this.WeightCount <- snd (System.Int32.TryParse(ls.[5]))
46         this
47     member this.DataArray with get() =[| this.Texcoord.X;this.Texcoord.Y;this.Position.X;this.Position.Y;this.Position.Z|]
48 
49 //三角形(包含Vert的索引)
50 type Md5Tri() =
51     member val Index = -1 with get,set
52     member val VertorIndexs = Array.create 3 0 with get,set
53     member this.SetValue(line:string) =
54         let ls = getLineData line // line.Split(' ','\t') |> Array.filter (fun p -> not (String.IsNullOrEmpty(p)))
55         let gi str = snd (System.Int32.TryParse(str))
56         this.Index <- gi ls.[1]
57         this.VertorIndexs <- [|gi ls.[2];gi ls.[3];gi ls.[4]|]
58         this
59 
60 //權重,(權重頂點用於計算Md5Vert,權重的JointIndex用於得到對應joint的四元數)
61 type Md5Weight() =
62     member val Index = -1 with get,set
63     member val JointIndex = -1 with get,set
64     member val Bias = 0.f with get,set
65     member val Position = Vector3.Zero with get,set
66     member this.SetValue(line:string) =
67         let ls = line.Split(' ','\t') |> Array.filter (fun p -> not (String.IsNullOrEmpty(p)))
68         let gf str = snd (System.Single.TryParse(str))
69         this.Index <- snd (System.Int32.TryParse(ls.[1]))
70         this.JointIndex <- snd (System.Int32.TryParse(ls.[2]))
71         this.Bias <- gf ls.[3]
72         this.Position <- Vector3(gf ls.[5],gf ls.[6],gf ls.[7])
73         this
74 
75 type Md5Mesh() =
76     let mutable vbo,ebo = 0,0
77     member val TexID = 0 with get,set
78     member val ShaderPath = "" with get,set
79     member val Verts = ArrayList<Md5Vert>() with get,set
80     member val Faces = ArrayList<Md5Tri>() with get,set
81     member val Weights = ArrayList<Md5Weight>() with get,set
82     member this.ElementCount with get() = this.Faces.Count * 3
蒙皮節點描述類

  這裡的類與檔案裡各個描述部分差不多都是一一對應,很好理解,因元組在F#編譯器級別的預設支援,使我們不用想盡辦法組織結構,讓結構和原始檔案保持一致就行,然後要用到的時候因函式式操作相關便利性,很少的程式碼就能拿到需要組合的資料。

  在下面,我們具體處理如何載入md5mesh檔案。

 1         let file = new StreamReader(fileName)
 2         while not file.EndOfStream  do
 3             let str = file.ReadLine()
 4             match str with
 5             | StartsWith "joints" true  -> 
 6                 let mutable isJoint = true
 7                 while isJoint  do
 8                     let joint = file.ReadLine()
 9                     isJoint <- not (joint.Contains("}"))
10                     if isJoint then this.Joints.Add(Md5Joint().SetValue(joint,this.Joints.Count))                    
11             | StartsWith "mesh" true ->
12                 let mutable isMesh = true
13                 let md5mesh = Md5Mesh()
14                 this.Meshs.Add(md5mesh)
15                 while isMesh do
16                     let mesh = file.ReadLine()
17                     match mesh with
18                     | StartsWith "shader" true  -> 
19                         let dict = Path.GetDirectoryName(fileName)
20                         let fileName = (getLineData mesh).[1]// mesh.Split(' ','\t') |> Array.filter (fun p -> not (String.IsNullOrEmpty(p))) |> fun p -> p.[1].Trim('\"')
21                         md5mesh.ShaderPath <- Path.Combine(dict,fileName)
22                     | StartsWith "vert" true  -> 
23                         md5mesh.Verts.Add(Md5Vert().SetValue(mesh))
24                     | StartsWith "tri" true  -> 
25                         md5mesh.Faces.Add(Md5Tri().SetValue(mesh))
26                     | StartsWith "weight" true  -> 
27                         md5mesh.Weights.Add(Md5Weight().SetValue(mesh))
28                     | StartsWith "}" true  -> 
29                         isMesh <- false
30                     | _ -> printfn "%s" ("---------"+str) 
31             | _ -> printfn "%s" str        
32         file.Close()
讀取蒙皮檔案資訊

  在這裡,差不多就把蒙皮檔案裡的所有資訊處理完畢。其實如果只是md5mesh,他就相當於一個複雜了些,包含了權重的OBJ模型,組織方式都大同小異,不信請看下面。我們記的在md5mesh前面骨骼也包含了頂點位置與四元數資訊,根據這個,可以求得預設的權重點具體位置,然後就能得到頂點的具體位置,然後得到面,然後繪製,下面這段程式碼可以在沒有md5anim檔案裡,繪製一個靜態的,相當於OBJ模型一樣功能的模型。  

 1 //先求得頂點的實際資料
 2         this.Meshs.ForEach(fun mesh -> 
 3             mesh.Verts.ForEach(fun vert ->
 4                 for i in [vert.WeightStart .. vert.WeightStart + vert.WeightCount - 1] do
 5                     let weigth = mesh.Weights.[i]
 6                     let joint = this.Joints.[weigth.JointIndex]
 7                     vert.Position <- vert.Position + (joint.Position + Vector3.Transform(weigth.Position,joint.Quat)) * weigth.Bias
 8                 )
 9             ) 
10         this.Meshs.ForEach(fun mesh -> mesh.CreateVBO())   
根據蒙皮檔案繪製模型

  這段程式碼比較簡單,就是上面所說,求面中的頂點,頂點根據權重求,權重根據骨骼當前狀態來得到,還是和上面一樣說明下,md5mesh裡的骨骼節點是模型座標系下的,所以骨骼節點不需要做轉化。

  這裡說下四元數,在3D中,我們表示旋轉一般有矩陣,尤拉角,四元數,平常我們所用都是矩陣與尤拉角,四元數用到複數,理解起來比較麻煩,我現在也只是記著一些四元數的特性,能實現平滑插值,點p用四元數旋轉後得到點p1=ap(a的逆).四元數和矩陣一樣,滿足結合律,但是不滿足交換律。四元數的有向量部分v(x,y,z)和一個分量w,幾何意義可以描述為對於一個向量n,旋轉@角,四元數就是[w=cos(@/2),sin(@/2)*n]=[w=cos(@/2),sin(@/2)*nx,sin(@/2)*ny,sin(@/2)*nz],根據這個定義,可以推匯出一些四元數的特性,如四元數的共軛和四元數代表相反的角位移,上面的p1=ap(a的逆).

  如果沒有md5anim檔案,MD5檔案也就和OBJ檔案一樣,只是一個靜態的模型,下面讓我們來分析md5anim的相關格式。下面一樣給出一個簡化了的樣式。

     

  各資訊我給出了基本標註,比較重要的每秒多少楨,楨的具體資訊,這個順序與前面md5mesh是對應的,父索引也是一樣的,不同的是,後面二個整數,一個表示應該讀frame的那些資料,一個表示讀的位置的起點。給出對應的程式碼格式。

 1 type Md5JointInfo() =
 2     member val Name = "" with get,set
 3     member val Index = -2 with get,set
 4     member val ParentIndex = -2 with get,set    
 5     member val Flags = 0 with get,set
 6     member val StartIndex = 0 with get,set
 7     member this.SetValue(line:string,ind:int) =
 8         let ls = getLineData line
 9         this.Name <- ls.[0]
10         this.ParentIndex <- getInt ls.[1]
11         this.Index <- ind
12         this.Flags <- getInt ls.[2]
13         this.StartIndex <- getInt ls.[3]
14         this
15 
16 type Md5BaseFrame() =
17     member val Index = -2 with get,set
18     //位移
19     member val Positions = ArrayList<Vector3>() with get,set
20     //旋轉
21     member val Quats = ArrayList<Quaternion>() with get,set  
22     member this.SetValue(line:string) =
23         let ls = getLineData line
24         let pos,quat =
25             let gf str = snd (System.Single.TryParse(str))
26             let x,y,z,a,b,c = (gf ls.[0]),(gf ls.[1]),(gf ls.[2]),(gf ls.[3]),(gf ls.[4]),(gf ls.[5])
27             let d = getw a b c
28             Vector3(x,y,z),Quaternion(a,b,c,d)
29         this.Positions.Add(pos)
30         this.Quats.Add(quat)
31 
32 type Md5Frame() =
33     member val Index = -2 with get,set
34     member val Points = ArrayList<float32>() with get,set
35     member this.SetValue(line:string) =
36         let ls = getLineData line
37         let ds = ls |> Array.map (fun p -> getFloat p)
38         this.Points.AddRange(ds)
39 
40 //楨動畫計算得出如下內容
41 type Md5SkeletonJoin() =
42     member val ParentIndex = -2 with get,set
43     //位移
44     member val Position = Vector3.Zero with get,set
45     //旋轉
46     member val Quat = Quaternion.Identity with get,set  
47 type Md5FrameSkeleton = ArrayList<Md5SkeletonJoin>
MD5動畫格式

  分別定義了,Md5JointInfo,Md5BaseFrame,Md5Frame,大家可以看出多了Md5SkeletonJoin與Md5FrameSkeleton,沒有與檔案裡的資訊對應,這裡就是要大家前面老注意的一個地方,在md5mesh檔案,給的骨骼節點座標已經是模型座標系下的,而md5anim給出的骨骼節點座標只是針對父骨骼節點裡的,Md5SkeletonJoin與Md5FrameSkeleton就是Md5Frame根據父骨骼節點求出的在模型座標系下的座標。

  下面首先是載入md5anim資訊的程式碼:

 1             let path = Path.Combine(Path.GetDirectoryName(fileName),animName.Value)
 2             let animFile = new StreamReader(path)
 3             while not animFile.EndOfStream  do
 4                 let str = animFile.ReadLine()
 5                 match str with
 6                 | StartsWith "frameRate" true  -> 
 7                     this.Animation.FrameRate <- getFloat (getLineData str).[1]  
 8                 | StartsWith "hierarchy" true ->
 9                     let mutable isJoinHierarchy = true 
10                     while isJoinHierarchy  do
11                         let joint = animFile.ReadLine()
12                         isJoinHierarchy <- not (joint.Contains("}"))
13                         if isJoinHierarchy then this.Animation.JointInfos.Add(Md5JointInfo().SetValue(joint,this.Animation.JointInfos.Count))  
14                 | StartsWith "bounds" true ->
15                     let mutable isbound = true 
16                     while isbound  do
17                         let bound = animFile.ReadLine()
18                         isbound <- not (bound.Contains("}"))
19                         if isbound then 
20                             let data = getLineData bound
21                             let a,b,c,x,y,z = getFloat data.[0],getFloat data.[1],getFloat data.[2],getFloat data.[3],getFloat data.[4],getFloat data.[5]
22                             this.Animation.Bounds.Add(Vector3(a,b,c),Vector3(x,y,z))               
23                 | StartsWith "baseframe" true ->
24                     let mutable isFrame = true 
25                     let mf = this.Animation.BaseFrame       
26                     while isFrame  do
27                         let frameLine = animFile.ReadLine()
28                         isFrame <- not (frameLine.Contains("}"))
29                         if isFrame then mf.SetValue(frameLine)                                           
30                 | StartsWith "frame" true ->
31                     let mutable isFrame = true 
32                     let mf = Md5Frame()
33                     mf.Index <- getInt (getLineData str).[1]
34                     this.Animation.Frames.Add(mf)
35                     while isFrame  do
36                         let frameLine = animFile.ReadLine()
37                         isFrame <- not (frameLine.Contains("}"))
38                         if isFrame then mf.SetValue(frameLine)                     
39                 | _ -> printfn "%s" str    
40             animFile.Close()
41             //把骨骼動畫中,各節點由父骨骼節點座標轉化成模型座標
42             this.Animation.CreateFrameSkeleton()
43             //生成紋理
44             this.Meshs.ForEach(fun mesh -> if  mesh.TexID = 0 && File.Exists mesh.ShaderPath then mesh.TexID <- TexTure.Load(mesh.ShaderPath))
45         
載入MD5動畫模型

  這部分程式碼也是一些IO操作,把讀到的資訊都放入Md5Animation裡去,這個類主要做二件事,一是得到正確的Md5SkeletonJoin與Md5FrameSkeleton,就是得到Md5Frame根據父骨骼節點求出的在模型座標系下的座標。然後一些,就是根據當前時間,當前楨率得到正確的插值,這部分和MD2插值差不多。請看主要程式碼:

 1 type Md5Animation() =
 2     let mutable currentTime = 0.f
 3     member val FrameRate = 24.f with get,set    
 4     //JointInfos集合物件的索引就是本身在檔案中的位置,他們本身的順序就是正序加1
 5     member val JointInfos = ArrayList<Md5JointInfo>() with get,set
 6     member val Bounds = ArrayList<Vector3*Vector3>() with get,set
 7     member val Frames = ArrayList<Md5Frame>() with get,set
 8     member val BaseFrame = Md5BaseFrame() with get,set    
 9     member val FrameSkeletonList = ArrayList<Md5FrameSkeleton>() with get,set
10     //二個目標,一是轉化Frames裡的資料成對應一楨的所有骨骼節點資訊
11     //二是把所有骨骼節點在父骨骼座標系中的位置轉化成模型座標系
12     member this.CreateFrameSkeleton() =
13         for frame in this.Frames do
14             let md5FrameSkeleton = Md5FrameSkeleton()
15             for jointInfo in this.JointInfos do
16                 //skeleton的順序因為JointInfos的特殊性,也是正序加1
17                 let skeleton = Md5SkeletonJoin()
18                 md5FrameSkeleton.Add(skeleton)
19                 skeleton.ParentIndex <- jointInfo.ParentIndex
20                 let position = this.BaseFrame.Positions.[jointInfo.Index]
21                 let quat = this.BaseFrame.Quats.[jointInfo.Index]
22                 let setFlags index =
23                     let flag = int (Math.Pow(float 2,float index))
24                     if jointInfo.Flags &&& flag = flag then frame.Points.[jointInfo.StartIndex + index] 
25                     else 
26                         match index with 
27                         | 0 -> position.X                    
28                         | 1 -> position.Y
29                         | 2 -> position.Z
30                         | 3 -> quat.X
31                         | 4 -> quat.Y
32                         | 5 -> quat.Z
33                         | _ -> quat.W
34                 let x,y,z,a,b,c = setFlags 0,setFlags 1,setFlags 2,setFlags 3,setFlags 4,setFlags 5
35                 let w = getw a b c
36                 //currentPos,currentQuat都是針對父骨骼來的座標,要轉化得到模型座標
37                 let currentPos,currentQuat=Vector3(x,y,z),Quaternion(a,b,c,w)
38                 skeleton.Position <- currentPos
39                 skeleton.Quat <- currentQuat
40                 if skeleton.ParentIndex >= 0 then
41                     let parentSkeleton = md5FrameSkeleton.[skeleton.ParentIndex]
42                     //先得到currentPos經過父骨骼四元數旋轉後的值
43                     let pos = Vector3.Transform(currentPos,parentSkeleton.Quat)
44                     //模型座標示下的點
45                     skeleton.Position <- parentSkeleton.Position + pos
46                     //模型座標系下的四元數
47                     skeleton.Quat <- Quaternion.Normalize(parentSkeleton.Quat * skeleton.Quat)
48             this.FrameSkeletonList.Add(md5FrameSkeleton) 
49     member this.CurrentTime 
50         with get() =
51             if currentTime > float32 this.Frames.Count/this.FrameRate then currentTime <- 0.f
52             currentTime
53         and set value = currentTime <- value
54     member this.GetCurrentFrameSkeleton() =
55         //得到當前的時間所在動畫迴圈的位置
56         let current = this.CurrentTime * this.FrameRate
57         //得到所在位置的當前楨索引,與運動到下一楨的位置
58         let currentFrame,currentStep= int (Math.Floor(float current)),current - float32 (Math.Floor(float current))
59         //得到下一楨索引,到楨尾就從頭開始
60         let nextFrame = if currentFrame < this.Frames.Count - 1 then currentFrame + 1 else 0
61         //得到當前楨,下一楨具體資訊
62         let currentSkeleton,nexSkeleton = this.FrameSkeletonList.[currentFrame],this.FrameSkeletonList.[nextFrame]
63         let joints = ArrayList<Md5Joint>()
64         for i in [|0 .. this.JointInfos.Count - 1|] do
65             //根據當前楨位置求得對應四元數與頂點的插值
66             let lerpPosition = Vector3.Lerp(currentSkeleton.[i].Position,nexSkeleton.[i].Position,currentStep)
67             let slerpQuat = Quaternion.Slerp(currentSkeleton.[i].Quat,nexSkeleton.[i].Quat,currentStep)
68             joints.Add(Md5Joint(Index = i,Position = lerpPosition,Quat = slerpQuat))
69         joints
MD5動畫計算模型座標下骨骼節點與線性插值

  關鍵部分我都寫了註釋,應該容易看明白,如上面所說,二件事,一是CreateFrameSkeleton,這個首先根據flag與startIndex讀取檔案,然後把在父骨骼座標系中的點轉化成模型座標系下的點。二是GetCurrentFrameSkeleton,分別得到所在時間的當前楨與下一楨,然後根據在這楨之間的位置插值得到各骨骼節點正確的座標。渲染部分在這裡,考慮到因為一個MD5模型本來包含幾部分Mesh,然後每部分Mesh又包含各楨的情況,再想用MD2中關鍵楨頂點資訊做VBO不現實,故直接用VA來輸出渲染。  

 1 type Md5Model(fileName:string,?animName:string) =
 2    member this.Render()=
 3         //生成骨骼節點的資訊
 4         let joints = this.Animation.GetCurrentFrameSkeleton()
 5         //根據骨骼節點生成頂點.也就是蒙皮
 6         this.Meshs.ForEach(fun mesh -> 
 7             mesh.Verts.ForEach(fun vert ->
 8                 vert.Position <- Vector3.Zero
 9                 for i in [vert.WeightStart .. vert.WeightStart + vert.WeightCount - 1] do
10                     let weigth = mesh.Weights.[i]
11                     let joint = joints.[weigth.JointIndex]
12                     vert.Position <- vert.Position + (joint.Position + Vector3.Transform(weigth.Position,joint.Quat)) * weigth.Bias
13                 )
14             ) 
15         //頂點繪製
16         this.Meshs.ForEach(fun mesh -> mesh.Render())  
17 
18 type Md5Mesh() =
19     member this.Render() =
20         let vboData = Array2D.init this.ElementCount 5 (fun i j ->
21             let a,b = i/3,i%3
22             this.Verts.[this.Faces.[a].VertorIndexs.[b]].DataArray.[j]
23             )
24         GL.InterleavedArrays(InterleavedArrayFormat.T2fV3f,0,vboData)
25         if this.TexID > 0 then
26             GL.Enable(EnableCap.Texture2D)
27             GL.BindTexture(TextureTarget.Texture2D,this.TexID)       
28         GL.DrawElements(BeginMode.Triangles,this.ElementCount,DrawElementsType.UnsignedInt,[|0..this.ElementCount - 1|])
29      
輸出MD5動畫

  到此,整個過程就差不多了,下面給出效果圖:

  

   程式碼:原始碼與執行檔案 http://files.cnblogs.com/zhouxin/MD5Load.zip 其中\bin\Release\CgTest.exe為可執行檔案

  其中EDSF前後左右移動,滑鼠右鍵加移動滑鼠控制方向,空格上升,空格在SHIFT下降。再發現,整個工程中,去掉OBJ,MD2模型後,加上DLL一共27M,壓縮下才5M,能上傳上來,前面每次都分開上傳給大家造成不便了其中為了突出MD5的重點,相應的法線沒有自動生成,相關方法可以看前面OBJ,MD2裡的,計算過程一樣。

  CPU和GPU各應該執行的操作讓我的理解應該是,一次計算很久變一次應該交給CPU,而在渲染過程快速,大量執行的程式碼應該交給GPU來算,下一步目標,改進裡面關於骨骼位置的計算,以及相應蒙皮的操作應該交給GPU,也就是放到著色器中去處理。

相關文章