• EasyXnote5关于批量绘图


    专栏:EasyX图形化编程

    问题引入

     之前的讲解中,我们可以发现创建的窗体在进行动画的显示时会出现闪烁现象,本节课将会一步一步探讨如何解决,可以使以后学习中的动画效果更加流畅。

    • 首先创建窗体,利用之前所学习的内容在窗体上绘制圆形,但这次是利用循环绘制出多个圆形,并且使其在窗体内不断运动,为其赋上随即初始坐标及运动方向,设置不同的运动速度,为将其全部保存在窗体内,可以设置为触碰到窗体即可反弹。

    绘制画面

    实现思路如下
    这次设置窗体颜色为白色。

    不要忘记使用cleardevice();重新粉刷窗体。

     在实现多个小球同时运行前,先看一个小球的情况,速度随机,初始运动方向随机,当碰到窗体边缘时反弹。
    代码如下

    int main()
    {
    	initgraph(800, 600);
    	setorigin(400, 300);
    	setaspectratio(1, -1);
    	setbkcolor(WHITE);
    	cleardevice();
    	srand(time(NULL));
    	int r = 50;
    	int x = rand() % 700 - 400+50;
    	int y = rand() % 500 - 300+50;
    	int vx = rand() % 3 +9;
    	int vy = rand() % 3 +9;
    	if (x % 2 == 0)
    	{
    		vx = -vx;
    	}
    	if (y % 2 == 0)
    	{
    		vy = -vy;
    	}
    	while (1)
    	{
    		cleardevice();
    		setfillcolor(BLACK);
    		solidcircle(x, y, r);
    		x += vx;
    		y += vy;
    		Sleep(50);
    		//碰到墙壁反弹
    		if (x >= 400 - r || x <= -400 + r)
    		{
    			vx = -vx;
    		}
    		if (y >= 300 - r || y <= -300 + r)
    		{
    			vy = -vy;
    		}
    	}
    	closegraph();
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    运行后如图
    在这里插入图片描述

     然而一个小球在窗体内运动是这样,要使所有小球运动起来,就需要记录每个小球的位置,方向,与绘制静态和绘制单个运动的小球不同,所有的小球都需要不断进行重新绘制,需要重复不断地计算每个小球移动后的坐标,不断记录更新。
    因为每个小球有很多变量类型,我们创建出一个结构体记录小球的每个变量特征。

    typedef struct
    {
    	int x;
    	int y;
    	int r;
    	int vx;
    	int vy;
    	COLORREF color;
    }ball;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    创建一个结构体数组,保存每一个小圆的信息
    定义小球数量方便更改看效果

    #define NUM 100

    创建结构体数组,保存每个小球的数据。

    ball balls[NUM];

    设置循环,为数组内的所有小球设置初始值

    	for (int i = 0; i < NUM; i++)
    	{
    
    		int m = -400 + r;
    		int n = 400 + r;
    		balls[i].x = rand() % (n-m + 1) + m;//圆形的全部都不能超出窗体,为之后的碰撞改变方向做铺垫
    		m = -300 + r;
    		n = 300 + r;
    		balls[i].y = rand() %(n-m + 1) + m;
    		m = 5;
    		n = 7;
    		balls[i].vx = rand() % (n-m + 1) + m;//速度在5~7之间
    		if (balls[i].x % 2 == 0)
    		{
    			balls[i].vx = -balls[i].vx;
    		}
    		balls[i].vy = rand() % (n-m + 1) + m;//速度在5~7之间
    		if (balls[i].y % 2 == 0)
    		{
    			balls[i].vy = -balls[i].vy;
    		}
    		balls[i].color = (RGB(rand() % 256, rand() % 256, rand() % 256));
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    这里小圆的初始坐标的位置值得一提,如果想要获得m到n的范围,假设m为-1,n为5。 n-m为6,rand()%6的范围是0~5。
    rand()%(m+n+1)的范围是0~6,再加上m,就得到了我们想要的范围。
    所以想要得到m到n的数据范围,可以写作rand()%(n-m+1)+m;

     开始进行绘制,利用循环绘制出所有的小球。在第二个for循环中更新小球的位置,判断每个小球是否撞击窗体,然后再次重新绘制,利用计算机的高速计算,就可以实现多个小球同时运动。
    (下图调小手机亮度)

    	while (1)//不断地重新更新小球
    	{
    		cleardevice();
    		for (int i = 0; i < NUM; i++)
    		{
    			setfillcolor(balls[i].color);
    			fillcircle(balls[i].x, balls[i].y, r);
    		}
    		Sleep(40);
    		for (int i = 0; i < NUM; i++)
    		{
    			if (balls[i].y >= 300 - r || balls[i].y <= -300 + r)
    			{
    				balls[i].vy = -balls[i].vy;
    			}
    			if (balls[i].x >= 400 - r || balls[i].x <= -400 + r)
    			{
    				balls[i].vx = -balls[i].vx;
    			}
    			balls[i].x += balls[i].vx;
    			balls[i].y += balls[i].vy;
    		}
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    运行代码
    在这里插入图片描述

    批量绘图解释

    运行后会发现画面有十分严重的闪烁现象。
    原因是什么呢?
    我们要了解要使程序在窗体上显示图像,需要进行那些步骤

    1,程序将图像放置在显示缓存区中
    2,显卡将缓存区的数据绘制到屏幕上

     把程序比作快递员,图像就是我们要运送的快递,显示缓存区就是驿站,把快递放在驿站里,等待着显卡将其送给收件人,就是将图像绘制到屏幕上。
     在这里1000个小球被看作一千个快递,快递员要一次一次将每个快递放置在驿站,收件人要一个一个取走包裹,快递员要送一千次,收件人要取1000次,在生活中,快递员通常是一次性运送多个快递,收件人也会一次取出多个快递,这样不用跑那么多次节省效率。
     那么为什么画面会出现一卡一卡的现象呢,就是快递小哥太累了来不及投送某些快递,也就是程序或显卡因为要处理的太多,无法及时工作。
     我们就利用生活中一次性送多个包裹,一次性接受多个包裹的方法来解决画面闪烁问题。将100个快递打包为一个大快递,直接运送大快递,显卡只需要从快递柜中取出一个大的合并的包裹就可以了。这样就可以大大提高绘图效率。
    因为是将多个数据打包后传输,所以也就称为批量绘图。
    然而批量绘图== 有利也有弊==
     单次绘图的情况下,只要有一个图像传递过来,就直接将其送至显卡,显示在屏幕上,而批量绘图要等到存至100个画面信息才会将其交给显卡,时效性降低了。但大多数程序对于显示的时效性要求没有那么高,所以这种方法还是可行的。因为批量绘图被广泛的使用在各个场景中。

    批量绘图使用

    先在一个小的例子中解释

    
    int main()
    {
    	initgraph(800, 600);
    	setorigin(400, 300);
    	setaspectratio(1, -1);
    	setbkcolor(RED);
    	cleardevice();
    	setfillcolor(RGB(23, 45, 147));
    
    	solidcircle(-320, 0, 40);
    	Sleep(1000);
    	solidcircle(-220, 0, 40);
    	Sleep(1000);
    	solidcircle(-120, 0, 40);
    	Sleep(1000);
    	solidcircle(-20, 0, 40);
    	Sleep(1000);
    	solidcircle(80, 0, 40);
    	Sleep(1000);
    	solidcircle(180, 0, 40);
    	Sleep(1000);
    
    	getchar();
    	closegraph();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    运行后每隔1秒才会绘制一个圆。
    在这里插入图片描述
    函数BeginBatchDraw
    在这里插入图片描述

    函数BeginBatchDraw可以开启批量绘图模式,开启批量绘图后,所有的绘图操作将不再进入显示缓存区,直到调用了EndBatchDraw函数结束批量绘图,才会将从BeginBatchDraw函数以来的所有绘图操作放入显示缓存区。

    添加两个函数进入上述代码,在第二次绘制圆形后开始进行批量绘制,在最后一次绘制圆前加入结束绘制的函数。
    运行后如图
    在这里插入图片描述
    可以发现,前两个绘制结束后,就不再进行绘制了,等了4秒后,后边的四个圆形立马就画出来了。
    再介绍一个函数FlashBatchDraw();
     BeginBatchDraw函数使用后,就只能等遇到EndBatchDraw函数才将图像传至显示缓存区。太霸道了吧,FlashBatchDraw给了他一巴掌,说,遇到我FlashBatchDraw,就要把在我之前的图像全部传过去,BeginBatchDraw一看是个大佬,顿时服软,就将遇到FlashBatchDraw前捕获的图像全部放了,然而过了FlashBatchDraw,他就继续捕获图像,不让其到达显示缓存区。BeginBatchDraw是坏人,抓图像,不让走,FlashBatchDraw是侠客,放开那些图像,但没抓BeginBatchDraw,EndBatchDraw就是警察,抓了Begin,解救图像。
     将BeginBatchDraw移至第一个绘图后,EndBetchDraw移至最后,在第三个绘图后加入FlashBatchDraw,运行观察效果。
    在这里插入图片描述
    可以发现结果如我们所料。


    回到上述画面闪烁的问题
    当累计绘制1000个小球后,在将这1000个小球打包装进显示缓存区,只需要在循环前加上BeginBatchDraw函数,在一千个小球初始化后加入FlashBatchDraw函数,在绘制循环结束时,关闭批量绘图即可。
    运行结果
    在这里插入图片描述
    由于插入图片大小有要求,所以不能录制太长的片段
    代码如下,大家可自行参考

    #include 
    #include 
    #include 
    #include 
    
    typedef struct
    {
    	int x;
    	int y;
    	int vx;
    	int vy;
    	COLORREF color;
    }ball;
    
    #define NUM 1000
    int main()
    {
    	initgraph(800, 600);
    	setorigin(400, 300);
    	setaspectratio(1, -1);
    	setbkcolor(WHITE);
    	cleardevice();
    	srand(time(NULL));
    	int r = 10;
    	ball balls[NUM];
    	for (int i = 0; i < NUM; i++)
    	{
    
    		int m = -400 + r;
    		int n = 400 + r;
    		balls[i].x = rand() % (n-m + 1) + m;//圆形的全部都不能超出窗体,为之后的碰撞改变方向做铺垫
    		m = -300 + r;
    		n = 300 + r;
    		balls[i].y = rand() %(n-m + 1) + m;
    		m = 5;
    		n = 7;
    		balls[i].vx = rand() % (n-m + 1) + m;//速度在5~7之间
    		if (balls[i].x % 2 == 0)
    		{
    			balls[i].vx = -balls[i].vx;
    		}
    		balls[i].vy = rand() % (n-m + 1) + m;//速度在5~7之间
    		if (balls[i].y % 2 == 0)
    		{
    			balls[i].vy = -balls[i].vy;
    		}
    		balls[i].color = (RGB(rand() % 256, rand() % 256, rand() % 256));
    	}
    	BeginBatchDraw();
    	while (1)//不断地重新绘制更新小球
    	{
    		cleardevice();
    		for (int i = 0; i < NUM; i++)
    		{
    			setfillcolor(balls[i].color);
    			fillcircle(balls[i].x, balls[i].y, r);
    		}
    		FlushBatchDraw();
    		Sleep(40);
    		for (int i = 0; i < NUM; i++)
    		{
    			if (balls[i].y >= 300 - r || balls[i].y <= -300 + r)
    			{
    				balls[i].vy = -balls[i].vy;
    			}
    			if (balls[i].x >= 400 - r || balls[i].x <= -400 + r)
    			{
    				balls[i].vx = -balls[i].vx;
    			}
    			balls[i].x += balls[i].vx;
    			balls[i].y += balls[i].vy;
    		}
    	}
    	EndBatchDraw();
    	getchar();
    	closegraph();
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78

    本章结束,希望能给大家带来帮助,谢谢大家

  • 相关阅读:
    存储过程,循环中定义变量
    Selenium实现批量将CSDN文章改为【粉丝可见】
    MyBatisPlus-多记录操作及逻辑删除
    Fourier变换中的能量积分及其详细证明过程
    海勒姆法则(Hyrum‘s Law)
    javascript正则表达式(语法以及正则表达式修饰符)
    C/C++程序的断点调试
    动态IP与静态IP的区别,你选对了吗?
    信奥中的数学:整除
    java毕业设计宠物寄养管理系统Mybatis+系统+数据库+调试部署
  • 原文地址:https://blog.csdn.net/qq_75270497/article/details/133576725