【计算机动画】蒙皮实验

编程入门 行业动态 更新时间:2024-10-09 18:22:45

【计算机动画】<a href=https://www.elefans.com/category/jswz/34/1744991.html style=蒙皮实验"/>

【计算机动画】蒙皮实验

定义了一个结构体叫做Boxman,里面存放的东西包括:

  • 关节树
  • 关节旋转矩阵
  • mesh引用
  • 存放各个关节到世界变换的矩阵
  • 硬编码的偏置矩阵

我们在初始化的时候就将mesh传递给Boxman,mesh包含的数据包括哪些点接受哪些骨骼的影响,结合预先硬编码的偏置矩阵就可以使用mesh对骨骼进行蒙皮了。

我用多叉树存放了一个人的骨骼。树的结构上篇已经画了,就不多说。树的节点定义如下:

struct Node
{//各个关节的矩阵 vmath::mat4 mat;//关节标识int bn;//子节点,这里最多也就四个Node* next[4];//构造函数Node(){bn = 0;next[0] = next[1] = next[2] = next[3] = 0;}
};

Boxman的定义如下:

struct Boxman
{//各个关节的矩阵,元素0是偏移矩阵,元素1是旋转矩阵,这个矩阵会实时变化vmath::mat4 root[2];vmath::mat4 left_leg_up[2];vmath::mat4 left_leg_down[2];vmath::mat4 right_leg_up[2];vmath::mat4 right_leg_down[2];vmath::mat4 body_middle[2];vmath::mat4 body_up[2];vmath::mat4 left_arm_up[2];vmath::mat4 left_arm_down[2];vmath::mat4 right_arm_up[2];vmath::mat4 right_arm_down[2];//mesh引用WIPModel3D * model_ref;//骨骼层次结构Node* hroot;//到世界坐标的最终矩阵vmath::mat4 mats[11];
}

在初始化的时候就初始化那些和绑定有关的数据,这里说的绑定数据就是mesh和骨骼相互关联的数据。一般在绑定骨骼的时候,动画师会把模型作为一个T姿态,然后将骨骼一一绑定并赋予权重,这个时候其实就是用来确定偏移矩阵、骨骼影响、权重等等绑定数据的(恩,至少我是这么理解的)。

Boxman的初始化代码如下:

