• 【计算机动画】蒙皮实验


    最近学习计算机动画,看书千万遍都不如动手做一遍,实现了一下完整的人物蒙皮,遇到一些问题,现在把完整的实现过程记一下。

    理论在前一篇已经讲完了,前面也实现了基本的关节动画,现在要实现的是,使用3Dmax建立一个完整的模型,然后使用它来进行蒙皮。

    我的基础工具写的不是很完善,为了不分心去写其他的东西,就用了一些很可笑的数据储存方式和各种硬编码,见怪不怪,以后空了去整理。

    我定义了一个结构体叫做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;
        else
            temp = 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 core
    
    layout (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中直接计算就好了:

    没有顶点混合:

    混合后:

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

  • 相关阅读:
    B轮公司技术问题列表(转)
    mysql函数之截取字符串
    谁才是真正的水果之王
    Mysql几种索引方式的区别及适用情况 (转)
    web安全之攻击
    css学习之样式层级和权重
    mysql中engine=innodb和engine=myisam的区别(转)
    mysql 创建表格 AUTO_INCREMENT
    mysql数据表的字段操作
    navicate使用小技巧
  • 原文地址:https://www.cnblogs.com/wubugui/p/4397348.html
Copyright © 2020-2023  润新知