【3D技术宅公社】XR数字艺术论坛  XR技术讨论 XR互动电影 定格动画

 找回密码
 立即注册

QQ登录

只需一步,快速开始

调查问卷
论坛即将给大家带来全新的技术服务,面向三围图形学、游戏、动画的全新服务论坛升级为UTF8版本后,中文用户名和用户密码中有中文的都无法登陆,请发邮件到324007255(at)QQ.com联系手动修改密码

3D技术论坛将以计算机图形学为核心,面向教育 推出国内的三维教育引擎该项目在持续研发当中,感谢大家的关注。

查看: 3251|回复: 3

骨骼动画(一)

[复制链接]
发表于 2006-12-11 23:24:23 | 显示全部楼层 |阅读模式
第一节:建立一个空的D3D运行平台
Keep in touch :zpxocivuby@126.com

目的:指导别人建立一个D3D的运行平台,介绍一些几本的概念
任务:编写教程,编写代码
阅读对象:d3d入门者,c/c++熟练,windows编程熟练,VC IDE熟练
描述:在进行我们的d3d游戏开发之路之前,我们要先建立一个可供我们运行的环境,做一些初始化的工作。

步骤:
1. 初始化一个d3d的各种设备
a) 建立一个windows窗口(用来……),窗口的基本的参数设置
b) 初始化D3D设备这里要分四步:
   i. 建立一个IDirect3D9 的COM(很多书中将COM说得如何如何庞大如何如何复杂,我们都用不上^_^,因为我们不是在做组件,我们只是使用COM,你只要将COM当作是一个结构,以及有好多方法来控制该结构就行了。如果你是一个java程序员那么你可以认为它是一个接口interface)接口。
   IDirect3D9* d3d9 = 0;
   d3d9 = Direct3DCreate9(D3D_SDK_VERSION);

  ii. 得到硬件设备的能力信息(也就是说,我们在操作d3d设备的时候要知道该设备有那些能力,比如说有的设备有支持剪裁(Device supports blits),支持2级缓冲,光栅化等等),只有在用有能力的设备上才能使用各种“能力“不然……(就像我不会飞,你非要让我飞一样)。
这次我们只要得到设备是否支持Device can support transformation and lighting in hardware. (是否支持转换和光照)

  iii. 建立我们的Device建立参数
   这个参数的作用是用来告诉D3D你要建立一个什么样的设备。比如是不是全屏啊,颜色模式是什么啊,缓冲的数量是多少阿等等。具体信息可以查看MSDN的D3DPRESENT_PARAMETERS信息(坚持看MSDN啊!好处很多,说不完啊);

  iv. 建立设备Device
   d3d9->CreateDevice(
     D3DADAPTER_DEFAULT,//适配器类型
     D3DDEVTYPE_HAL, //设备类型(改参数是由第二步中得到设备能力来决定的)
     hwnd,//窗口句柄,(什么?你说什么是句柄?你就当它是握在你手中的炒锅的锅柄,你可以方便的将炒锅(窗口)反过来翻过去(各种窗口操作)。当然句柄还有很多功能,在这里我们只用到这些,当使用到别的功能时我再说)。
     D3DCREATE_HARDWARE_VERTEXPROCESSING,// D3DCREATE标记这里我们使用的是硬件处理顶点向量能力(改参数的确定需要第二步的判断。)这下知道第二步的重要性了吧
     &d3dpp,//设备参数,第三步设定的参数
     device);//返回的设备的指针(什么?什么是指针?你可能不太适合读这个文档,建议你把它丢弃。请注意我的阅读对象)

好了,我们终于建立了一个IDirect3DDevice9这个东东是我们以后进行D3D游戏开发的终极直接操作对象。(就像CS中的大狙^_^)。

2. 将要绘制的数据进行初始化(包括,顶点向量的初始化,顶点索引的初始化)
   a) 建立顶点向量缓冲
   b) 建立顶点索引缓冲
   c) 充填缓冲数据
   d) 设置视点(就是我们的观察点)

