骨骼动画的原理及在Unity中的使用

制做骨骼动画


咱们看看这几步操做后,咱们获得了那些数据:
1.每一个皮肤顶点的初始世界坐标。
2.每一个骨骼关节顶点的初始世界坐标。
3.每一个顶点被骨骼顶点的影响信息。
4.骨骼如何移动。git

骨骼动画原理

核心: 经过骨骼带动皮肤运动,也就是经过骨骼的移动动态计算mesh上的点的位置github

过程:

1.将mesh上的点转换为骨骼空间上的点。
骨骼空间就是以关节为原点肯定的空间,并非一个实体。
2.经过缩放、旋转、平移将骨骼移动到新的位置。
3.根据骨骼的新位置计算mesh顶点新世界坐标(骨骼移动,但mesh顶点与骨骼的相对位置不变,因此产生了顶点随骨骼移动的感受),若一个顶点被多个骨骼影响,则要进行顶点混合计算新世界坐标。ide

举例

初始位置(绑定姿式):

骨骼移动后的位置:

1.计算小臂上一点S在小臂空间中的位置。
这个就要根据初始的骨骼位置和mesh上顶点的位置来计算了,也就是常说的绑定姿式状态。
先说一下该例中每一个坐标的意义:
(x1,y1,z1):左肩关节SL的世界坐标。
(x2,y2,z2):左肘关节在以左肩关节为原点的坐标系本地坐标。
(x3,y3,z3):附着于左小臂上的皮肤上的一点S的世界坐标。
这里为了简单,假设全部的关节都没有通过旋转和缩放。
先将S转换到经过SL肯定的空间中,也就是大臂UAL空间,直接减去SL的坐标便可(x3-x1,y3-y1,z3-z1)。
再将S点在大臂UAL空间中的坐标转换到小臂LA空间中,直接减去EL的坐标便可(x3-x1-x2,y3-y1-y2,z3-z1-z2)。动画

以上只是一个简单的说明,而实际的使用中,初始的骨骼位置多是经过缩放、旋转、平移后获得的,通常会经过矩阵的方式来表示这一系列变换。
关于OpenGL中的坐标转换能够参考这个连接的说明LearnOpenGL CN
看完这个咱们就应该知道如何将一个子坐标空间的点转化为世界坐标了。下面再使用该例子进行举例。
先计算模型矩阵再求模型矩阵的逆矩阵:

或者直接将求模型矩阵的运算反过来也能够:

获得World→EL矩阵后就可应直接经过矩阵运算直接将世界坐标上的一点转化为EL空间上的一点了。

注意:
1.注意矩阵的运算顺序,由于矩阵运算是不知足交换率的,若是顺序错了,结果极可能也就错了。
2.矩阵EL→World通常叫作EL空间的模型(model)矩阵,矩阵World→EL通常叫作EL空间的绑定姿式矩阵(bindpose)
3.有时存储的Mesh顶点信息不是直接的世界坐标,而是一个有层次结构的mesh,但这并不影响流程,只要在运算时增长一步将这些顶点转化为世界坐标的操做便可。
2.计算EL顺时针旋转90°后S点的位置。
直接经过左肩SL,左肘关节EL的缩放、旋转、平移信息计算小臂LA空间的模型矩阵,使用上一步算出的小臂LA空间坐标乘以该模型矩阵即算出了该点收到骨骼移动的影响后的位置。

3.顶点混合
有一些顶点不必定只受一个骨骼的影响,可能受多个骨骼的影响,此时就要经过顶点混合计算该点的新坐标。
如今假设点S同时受SL,EL的影响且影响权重分别为0.4,0.6。
①分别计算点S在SL、EL空间中的本地坐标。

②分别计算点S在SL、EL移动后的世界坐标。

③根据SL、EL对点S的影响权重混合坐标,得到新的世界坐标。

这里举例的是受两个骨骼影响的状况,受3个、4个时原理也是相同的,只不过运算量会更大一些。3d

小结

1.空间的平移、旋转、缩放均可以用矩阵来表示,并且这些矩阵也能够结合在一块儿成为一个矩阵,多层空间结构的变换也同样能够组合为一个矩阵。
2.如今看来骨骼动画的核心其实就是几个矩阵乘法的问题,大概就是这样:

其中model矩阵随着动画的播放不停的变化,也就实现了骨骼带动皮肤的功能。
假设一个顶点受多个骨骼影响,那么就再根据权重混合一下。
3.mesh的初始位置、bindpose、影响因素都是经过制做该骨骼动画模型时肯定的,能够参考第一小节制做骨骼动画的过程。code

Unity中的骨骼动画

咱们这里以一个Mixamo上的免费资源Samba Dancing为例。orm

资源下载


资源导入

直接将资源拖入Unity中便可,能够看到在Unity中生成了一个文件夹和一个预制件。
htm

加入动画

1.把模型prefab拖入场景中。
2.而后将mixamo.com动画拖到场景中的Samba Dancing中,Unity会自动生成对应的Animator Controller。
blog

运行场景,查看动画效果

直接点击运行便可。ci

数听说明

动画数听说明:


左边是每一帧变化的骨骼,右边是每一个骨骼关键帧的平移,旋转,缩放信息。

模型信息说明

Skinned Mesh Renderer属性详解

Cast Shadows:是否投射阴影。
Receive Shadows:是否接收阴影。
Materials:材质。
Use Light Probes:是否使用光探针。
Reflection Probes:反射探针设置。
Anchor Override:网格锚点。
Lightmap Parameters:光照烘培参数。
Quality:每一个顶点最多收到的骨骼影响数量。
Update When Offscreen:当mesh在屏幕外时是否更新,依据RootBone和Bounds判断。
Mesh:Mesh信息。
mesh信息包含了每一个顶点的位置信息,受骨骼影响的权重信息、切线、法线、UV映射信息。
RootBone:根骨骼,有两个做用。
1.做为mesh在屏幕外时是否更新的依据。
2.进行坐标计算时的Root空间。
在Unity中计算mesh上一点位置的流程大概是这样的:

先经过上述一系列计算获得点在RootBone空间中的位置,上述过程对开发者时不可见的。而后将接下来的步骤交给Material中的Shader解决。查看Shader文件能够看到,在顶点着色其中第一步会给输入的点乘以一个MVP矩阵获取该点在屏幕上的位置,其中的M就是RootBone的模型矩阵。
以Unity 5.37的Standard Shader为例,截取其使用的顶点着色器的一部分

VertexOutputForwardBase vertForwardBase (VertexInput v)
{
    UNITY_SETUP_INSTANCE_ID(v);
    VertexOutputForwardBase o;
    UNITY_INITIALIZE_OUTPUT(VertexOutputForwardBase, o);
    UNITY_TRANSFER_INSTANCE_ID(v, o);
    UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);

    float4 posWorld = mul(unity_ObjectToWorld, v.vertex);
    #if UNITY_REQUIRE_FRAG_WORLDPOS
        #if UNITY_PACK_WORLDPOS_WITH_TANGENT
            o.tangentToWorldAndPackedData[0].w = posWorld.x;
            o.tangentToWorldAndPackedData[1].w = posWorld.y;
            o.tangentToWorldAndPackedData[2].w = posWorld.z;
        #else
            o.posWorld = posWorld.xyz;
        #endif
    #endif

能够看到,经过posWorld = mul(unity_ObjectToWorld, v.vertex);对顶点以RootBone空间为基础作了转换。

Bounds:根骨骼的边界。

若是有什么错误,但愿各位在博客下留言指正,我会尽快改正。

相关文章
相关标签/搜索