计算机图形学上机报告(二)

1.学习了三种绘制直线的算法:DDA(digital differential analyzer)算法、增量DDA算法、中点画线算法的数学原理;

2.利用C++语言编写代码实现用DDA算法使用黄线画出从(0,0)点到(500,400)点画线;

3.利用C++语言编写代码实现用DDA算法使用黄线画出从(0,0)点到(400,500)点画线;

4.利用C++语言编写代码实现用增量DDA算法使用白线画出从(500,0)点到(0,500)点画线;

5.利用C++语言编写代码实现用中点画线法算法使用蓝线画出从(0,150)点到(500,250)点画线。

Visual Studio 2022版

Windows 10/11

1.DDA(digital differential analyzer)算法画直线

由于真实的直线是连续的,而屏幕由一个个像素点组成,是离散的,所以在屏幕上绘制直线时,需要点亮与直线最为逼近的像素点,从而显示出完整直线的近似形状。

而DDA算法就是一种逼近方法,设直线方程为:

​​​​​​​

​​​​​​​

则根据前一个点的纵坐标确定后一个点的纵坐标,再对求得的y坐标进行四舍五入的取整,就可以得到近似后点亮点的屏幕坐标,将这些坐标依次点亮即可。

在C++语言中实现时,首先根据直线起点和终点的坐标求得直线的斜率,再利用循环每次横坐标增加1,根据公式求出对应的纵坐标值,并依次点亮所以纵坐标值的浮点数,接着对其进行int(y+0.5)四舍五入的取整操作即可,这样就可以实现DDA算法。需要注意,在斜率k和y的初始值都需要为浮点类型数据,而其他变量和取整后的y都是整型变量。而直线的颜色就是每一个像素的颜色,在使用<graphics.h>库的成员函数putpixel时可以修改颜色变量为不同的值。

在绘制斜率在[-1,1]区间范围内的直线,由于x的值增加,y的值可以发生明显变化,处于同一行列的纵坐标值较少,可以明显地勾勒出直线的形状,像素点相对连续,比较逼近真实直线。而对于斜率在(- ,-1] [1,+∞ )区间范围内的直线,随着x的变化,y的值改变不大,许多像素点均位于同一列,点亮的像素点比较离散,逼近效果较差,不能反映出真实直线的形状,对于这种情况,可以将直线方程更改为x=ky+b 进行计算,根据公式计算纵坐标每增加1,点亮的横坐标位置,每一个点亮的横坐标与前一个横坐标之间的关系为xi=yi+k ,进而在屏幕上绘制出完整直线,这样像素点相对连续,逼近效果好,所以DDA算法仅适用于绘制斜率在[-1,1]区间范围内或斜率在(- ,-1] [1,+∞ )区间范围内的直线,由于算法中的方程是固定的,无法进行更换,所以一个程序只能实现一种斜率范围内的直线绘制,若想要绘制另一种斜率范围内的直线,只能再编写一个另一种直线方程对应的程序,这需要根据函数的斜率进行人工判断后,选择对应斜率的程序进行绘制,非常麻烦,且不具有普遍性,不能直接应用到所有直线中,这种方法较为局限。

2.增量DDA算法画直线

为了改进DDA算法绘制直线中需要根据斜率范围判断直线方程选择的局限性,采用增量DDA算法绘制直线。

增量DDA主要加入了对直线斜率的判断,若直线斜率在[-1,1]区间范围内,则选取x为增量,根据公式计算x每增加1,y的值;若直线斜率在(- ,-1] [1,+∞ )的区间范围内,则选取y为增量,根据公式计算y每增加1,x的值。这就相当于将DDA算法的两种情况综合到一起,利用同一个代码进行实现。

在C++语言中实现时,可以根据直线起点和终点坐标,判断x、y方向增量的绝对值大小,如果x方向增量绝对值大,则对应|k|<1的情况,利用除法将x方向的增量置于1,y方向的增量则为对应的斜率;否则,对应|k|>1的情况,利用除法将y方向的增量置于1,x方向的增量则为对应的斜率;对于|k|=1的情况,可以在判断时将其归为上述两种情况的任意一种。最后取大的增量作为循环次数,每一次循环将x、y的值增加对应的增量即可,所有方向均进行四舍五入,这样每次增加1的方向还是增加1,而另一个方向可以实现四舍五入。通过将所有点亮的像素点相连,即可在计算机屏幕上得到完整的近似直线。而直线的颜色就是每一个像素的颜色,在使用<graphics.h>库的成员函数putpixel时可以修改颜色变量为不同的值。

增量DDA算法主要对DDA算法只能应用于直线斜率|k|>1或|k|<1的情况单独进行绘制,一个程序不能适用于所有直线的局限性进行改进,改进后可以实现对所有直线的绘制。但是其还是需要对两个数分别进行四舍五入取整,算法效率较低。

