EGE绘图之三 动画
EGE专栏:EGE专栏
上一篇:EGE绘图之二 窗口刷新
下一篇:EGE绘图之四 Gif动图播放
目录
- 1. 动画介绍
- 1.1 视觉暂留与似动知觉
- 1.2 动画原理
- 1.3 相关概念
- 1.3.1 帧
- 1.3.2 帧数与帧率
- 1.3.3 电影电视帧率和游戏帧率 (FPS)
- 1.3.4 屏幕(显示器)刷新频率 (HZ)
- 2. EGE动画
- 2.1 绘制与清屏
- 2.2 帧率控制
- 2.2.1 获取实际帧率 getfps()
- 2.3 帧循环
- 2.3.1 EGE中的帧循环
- 3 动画示例
- 3.1 太极
- 3.2 帧动画
- 3.3 碰撞小球
- 3.3.1 单个小球
- 3.3.2 多个小球
1. 动画介绍
动画顾名思义,就是动起来的画面。画面为什么会动起来了呢?在回答这个问题之前,我们先引入一些概念。
1.1 视觉暂留与似动知觉
视觉暂留
人眼在观察景物时,光信号传入大脑神经,需经过一段短暂的时间,光的作用结束后,视觉形象并不立即消失,这种残留的视觉称“后像”,视觉的这一现象则被称为 “视觉暂留”。
视觉暂留现象大约会持续0.1~0.4秒的时间(下面以0.1秒来说)
。也就是说,间隔0.1秒内显示的图像会重叠在一起,使得当画面切换后,上次的画面影像依然有所停留,和这次的画面混合在一起。当然,这个视觉暂留会随时间衰减,并不是在这0.1秒时间里图像都是清晰的,而是会不断衰减,直至消失。
下面是视觉暂留的现象,由于视觉暂留,两个不同的图像混合在了一起。。。
似动现象
似动知觉是指在一定的条件下人们把客观静止的物体看成是运动的,或把客观上不连续的位移看似是连续的运动,这是一种心理学上的现象。似动知觉的产生是有条件的,需要一定的时间间隔和空间间隔。
gif动图和电影动画之类的都是利用似动知觉而让人感觉是在连续变化的,不同画面之间的连续与微小差别,满足了似动知觉的条件。
1.2 动画原理
动画是按一定时间间隔显示静止的图像的,但是由于似动知觉,虽然是静止的图像,但由于间隔时间短且变化小,视觉系统便感受到的是连续运动。
视觉暂留现象并非是感知到画面运动的原因,而是保证了画面的质量,由于视觉暂留现象,即使中间有很短的时间内图像消失,也能看到画面连续,空缺部分被视觉暂留所补充。
1.3 相关概念
1.3.1 帧
动画是利用似动现象,通过以一定的时间间隔连续播放静止的画面给视觉看起来像是连续变化的画面。帧就是影像动画中最小单位的单幅影像画面,一帧就是一副静止的画面。
如下面的一个Gif动图,总帧数为25,通过以30ms一帧的速度来切换画面,看起来就是一个连续的画面。
1.3.2 帧数与帧率
帧数,即帧的数量。上面的动画帧数就是25,由25帧组成。
帧率,即帧的速率,一般用 每秒帧数(FPS) 表示。FPS,即 Frame per Second的缩写,是每秒生成画面帧的数量。
帧率的大小会影响动画的流畅度,由于人眼视觉暂留现象,,使得帧率越大看起来越流畅。
下面是以不同的帧率显示的蹦床,当然,三张图开始时间有点错位,没有统一,但是完成一整个动作的时间是相等的。可以看出,当帧率为较低的15FPS时,觉得画面有些卡顿和不连贯,达到 60FPS 时便可以看到流畅的动画
1.3.3 电影电视帧率和游戏帧率 (FPS)
电影的标准帧率为24FPS,电视的标准帧率为25FPS,这个帧率已经使得画面流畅,现在的电影大多使用24FPS的帧率。当然,为了更流畅,有些高清高画质视频使用了50FPS或60FPS等高帧率。
游戏中的帧率一般要达到60FPS以上才会感到流畅。
电影使用24FPS便可以感到流畅,足以满足观看需求,而游戏却感到卡顿是因为电影使用了 动态模糊 技术,拍摄时的控制一定的曝光时间(如每帧的一半),使得每一帧保留一定的物体轨迹(如同拍摄星轨图,长时间曝光,看到的是运行的轨迹,而不是一个点)
,帧是模糊的,但使得播放时看起来更连贯。
想看动态模糊效果的话,可以在播放视频时暂停,暂停时就显示视频的其中一帧。可以看到,当画面是在快速动的时候,暂停时看到的画面都不会很清晰,总有模糊感,如果画面变动的非常快,就会看到模糊得厉害,还带有残影,如果是画面运动幅度较小的,残影就不是很明显。视频帧的模糊,反而有利于运动画面的感觉连贯性。
而在游戏中,每一帧都是高清帧,正是由于高清帧,使得辨别度太高,所以低帧率时人眼区分度更高,卡顿感更明显。通过对画面进行动态模糊处理,在低帧率时能得到更好的显示效果。
游戏一般出现的基准帧率为:30, 60, 120, 240 FPS
1.3.4 屏幕(显示器)刷新频率 (HZ)
屏幕刷新频率 是指显示器每秒能显示的帧数,单位是赫兹 (HZ)。每隔一定时间,显示器从显存中读取数据,并将画面显示出来。一般的笔记本多为液晶显示器,液晶显示器的刷新率为 60HZ 左右。
可以通过在桌面空白处鼠标右键单击,在右键菜单中选择 “显示设置”,点击 “高级显示设置” 就能看到显示器信息。
常见显示器刷新频率也有 144HZ的,这样能看到更加流畅的效果。还有高达240HZ 的显示器。
游戏帧率也要配合显示器刷新频率,即使游戏帧率为 240FPS,显示器刷新频率为 60HZ,那么显示出来最多也只能是 60FPS。同样,显示器刷新频率为 144HZ,游戏帧率为 60HZ,那么显示效果也只能是 60FPS 。
当帧率和刷新频率不一致时,有可能会产生 画面撕裂 现象,可以通过 垂直同步(V-Sync) 技术 、G-Sync 技术 和 Free-Syhc 技术等减少影响。
2. EGE动画
2.1 绘制与清屏
EGE中,初始化完图形环境后,就可以直接使用绘图函数进行绘画。EGE默认开启的是自动渲染,即时间到达或调用延时类函数会触发刷新,将绘画内容显示到屏幕上。
当绘制比较复杂时,处于绘制过程中的刷新容易造成画面闪烁,所以一般是设置成 手动渲染 模式。
手动渲染模式一般在 initgraph() 中第三个参数设置。在初始化时调用一次即可,不要在程序中多次设置,这样反而会引起闪烁,而且渲染模式没有切换的。
initgraph(640, 480, INIT_RENDERMANUAL);
由于在绘制过程中图形数据都是写到同一块帧缓冲区上,如果能够将上一帧的绘制内容完全覆盖,那就无需清屏,否则就需要使用清屏函数cleardevice(),将上一帧的绘制内容清除,避免留下痕迹。
cleardevice();
清屏分为 部分清屏 和 全部清屏,如果在一小块区域内能很容易地将痕迹清除,那就使用部分清屏,在对应区域使用 bar() 绘制一个背景色填充矩形将其覆盖即可。
EGE 绘图函数分为普通函数和带抗锯齿的高级函数。可以在效率和美观两者之间进行取舍。如果不是非常复杂的绘图,优先使用高级绘图函数。
2.2 帧率控制
EGE 中 帧率控制 使用 delay_fps(),延时使用 delay_ms() 。
控制帧率可以使画面保持一定的流畅度,延时又可以减少CPU性能的消耗。为了动画流畅,应控制和显示刷新频率一致。多数显示器的刷新频率为 60Hz ,所以在程序中一般设置帧率为60FPS,当然,如果显示器的刷新频率更高,也可以设置更大的帧率。如果性能允许,最好不要设置低于24FPS的帧率,这是普通电影常用的帧率,而电影里的画面一般都比较模糊,这有利于画面的连贯性,而程序生成的动画图形都十分清晰,人眼更容易分辨,也更容易感觉到卡顿。
2.2.1 获取实际帧率 getfps()
实际运行时,如果绘制每一帧耗费的时间都比较长,就达不到帧率要求。可以通过调用 getfps() 获取帧率。
float getfps();
返回的是帧率 (FPS)。
调用如下,然后可以通过 xyprintf() 等显示出来。
float fps = getfps();
xyprintf(0, 0, "FPS:%.3f", fps);
2.3 帧循环
游戏和图形界面的本质是绘图,动态的画面更需要不断地进行绘图。然而绘图不是随意的,需要遵循一定的规则来呈现。帧循环在程序中是负责渲染画面的部分。
在游戏中,游戏帧循环也叫游戏主循环,会不断地重复以下动作:
- 处理用户输入(鼠标,键盘等)或其它事件
- 更新数据
- 绘图
需要在循环中不断地获取用户输入,否则用户就无法控制游戏运行,并且处理用户实时性要好,否则就会有明显的延迟卡顿。
2.3.1 EGE中的帧循环
因为需要不断地进行绘图,所以最好设置 手动渲染模式,提升绘制效率并减少闪烁。
EGE中帧循环结构大致如下:
for ( ; is_run(); delay_fps(60)) {//清屏cleardevice();//处理用户输入、定时等时间handleEvent();//更新数据updateData();//绘图draw();
}
关于 cleardevice() 的位置
cleardevice() 是用来清屏的,目的是将之前画面清除,以免影响本次的绘制内容,所以在每帧开始绘图之前清屏即可。不能在绘图之后,因绘图之后清屏意味着之前绘制的图形全部被清除,相当于白画了。调用 delay_fps() 后,绘制在帧缓冲中的内容将被显示一段时间,所以需要保证在调用 delay_fps() 时,帧缓冲中的内容是完整的一帧画面。
清屏操作是非常快的,不用担心会影响帧率,该考虑是清屏后的重绘问题,可对复杂的不变的画面部分缓存到图像上,绘制帧时直接绘制到窗口上即可。
3 动画示例
3.1 太极
下面绘制一个旋转太极图,圆形这种,一般开启抗锯齿,否则绘制出来的效果极差。(下图中录制生成gif导出只用黑白两色,所以边沿有锯齿)
太极图由黑白双鱼组成,如下图,先画两个半圆,再分别画鱼头部分即可,既然是动画,那肯定得变化,这里变化的就是太极图的角度,通过不断改变旋转的角度,然后清屏绘制,就会看到不断旋转的动图。
#include <graphics.h>
#include <cmath>//以(cx, cy)为中心的圆环,外圆半径为outerRad, 颜色为outerColor,内圆半径为innerRad, 颜色为innerColor
void annulus(float cx, float cy, float outerRad, color_t outerColor, float innerRad, color_t innerColor);//以(cx, cy)为中心绘制半径为radius的角度为angle的太极图
void taichi(float cx, float cy, float radius, float angle);int main() {initgraph(640, 640, INIT_RENDERMANUAL);setbkcolor(WHITE);//开启抗锯齿,使圆更平滑ege_enable_aa(true);float angle = 0;//帧循环for (; is_run(); delay_fps(60)) {//清屏cleardevice();//绘制太极图taichi(320, 320, 200, angle);//角度变化angle += PI / 8;}getch();closegraph();return 0;
}//圆环
void annulus(float cx, float cy, float outerRad, color_t outerColor, float innerRad, color_t innerColor)
{setfillcolor(outerColor);ege_fillellipse(cx - outerRad, cy - outerRad, 2 * outerRad, 2 * outerRad);setfillcolor(innerColor);ege_fillellipse(cx - innerRad, cy - innerRad, 2 * innerRad, 2 * innerRad);
}//太极
void taichi(float cx, float cy, float radius, float angle)
{float left = cx - radius, top = cy - radius;float width = 2 * radius, height = 2 * radius;color_t colWhite = EGEACOLOR(0xFF, WHITE), colBlack = EGEACOLOR(0xFF, BLACK);//白半圆setfillcolor(colWhite);ege_fillpie(left, top, width, height, angle + 90, 180);//黑半圆setfillcolor(colBlack);ege_fillpie(left, top, width, height, angle - 90, 180);//鱼眼中心偏移位置float radian = (angle + 90) * PI / 180;float dx = radius / 2 * cos(radian), dy = radius / 2 * sin(radian);//黑鱼头部annulus(cx + dx, cy + dy, radius / 2, colBlack, radius / 6, colWhite);//白鱼头部annulus(cx - dx, cy - dy, radius / 2, colWhite, radius / 6, colBlack);//太极黑边框setlinewidth(2);setcolor(colBlack);ege_ellipse(left, top, 2 * radius, 2 * radius);
}
3.2 帧动画
EGE绘图之四 Gif动图播放
对于GIF动图,可以用 EGE绘图之四 Gif动图播放 中的 Gif 类对其进行加载,然后不断地绘制其每一帧,就能形成动图,许多小游戏中的人物行走动作,也是如此。
由于 Gif 类内置时间计算,会根据 play() 调用时间和当前时间计算出应该绘制哪一帧(Gif动图每一帧是有延时时间)
,所以不需要手动控制绘制哪一帧,但是如果想要自己控制,也是可以的,调用 drawFrame( i) 即可绘制第 i 帧,并且超出帧数后会自动循环。
如果是帧序列图像,可以读取到 PIMAGE 数组,然后自行根据时间或者帧来绘制对应的图像帧。
下面是Gif动图自行绘制帧的一个示例,需要使用上面的西蓝花动图,可以到上面将图片另存为,然后在程序中将图片名改一下(相对路径和绝对路径,文件名,这个懂的吧?)
,需要注意的是,字符串前面需要加个 L,宽字符。
Gif 类源文件请到 EGE绘图之四 Gif动图播放 获取
#include <graphics.h>
#include "Gif.h"int main() {initgraph(800, 600, INIT_RENDERMANUAL);setbkcolor(WHITE);Gif gif(L"上面西蓝花的那个动图.gif");gif.play();for (; is_run(); delay_fps(60)) {//清屏cleardevice();gif.draw();}closegraph();return 0;
}
下面则是手动控制绘制 Gif 的每一帧帧
#include <graphics.h>
#include "Gif.h"int main() {initgraph(800, 600, INIT_RENDERMANUAL);setbkcolor(WHITE);Gif gif(L"小白菜.gif");//获取第一帧延时,作为帧延时int delayTime = gif.getDelayTime(0); if (delayTime == 0)delayTime = 20;int frame = 0;for (; is_run(); delay_fps(60)) {//清屏cleardevice();//计算当前帧int curFrame = int(frame++ * (1000.0 / 60) / delayTime);gif.drawFrame(curFrame);}closegraph();return 0;
}
3.3 碰撞小球
3.3.1 单个小球
使用抗锯齿绘制的一个小球,在窗口内不断碰撞 反弹,颜色和透明度随时间随变化,有淡入淡出效果。
#include <graphics.h>int main(void)
{initgraph(640, 480, INIT_RENDERMANUAL);setbkcolor(EGERGB(0xFF, 0xFF, 0xFF));setcolor(BLACK);setbkmode(TRANSPARENT);setfont(16, 0, "黑体");//开启抗锯齿ege_enable_aa(true);const int speed = 2; //速度常量int r = 80; //半径int cx = r, cy = r; //圆心int dx = speed, dy = speed; //x, y方向上分速度int alpha = 0, da = 1; //alpha为当前alpha值,da为alpha变化增量int colH = 0; //HSV颜色中的H值(色调)for (; is_run(); delay_fps(60)){//位置更新cx += dx;cy += dy;//碰撞检测if (cx - r <= 0) dx = speed; //碰左if (cy - r <= 0) dy = speed; //碰上if (cx + r >= getwidth() - 1) dx = -speed; //碰右if (cy + r >= getheight() - 1) dy = -speed; //碰下// 改变alpha值,参数范围为 0 ~ 0xFF(255)alpha += da;if (alpha <= 0) da = 1;if (alpha >= 0xFF) da = -1;if (++colH > 360)colH = 0;color_t color = HSVtoRGB(colH, 1, 1);cleardevice();setfillcolor(EGEACOLOR(alpha, color)); //设置颜色ege_fillellipse(cx - r, cy - r, 2 * r, 2 * r); //绘制小球 xyprintf(0, 0, "FPS:%.3f", getfps()); //显示帧率}closegraph();return 0;
}
3.3.2 多个小球
在碰撞小球的基础上,将小球封装成结构体,记录小球的数据信息,然后就可以同时显示多个小球。每个小球有初始化,更新数据,绘制动作。
很多人初学对多线程有误解,认为多个东西运动就得用多线程。每一帧里对所有小球的位置重新计算一遍即可,完成这个计算并不需要多线程,相反,多线程反而使得图形绘制的顺序混乱。如果不是对于线程操作很熟,不要乱用多线程。
不要想着多个小球就得用多线程,这和多线程没啥关系,只要在每一帧里对多个小球更新绘制即可。
#include <graphics.h>
#include <stdlib.h>
#include <time.h>
#include <Windows.h>const int speed = 2; //速度常量
const int SCREEN_WIDTH = 640;
const int SCREEN_HEIGHT = 480;typedef struct Circle
{int r; //半径int cx, cy; //圆心int dx, dy; //x, y方向上分速度int alpha, da; //alpha为当前alpha值,da为alpha变化增量int colH; //HSV颜色中的H值(色调)
}Circle;void initCircle(Circle* circle)
{//大小为[40, 60)circle->r = 40 + rand() % 20;//位置限制在窗口内circle->cx = circle->r + rand() % (getwidth() - 2 * circle->r);circle->cy = circle->r + rand() % (getheight() - 2 * circle->r);//速度为[-1, 1]circle->dx = rand() % 3 - 1, circle->dy = rand() % 3 - 1;//随机颜色和透明度circle->alpha = rand() % 256;circle->da = rand() % 2 * 2 - 1; // 值为-1或1circle->colH = rand() % 360;
}void updateCircle(Circle* circle)
{//位置更新circle->cx += circle->dx;circle->cy += circle->dy;//碰撞检测if (circle->cx - circle->r <= 0) circle->dx = speed; //碰左if (circle->cy - circle->r <= 0) circle->dy = speed; //碰上if (circle->cx + circle->r >= getwidth() - 1) circle->dx = -speed; //碰右if (circle->cy + circle->r >= getheight() - 1) circle->dy = -speed; //碰下// 改变alpha值,参数范围为 0 ~ 0xFF(255)circle->alpha += circle->da;if (circle->alpha <= 0)circle->da = 1;if (circle->alpha >= 0xFF)circle->da = -1;//更新色相if (++circle->colH >= 360)circle->colH = 0;
}void drawCircle(Circle* circle)
{//计算颜色color_t color = HSVtoRGB(circle->colH, 1, 1);//设置颜色并绘制setfillcolor(EGEACOLOR(circle->alpha, color)); ege_fillellipse(circle->cx - circle->r, circle->cy - circle->r,2 * circle->r, 2 * circle->r); //绘制小球
}int main(void)
{timeBeginPeriod(1);initgraph(640, 480, INIT_RENDERMANUAL); //创建窗口,手动渲染setbkcolor(BLACK); //背景色setcolor(WHITE); //前景色(在这里就只和文字颜色有关)setbkmode(TRANSPARENT); //文字背景色透明setfont(16, 0, "黑体"); //设置字体srand(time(NULL)); //随机数种子//开启抗锯齿ege_enable_aa(true);//创建多个小球const int numCircle = 20;Circle circle[20];//初始化for (int i = 0; i < numCircle; i++)initCircle(&circle[i]);for (; is_run(); delay_fps(60)){//更新for (int i = 0; i < numCircle; i++) {updateCircle(&circle[i]);}//清屏cleardevice();//绘制for (int i = 0; i < numCircle; i++) {drawCircle(&circle[i]);}xyprintf(0, 0, "FPS:%.3f", getfps()); //显示帧率}closegraph();timeEndPeriod(1);return 0;
}
EGE专栏:EGE专栏
上一篇:EGE绘图之二 窗口刷新
下一篇:EGE绘图之四 Gif动图播放
EGE绘图之三 动画相关推荐
- EGE绘图之二 窗口刷新
EGE专栏:EGE专栏 上一篇:EGE绘图之一 绘图讲解 下一篇:EGE绘图之三 动画 EGE绘图之二 窗口刷新 目录 一.EGE的窗口刷新 1. EGE窗口刷新流程 2. 渲染模式 2.1 自动渲染 ...
- EGE绘图之一 绘图讲解
EGE专栏:EGE专栏 下一篇:EGE绘图之二 窗口刷新 目录 1.1 绘图初始设置 1.2 帧循环 1.2.1 包含动画的程序简略结构 1.3 视觉暂留现象 1.4 清屏 1.4.1 部分清屏 1. ...
- SwiftUI WWDC21 新绘图和动画组件之 01 Canvas 支持即时模式绘制的视图类型
SwiftUI WWDC21 新绘图和动画组件之 01 Canvas 支持即时模式绘制的视图类型 Canvas 一种支持即时模式绘制的视图类型. struct Canvas<Symbols> ...
- 【Qt OpenGL】Qt Creator中的3D绘图及动画教程
Qt Creator中的3D绘图及动画教程(参照NeHe) 刚刚学习了Qt Creator,发现Qt提供了QtOpenGL模块,对OpenGL做了不错的封装,这使得我们可以很轻松地在Qt程序中使用Op ...
- matlab 绘图与动画制作
目录 一.MATLAB常用绘图函数 1.plot() 函数 2.surf() 函数 3.ezplot() 函数 二.动画绘制 1.绘制原理 2.绘制步骤 3.代码模板 三.举个栗子 example 1 ...
- ICCV 2021: 手绘图变动画
点击上方"机器学习与生成对抗网络",关注星标 获取有趣.好玩的前沿干货! 新智元报道 来源:arXiv 编辑:LRS [新智元导读]有了AI技术的加持以后,普通人借助各种辅 ...
- 第16章 绘图与动画程序设计
形状类: 过渡动画: 关键帧.关键值.插值器: 时间轴动画 16.1.1 直线类Line [例16.1 ] 绘制红.绿.蓝三条直线,红线设置为虚线.绿.蓝两条直线通过坐 标的属性绑定使它们成为交叉线. ...
- Android面试收集录 2D绘图与动画技术
1.如何在Android应用程序的窗口上绘制图形? 继承View 实现View中的onDraw()方法 2.如何绘制圆,空心椭圆? canvas.drawArc或canvas.drawCircle方法 ...
- gif透明背景动画_制作一个绘图GIF动画
GIF动画制作软件挺多,但无外乎就是几张图片拼合.最近偶尔翻起了日本的一个小软件,却能像PPT变体一样制作GIF动画,而且还有动态线条功能,下面我们来看一下制作方法: 1.首先导入一张图片,目的是为描 ...
最新文章
- java程序解压/压缩.gz文件
- NodeJs教程(介绍总结!)终于在网上找到一个靠谱点的了T_T
- 大势至电脑文件防泄密软件_有了数据防泄密软件,还会担心企业文件泄漏吗?...
- Emacs自带的小游戏
- linux cut性能,Linux cut
- Mac终端Terminal使用
- Fiori Elements里General Information的设计原理
- 中文字体其实也可以用在网页上的
- 动机的寓言:孩子为谁在玩
- 框架自主搭建bitnami.com
- 8.PHP核心技术与最佳实践 --- 缓存
- 【电脑控制手机屏幕】windows11、10自带投屏功能,三步解决
- arduino——ATtiny85 SSD1306 + DHT
- 自己制作一个计时器、倒计时器
- 【Java从零到架构师第③季】【41】SpringBoot-配置文件_YAML_lombok_设置Banner
- 如何用迅捷PDF转换器获取PDF文件中的图片
- 开源中国 码云 代码提交
- STC 模拟eeprom数据丢失
- 基于QT的CHAI3D开发框架搭建
- 前端学习笔记-4.2php实现注册功能
热门文章
- 设银行1年期定期存款年利率c语言,4.计算定期存款本利之和设银行定期存款的年......
- 定期存款可以提前取出来吗_定期存款可以提前取出来吗 定期存款提前取出利息是怎么算的...
- 毕业了,你有多少本钱?(文/徐小平)
- 关于编程,鲜为人知的真相
- 使用Ansible管理您的OpenStack云:第二天的操作
- Class类(基本介绍、常用方法)
- How OS technology is being deployed in science?
- linux命令行界面上滑,获得Linux命令行平滑体验的5条技巧
- 扫描线面积并、面积交模板
- 『津津乐道播客』世界艾滋病日话题征集