Boxman(WIPModel3D * model_ref){//初始化所有的最终结果矩阵为单位矩阵,因为之后会将矩阵注意乘上去for(int i=0;i<11;i++){mats[i] = vmath::mat4::identity();}//mesh引用this->model_ref = model_ref;//初始化偏移矩阵,偏移矩阵是事先硬编码设定好的root[0] = vmath::translate(0.f,-40.f,0.f);left_leg_up[0] = vmath::translate(3.75f,-40.f,0.f);left_leg_down[0] = vmath::translate(3.75f,-20.f,0.f);right_leg_up[0] = vmath::translate(-3.75f,-40.f,0.f);right_leg_down[0] = vmath::translate(-3.75f,-20.f,0.f);body_middle[0] = vmath::translate(0.f,-55.f,0.f);body_up[0] = vmath::translate(0.f,-70.f,0.f);left_arm_up[0] = vmath::translate(10.f,-70.f,0.f);left_arm_down[0] = vmath::translate(10.f,-55.f,0.f);right_arm_up[0] = vmath::translate(-10.f,-70.f,0.f);right_arm_down[0] = vmath::translate(-10.f,-55.f,0.f);//初始化旋转矩阵root[1] = vmath::rotate(0.f,0.f,1.f,0.f);left_leg_up[1] = vmath::rotate(0.f,0.f,1.f,0.f);left_leg_down[1] = vmath::rotate(0.f,0.f,1.f,0.f);right_leg_up[1] = vmath::rotate(0.f,0.f,1.f,0.f);right_leg_down[1] = vmath::rotate(0.f,0.f,1.f,0.f);body_middle[1] = vmath::rotate(0.f,0.f,1.f,0.f);body_up[1] = vmath::rotate(0.f,0.f,1.f,0.f);left_arm_up[1] = vmath::rotate(0.f,0.f,1.f,0.f);left_arm_down[1] = vmath::rotate(0.f,0.f,1.f,0.f);right_arm_up[1] = vmath::rotate(0.f,0.f,1.f,0.f);right_arm_down[1] = vmath::rotate(0.f,0.f,1.f,0.f);//创建骨骼层次树,并赋予关节矩阵和标识Node* mroot = new Node;mroot->mat = vmath::translate(0.f,40.f,0.f)*root[1];mroot->bn = 0;Node* mleft_leg_up = new Node;mleft_leg_up->mat = vmath::translate(-3.75f,0.f,0.f)*left_leg_up[1];mleft_leg_up->bn = 1;Node* mleft_leg_down = new Node;mleft_leg_down->mat = vmath::translate(0.f,-20.f,0.f)*left_leg_down[1];mleft_leg_down->bn = 2;Node* mright_leg_up = new Node;mright_leg_up->mat = vmath::translate(3.75f,0.f,0.f)*right_leg_up[1];mright_leg_up->bn = 3;Node* mright_leg_down = new Node;mright_leg_down->mat = vmath::translate(0.f,-20.f,0.f)*right_leg_down[1];mright_leg_down->bn = 4;Node* mbody_middle = new Node;mbody_middle->mat = vmath::translate(0.f,15.f,0.f)*body_middle[1];mbody_middle->bn = 5;Node* mbody_up = new Node;mbody_up->mat = vmath::translate(0.f,15.f,0.f)*body_up[1];mbody_up->bn = 6;Node* mleft_arm_up = new Node;mleft_arm_up->mat = vmath::translate(-10.f,15.f,0.f)*left_arm_up[1];mleft_arm_up->bn = 7;Node* mleft_arm_down = new Node;mleft_arm_down->mat = vmath::translate(0.f,-15.f,0.f)*left_arm_down[1];mleft_arm_down->bn = 8;Node* mright_arm_up = new Node;mright_arm_up->mat = vmath::translate(10.f,15.f,0.f)*right_arm_up[1];mright_arm_up->bn = 9;Node* mright_arm_down = new Node;mright_arm_down->mat = vmath::translate(0.f,-15.f,0.f)*right_arm_down[1];mright_arm_down->bn = 10;mroot->next[0] = mleft_leg_up;mroot->next[1] = mright_leg_up;mroot->next[2] = mbody_middle;mleft_leg_up->next[0] = mleft_leg_down;mright_leg_up->next[0] = mright_leg_down;mbody_middle->next[0] = mleft_arm_up;mbody_middle->next[1] = mright_arm_up;mbody_middle->next[2] = mbody_up;mleft_arm_up->next[0] = mleft_arm_down;mright_arm_up->next[0] = mright_arm_down;hroot = mroot;}

初始化好这个Boxman就可以开始考虑计算的问题了,我比较傻缺,所以用的都是写傻缺方法,关节旋转我就是直接对关节的矩阵乘以一个旋转矩阵,然后递归的计算骨骼层次树把所有的矩阵实时计算存到Boxman::mats[11]里面去。然后在draw的时候直接把这些矩阵传到shader里面,进行对应的计算就好了。

遍历层次树的代码很简单,由于我要同时计算新的旋转矩阵,所以有点长:

void push(Node* n,vmath::mat4 m){if(n){switch (n->bn){case 0:n->mat *= root[1];break;case 1:n->mat*=left_leg_up[1];break;case 2:n->mat*=left_leg_down[1];break;case 3:n->mat*=right_leg_up[1];break;case 4:n->mat*=right_leg_down[1];break;case 5:n->mat*=body_middle[1];break;case 6:n->mat *= body_up[1];break;case 7:n->mat*=left_arm_up[1];break;case 8:n->mat*=left_arm_down[1];break;case 9:n->mat*=right_arm_up[1];break;case 10:n->mat*=right_arm_down[1];break;default:break;}mats[n->bn] = m*n->mat*mats[n->bn];}else{return;}for(int i=0;i<4;i++){if(n->next[i]){push(n->next[i],mats[n->bn]); }}}void calc(Node* node){push(node,vmath::mat4::identity());}

draw就简单了,无非就是把一堆矩阵全部传入shader算就行了,比较傻缺:

void draw(){boxman_shader.begin();boxman_shader.set_uniform_matrix("m00",root[0]);boxman_shader.set_uniform_matrix("m10",left_leg_up[0]);boxman_shader.set_uniform_matrix("m20",left_leg_down[0]);boxman_shader.set_uniform_matrix("m30",right_leg_up[0]);boxman_shader.set_uniform_matrix("m40",right_leg_down[0]);boxman_shader.set_uniform_matrix("m50",body_middle[0]);boxman_shader.set_uniform_matrix("m60",body_up[0]);boxman_shader.set_uniform_matrix("m70",left_arm_up[0]);boxman_shader.set_uniform_matrix("m80",left_arm_down[0]);boxman_shader.set_uniform_matrix("m90",right_arm_up[0]);boxman_shader.set_uniform_matrix("m100",right_arm_down[0]);boxman_shader.set_uniform_matrix("m0",mats[0]);boxman_shader.set_uniform_matrix("m1",mats[1]);boxman_shader.set_uniform_matrix("m2",mats[2]);boxman_shader.set_uniform_matrix("m3",mats[3]);boxman_shader.set_uniform_matrix("m4",mats[4]);boxman_shader.set_uniform_matrix("m5",mats[5]);boxman_shader.set_uniform_matrix("m6",mats[6]);boxman_shader.set_uniform_matrix("m7",mats[7]);boxman_shader.set_uniform_matrix("m8",mats[8]);boxman_shader.set_uniform_matrix("m9",mats[9]);boxman_shader.set_uniform_matrix("m11",mats[10]);boxman_shader.set_uniform_matrix("mv_matrix",mv_matrix);boxman_shader.set_uniform_matrix("proj_matrix",proj_matrix);boxman_shader.set_uniform_i("vn",0);model_ref->renderer(GL_TRIANGLES);glPointSize(20);glDrawArrays(GL_POINTS,0,1);boxman_shader.end();glDisable(GL_CULL_FACE);glPolygonMode(GL_FRONT_AND_BACK   ,GL_LINE   );boxman_shader.begin();boxman_shader.set_uniform_matrix("m00",root[0]);boxman_shader.set_uniform_matrix("m10",left_leg_up[0]);boxman_shader.set_uniform_matrix("m20",left_leg_down[0]);boxman_shader.set_uniform_matrix("m30",right_leg_up[0]);boxman_shader.set_uniform_matrix("m40",right_leg_down[0]);boxman_shader.set_uniform_matrix("m50",body_middle[0]);boxman_shader.set_uniform_matrix("m60",body_up[0]);boxman_shader.set_uniform_matrix("m70",left_arm_up[0]);boxman_shader.set_uniform_matrix("m80",left_arm_down[0]);boxman_shader.set_uniform_matrix("m90",right_arm_up[0]);boxman_shader.set_uniform_matrix("m100",right_arm_down[0]);boxman_shader.set_uniform_matrix("m0",mats[0]);boxman_shader.set_uniform_matrix("m1",mats[1]);boxman_shader.set_uniform_matrix("m2",mats[2]);boxman_shader.set_uniform_matrix("m3",mats[3]);boxman_shader.set_uniform_matrix("m4",mats[4]);boxman_shader.set_uniform_matrix("m5",mats[5]);boxman_shader.set_uniform_matrix("m6",mats[6]);boxman_shader.set_uniform_matrix("m7",mats[7]);boxman_shader.set_uniform_matrix("m8",mats[8]);boxman_shader.set_uniform_matrix("m9",mats[9]);boxman_shader.set_uniform_matrix("m11",mats[10]);boxman_shader.set_uniform_matrix("mv_matrix",mv_matrix);boxman_shader.set_uniform_matrix("proj_matrix",proj_matrix);boxman_shader.set_uniform_i("vn",1);model_ref->renderer(GL_TRIANGLES);glPointSize(20);glDrawArrays(GL_POINTS,0,1);boxman_shader.end();glDisable(GL_CULL_FACE);glPolygonMode(GL_FRONT_AND_BACK   ,GL_FILL   );for(int i=0;i<11;i++){mats[i] = vmath::mat4::identity();}//重置所有旋转矩阵以便下一帧更新body_up[1] = vmath::mat4::identity();root[1] = vmath::mat4::identity();left_leg_up[1] = vmath::mat4::identity();left_leg_down[1] = vmath::mat4::identity();right_leg_up[1] = vmath::mat4::identity();right_leg_down[1] = vmath::mat4::identity();body_middle[1] = vmath::mat4::identity();left_arm_up[1] = vmath::mat4::identity();left_arm_down[1] = vmath::mat4::identity();right_arm_up[1] = vmath::mat4::identity();right_arm_down[1] = vmath::mat4::identity();}

这里有两个drawcall的原因是,要绘制一次法线,而法线的显示模式和模型的显示模式不一样。

在运行的时候操作关节直接更新旋转矩阵就行了,下面就是更新头部旋转的接口:

 void rotate_head(float degree){body_up[1] = vmath::rotate(degree,0.f,1.f,0.f);}

Shader的实现就是很简单的乘以矩阵做变换就好,因为没有权重,要加进去又比较困难,我又做了一个傻缺决定,直接把顶点的属于哪个骨骼控制直接写到顶点数据的x上,原因是我的模型数据建的很精确,所有的坐标数据小数点3位之后全是0.于是顶点数据变成了这样:

v  -5.5060 70.0000 -5.5000
v  -5.5060 81.0000 -5.5000
v  5.5060 81.0000 -5.5000
v  5.5060 70.0000 -5.5000
v  -5.5060 70.0000 5.5000
v  5.5060 70.0000 5.5000
v  5.5060 81.0000 5.5000
v  -5.5060 81.0000 5.5000

那个060就代表这个顶点会被骨骼6影响……这个数据在shader中取出来在减去就好了……不过不修正形状变化也不大的>_<

shader代码如下:

#version 410 core
layout (location = 1) in vec3 a_normal;
layout (location = 2) in vec4 a_position;
layout (location = 3) in vec4 a_weight;
//一大堆uniform矩阵
uniform int bone;
uniform int blend;out vec3 normal;
out vec3 onormal;
out vec4 ccc;vec4 red = vec4(1.0,0.0,0.0,1.0);void main()
{vec4 new_p = a_position;int l = int(a_position.x*10);float x = float(a_position.x*10-l);float temp ;//加0.5修正精度丢失造成的错误if(x*100>=0)temp = x*100+0.5;elsetemp = x*100-0.5;int xx = abs(int(temp));new_p.x = float(a_position.x - x);switch(xx){case 0:gl_Position = mv_matrix*m0*m00*a_position;break;case 1:gl_Position = mv_matrix*m1*m10*a_position;break;case 2:gl_Position = mv_matrix*m2*m20*a_position;break;case 3:gl_Position = mv_matrix*m3*m30*a_position;break;case 4:gl_Position = mv_matrix*m4*m40*a_position;break;case 5:gl_Position = mv_matrix*m5*m50*a_position;break;case 6:gl_Position = mv_matrix*m6*m60*a_position;break;case 7:gl_Position = mv_matrix*m7*m70*a_position;break;case 8:gl_Position = mv_matrix*m8*m80*a_position;break;case 9:gl_Position = mv_matrix*m9*m90*a_position;break;case 10:gl_Position = mv_matrix*m11*m100*a_position;break;}normal = normalize(mat3(mv_matrix)*a_normal);onormal = a_normal;
}

这里没有透视矩阵,因为我使用了Geometry Shader来生成法线,透视矩阵放到那里面去了。

#version 410 corelayout (triangles) in;
layout (triangle_strip,max_vertices = 3) out;
uniform mat4 mv_matrix;
uniform mat4 proj_matrix;
uniform int vn;
in vec3 onormal[];
in vec3 normal[];
out vec3 normalo;
void main()
{vec4 position;int i;for(i=0;i<gl_in.length();i++){if(vn==0){vec3 n = normal[i];normalo = n;gl_Position = proj_matrix*gl_in[i].gl_Position;EmitVertex();}else{vec3 on = onormal[i];vec3 n = normal[i];normalo = n;gl_Position = proj_matrix*gl_in[i].gl_Position;EmitVertex();EmitVertex();position = gl_in[i].gl_Position + 6*vec4(n,0.f);gl_Position = proj_matrix*position;EmitVertex();EndPrimitive();}}EndPrimitive();
}

最后在主程序中,每一帧calc一次,再draw一次就可以基本实现功能了。

最后上个图吧(红色那些是面法线):

我就没有去设计如何对每个顶点的权值,要这么做,我只能手动去设置,但是顶点实在太多时间有限就没有加入顶点混合功能。我另外使用一个顶点和关节都很少的模型写了顶点混合,原理都是你一样的,只要合适的加入权重,在shader中直接计算就好了:

没有顶点混合:

混合后:

这些动画姿势都是手动旋转出来的,关于动画数据怎么回事,目前还不是很懂。

更多推荐

【计算机动画】蒙皮实验

本文发布于:2024-02-25 07:47:09,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1698270.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:蒙皮   计算机   动画

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!