3D模型动画技术 - 皮肤变形计算(skinned meshes)

骨骼 skeleton就是表示其物体支架的向量。它提供了网格(meshes)分级,方便驱动一个角色的动画系统。骨骼的表面由皮肤包围,就成为一个立体模型了。这些皮肤是由向量和几何图形组成的。各个骨骼相互影响,以此达到模拟真实动作的效果,如人手运动。

与前面一般动画不同的是 root坐标不一定是世界坐标,比如是人的躯干坐标。

顶点混合( Vertex Blending)

关节处的皮肤在实际世界中是会邹起来的,如下图:

3D模型动画技术 - 皮肤变形计算(skinned meshes)_第1张图片

要模拟这个效果就需要用到vertex blending这个技术了。

关节处的vertex是会由两个甚至是多个骨骼影响的,那么就需要利用加权的方法,确定关节处的vertex受骨骼影响的程度。这些权一般是由美术确定的(利用maya等软件),然后保存到3d模型文件,如.x文件。设置好的话,就会产生平滑的动画,而不会像之前的机械手那么生硬的动作。

一般一个顶点最多只需要受4个骨骼影响就可以产生平滑的动画了。

毫无意外的,这些骨骼的信息都应该由一个结构体来表示,如下:

3D模型动画技术 - 皮肤变形计算(skinned meshes)_第2张图片

利用这样的结构体代表顶点信息,渲染这些顶点就是vertex blending了。

计算顶点v的最终位置的公式是:

v = w0*v*F0 + w1*v*F1……wn-1*v*Fn-1

等于把这些v受影响的因素都加起来了。其中w0,w1...wn-1是权重,加起来应该是1; F0,F1,...Fn-1是转换矩阵

如果有单位法向量的话,也需要转换单位法向量:

n = w0*n*F0 + w1*n*F1……wn-1*n*Fn-1

这个可以自己编程在shader中计算,shader很灵活的,这也是为什么shader要成为主流的原因之一吧。

下面是vertex shader的代码片段,假设影响vertex blending的是两个骨骼:

uniform extern float4x4 gWorld;
uniform extern float4x4 gWorldInvTrans;
uniform extern float4x4 gWVP;

// The matrix palette.
uniform extern float4x4 gFinalXForms[35];

OutputVS VBlend2VS(float3 posL    : POSITION0,
                   float3 normalL : NORMAL0,
                   float2 tex0    : TEXCOORD0,
                   float weight0  : BLENDWEIGHT0,
                   int4 boneIndex : BLENDINDICES0)
{
      // Zero out our output.
      OutputVS outVS = (OutputVS)0;

      // 计算顶点的公式算法.这个是本章重点
      float weight1 = 1.0f - weight0;//因为权重加起来是1的,所以weight1+weight0 = 1;
 
      float4 p = weight0 * mul(float4(posL, 1.0f),
            gFinalXForms[boneIndex[0]]);
      p       += weight1 * mul(float4(posL, 1.0f),
            gFinalXForms[boneIndex[1]]);
      p.w = 1.0f;//表示顶点,所以w值为1

      // 计算单位法向量的公式算法,这个也是本章重点
      float4 n = weight0 * mul(float4(normalL, 0.0f),
            gFinalXForms[boneIndex[0]]);
      n       += weight1 * mul(float4(normalL, 0.0f),
            gFinalXForms[boneIndex[1]]);
      n.w = 0.0f;//表示方向,所以为值0

      // Transform normal to world space.
      outVS.normalW = mul(n, gWorldInvTrans).xyz;

      // Transform vertex position to world space.
      float3 posW  = mul(p, gWorld).xyz;

      // Transform to homogeneous clip space.
      outVS.posH = mul(p, gWVP);
      .
      . // 其他代码与本章无关.
      .
}

 

骨骼Bone的数据结构可以自己写,但是D3DX提供了一个方便的类:D3DXFRAME

这个是D3DX的一个类,表示一个bone,但是为什么不直接叫bone呢?因为这个类也可以代表非bone的架构,比如房子的各个部分的表示等。

typedef struct _D3DXFRAME {
      LPSTR Name;
      D3DXMATRIX TransformationMatrix;
      LPD3DXMESHCONTAINER pMeshContainer;
      struct _D3DXFRAME *pFrameSibling;
      struct _D3DXFRAME *pFrameFirstChild;
} D3DXFRAME, *LPD3DXFRAME;


但是这个数据结构没有带根节点,那么我们只好自己定义了。再次看的出来“数据结构和算法”的基本功夫是多么重要的。

struct FrameEx : public D3DXFRAME
{
      D3DXMATRIX toRoot;
};

然后我们可以循环所有D3DXFRAME类,建造到根节点的变换矩阵。(又是数据结构和算法的知识了)

void SkinnedMesh::buildToRootXForms(FrameEx* frame,
                                     D3DXMATRIX& parentsToRoot)
{
      // 这里使用C++的同名变量节省一点空间.
      D3DXMATRIX& toParent = frame->TransformationMatrix;
      D3DXMATRIX& toRoot   = frame->toRoot;
      toRoot = toParent * parentsToRoot;//相当于frame->toRoot = toParent * parentsToRoot;

      FrameEx* sibling    = (FrameEx*)frame->pFrameSibling;
      FrameEx* firstChild = (FrameEx*)frame->pFrameFirstChild;

      // Recurse down siblings.
      if( sibling )
            buildToRootXForms(sibling, parentsToRoot);

      // Recurse to first child.
      if( firstChild )
            buildToRootXForms(firstChild, toRoot);
}

然后如下调用:

D3DXMATRIX identity;
D3DXMatrixIdentity(&identity);
buildToRootXForms((FrameEx*)mRoot, identity);

identity表示是对角单位矩阵,也就相当于数学的1,不过这里的是矩阵的1,任何数乘以对角单位矩阵都等于1.

因为我们本来就把根节点放置到世界坐标中,所以不需要转移或者变形。

你可能感兴趣的:(动画,skin,骨骼动画,3D模型,meshes,皮肤变形)