3. 进入绘制图像循环(在此绘制每一帧的图像,也就是我们常说的动画制作了)
在这里你就要做你想做的事了(do what you want to do!)哈哈,我们来做一个三角形转换的绘制循环

  a) 首先你要在(数据进行初始化(包括,顶点向量的初始化,顶点索引的初始化))中设定三个顶点向量缓冲,以及一个顶点索引缓冲(什么?为什么要有顶点索引缓冲?这个问题问的好,我保证你是个聪明的baby。这个东东是用来告诉d3d哪些顶点是哪个三角形的你建立的三个顶点,当然可以知道是这三个顶点画三角形,但是如果你有四个,五个……你如何让d3d知道那几个顶点是那个三角形的点啊?所以要在顶点索引中确定三角形的索引顶点.)
建立一个向量缓冲

  Device->CreateVertexBuffer( 4 * sizeof(Vertex), D3DUSAGE_WRITEONLY, Vertex::FVF, D3DPOOL_MANAGED, &VB, 0);
   (^_^这个函数我会详细介绍现在你先不要管了)

设置向量点
   Vertex* vertices;
   VB->Lock(0, 0, (void**)&vertices, 0);//锁定向量缓冲

  // vertices of a unit cube
   vertices[0] = Vertex(-1.0f, -1.0f, -1.0f);
   vertices[1] = Vertex(-1.0f, 1.0f, -1.0f);
   vertices[2] = Vertex( 1.0f, 1.0f, -1.0f);
   VB->Unlock();//解锁向量缓冲

建立顶点索引缓冲
   Device->CreateIndexBuffer( 6 * sizeof(WORD), D3DUSAGE_WRITEONLY, D3DFMT_INDEX16, D3DPOOL_MANAGED, &IB, 0)
   (^_^参数信息请查MSDN好了)

设置索引值
   WORD* indices = 0;
   IB->Lock(0, 0, (void**)&indices, 0);

  // front side
   //其中indices[0]=0;的意思是第1个三角行的第0个顶点是在顶点向量的第0个值,就是vertices[0]的值.同理indices[1] = 1; 的意思是第1个三角行的第1个顶点是在顶点向量的第1个值,就是vertices[1]的值, 同理indices[2] = 2; 的意思是第1个三角行的第2个顶点是在顶点向量的第2个值,就是vertices[2]的值.那,这样这个三角形就定义好了
   indices[0] = 0; indices[1] = 1; indices[2] = 2;
   IB->Unlock();

你还可以再加一个三角形,但是现在只有三个顶点啊,三个顶点只能有一个三角形啊(不错!小学毕业了!^_^),没关系我们再加一个顶点
   vertices[3] = Vertex( 1.0f, -1.0f, -1.0f);
同时向量索引也要加上几句
   indices[3] = 0; indices[4] = 2; indices[5] = 3;
   //( 加三句?三个点当然要加三句了)
   //其中indices[3]=0;的意思是第2个三角行的第0个顶点是在顶点向量的第0个值,就是vertices[0]的值.同理indices[4] = 2; 的意思是第2个三角行的第1个顶点是在顶点向量的第2个值,就是vertices[2]的值, 同理indices[5] = 3; 的意思是第2个三角行的第2个顶点是在顶点向量的第3个值,就是vertices[3]的值.那,这样两个三角形就定义好了

  b) 下面就是我们真正的动画作图了(^_^ cover me其实还不能动)

  Device->Clear(0, 0, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 0xffffffff, 1.0f, 0);//清屏
   Device->BeginScene();//开始画屏

  Device->SetStreamSource(0, VB, 0, sizeof(Vertex));//设置缓冲的输入
   Device->SetIndices(IB);//设置顶点索引缓冲
   Device->SetFVF(Vertex::FVF);设置FVF(Flexible Vertex Format 灵活的向量格式)

记得刚才有点东西没讲吗?对了就是那句Device->CreateVertexBuffer();

我们来看看它的参数

HRESULT CreateVertexBuffer(
   UINT Length,//长度一般为n*sizeof(Vertex)n是你要建立的顶点的数量,程序中我设了n=4(四个点)
   DWORD Usage,// D3DUSAGE_WRITEONLY
   Informs the system that the application writes only to the vertex buffer. Using this flag enables the driver to choose the best memory location for efficient write operations and rendering. Attempts to read from a vertex buffer that is created with this capability will fail. Buffers created with       D3DPOOL_DEFAULT that do not specify D3DUAGE_WRITEONLY might suffer a severe performance penalty(通知系统只能向顶点写模式,它要求必须选择最适合的内存单元,有效的提供给设备写操作和渲染。如果尝试失败就建立一个默认的模式)
   DWORD FVF,//灵活向量模式
   D3DPOOL Pool,//池管理类型D3DPOOL_MANAGED
   IDirect3DVertexBuffer9** ppVertexBuffer,// 返回的顶点向量缓冲
   HANDLE* pHandle//NULL
);

  其中有一个参数要注意FVF这个参数的英文的意思是Flexible Vertex Format 灵活的向量格式为什么呢?大家有没有注意到第一个参数的sizeof(Vertex) Vertex类型,这个类型不是D3D的类型,是我们自定义的类型这个类型是这样的:

