最近刚开始接触计算机动画,一片空白,尝试做了一下3D蒙皮,发现数学真是差得难以想象,矩阵的什么的全部忘记了,真是接下来该恶补一下基础了……
在这里把遇到的几个问题记录一下。
主要参考书目:《计算机动画算法与技术》《3D游戏》(英国人写的书已经绝版了)《3D游戏引擎设计——实时计算机图形学应用方法》 《骨骼蒙皮动画(Skinned Mesh)的原理解析》
关节就是一个变换矩阵,在层次储存中,一般会从根节点开始一层层的储存关节矩阵:
所以每个关节储存的矩阵都是建立根关节之上的。
以3个关节为例,转换如下:
—(T0世界矩阵)—>Link0—(T1相对Link0的Link1转换,相对旋转R0)—>Link1—(T1.1相对Link1的Link1.1转换,相对旋转R1)—>Link1.1
另外每个关节的变换无非就是旋转加平移。
故,设有一在Link1.1的坐标中有顶点V,那么将其转换到世界坐标V'就是:
V' = T0*T1*R1*T1.1*R1*V(这里使用列向量)
这个其实就是将局部坐标中的顶点通过这一系列的矩阵变换到世界坐标中。
在蒙皮顶点混合的时候由于我写的程序还是相当蛋疼的,我为了方便导入把一个模型拆成了3个模型,然后把三个模型一一变换到世界空间,这样免去从模型空间到骨骼空间的计算,但是在混合的时候需要根据骨骼来装换顶点到对应的骨骼空间,这些东西都在shader中计算完成。(虽然严格的讲这个叫做关节动画==)
#version 410 core layout (location = 2) in vec4 a_position; layout (location = 3) in vec4 a_weight; uniform mat4 mv_matrix; uniform mat4 proj_matrix; uniform mat4 b1_matrix; uniform mat4 b2_matrix; uniform mat4 b3_matrix; uniform int bone; //骨骼长度 float bone_len = 47.2441f; void main() { vec4 new_a_position = a_position; vec4 new_a_position1 = a_position; //向上偏移的顶点 new_a_position.y += bone_len; //向下偏移的顶点 new_a_position1.y -= bone_len; //对不同的骨骼做不同的变换 switch(bone) { case 1: gl_Position = proj_matrix*mv_matrix*(a_weight.x*b1_matrix*a_position+a_weight.y*b2_matrix*new_a_position1); break; case 2: gl_Position = proj_matrix*mv_matrix*(a_weight.x*b1_matrix*new_a_position+a_weight.y*b2_matrix*a_position+a_weight.z*b3_matrix*new_a_position1); break; case 3: gl_Position = proj_matrix*mv_matrix*(a_weight.y*b2_matrix*new_a_position+a_weight.z*b3_matrix*a_position); break; default: break; } }
然后是截图:
混合顶点后
最后需要考虑的是蒙皮,蒙皮意味着只有一个mesh,由骨骼来决定这个mesh中每个顶点的位置。
一般来讲,会在建模工具中就计算好一个初始姿态,即T姿态。这时候其实可能会计算从对应的骨骼空间变换到T姿态模型空间的逆矩阵(意思是mesh上的任何一个顶点(只要这个点被认为在对应的骨骼空间中,所以有负值的坐标)都可以通过这个矩阵转换到模型空间),有了这个逆矩阵,在蒙皮的时候就可以使用此逆矩阵将所有模型空间的顶点转换到对应的骨骼空间中,而且将一个顶点转换到任何一个骨骼空间中都是正确的,这就直接导致可以使用权重来决定每个顶点收各个骨骼的多少影响,而最后的位置就只需要一个公式就能解决了。