前面我们分析了静态模型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