EGE绘图之一 绘图讲解

EGE专栏:EGE专栏

下一篇:EGE绘图之二 窗口刷新

  前面已经介绍了各种绘图方法,下面就来说说EGE绘图的流程。
  程序很少有只显示一张静止的图画的,在之前绘图基础的例程,之所以只显示一张静止图然后就结束,这主要是对功能的一个简单测试,突出主要内容,避免有过多不必要的代码混杂其中。
  在有用户交互的程序中,画面在随时间不断更新的同时,也会根据用户的操作显示不同的内容。像平时玩的游戏,画面也在不断地变化,用户可以通过鼠标按键进行输入,来控制游戏人物的动作。

  想要让画面动起来,就需要不断改变画面的内容,这样我们看到的画面才是动起来的。

1.1 绘图初始设置

  绘图需要先进行一定的设置,比如各种绘图颜色,画线时的线宽,文字设置等等,这个要根据实际的需求来进行设置。有些设置只需要在初始化时设置一次即可,如手动渲染模式,文字背景透明等。而有一些则需要在绘图过程不断调整,如前景色,填充颜色等。

一般的绘图设置

  如果是动画的话,需要设置为手动渲染模式, 这个设置可以减少绘图时的闪烁。
  设置方式为在 initgraph() 中的第三个参数加上 INIT_RENDERMANUAL,如:
initgraph(640, 480, INIT_RENDERMANUAL)

#include <graphics.h>

int main()
{
	initgraph(640, 640, INIT_RENDERMANUAL);		//初始化窗口,手动渲染模式
	setcaption("EGE");		//设置窗口标题

	
	setbkcolor(WHITE);				//设置窗口背景
	setcolor(BLACK);				//设置前景色
	setbkmode(TRANSPARENT);			//设置文字背景色为透明
	setfont(12, 0, "楷体");			//设置默认文字字体的大小
	ege_enable_aa(true);			//开启窗口上的抗锯齿

	/*绘图和交互部分*/

	/*绘图和交互部分*/
	
	getch();
	
	closegraph();
	return 0;
}

1.2 帧循环

  帧循环就是程序中绘制画面的循环,它负责循环处理用户的各种键盘、鼠标等输入,然后改变某些数据,如物体的坐标等,然后再根据这些数据生成一帧帧的画面内容。
  一个画面就是一帧。如果一个动画每秒切换60个画面, 那么就是60帧每秒, 即 60 FPS, (FPS, 即 frames per second, 帧每秒)。
  帧循环一般把帧率控制在 60FPS 左右, 这和很多显示器的刷新率是一致的,而且能保持较高的流畅度。

EGE中的帧循环如下:

for ( ; is_run(); delay_fps(60)) {
	//清屏
	cleardevice();		
	
	//处理用户输入
	
	//绘制画面
	
}

这里面出现了两个函数:

  • is_run() 用于判断EGE窗口环境是否还在运行。如果EGE窗口被点击右上角退出按钮关闭后,is_run() 就会返回 false, 然后退出循环。
  • delay_fps(60) 是延时函数,每一次循环运行到这里的时候程序就会等待一段时间,然后继续运行。通过延时来控制帧率, delay_fps(60) 则是控制帧率为60FPS所以如果每一帧的计算和绘制占用的时间平均少于 1/ 60 秒,那么每秒就会只执行60次循环

1.2.1 包含动画的程序简略结构

#include <graphics.h>