struct Vertex
{
   Vertex(){}
   Vertex(float x, float y, float z)
   {
     _x = x; _y = y; _z = z;
   }
   float _x, _y, _z;
   static const DWORD FVF;
};
const DWORD Vertex::FVF = D3DFVF_XYZ;

  里面有四个内容,_x,_y,_z, FVF.我们再看它的构造函数Vertex(float x, float y, float z)只构造了_x,_y,_z,而FVF是在外部定义的常量,说明FVF不是Vertex的有效使用数据,它只不过是一个描述信息罢了,那么是描述什么的呢?

  我们建立了Vertex,并且又向D3D传递了这个参数,是不是应该告诉D3D该结构是什么样的格式呢?对了,FVF就是要告诉D3D这个信息,这里我们让FVF=D3DFVF_XYZ是说这个格式是由xyz三个定点的用户自定义格式。当然还有其他的格式比如说xyzRGB(r,g,b)三顶点和一个顶点颜色格式D3DFVF_XYZ|D3DFVF_DIFFUSE等等。你可以参考D3DFVF的MSDN信息

  // Draw cube.
   Device->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, //绘制类型
   0,
   0,
   4, //顶点数量
   0,
   2);//三角形数量
请参阅MSDN 文档

  Device->EndScene();
   Device->resent(0, 0, 0, 0);

好了到此,我们这节的任务圆满完成了^_^看看源代码吧,我都有注释的

  我得运行环境是VC7.1可能有的人打不开工程,没事你可以用VC6建立一个空的Windows项目将文件引入,RUN,OK!good luck!

  申明:代码是引用Frank Luna他老人家的代码,我无意窜取他的劳动果实,只是懒得重新写代码所以拿来用一下。过后我会自己再写个比较复杂的,之所以用他老人家的代码是因为直接明了,没有那么多的类阿,模式啊什么的。代码中我会添加注释信息

 楼主| 发表于 2006-12-11 23:25:06 | 显示全部楼层
骨骼动画(二)

类型:转贴 | 来源:整理| 时间: 2006-05-22

第二节:建立一个空的D3D运行平台
Keep in touch :zpxocivuby@126.com

目的:指导别人使用矩阵
任务:编写教程,编写代码
阅读对象:d3d入门者,c/c++熟练,windows编程熟练,VC IDE熟练
描述:
矩阵在3d游戏当中担当着十分重要的角色。虽然在某些方面不如四元数(quaternion)但是我们不得不承认矩阵是计算机图形学的基础。

下面我们就来学习一下这个东东

矩阵的概念
What are matrices and how do they work? (矩阵是什么和它们怎样工作)

矩阵其实是一个高级的数学主题(参看线性代数),所以,在此我只能尽力地、简要地概括一下。矩阵就像是一个表格,有一定数目的行与列;每个格子中都一个数字或表达式。通过特定的矩阵,我们可以对3D对象中的顶点坐标进行运算,来实现类似移动、旋转、缩放这样的操作。在DirectX中,矩阵就是4X4的数表。下面的这幅图是一个矩阵的例子,这个矩阵能使一个对象(由它的所有顶点)缩放到原来的五倍。
 

那么我们用它来作甚么啊?其实很简单我们仅仅用它来做变换。

首先,给出一个点a(x,y,z),它是相对于我们想象空间点o(0,0,0)的,如果相对原点改变为(1,0,0)怎么半啊?那么a点变成(x-1,y,z).

这么说吧,其实说白了矩阵就是将一个位置的点通过变换矩阵移动到另一个位置(这样才能产生动画啊!)。你不用把矩阵看的很神秘,你就把它当成一个可以让你随心所欲的变换位置的工具。

基本的矩阵操作函数有以下几种:

