一般来讲,在D3D和OpenGL中最简单的动画实现方式就是对模型进行矩阵变换,改变模型的各个组成部分的相对位置以及在世界坐标系中的绝对坐标。在许多初级的OpenGL教程中,往往以这种动画为例子来说明矩阵变换的含义,但是这种动画制作方式,只适合于刚体运动的描述,所为极为有限,当需要产生难以进行数学描述的模型扭曲变形动画时,就变得很难实现。通常对于这种情况我们使用不断修改顶点缓冲区数据的方法来完成从一个关键桢到另外一个关键桢的模型变形过程,当模型数据量比较大的时候,这种方法是很低效的,而在DirectX9中我们可以很容易的利用Vertex Shader的Tweens技术来完成。
下面是笔者写的例程中的几幅截图:
一 原理
实现关键桢动画的生成原理都是相同的,就是差值。实际上所谓Tweens就是差值的意思。所以原理很简单,可用下图表示
假设我们已知在KeyFrame1中 顶点 VertexA = VertexValue1,
在KeyFrame2中顶点 VertexA = VertexValue2,所以当生成动画时我们只需让
VertexA = VertexValue1*(1-Weight) +VertexValue2*Weight (Weight 从0 ——>1变化)就可以实现动画效果了。当所有的顶点都按规则变化模型的变形动画就产生了。
二 实现
我们知道Vertex shader能够在渲染前能够对顶点进行逐个变换和修改,利用这一点很容易我们就可以实现关键桢动画的生成。具体操作如下:
1、确定Vertex Shader的输入
两个顶点数据流,索引号可假设为0(作为KeyFrame1)和1(作为KeyFrame2),分别指向顶点缓冲区pVBKeyFrame1和pVBKeyFame2,分别存储顶点在两个关键帧中的值
Tweens的权值,即前面所提到的Weight,用来确定当前要输出的顶点值
2、渲染方法
在读入模型文件时我们创建索引缓冲区,确定好索引号与顶点之间的对应关系,在渲染时使用索引缓冲区渲染(DrawIndexedPrimitive()),而这时索引缓冲区指向的是Tweens方法利用两个输入缓冲区生成一个新的顶点缓冲区。这种做法的好处是减少了实际进行运算的顶点数目(对共享顶点进行变换),提高了速度,并且易于控制,不易出错。
整个处理流程如下图所示:
其中,所有的模型使用共享的索引缓冲区,所以对所使用的关键桢模型是有要求的,即要求模型的索引值对应的顶点具有相同的含义,换句话说,假设index =1对应的三角形在模型1中是手指甲,则在模型二中也应保证该对应关系,一般来讲是用常用的三维建模工具变换所得的模型对这一点能够保证,但切忌不要增删顶点数目,这样会导致对应关系混乱,因此在笔者的例程中对这种情况进行了检查。
三 程序
下面是vs的代码段
;------------------------------------------------------------------------------
; Constants specified by the app
; c0 = ( 1-fWeight,fWeight, 0.0f, 0.0f ),其中1-fWeight作为数据流0的权值,fWeight作为数据流1的权值
; c1-c4 = matWorldViewProjection
; c5 = light color
; c6 = Diffuse color
; c7 = Ambient color
; c8 = 0.0f, 1.0f, 0.0f,0.0f; 保存了一些常用常量0,1
;
; 顶点声明
; v0,v1 = 数据流0,1的位置
; v2,c3 = 数据流0,1的法线
; v4 = 纹理坐标
;------------------------------------------------------------------------------
vs.1.1
;数据流0中的顶点位置
dcl_position0 v0
;数据流1中的顶点位置
dcl_position1 v1
;数据流0中的法线
dcl_normal0 v2
;数据流1中的法线
dcl_normal1 v3
;文理坐标
dcl_texcoord0 v4
;------------------------------------------------------------------------------
; 顶点变换
;------------------------------------------------------------------------------
; 用v0和v1根据权值生成新坐标
mul r0, v0, c0.x
mul r1, v1, c0.y
add r2, r0, r1
; 坐标变换并输出
m4x4 oPos, r2, c1
;------------------------------------------------------------------------------
; 光照计算
;------------------------------------------------------------------------------
; 在法线v2和v3中差值生成新法线
mul r0, v2, c0.x
mul r1, v3, c0.y
add r2, r0, r1
; 光照计算
dp3 r1.x, r2, c5 ; r1 = normal dot light
max r1.x, r1.x, c8.x ; if dot < 0 then dot = 0
mul r0, r1.x, c6 ; Multiply with diffuse
add r0, r0, c7 ; Add in ambient
min oD0, r0, c8.y ; clamp if > 1
;------------------------------------------------------------------------------
; 纹理坐标直接输出
;------------------------------------------------------------------------------
mov oT0.xy, v4
其中涉及到,法线的变换,及简单的光照计算,文理输出等,不多说了,大家自己看程序吧
四 关于本文和例程
这是笔者的第一个vertex shader程序,写完之后觉得或许对大家可能会有所帮助所以写了这篇东西,主要针对初学者,希望学习的时候能够省些力气。
本例程的框架使用了d3d的框架程序,代码参考了direct9 SDK中的DolphinVS例程,
程序实现了多个模型之间的差值变换,增加关键帧数目只需执行方法CTweenNeed::addKeyFrameFromXFile()即可。