计算机图形:动画
基本概念
计算机动画(computer animation):场景中任何随时间而发生的视觉变化。
计算机生成的动画,通过几何变换改变对象位置、大小,随时间改变颜色、不透明度、纹理,改变光照效果和其他参数及照明和绘制过程等来实现。
创建动画序列2种基本方法:实时动画(real-time animation)、逐帧动画(frame-by-frame animation)。
- 实时动画:每个片段生成后,立即播放,其速率必须符合刷新率约束。
- 逐帧动画:场景中每一帧都是单独生成、存储的,这些帧可记录在胶片上连贯地显示出来。
动画的光栅方法
光栅扫描系统:一次一帧地生成动画序列,因而可将生成的帧保存到文件中供以后观看。
存在的问题:
如果创建一帧的时间 > 刷新周期,则导致运动漂移和帧破裂的显示缺陷。
双缓存
光栅系统中,生成实时动画一种方法:使用2个刷新缓存,称为双缓存,解决前面的缺陷问题。
-
工作原理:初始状态时,在第一个缓存中创建动画的一帧;然后,当用该缓存中的帧刷新屏幕时,在第二个缓存中创建下一帧;下一帧创建完后,互换2个缓存角色。
-
双缓存的好处:对创建一帧时间、刷新周期,都没有要求。
缺点:如果 创建一帧时间≈n*刷新周期,则容易出现不规则动画帧率的问题。
解决办法:
1)程序中加入少许时延;
2)改变运动/场景描述,以缩短创建帧的时间。
为支持双缓存,OpenGL提供2个函数:
1)激活双缓存, glutInitDisplayMode(GLUT_DOUBLE)
(init中调用);
2)互换缓存角色,glutSwapBuffers()
(display中调用)。
用光栅操作生成动画
- 用矩形像素阵列的块移动,可以为一些应用生成实时光栅动画,如游戏程序。
- 沿一个二维路径用颜色表变换(color-table transformation)来实现定义对象的动画,并将相继块的像素值设为颜色表入口。
动画序列的设计
一个动画序列设计步骤:
- 故事情节拆分
- 对象定义
- 关键帧描述
- 插值帧的生成
情节版(storyboard):动作的概述,讲一个运动序列定义为一组即将发生的事件。根据目标动画的类型,情节版可以由一组粗略的素描+对运动的简单描述组成,也可为一个关于动作的基本思路的列表。
为动作的每个参加者给出对象定义(object definition),对象可以用基本形体如多边形、样条曲线定义。
关键帧(key frame)是动画序列中特定时刻的一个场景的详细图示。每个关键帧中,每个对象/角色的位置依赖于该帧的时刻。
插值帧(in-betweens)是关键帧之间的帧。插值帧的数量取决于用来显示动画的介质。电影胶片要求24帧/秒,图形终端按60帧/秒刷新。一般,运动时间间隔设为每一对关键帧之间有3~5个插值帧。
传统动画技术
挤压和拉伸(squash and stretch)模拟加速效果(非刚性物体)的重要技术。弹跳球体下落过程被拉伸,接触到地面一瞬间被挤压。
定时(timing)确定运动帧之间的间隔。弹跳球运动帧之间的位置随着球的速度增加,而可能变化。
计算机动画语言
- 动画描述
场景描述(scene description):包含对象和光源定位,光度参数(光源强度和表面照明特性)的定义,照相机参数(位置、方向、镜头特性)的设定。
动作描述:包括对象和照相机的运动路径安排。一般的图形子程序:观察、投影变换、几何变换、可见面识别等。
关键帧系统(key-frame system)通用的动画软件包的一个组件,可从用户描述的关键帧生成插值帧。
参数系统(parameterized system)将对象的运动特征作为对象定义的一部分进行描述。可调整参数控制某些对象特征,如自由度、运动限制、形体变化等。
脚本系统(scripting system)允许通过用户输入的脚本来定义对象描述和动画序列。
关键帧系统
可从两个(或多个)关键帧的描述生成一组插值帧。运动路径可以用运动学描述(kinematic description),如一组样条曲线给出运动路径,或者基于物理的运动描述。
cels(celluloid transparencies):复杂场景,可以将一帧分解为多个cels的部分或对象。背景和场景中每个角色分别放在不同幻灯片上,按从背景到前景的顺序叠到一起,然后拍照得到一个完整的帧。
对象形状由于对象变换,而随时间改变,如衣服、面部特征、放大的细节、爆裂或分解,或将一个对象变换成另一个,等。
变形
变形(morphing):对象的形状从一个形态到另一个形态的变换。动画师通过在两个关键帧之间的插值帧,来转变多边形的形状来为变形建模。
如下图,从关键帧k如何到关键帧k+1?
可以在关键帧k中,添加一个顶点,使其与关键帧k+1顶点数相同。
如何添加?
可以使用线性插值的方法,产生插值帧。
下图展示关键帧k中一条线段变换到关键帧k+1中两天连接的线段的线性插值:
把一个关键帧中添加一定量的边或顶点,称为关键帧补偿通用预处理规则。
- 补偿边
参数\(L_k、L_{k+1}\)表示2个相继帧的线段数。待补偿的线段数量确定如下:
且
边补偿预处理步骤:
1)将边数少的关键帧的\(N_e\)条边分成\(N_s+1\)部分;
2)将边数少的关键帧的余下边分成\(N_s\)部分。
例子:如果\(L_k=15,L_{k+1}=11\),如果进行边补偿?
将关键帧k+1的4条边的每条边分成2部分,关键帧k+1的剩余边不做处理(分成1部分,相对于不处理)。
- 补偿顶点
参数\(V_k、V_{k+1}\)表示两个连续帧的顶点数。待补偿的顶点数量确定如下:
且
顶点补偿预处理步骤:
1)在点数最少的关键帧中,在\(N_{ls}\)线段的部分中添加\(N_p\)个点;
2)在点数最少的关键帧中,在余下的边中添加\(N_p-1\)个点。
例子:对于三角形变换到四边形的线性插值,三角形顶点数\(V_k=3\),四边形顶点数\(V_{k+1}=4\),\(N_{ls}, N_p\)均为1。因此,需要在三角形的一条边上添加一个点,余下边不用添加点。
模拟加速度
曲线拟合算法:给定关键帧的顶点位置,可使用线性或非线性路径进行拟合,从而模拟加速度。常用来指定关键帧之间的动画。
- 匀速运动
加速度a=0的匀速运动,用等间隔的插值帧。假如需要在时间点t1和t2的关键帧之间插入n帧,这段时间分成n+1段,则插值帧的间隔:
那么,第j个插值帧的时刻:
- 加速运动
加速度a≠0,可用样条曲线或三角函数(sin/cos等),模拟动画路径的启动、减速。
\(1-cos \theta, \theta in (0,{\pi \over 2})\)模拟正向加速度,让帧之间的时间间隔增加,使得对象移动加快时有较大的位置变化。
1-cos函数特点:开始增加慢,后面增加快。
对于n个插值帧,第j个插值帧的时刻:
其中,\(\delta t\)是2个关键帧之间的时间差。
- 减速运动
用\(\sin \theta, \theta \in (0, {\pi\over 2})\)来模拟减速。一个插值帧时间位置:
sin函数特点:开始增加块,后面增加慢。
不管是1-cos,还是sin函数,异或其他曲线,都是模拟加速度,调以整插入帧的时间间隔。
OpenGL动画函数
- 激活双缓存
glutInitDisplayMode(GLUT_DOUBLE);
产生2个交替刷新屏幕的缓存:前缓存(front buffer)、后缓存(back buffer)。前缓存作为当前显示窗口的刷新缓存,另一个缓存中创建动画的下一帧。
- 互换缓存角色
glutSwapBuffers();
- 查询是否支持双缓存
有些硬件系统可能不支持双缓存等动画特性,可用下面函数查询:
glutGetBooleanv(GL_DOUBLEBUFFER, status);
如果系统中有前缓存、后缓存,则数组status返回GL_TRUE;否则,返回GL_FALSE。
- 连续执行的动画
如果希望连续产生动画,而不停止,可调用:
glutIdleFunc(animationFcn);
animationFcn由调用者指定的回调函数,设为NULL或0,可以禁止glutIdleFunc。
glutIdleFunc会确保OpenGL在空闲(没有键盘、鼠标等输入事件)时,animationFcn作为背景函数执行,常用于绘制连续动画。