1. D3DXMatrixRotationX(D3DXMATRIX *out ,FLOAT Angle);//围绕x轴转动
2. D3DXMatrixRotationY(D3DXMATRIX *out ,FLOAT Angle);//围绕y轴转动
3. D3DXMatrixRotationZ(D3DXMATRIX *out ,FLOAT Angle); //围绕z轴转动
4. D3DXMatrixRotationYawPitchRoll(D3DXMATRIX *out ,FLOAT x, FLOAT y, FLOAT z)//在xyz方向上偏移x,y,z大小
5. D3DXMatrixScaling(D3DXMATRIX *out ,FLOAT x, FLOAT y, FLOAT z)//缩放比例
6. D3DXMatrixShadow(D3DXMATRIX *out , D3DXVECTOR4* pLight, D3DXPLANE *pPlane)//平面投影矩阵
7. D3DXMatrixTranslation()//建立移动矩阵
8. D3DXMatrixTransformation(D3DXMATRIX *pOut, D3DXVECTOR3 *pScalingCenter, D3DXQUATERNION *pScalingRotation, D3DXVECTOR3 *pScaling, D3DXVECTOR3 *pRotationCenter, D3DXQUATERNION *pRotation, D3DXVECTOR3 *pTranslation);

这个函数我要给大家仔细的讲一下(竖起耳朵听啊^_^)

第一个参数:D3DXMATRIX *pOut返回的转换矩阵
第二个参数D3DXVECTOR3 *pScalingCenter 缩放的原点
第三个参数D3DXQUATERNION * pScalingRotation 这是个四元素(什么?什么是四元素?看来这个函数介绍要花点时间了。D3DXQUATERNION这个结构有四个内容x,y,z,w其中xyz表示一个向量,w则表示一个旋转度,该结构表示的是围着xyz向量旋转w)
第四个参数D3DXVECTOR3 *pScaling 缩放向量(同上面两个参数联系起来那就是在pScalingCenter原点,pScaling方向上缩放|pScaling|大小,或旋转pScalingRotation)
第五个参数旋转圆点
第六个参数:旋转的四元素
第七个参数:旋转向量

当得到你想要的转换矩阵之后,你就可以用ID3DXEffectStateManager::SetTransform()来设置你要操作的对象了,参数说明

D3DTRANSFORMSTATETYPE State, 转换的类型,一般是D3DTS_VIEW(视点转换), D3DTS_PROJECTION(项目转换)等
D3DMATRIX *pMatrix转换矩阵

现在来看看代码吧

其他部分都一样,有两个地方需要解释一个是在setup()方法中,另一个是在Display()方法中

D3DXVECTOR3 position(0.0f, 0.0f, -5.0f);//
D3DXVECTOR3 target(0.0f, 0.0f, 0.0f);
D3DXVECTOR3 up(0.0f, 1.0f, 0.0f);

//以上我们定义了三个向量,这三个向量是用来建立左手观察矩阵的
D3DXMATRIX V;
D3DXMatrixLookAtLH(&V, &position, &target, &up);

建立左手观察矩阵的函数

概念讲述

左手观察矩阵

下面的两幅图演示了左手3D 迪卡尔坐标系统是怎样工作的。

Fig 2.3 Fig 2.4

为什么称为左手观察矩阵呢?这样,你将左手平放在胸前,拇指与其他指头垂直,掌心向上,你可以看到掌心的方向与图2.3的y方向是一样的食指的方向与x方向是一样的拇指的方向与z轴的方向是一样的。(你可能会问,那有左手的,就应改有右手的啊,是的有右手的,只要将图2.3中的x轴方向反过来就是了)

Device->SetTransform(D3DTS_VIEW, &V);

设置视点转换

//以下是设置项目转换矩阵
D3DXMATRIX proj;
D3DXMatrixPerspectiveFovLH(
&proj,
D3DX_PI * 0.5f, // 90 - degree
(float)Width / (float)Height,
1.0f,
1000.0f);
Device->SetTransform(D3DTS_PROJECTION, &proj);

在Display()方法中需要添加几行代码

D3DXMATRIX Ry;

//申明一个矩阵
static float y = 0.0f;
D3DXMatrixRotationY(&Ry, y);

//以y的偏移量建立y轴旋转矩阵
y += timeDelta; //还记得timeDelta时代表什么吗?对了它是当前的时间差(上次循环到这次的时间,很少啊(毫秒艾!!!))

