计算机图形:动画

基本概念

计算机动画(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中调用)。

用光栅操作生成动画

  1. 用矩形像素阵列的块移动,可以为一些应用生成实时光栅动画,如游戏程序。
  2. 沿一个二维路径用颜色表变换(color-table transformation)来实现定义对象的动画,并将相继块的像素值设为颜色表入口。

动画序列的设计

一个动画序列设计步骤:

  1. 故事情节拆分
  2. 对象定义
  3. 关键帧描述
  4. 插值帧的生成

情节版(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个相继帧的线段数。待补偿的线段数量确定如下:

\[\tag{1} L_{max}(L_k, L_{k+1}), L_{min}=min(L_k, L_{k+1}) \]

\[\tag{2} \begin{aligned} N_e&=L_{max}mod L_{min}\\ N_s&=int({L_{max}\over L_{min}}) \end{aligned} \]

边补偿预处理步骤:
1)将边数少的关键帧的\(N_e\)条边分成\(N_s+1\)部分;
2)将边数少的关键帧的余下边分成\(N_s\)部分。

例子:如果\(L_k=15,L_{k+1}=11\),如果进行边补偿?

\[N_e=L_{max}mod \ L_{min}=15 mod \ 11=4 \]

\[N_s=int({L_{max}\over L_{min}})=int({15\over 11})=1 \]

将关键帧k+1的4条边的每条边分成2部分,关键帧k+1的剩余边不做处理(分成1部分,相对于不处理)。

  • 补偿顶点

参数\(V_k、V_{k+1}\)表示两个连续帧的顶点数。待补偿的顶点数量确定如下:

\[\tag{3} V_{max}=max(V_k,V_{k+1}), V_{min}=min(V_k,V_{k+1}) \]

\[\tag{4} \begin{aligned} N_{ls}&=(V_{max}-1)mod \ (V_{min}-1)\\ N_p&=int({V_{max}-1\over V_{min}-1}) \end{aligned} \]

顶点补偿预处理步骤:
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段,则插值帧的间隔:

\[\tag{5} \delta t = {t_2-t_1\over n+1} \]

那么,第j个插值帧的时刻:

\[tB_j=t_1+j\delta t, j=1,2,...,n \]

  • 加速运动

加速度a≠0,可用样条曲线或三角函数(sin/cos等),模拟动画路径的启动、减速。

\(1-cos \theta, \theta in (0,{\pi \over 2})\)模拟正向加速度,让帧之间的时间间隔增加,使得对象移动加快时有较大的位置变化。

1-cos函数特点:开始增加慢,后面增加快。

对于n个插值帧,第j个插值帧的时刻:

\[\tag{6} tB_j=t_1+\delta t[1-\cos ({\pi\over 2}\cdot {j\over n+1})], j=1,2,...,n \]

其中,\(\delta t\)是2个关键帧之间的时间差。

  • 减速运动

\(\sin \theta, \theta \in (0, {\pi\over 2})\)来模拟减速。一个插值帧时间位置:

\[\tag{7} tB_j=t_1+\delta t\sin({\pi\over 2}\cdot {j\over n+1}), j=1,2,...,n \]

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作为背景函数执行,常用于绘制连续动画。

posted @ 2023-10-27 22:43  明明1109  阅读(51)  评论(0编辑  收藏  举报