骨骼skeleton就是表示其物体支架的向量。它提供了网格(meshes)分级,方便驱动一个角色的动画系统。骨骼的表面由皮肤包围,就成为一个立体模型了。这些皮肤是由向量和几何图形组成的。各个骨骼相互影响,以此达到模拟真实动作的效果,如人手运动。
与前面一般动画不同的是 root坐标不一定是世界坐标,比如是人的躯干坐标。
顶点混合(Vertex Blending)
关节处的皮肤在实际世界中是会邹起来的,如下图:
要模拟这个效果就需要用到vertex blending这个技术了。
关节处的vertex是会由两个甚至是多个骨骼影响的,那么就需要利用加权的方法,确定关节处的vertex受骨骼影响的程度。这些权一般是由美术确定的(利用maya等软件),然后保存到3d模型文件,如.x文件。设置好的话,就会产生平滑的动画,而不会像之前的机械手那么生硬的动作。
一般一个顶点最多只需要受4个骨骼影响就可以产生平滑的动画了。
毫无意外的,这些骨骼的信息都应该由一个结构体来表示,如下:
利用这样的结构体代表顶点信息,渲染这些顶点就是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.
因为我们本来就把根节点放置到世界坐标中,所以不需要转移或者变形。