if( y >= 6.28f ) //为什么要判断大于6.28呢?2∏是一周啊∏≈3.14啊,你可以试着改变一下该值看看有什么事情发生
y = 0.0f;

Device->SetTransform(D3DTS_WORLD, &Ry);

好了讲完了,运行一下看看吧,一个四边形围着y轴在转啊!^_^

下一节我们要进入我们的正题了――骨骼动画的基本概念,我会带你一步步的进入到(母体matrix中)嘿嘿

今天又写完了一界了不知道大家看了有没有效果,或者说我的方法是不是有问题,如果你有好的建议或者意见请与我联系 zpxocivuby@126.com

 楼主| 发表于 2006-12-11 23:26:06 | 显示全部楼层
骨骼动画(三)

类型:转贴 | 来源:整理| 时间: 2006-05-24

第三节、简单的骨骼动画、骨骼动画理论
Keep in touch: zpxocivuby@126.com

目的:指导别人使用矩阵
任务:编写教程,编写代码
阅读对象:d3d入门者,c/c++熟练,windows编程熟练,VC IDE熟练
描述:
这一届我们来做一个简单的骨骼动画(非常简单啊!只有两个线段啊!)并且介绍一下实现思想是什么

在3d世界中,人物的动画过程主要有两种实现方式:

1. key frame animation(关键帧动画)

a) 是将要播放的关键帧都做好放到存储介质中(文件,内存),播放时一帧帧的调出来

2. (骨骼动画)

a) 是将动作对象分解成一个个的键(joint),和骨(bone)要使对象动作只要将joint和bone进行矩阵坐标转换就可以了

第一种方式我们暂时不研究(等我写完骨骼动画后我会再介绍的),我们只关心第二种实现方式。

下图就是一个最最基本的骨骼构成模式

 

图1

其中键有:A,B,C,D;骨有a,b,c;我们现在假设ABCD的坐标是A(1,0,Z),B(0,0,Z)C(0,1,Z)D(1,1,Z),我们想让a,c骨骼动起来,而b不动

 

图2

经过变换之后我们的图1就变成图2了。现在我们来看看用数学表达是怎样表示上面的转换我们设C>C’转了r度,D>D’在C不变的情况下(什么?为什么C不变?这个……你自己考虑吧)转了t度,则C’的坐标是(x’,y’,Z),D’的坐标是(x’’,y’’,Z)。什么坐标是怎么计算出来的?你可真着急啊当然是通过矩阵变换的啊

计算步骤:

第一步:我们首先认为C和D点是一起的也就是说当C移动时D也随之移动,这样我们可以计算出C’和D’’的坐标都是绕A点以垂直于AC和CD的平面的向量旋转的,通过转换矩阵我们可以得到C’D’’的向量坐标,
  

第二步: 再处理D’’向D’转动,这是可以将C”点看成是转点,计算得出D’的向量坐标

好了,我们来看一下代码吧

为了方便操作,我将原本在setup函数中的vertices变量放到了外边,成了全局变量(这样不是一个好的方法,但这只是一时之举,少后我将会改正)

Vertex* vertices;

在程序中我们将键(joint)设成一个点,骨(bone)则是一个线段
其中要注意的代码只有一段,那就是在display()函数中的下列语句

//申明了四个点,其中tempA,tempB,tempC各代表ACD三个点tempD代表了一个临时的数据存放空间,主要是用来放转换之后的C’C’’D’的位置数据
D3DXVECTOR3* tempA = new D3DXVECTOR3(vertices[0]._x,vertices[0]._y,vertices[0]._z); //将ABC复制到temp里面
D3DXVECTOR3* tempB = new D3DXVECTOR3(vertices[1]._x,vertices[1]._y,vertices[1]._z);
D3DXVECTOR3* tempC = new D3DXVECTOR3(vertices[2]._x,vertices[2]._y,vertices[2]._z);
D3DXVECTOR3* tempD =new D3DXVECTOR3() ;

//计算BA
D3DXVECTOR3* BA = &(*tempB-*tempA);//计算出向量BA
//计算CA
D3DXVECTOR3* CA = &(*tempC-*tempA); //计算出向量CA

//将BA在垂直BA方向上旋转ax