3.中点画线算法

中点画线算法与DDA算法直线的近似方式不同,中点画线算法是判断真实纵坐标的值与上下两个坐标的中点的位置关系,若真实纵坐标位于交点上方,则点亮上面的点;否则,点亮下面的点。

而判断点与直线的关系需要利用直线的解析方程,假设直线的起点、终点分别为x0,y0x1,y1 ,则直线解析方程为

​​​​​​​

其中,a=x1-x0b=y1-y0

由此可以判断点和直线的关系,当F(xt,yt)=0 时,点在直线上,当F(xt,yt)>0 时,点在直线上方,当F(xt,yt)<0 时,点在直线下方。将下一个位置的中点坐标代入直线方程中,通过上述关系,就可以判断出下一个点亮像素点是位于上方还是下方。所以,下一个点坐标代入直线方程,可得判别式

由于axi+byi+c=0 ,所以d=a+0.5b ,由于判别式d的判别只与符号有关,因此为了计算方便,可以使其扩大一倍,即d1=2a+b 。接着,判断d的符号,若d<0,则中点在直线下方,下一个点亮的像素点为(xi+1,yi+1) ,下一个判别式为d1=Fxi+2,yi+1.5 ,将上一个d的值代入,可得d2=d1+a+b ;若d>0,则中点在直线上方,下一个点亮的像素点为(xi+1,yi) ,下一个判别式为d1=Fxi+2,yi+0.5 ,将上一个d的值代入,可得d2=d1+a 。通过进行迭代,可以得到通过每一点的坐标及判别式的值求出下一个点判别式的值,从而确定下一个点亮像素点的坐标。

 

图1 中点画线算法流程图

在C++语言中实现中点画线算法时,所有直线都按照x方向增加1即可,通过起点坐标求出下一个点的判别式的值以及下一个点坐标位置,这里通过选择结构实现,d<0或d>0两个条件分别对应不同语句,分别为x、y分别加1和只有x加1,并继续利用下一个点坐标求得下一个判别式的值和再下一个点亮像素点的位置。点亮从起点到终点的每一个像素点,最终连接成直线。而直线的颜色就是每一个像素的颜色,在使用<graphics.h>库的成员函数putpixel时可以修改颜色变量为不同的值。

这个算法本质上是通过与中点的关系决定点亮点的坐标,因此不受到直线斜率的限制,可以直接画出任意斜率的直线,且近似程度较好,画出的直线质量较好。

1.用DDA算法使用黄线画出从(0,0)点到(500,400)点画线

//头文件

#include<graphics.h> //包含画图函数

#include <conio.h> //输入输出图形

#include<math.h> //数学函数库

using namespace std;

void DDA(int x0, int y0, int x1, int y1, int color = YELLOW) {

    float x, y;

    float dx, dy,k;//建立deltax、deltay和斜率k

    dx = float(x1 - x0);//计算deltax

    dy = float(y1 - y0);//计算deltay

    k = dy / dx;//计算斜率k

    y = y0;//设置y的初始值

    for (x = x0; x <= x1; x++)

    {

         putpixel(x, int(y + 0.5), color);//在屏幕相应位置绘制像素点

         y += k;//每次循环后y增加k

    }//按照x每次增加1计算y的值,利用循环绘制出每一个点亮的像素点,最后凭借成直线

}//DDA(数值微分法)函数,绘制一条直线,只适用于|k|小于1的情况

void main() {

    initgraph(500, 500);//初始化图形系统,创建图窗,大小为500×500像素

    DDA(0, 0, 500, 400,YELLOW);//绘制黄色直线

    _getch();//相当于按任意键继续

    closegraph();//关闭图窗

    system("p0ause");//保留界面

}

2.用DDA算法使用黄线画出从(0,0)点到(400,500)点画线

//头文件

#include<graphics.h> //包含画图函数

#include <conio.h>//输入输出图形

#include<math.h> //数学函数库

using namespace std;

void DDA(int x0, int y0, int x1, int y1, int color = YELLOW) {

    float x, y;

    float dx, dy, k;//建立deltax、deltay和斜率k

    dx = float(x1 - x0);//计算deltax

    dy = float(y1 - y0);//计算deltay

    k = dx / dy;//计算斜率k

    x = x0;//设置y的初始值

    for (y = y0; y <= y1; y++)

    {

         putpixel(int(x+0.5), y, color);//在屏幕相应位置绘制像素点

         x += k;//每次循环后x增加k

    }//按照y每次增加1计算x的值,利用循环绘制出每一个点亮的像素点,最后凭借成直线

}//DDA(数值微分法)函数,绘制一条直线,只适用于|k|大于1的情况