int main()
{
	initgraph(640, 640, INIT_RENDERMANUAL);		//初始化窗口,手动渲染模式
	setcaption("EGE");		//设置窗口标题

	
	setbkcolor(WHITE);				//设置窗口背景为白色
	setcolor(BLACK);				//设置前景色为黑色
	setbkmode(TRANSPARENT);			//设置文字背景色为透明
	setfont(12, 0, "楷体");			//设置文字大小和字体

	/*绘图部分*/
	for ( ; is_run(); delay_fps(60)) {
		cleardevice();		//清屏
		//处理用户输入
	
		//绘制画面
	
	}
	/*绘图部分*/
	
	getch();
	
	closegraph();
	return 0;

1.3 视觉暂留现象

  视觉暂留现象又叫余晖效应,也就是光对视网膜所产生的视觉在光停止作用后,仍保留一段时间的现象。这个视觉停留时间约0.1 ~ 0.4 秒。如果前一个停留的影像还未消失,后一个就已经出现,那么前后两个影像就会融合起来,形成连续的视觉效果。
  如下面火把旋转看到的会是一个圆圈。

在这里插入图片描述
  两个影像出现的间隔越短,看起来越流畅。如果两个画面之前的时间间隔太长,那么看起来就会很不流畅,有卡顿的感觉。由视觉暂留时间最小约0.1秒,那么大于10FPS的看起来就能形成动画了。FPS越大,看起来越流畅,达到24FPS时就能感到流畅,电影的标准频率是24FPS。而显示器的刷新频率一般是60FPS。所以我们的帧循环一般也设置在60FPS

1.4 清屏

  窗口会分配有一块用于存储显示内容的缓冲区,缓存区存有对应窗口每个像素的数据。显示时,会读取这块缓存区上的数据,然后显示到屏幕上。而我们绘制图像时,就是更改这块缓存区上的数据。

  绘图时,我们一般只是需要在窗口上画一小部分,那就只更改了缓存区那一部分上的数据。那其它没被修改的就会留下来。如果我们画的部分已经将以前图像覆盖掉,那就没问题。但如果没被覆盖,画面就会出现问题,有上一次绘图留下来的痕迹。所以一般我们在画下一幅画面之前,会将整个窗口上的图像清空,即把缓存区上的像素颜色数据都改成背景色, 这个就叫清屏,然后重新画我们的图像。

  当然,如果你每一次绘图都能将上一次绘图的多余痕迹给覆盖掉,那就无需清屏。但是在一般情况下,都需要进行清屏。

  EGE中使用 cleardevice() 清屏,会把窗口都填充成背景色。
  实际上 cleardevice() 是把一张图像都填充成背景色,如果没有传入参数,就默认成对窗口清屏。

void cleardevice(PIMAGE pimg = NULL);

  需要清屏的还有文字,如果不清屏,因为文字经常是背景色透明的,那文字就会重叠起来。

  当然,如果文字不透明,并且所带的色块正好是背景色,那就看不出色块,并且色块会把之前的文字给覆盖掉,那文字部分就不需要清屏。

下面来看看清屏的作用

  我们做一个小球跟随鼠标移动的程序。下面是没清屏,直接画一个小球,可以看到,原来的图像残留了下来。
在这里插入图片描述
而如果画小球之前进行一次清屏,那么就可以看到小球移动。
在这里插入图片描述

  所以我们绘制是如下所示的流程:

for ( ; is_run(); delay_fps(60)) {
	计算
	
	cleardevice();		//清屏
	draw();		//绘制图像	
}

1.4.1 部分清屏

  有设置视口区域的函数,有一个函数只会清除视口内的部分,即

clearviewport()

  所以如果我们只需要清除一个矩形区域,我们可以调用 setviewport() 将视口设置在那个区域,然后调用 clearviewport() 清除。

1.5 图像缓存

  有时候我们会在窗口上画很多物体,只改变一个物体时,如果清屏的话,窗口上的图像就不见了,这样就需要重新绘制图像,把前面绘图的整个流程重新执行一遍。但是如果绘制的整个流程很复杂,会很耗费时间和性能,每帧绘制时间过长时,甚至会使帧率减小,画面卡顿。
  如果每一帧的绘制需要很多步骤,要花费很长时间,那我们可以多准备图像缓存,把经过很多绘制步骤的不变的部分绘制到一张图像上,保存起来。将很少变化的部分又绘制到另一张图像上,当出现改变时才会重新绘制这张图像。最后变化的部分单独绘制。然后按绘制顺序把各部分一起绘制到窗口上。这样就能减少绘制时间。但是需要注意绘制的先后顺序,因为后绘制的图像会把先绘制的图像给覆盖掉。
  如果按照绘制的顺序,不变的部分中间要绘制变化的部分,那么不变的部分要分开放在多张图像上。
  使用图像缓存,会占用一些空间,所以要对时间和空间进行衡量,不能大量且随意地使用缓存,否则会占用大量的运行内存空间。

1.5.1 图像缓存需要使用ARGB颜色

  因为如果图像缓存不使用ARGB颜色,即使我们只往图像缓存上绘制了一个小球,图像绘制窗口上时,将会是一个大的色块,只是中间画有一个小球。我们需要的应该是除了我们画图像缓存上的图像外,其它都是透明的。
  因为需要透明度 alpha,所以将图像缓存绘制到窗口上时,需要使用putimage_withalpha()
  并且在图像缓存上绘制时,需要设置填充色和前景色都为ARGB颜色,因为 putimage_withalpha() 就是使用的ARGB颜色,只设置 RGB 颜色值,相当于 alpha 值为 0, 也就是全透明,这样是看不到绘图的图像的。所以需要设置ARGB 颜色,并且绘图的颜色透明度不能为 0
  当然,绘制图像到图像缓存时,使用 putimage() 就可以了, putimage_withalpha() 是在将图像缓存绘制到窗口或其它图像上时使用。

1.5.2 图像缓存绘制流程

  • 创建图像缓存(注意要把图像缓存的 alpha 设为 0(一开始创建alpha不为 0 ,需要主动设置)
  1. 创建图像缓存,大小根据实际来定
PIMAGE imageBuff = newimage(500, 500);
  1. 设置图像的所有颜色值为0,即透明度为0的纯黑色,不用设置透明度
    但如果图像之前使用过,那就需要将图像缓存alpha值设为0, 即全透明

  虽然我们是需要将图像缓存背景色设置为透明,但是创建时,默认就是透明的。如果想要主动设置透明度,也是可以的。
  下面的函数就是设置图像整体透明度的。

ege_setalpha(0, imageBuff);

  或者使用ARGB颜色,设置 背景色 alpha 的值为0,然后清屏

setbkcolor_f(EGEACOLOR(0, BLACK), imageBuff);
cleardevice(imageBuff);
  1. 然后就可以在图像缓存上直接绘制图像
    绘制图片时使用普通的 putimage() 即可,但要在图像缓存上使用绘图函数绘制图形,要使用ARGB颜色,所以要用 高级绘图函数。因此需要为图像缓存设置带透明度的填充颜色,前景色。(普通的绘图函数使用的RGB颜色相当于透明度为0,绘制后对应的像素点颜色透明度为0,使用putimage_withalpha你是看不到绘制的图像的)
setcolor(EGEACOLOR(0xff, YELLOW), imageBuff);
setfillcolor(EGEACOLOR(0xff, YELLOW), imageBuff);
  • 将图像缓存绘制到窗口上(注意要使用putimage_withalpha)
putimage_withalpha(NULL, imageBuff, 0, 0);

下面做个使用图像缓存的示例:
在这里插入图片描述
  把复杂的绘图绘制在图像缓存上(注意这里的绘图并不复杂,只是做个演示)。帧循环中直接绘制图像缓存到窗口上。
  可以把putimage_withalpha() 注释掉, 把putimage() 取消注释,可以看到,如果不用putimage_withalpha(), 绘制到窗口上时是会带背景色块的,和我们的初衷不符。

#include <graphics.h>

int main()
{
	initgraph(600, 600, INIT_RENDERMANUAL);			//初始化窗口
	setcaption("EGE鼠标消息处理");	//设置窗口标题

	setbkcolor(WHITE);		//设置窗口背景为白色
	setcolor(RED);			//设置前景色为红色
	setfillcolor(RED);		//设置填充色为红色

	//创建图像缓存
	PIMAGE imageBuff = newimage(400, 400);

	//将图像设置成全透明,这个是必要步骤
	ege_setalpha(0, imageBuff);

	//设置ARGB颜色
	setfillcolor(EGEACOLOR(0xff, YELLOW), imageBuff);

	//把很复杂的绘制操作绘制到图像缓存上
	for (int i = 0; i < 6; i++) {
		for (int j = 0; j < 6; j++)
			//使用高级函数绘图
			ege_fillellipse(i * 100, j * 100, 100, 100, imageBuff);
	}

	for (; is_run(); delay_fps(60)) {
		mouse_msg msg = { 0 };

		while (mousemsg()) {
			msg = getmouse();
		}

		//鼠标移动则绘画
		if (msg.is_move()) {
			cleardevice();

			//把图像缓存绘制到窗口上,
			putimage_withalpha(NULL, imageBuff, 0, 0);

			//把上面那句换成这句试试看
			//putimage(0, 0, imageBuff);

			//画动的部分
			fillellipse(msg.x, msg.y, 30, 30);
		}
	}

	closegraph();

	return 0;
}

1.6 画面闪烁

  动画是依靠视觉暂留现象而形成的。由于视觉暂留现象,人眼会将连续的两幅图像组合起来,形成动画。如果显示时连续的两个图像相差太大,那么人眼就会看到闪烁。
  平时的清屏,会将窗口都填充为背景色,这会引起图像变化极大。绘图中很可能会刷新窗口,就把没绘制完的图像显示到窗口上,有些部分就没画,后面还是背景色,这样我们就看到的背景色被不断填充的过程, 变化极大。如果整个过程时间比较长。就会看到窗口图像的绘制变化过程,就会看到闪烁。
  而通过调用

setrendermode(RENDER_MANUAL);	//设置为手动渲染模式

  设置为手动更新模式后,绘图函数绘图后不会马上更新窗口,直到调用delay_fps() / delay_ms() 等带有等待特性的函数时才会更新窗口。因为一般都是在绘制完成后才会调用到 delay_fps(),所以显示的是完整的一帧。

EGE延时类函数:

具有刷新窗口功能不具有刷新窗口功能
delay_fps()delay()
delay_jfps()api_sleep()
delay_ms()ege_sleep()
Sleep()(不建议使用,不统一)
getch()
getkey()
getmouse()

  实际绘图需要绘制完一帧之后再显示,这样就看不到窗口的绘制过程。cleardevice() 调用后出现的窗口背景色也不会看到,因为要等到调用delay类函数才会更新,此时画面已经画完了。

  所以如果画面需要不断清屏绘制,并且绘图过程中会出现画面变化极大的,一般都会设置为手动渲染模式 这个设置。

  手动渲染模式 最常见的是在 initgraph() 的窗口初始化模式参数中设置(或者使用setinitmode())

initgraph(640, 480, INIT_RENDERMANUAL);

或者
setinitmode(INIT_RENDERMANUAL);
initgraph(640, 480);

  也可以在初始化之后调用 setrendermode()

setrendermode(RENDER_MANUAL);

下面来一个绘制示例

  一个徐徐展开的窗口。initgraph() 可以改变窗口大小,但是改变后,窗口会被填充成背景色。

  程序中故意在窗口的绘制过程中加了一个循环,用来延时, 背景色显示使足够的时间,以模拟费时计算和绘图的过程。
  如果不设置手动模式,每次绘制都会刷新窗口,将绘制过程显示出来,露出背景色,这与完成时的画面是相差极大的,就会看到闪烁。
  设置手动模式后,即使中间有延时,但是窗口没有被刷新,绘制的过程就没有显示,直到遇到 delay_fps() 时才会更新,此时画面已经绘制完成,看到的都是完成时的画面。

(下面的图片只是录屏的帧率低(10FPS),实际运行并不抖)

在这里插入图片描述
  可以把 setrendermode(RENDER_MANUAL); 取消注释,试试手动模式的效果。

#include <graphics.h>

int main()
{
	int screenWidth = 640, screenHeight = 480;
	initgraph(200, screenHeight, 0);

	//把下面这句取消注释试试
	//setrendermode(RENDER_MANUAL);

	setbkcolor(WHITE);

	//画霓虹
	for (int width = 200; width < screenWidth; delay_fps(60), width++) {
		//改变窗口大小
		initgraph(width, screenHeight);
		
		//窗口改变大小后,会被清屏,所以要从头开始画
		for (int i = 0; i < width; ++i)
		{
			//故意延时,以模拟费时计算和绘图过程
			int time = 30000;
			while (time--) {
			}

			setcolor(hsl2rgb(i * 360.0f / screenWidth, 1.0f, 0.5f));
			line(i, 0, i, screenHeight);
		}
	}

	getch();

	closegraph();
	return 0;
}

1.7 帧率获取 getfps()

功能:
  这个函数用于获取当前刷新率,即显示的帧率。

声明:

float getfps(
    int flag = 1
);

参数:
flag
  仅能为0或者1,如果为1,查询的是逻辑帧数;如果为0,查询的是渲染帧数。两者之差可以得到无效帧数(被跳过渲染的帧数,仅在调用 delay_jfps() 会产生)。如果没有调用过 delay_jfps(),那么两者无区别。

返回值:
  返回当前刷新率。

说明:
  FPS(Frames Per Second):每秒帧数。通常,这个帧数在动画或者游戏里,至少要达到 30才能基本流畅。现代液晶显示器均使用 60FPS 的刷新率,所以,如果你希望在你的显示器上达到最佳效果,那你需要至少60FPS
  而使内部 FPS 计数增加的方式是当你绘图后,调用 delay族函数,如: delay, delay_ms, delay_fps, Sleep,否则你不调用这些函数时,FPS永远为0而不会变化。


专栏:EGE专栏

下一篇:EGE绘图之二 窗口刷新

  • 31
    点赞
  • 73
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

依稀_yixy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值