D3DXMatrixRotationAxis(&RAba,(D3DXVec3Cross(tempD,BA,CA)),ax);//前面介绍过这个函数
// D3DXVec3Cross函数的功能是将BA向量和CA向量进行X(不能读成”埃克司”啊,应该是“叉乘”)乘,(得到的是一个垂直于BA和CA的向量)
D3DXMatrixRotationAxis(&RAcb,(D3DXVec3Cross(tempD,BA,CA)),ax);
D3DXVec3TransformNormal(BA,BA,&RAba);//该函数的功能是将BA向量通过转换矩阵RAba变成BA’
D3DXVec3TransformNormal(CA,CA,&RAcb);

*tempB = (*BA+*tempA);
//重新对内存中的数据进行复制
vertices[1]._x=tempB->x;
vertices[1]._y=tempB->y;
vertices[1]._z=tempB->z;
*tempC = (*CA+*tempA);

//计算C'B'
D3DXVECTOR3* CB = &(*tempC-*tempB);
//将C'B'在垂直C'B'方向上旋转ax
D3DXMatrixRotationAxis(&RAba,(D3DXVec3Cross(tempD,BA,CA)),-ax);
D3DXVec3TransformNormal(CB,CB,&RAba);
*tempC = (*CB+*tempB);
vertices[2]._x=tempC->x;
vertices[2]._y=tempC->y;
vertices[2]._z=tempC->z;

好了运行一下吧,看看有什么效果!

怎么样是不是很“裸”(这是我上大学时,我们班人常说的一个字,意思说:很差劲,很白痴,无能,发泄的意思,反正不是个好东西了,是我们班人发明的哦,没有授权你不能使用的啊“^_^”)

的确这段代码对付这两个线段是可以了,可是要有上百个上千个怎么办啊?没关系了,我们可以将代码重构一下。写的更加复杂一些,并且使用一些OO(面向对象)方法,和design pattern(设计模式),这些将会在下一界中介绍。

备:写这些东西好累啊,白天上班晚上还要回来写东西真的很累啊,所以这个周休息一下下周再写。

 楼主| 发表于 2006-12-11 23:27:16 | 显示全部楼层

 骨骼动画(Skeletal Animation)(四)

类型:转贴 | 来源:整理| 时间: 2006-05-26

我们的目标是根据骨骼动画来更新模型。

1)骨骼动画数据。上一节中我们已经读出了AnimationSet、Animation和AnimationKey这些动画数据,我们现在要做的就是把它们应用到骨骼上面去。AnimationSet只是标明了我们要播放的动画名称,关键的处理在Animation和AnimationKey上面。Animation包含了所对应的骨骼名称,下属的AnimationKey包含了坐标变换的类型以及对应的时间戳,我们也把AnimationKey看做一个关键帧。下面要做的就是根据当前时间判断动画落在哪两个关键帧中间,例如key1和key2,然后求出插值系数scaler,

scaler = (当前时间-key1.时间)/(key2.时间-key1.时间)

求出插值系数后,骨骼的当前位置就可以用下面的方法求出,注意各种key类型求插值的方法不一样,

switch( Key的类型 )
{
case 旋转:

...

// 四元数插值
D3DXQUATERNION RotationQuaternion;
D3DXQuaternionSlerp(
&RotationQuaternion,
&pAnimationKey->pQuaternionKeys[Key1].value,
&pAnimationKey->pQuaternionKeys[Key2].value,
Scaler);

// 应用旋转矩阵
D3DXMATRIX RotationMatrix;
D3DXMatrixRotationQuaternion( &RotationMatrix, &RotationQuaternion );
pAnimation->pBone->TransformationMatrix *= RotationMatrix;

break;

case 平移和缩放:

...

// 矢量插值
D3DXVECTOR3 InterpolatedVector =
pAnimationKey->pVectorKeys[Key1].value + Scaler *
( pAnimationKey->pVectorKeys[Key2].value - pAnimationKey->pVectorKeys[Key1].value );

if( pAnimationKey->Type == XAnimationKey::KeyType::Scaling )
{
// 应用缩放矩阵
D3DXMATRIX ScalingMatrix;
D3DXMatrixScaling(
&ScalingMatrix, InterpolatedVector.x, InterpolatedVector.y, InterpolatedVector.z );

pAnimation->pBone->TransformationMatrix *= ScalingMatrix;
}
else
{
// 应用平移矩阵
D3DXMATRIX TranslationMatrix;
D3DXMatrixTranslation(
&TranslationMatrix, InterpolatedVector.x, InterpolatedVector.y, InterpolatedVector.z );
pAnimation->pBone->TransformationMatrix *= TranslationMatrix;
}

break;

case 坐标变换矩阵:

...

// 矩阵插值
D3DXMATRIX TransformMatrix =
pAnimationKey->pMatrixKeys[Key1].value + Scaler *
( pAnimationKey->pMatrixKeys[Key2].value - pAnimationKey->pMatrixKeys[Key1].value );

// 应用坐标变换矩阵
pAnimation->pBone->TransformationMatrix *= TransformMatrix;

break;
} // switch