void main() {

    initgraph(500, 500);//初始化图形系统,创建图窗,大小为500×500像素

    DDA(0, 0, 400, 500, YELLOW);//绘制黄色直线

    _getch();//相当于按任意键继续

    closegraph();//关闭图窗

    system("pause");//保留界面

}

3.用增量DDA算法使用白线画出从(500,0)点到(0,500)点画线

//头文件

#include<graphics.h> //包含画图函数

#include <conio.h>//输入输出图形

#include<math.h> //数学函数库

using namespace std;

void DDAplus(int x0, int y0, int x1, int y1, int color = WHITE) {

    float x, y;

    float xIncre, yIncre;//x为主方向方向的斜率和y为主方向的斜率

    float dx, dy,eps, k;//建立deltax、deltay和斜率k

    dx = x1 - x0;//计算deltax

    dy = y1 - y0;//计算deltay

    //判断主方向

    if (abs(int(dx)) > abs(int(dy)))

         eps = abs((int(dx)));

    else

         eps = abs((int(dy)));

    x = x0;

    y = y0;

    xIncre = dx / eps;

    yIncre = dy / eps;

    for (k=0; k<=eps; k++)

    {

         putpixel(int(x + 0.5), int(y + 0.5), color);//在屏幕相应位置绘制像素点

         x += xIncre;//每次循环后x增加x为主方向的斜率

         y += yIncre;//每次循环后y增加y为主方向的斜率

    }//若判定完主方向,另一个方向通过除法自动增1,利用循环绘制出每一个点亮的像素点,最后凭借成直线

}//DDA(数值微分法)函数,绘制一条直线,只适用于|k|为任意值的情况

void main() {

    initgraph(500, 500);//初始化图形系统,创建图窗,大小为500×500像素

    DDAplus(500, 0, 0, 500, WHITE);//绘制白色直线

    _getch();//相当于按任意键继续

    closegraph();//关闭图窗

    system("pause");//保留界面

}

4.用中点画线法算法使用蓝线画出从(0,150)点到(500,250)点画线

//头文件

#include<graphics.h> //包含画图函数

#include <conio.h>//输入输出图形

#include<math.h> //数学函数库

using namespace std;

void MidPoint(int x0, int y0, int x1, int y1, int color = BLUE) {

    int a, b, d, d1, d2, x, y;//定义变量

    a = y0 - y1;//直线方程参数

    b = x1 - x0;//直线方程参数

    d = 2 * a + b;//交点与中点初始差值

    //计算交点与中点距离的累加值,根据上一个d的符号而异

    d1 = 2 * a;//这是d>0时的情况

    d2 = 2 * (a + b);//这是d<0时的情况

    x = x0;//x的初始位置

    y = y0;//y的初始位置

    putpixel(x, y, color);//画出初始位置

         while (x < x1) {

             if (d < 0) {

                x++;

                 y++;

                  d += d2;

             }//当d<0时,取上点为亮点,累加d的值

             if (d > 0) {

                  x++;

                  d += d1;

             }//当d<0时,取下点为亮点,累加d的值

             putpixel(x, y, color);//画出整条直线

    }//判断d的情况,若d<0,则亮点在交点上面,若d<0,则亮点在交点下面

        

}//实现通过中点画线算法绘制一条通过(x0,y0),(x1,y1)的直线

void main() {

    initgraph(500, 500);//初始化图形系统,创建图窗,大小为500×500像素

    MidPoint(0, 150, 500, 250, BLUE);//绘制蓝色直线

    _getch();//相当于按任意键继续

    closegraph();//关闭图窗

    system("pause");//保留界面

}

1.用DDA算法使用黄线画出从(0,0)点到(500,400)点画线

 

2.用DDA算法使用黄线画出从(0,0)点到(400,500)点画线

 

3.用增量DDA算法使用白线画出从(500,0)点到(0,500)点画线

 

4.用中点画线法算法使用蓝线画出从(0,150)点到(500,250)点画线

 

本次上机主要利用C++语言编写了实现DDA算法、增量DDA算法和中点画线算法的相应程序。

对于计算机上直线的绘制,由于直线方程是连续的函数,而计算机屏幕是离散的像素点,因此无法直接在计算机屏幕上显示光滑的直线,只能绘制出最逼近直线方程的近似直线在屏幕上显示。在计算机屏幕上显示的直线有如下要求:直线要直;直线的端点要准确,即无定向性和断裂情况;直线的亮度、色泽要均匀;画线的速度要快;要求直线具有不同的色泽、亮度、线型等。所以,为了达到这个要求,就需要依次点亮屏幕上所有位置最逼近直线方程的像素点,使显示的直线尽可能逼近真实的直线,尽可能显得光滑。所以,画直线算法需要解决的核心问题就是离散的像素点对连续函数的近似问题。画直线对算法设计的要求为:直线段要显得笔直;线段端点位置要准确;线段的亮度要均匀;转换算法速度要快。以上三种算法根据不同的解析几何方法,对连续的直线函数进行了近似,但三者各有利弊,效率和应用范围不同。

三种算法从前到后是逐渐进行改进的。首先,DDA算法是原理上最简单的算法,由于其取近似像素点的方式,只能局限于计算斜率|k|<1的情况,因此,需要根据直线的斜率分别采用不同的直线方程,不同的增量,编写不同的程序,才能进行计算,需要进行人为判断,人为选择不同的程序,这大大增加了这种算法的局限性,限制了其广泛应用。此算法的斜率和增加方向的坐标都为浮点类型,而其他数据类型均为整型,编写程序操作不便。且此算法需要对点亮点位的浮点坐标值进行四舍五入,需要用到取整操作,在每次循环中都需要进行一遍,算法执行时间较长,效率比较低。对DDA算法进行复杂度分析,可以得到其时间复杂度为O(n),但是DDA算法的近似原理就是对浮点数四舍五入进行取整,每一次循环都需要进行一次取整操作,改变变量的数据类型,这就大大降低了算法的效率,增加了绘制直线的时间。

而为了解决DDA算法不能适用于绘制所有直线的问题。采用增量DDA算法进行改进,这样就可以根据斜率的范围自动选择增量的方向,从而实现对于所有斜率直线的绘制。增量DDA算法解决了DDA算法的应用范围问题,但是在执行效率方面却没有改进。首先,其没有改进DDA算法浮点数和整数数据类型不同的问题;其次,对增量DDA算法进行时间复杂度分析,可以发现其时间复杂度仍然为O(n),但是对于每一次循环,需要进行两次变量的取整操作,这就使得增量DDA算法的执行效率基本为DDA算法的1/2,执行时间为DDA算法的二倍,算法效率反而变低。所以,增量DDA算法仅仅是在适用范围和方便程度上进行了增强,在效率上甚至不如DDA算法。

而中点画线算法则可以说是改进了DDA算法和增量DDA算法的所有的不足。首先,DDA算法可以的核心原理是通过判断直线真实交点与中点位置关系,从而得到点亮像素点的坐标,所以其不存在因为直线斜率变化就在一个方向上难以逼近的问题,使用于所有直线。其次,中点DDA不需要对浮点数进行四舍五入,也不需要计算直线的斜率,所有变量的数据类型均为整型,只需要计算整型判别式的正负,就可以实现画直线算法的核心内容——离散的像素点对连续函数的近似问题。对与直线近似的像素点进行定位和绘制。最后,对中点画线算法进行时间复杂度分析,可以得到其时间复杂度为O(n),与DDA算法、增量DDA算法相同,但是在每一次循环中,不需要再进行取整改变数据类型,这就大大提高了算法的执行效率,减小了绘制直线的时间。

通过上述分析,可以看出中点画线算法是在计算机中绘制直线的最优算法。而在一些对算法效率要求不高的情况下,也可以应用DDA算法或增量DDA算法绘制直线。

而在C++中,实现图形的绘制需要借助一些外部的库来实现图形的绘制,其中<graphics.h>库为C++的图形库,包含许多图形,可以利用其成员函数进行图像的绘制;<conio.h>库控制台输入输出函数头文件,其中con是控制台Console英文缩写,io是Input/Output输入输出,它还定义了通过控制台进行数据输入和数据输出的函数,如键盘输入、屏幕输出等,可以控制图像在屏幕上的显示。在绘制时首先使用intigraph(width,height)函数初始化图窗,width、height为图窗的长度和宽度。执行完绘制直线的函数之后,利用_getch()函数进行程序窗口中按任意键继续的函数操作,最后用closegraph()关闭图形界面。而每一个循环中最基础的画像素点函数putpixel就是<graphics.h>库的成员函数,其使用方法为putpixel(int x,int y,int color),其中x、y为需要点亮的像素点在图窗屏幕坐标系中的位置,color为点亮的颜色。这些函数在今后的计算机图形学上机实验中可能会经常用到。

最后一个问题就是图窗中的屏幕坐标系与平面直角坐标系不同,经过了旋转,屏幕坐标系原点在图窗左上方,x轴为图窗上边的边,y轴为图窗左边的边,所以最后在图窗中显示的直线可能与我们平时认知中的直线是不同的,需要对比其与正常平面直角坐标系的位置关系,来判断绘制出的直线究竟是否正确。

注意:本上机报告适用于中国地质大学(北京)测绘工程专业计算机图形学课程,其他学校也可以参考使用。这些代码只是起到启发作用,不能完全照搬,具体细节还需要自己斟酌修改。

如有问题欢迎在评论区留言,让我们共同交流,一起进步!

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值