这样我们就把根据当前时间计算出来的插值坐标变换矩阵应用到骨骼上了。

2)骨骼数据。在从文件中读出来的时候,我们已经利用pFrameSibling和pFrameFirstChild两个字段构造了一个层次结构。注意骨骼中的TransformationMatrix包含的是当前骨骼相对于父骨骼的坐标变换,应用到mesh上的时候,我们需要的相对于根骨骼的坐标变换。因此我们要做一下处理,简单的一个递归调用,

//-----------------------------------------------------------------------------
// 名称: UpdateHierarchy
// 描述: 计算本节点及所有兄弟、子节点相对于根节点的偏移矩阵
//-----------------------------------------------------------------------------
void UpdateHierarchy( D3DXMATRIX *matTrans = NULL )
{
D3DXMATRIX matIdentity;

// 根节点的偏移矩阵为单位矩阵
if( NULL == matTrans )
{
D3DXMatrixIdentity( &matIdentity );
matTrans = &matIdentity;
}

// 计算偏移矩阵
matCombined = TransformationMatrix * ( *matTrans );

// 更新兄弟节点
if( pFrameSibling )
( ( D3DXFRAME_EX* )pFrameSibling )->UpdateHierarchy( matTrans );

// 更新子节点
if( pFrameFirstChild )
( ( D3DXFRAME_EX* )pFrameFirstChild )->UpdateHierarchy( &matCombined );
}

通过在根骨骼上的一次调用,我们就可以在自定义的matCombined字段中得到各个骨骼相对于根骨骼的坐标变换矩阵.

3)mesh数据。mesh数据相对简单,ID3DXMesh和ID3DXSkinInfo接口为我们了做大部分的工作。不过天底下没有免费的午餐,为了让它们运转起来,我们还是要做一些额外的努力。在步骤1里面我们已经通过插值得到了骨骼当前的坐标变换矩阵,不过这个坐标变换是相对于模型本地坐标的,为了应用到mesh上,我们需要将坐标对齐到mesh的中心,

// 首先bone转换到以mesh中心为坐标原点的坐标系,然后再应用frame的坐标变换矩阵
for( DWORD i=0; i<m_pRootMeshContainer->pSkinInfo->GetNumBones(); i++ )
{
m_pRootMeshContainer->pBoneMatrices =
*( m_pRootMeshContainer->pSkinInfo->GetBoneOffsetMatrix( i ) );

if( m_pRootMeshContainer->ppFrameMatrices )
m_pRootMeshContainer->pBoneMatrices *= *m_pRootMeshContainer->ppFrameMatrices;
}

这样所有的数据都准备好了,用ID3DXSkinInfo的方法来更新骨骼关联的每个顶点的坐标,(每个顶点根据关联的所有骨骼的坐标变换矩阵乘以对应的权重再相加来得到最终应用到顶点上的坐标变换矩阵)

m_pRootMeshContainer->pSkinInfo->UpdateSkinnedMesh(
m_pRootMeshContainer->pBoneMatrices, NULL, pSrcVertex, pDestVertex );

到此一切准备都结束了! 绘制Mesh的动作和平常一样,设置材质和纹理,然后调用DrawSubSet方法,这个想来对大家是没有什么难度的事情。怎么样,是不是想从头再看一遍回味一下呢? 呵呵。

(附件里面是测试工程,编译环境为VS.NET2003, DXSDK(October 2004)。大家有什么不明白或者我哪里说错的,欢迎给我写信或者留言。)

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|小黑屋|3D数字艺术论坛 ( 沪ICP备14023054号 )

GMT+8, 2025-2-6 06:59

Powered by Discuz! X3.4

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表