计算机图形学课程设计有效边表填充算法的实现.docx
- 文档编号:27418317
- 上传时间:2023-06-30
- 格式:DOCX
- 页数:30
- 大小:211.64KB
计算机图形学课程设计有效边表填充算法的实现.docx
《计算机图形学课程设计有效边表填充算法的实现.docx》由会员分享,可在线阅读,更多相关《计算机图形学课程设计有效边表填充算法的实现.docx(30页珍藏版)》请在冰豆网上搜索。
计算机图形学课程设计有效边表填充算法的实现
计算机图形学课程设计
设计题目改进的有效边表算法对多边形的填充
学院名称信息科学与技术学院
专业名称计算机科学与技术
学生姓名刘柯
学生学号201213030112
任课教师梅占勇
设计(论文)成绩
教务处制
2015年9月28日
一、设计内容与要求
1.1设计题目
用改进的有效边表算法实现多边形的填充
1.2设计内容
使用OpenGL实现用改进的有效边表算法填充多边形
1.3设计目标
参照课本上改进的有效边表算法的思想,实现该算法的C语言代码,并用该算法搭配OpenGL以像素点的方式绘制出给定顶点坐标的多边形。
二、总体设计
2.1多边形的表示
在计算机图形学中,多边形有2种重要的表示方法:
顶点表示和点阵表示。
顶点表示用多边形的顶点序列来刻画多边形,这种方法直观、几何意义强,占用内存少,应用普遍,但它没有明确指出哪些像素在多边形内,故不能直接用于面着色。
点阵表示用位于多边形内的像素的集合来刻画多边形。
这种表示法虽然失去了许多重要的几何信息,但便于运用帧缓存表示图形,是面着色所需要的图形表示形式。
大多数图形应用系统采用顶点序列表示多边形,而顶点表示又不能直接用于显示,那么就必须有从多边形的顶点表示到点阵表示的转换,这种转换称为多边形的扫描转换或多边形的填充。
即从多边形的顶点信息出发,求出位于其内部的各个像素,并将其颜色值写入帧缓存的相应单元中。
2.2x-扫描线算法
x-扫描线算法的基本思想是,按照扫描线的顺序,计算扫描线与多边形的相交区间,再用要求的颜色显示这些区间的像素,即完成填充工作。
区间的端点可以通过计算扫描线与多边形边界线的交点获得。
根据此原理,x-扫描线算法可以填充凸的、凹的或带有孔的多边形区域。
x-扫描线的算法步骤如下:
(1)确定多边形顶点的最小和最大y值(ymin和ymax),得到多边形所占有的最大扫描线数。
(2)从y=ymin到y=ymax,每次用一条扫描线填充。
每一条扫描线填充的过程分为4个步骤:
1求交。
计算扫描线与多边形所有边的交点。
2排序。
把所有交点按x坐标递增的顺序进行排序。
3交点配对。
配对第一与第二、第三与第四个交点等,每对交点代表一个填充区间。
4区间填色。
把这些相交区间内的像素置成不同于背景色的填充色。
x-扫描线算法在处理每条扫描线时,需要与多边形的所有边求交,这样处理效率非常低。
因为一条扫描线往往只与少数几条边相交,甚至与整个多边形都不相交。
因此,有必要对算法进行改进。
2.3改进的有效边表算法
2.3.1改进的有效边表算法
将x-扫描线算法加以改进可以得到改进的有效边表算法,也称y连贯算法。
改进可以从三个方面进行:
首先,在处理一条扫描线时,仅对与它相交的多边形的边(有效边)求交;其次,利用扫描线的连贯性,考虑到当前扫描线与各边的交点顺序与下一条扫描线与各边的交点顺序很可能相同或非常相似,因此在当前扫描线处理完毕之后,不必为下一条扫描线从头开始构造交点信息;最后,利用多边形的连贯性,认为若某条边与当前扫描线相交,则它很可能也与下一条扫描线相交且其交点与上一次的交点相关。
如下图所示,设直线的斜率为k,若y=yi时,x=xi;则当y=yi+1时,有xi+1=xi+1/k。
2.3.2有效边表
有效边(ActiveEdge)是指与当前扫描线相交的多边形的边,也称活性边。
把有效边按与扫描线交点x坐标递增的顺序存放在一个链表中,此链表称为有效边表(ActiveEdgeTable,AET)。
有效边表的每个结点存放对应边的如下信息:
其中,x为当前扫描线与有效边交点的x坐标;ymax是有效边所在的最大扫描线值,通过它可以知道何时才能“抛弃”该边;1/k是边斜率的倒数;next是下一个结点的指针。
如下图所示的多边形P0P1P2P3P4P5P6,其顶点表示为:
P0(7,8),P1(3,12),P2(1,7),P3(3,1),P4(6,5),P5(8,1),P6(12,9)。
当y=8时的有效边表如下图所示:
2.3.3边表
有效边表给出了扫描线和有效边交点坐标的计算方法,但是没有给出新边出现的位置坐标。
为了方便有效边表的建立与更新,就需要构造一个边表(EdgeTable,ET),用以存放扫描线上多边形各条边的信息。
由于水平边的1/k为∞,并且水平边本身就是扫描线,所以在建立边表时不予考虑。
边表的构造分为4个步骤:
1首先构造一个纵向链表,链表的长度为多边形占有的最大扫描线数,链表的每个结点,称为一个桶,对应多边形覆盖的每一条扫描线。
2将每条边的信息装入与该边最小y坐标(ymin)相对应的桶中。
也就是说,若某边的较低端点为ymin,则该边就放在相应的y=ymin的扫描线桶中。
3每条边的数据形成一个结点,内容包括该扫描线与该边的初始交点x(即较低端点的x坐标值),该边最大y坐标值ymax,以及斜率的倒数1/k和下一个边结点的指针next:
4同一桶中若干条边按x|ymin由小到大排列,若x|ymin相等,则按1/k由小到大排序。
从上面可以看出,边表是有效边表的特例,有效边表和边表可以使用同一个数据结构来表示。
对于多边形P0P1P2P3P4P5P6,它的边表结构如下图所示:
三、详细设计
3.1改进的有效边表算法的实现
改进的有效边表算法具体实现过程如下:
1初始化边表ET。
为了方便边表的建立,可以定义sort()函数对多边形的边进行排序,保证边表中每个桶中的边是有序的。
同时定义一个creat_edge_table()函数,该函数需要多边形的顶点信息作为参数传入,并返回一个边表指针。
2初始化有效边表AET。
从ET表中取出第一个不为空的桶与AET表合并。
为了初始化AET表,可以定义一个init_active_table()函数,该函数传入边表指针作为形参,返回一个有效边表指针。
3从AET表中取出交点进行填充。
填充时设置一个布尔值b(初值为假),令指针从有效边表的第一个结点(也就是扫描线与有效边的交点)开始遍历。
每访问一个结点,把b取反一次。
若b为真,则把从当前结点的x值开始(设为x1)到下一结点的x值结束(设为x2)的区间[x1,x2]用多边形色填充。
填充时为了避免多边形区域扩大化,需要对区间边界进行如下处理:
若x是整数,则按“左闭右开”的原则处理,即左边界上的x(x1)不变,右边界上的x(x2)减1;若x不是整数,左边界上的x(x1)向右取整,右边界上的x(x2)向左取整。
4更新AET表。
设当前AET表中扫描线为y,首先更新扫描线为y=y+1,然后删除AET表中所有ymax=y的边结点;再根据xi+1=xi+1/k计算并修改AET表中各边结点的x坐标,同时合并ET表对应于扫描线y的桶中的边,按次序插入到AET表中,形成新AET表。
此步骤满足多边形的“下闭上开”原则。
此过程用到自定义的函数delete_edge()、add_edge()、update_active_table()。
5判断AET表是否为空。
若为空,则结束;否则转到③
3.2有效边表算法程序流程图
四、测试结果
为了便于观察多边形的填充,本程序将像素点进行放大处理,400x300的分辨率投影到20x15,并绘制出网格,使用OpenGL画出多边形的边框。
使用了Sleep()函数来延时生成像素点,并且每填充一个像素点刷新一次,给人动态直观的感受。
1在不处理边界的情况下,如下图所示,多边形的区域可能会扩大化。
2对边界进行处理后,结果如下:
3为了验证改进的有效边表填充算法,现将本程序的像素大小恢复为1:
1,按比例扩大多边形的顶点坐标,测试结果如下:
从上图可以看出填充过后的多边形与原多边形P0P1P2P3P4P5P6的形状一致,该算法验证通过。
4测试其他多边形
长方形:
三角形:
五角星:
从以上结果来看,该算法基本得到完美实现。
而程序的执行时间来看,生成边表的时间(包括分配内存、对桶中的结点进行排序等)远比填充像素点的时间要长。
因此,该算法的效率虽然很高,但对于表的维护和排序开销太大,它适合软件实现而不适合硬件实现。
五、总结
通过本次课程设计,我掌握了多边形的填充算法,了解了OpenGL的运行结构,熟悉了很多OpenGL的函数,对OpenGL编程也产生的浓厚的兴趣。
同时也巩固了对计算机图形学知识的掌握,锻炼了我对手实验的能力,看清了自己的水平,认识到了自己的不足。
在本次课程设计中,存在一些不足之处。
比如对边界的处理,课本上的说法模糊不清,在网上也没有找到相应的解答,我只能根据自己的理解来试着解决;这方面也存在没有及时和老师沟通的原因。
从这一点上,我认识到理论和实践的差别,我们应该多实践才能真正掌握理论。
还有就是完全填充后的多边形,边缘有“锯齿”现象,不知道是我程序的原因还是算法的问题。
或许对于多边形的填充算法还应该处理“锯齿”。
六、源代码
//源代码仅包含文件PolygonFilling.cpp
#include
#include
#include
#include
#defineEPSILON0.000001//最小浮点数
//点结构体
structPoint
{
intx;//x坐标
inty;//y坐标
};
//线结构体
structLine
{
Pointhigh_point;//高端点
Pointlow_point;//低端点
intis_active;//是否为有效边,水平边(0),非水平边
(1)
doubleinverse_k;//斜率k的倒数
};
//边结点
structEdgeNode
{
doublex;//扫描线与边交点的x坐标(边的低端点的x坐标)
inty_max;//边的高端点的y坐标ymax
doubleinverse_k;//斜率k的倒数
EdgeNode*next;//下一个边结点的指针
};
//有效边表
structActiveEdgeTable
{
inty;//扫描线y
EdgeNode*head;//边链表的头指针
};
//桶结点
typedefstructBucket
{
inty;//扫描线y
EdgeNode*head;//边链表的头指针
Bucket*next;//下一个桶的指针
}EdgeTable;
intcompare(Pointp1,Pointp2);
Line*create_lines(Pointpoints[],intn);
Pointget_lowest_point(Linelines[],intn);
Pointget_highest_point(Linelines[],intn);
voidswap(Line&l1,Line&l2);
voidsort(Linelines[],intn);
EdgeTable*create_edge_table(Linelines[],intn);
ActiveEdgeTable*init_active_table(EdgeTable*edge_table);
voiddelete_edge(ActiveEdgeTable*active_table,inty_max);
voidadd_edge(ActiveEdgeTable*active_table,EdgeNodeedge);
ActiveEdgeTable*update_active_table(ActiveEdgeTable*active_table,EdgeTable*edge_table);
voidDrawPolygon(Pointpoints,intn);
voidDrawGrid(intx,inty);
voidFill(Pointpoints[],intn);
voidInitial();
voidDisplay();
intmain(intargc,char*argv[])
{
glutInit(&argc,argv);
glutInitDisplayMode(GLUT_SINGLE|GLUT_RGB);
glutInitWindowSize(400,300);
glutInitWindowPosition(100,120);
glutCreateWindow("PolygonFilling");
glutDisplayFunc(Display);
Initial();
glutMainLoop();
return0;
}
//比较2个点的高度
intcompare(Pointp1,Pointp2)
{
if(p1.y>p2.y)
return1;
elseif(p1.y==p2.y)
return0;
return-1;
}
//由点数组生成线段数组
Line*create_lines(Pointpoints[],intn)
{
Line*lines=(Line*)malloc(n*sizeof(Line));
for(inti=0;i { Pointp1=points[i]; Pointp2=points[(i+1)%n]; intresult=compare(p1,p2); if(result==0) lines[i].is_active=0; else lines[i].is_active=1; lines[i].high_point=result>0? p1: p2; lines[i].low_point=result<0? p1: p2; lines[i].inverse_k=(double)(p2.x-p1.x)/(double)(p2.y-p1.y); } returnlines; } //获取线数组中最低的端点 Pointget_lowest_point(Linelines[],intn) { Pointlowest_point=lines[0].low_point; for(inti=1;i { Pointlow_point=lines[i].low_point; if(compare(lowest_point,low_point)>0) lowest_point=low_point; } returnlowest_point; } //获取线数组中最高的端点 Pointget_highest_point(Linelines[],intn) { Pointhighest_point=lines[0].high_point; for(inti=1;i { Pointhigh_point=lines[i].high_point; if(compare(highest_point,high_point)<0) highest_point=high_point; } returnhighest_point; } //交换2个Line对象 voidswap(Line&l1,Line&l2) { Linetemp=l1; l1=l2; l2=temp; } //对线数组进行排序 voidsort(Linelines[],intn) { //先按低端点的y坐标进行升序排序 for(inti=0;i { intmin_index=i; for(intj=i+1;j { if(lines[j].low_point.y min_index=j; } swap(lines[i],lines[min_index]); } //再将有序数组按低端点的x坐标升序排列,若x坐标相等,按inverse_k升序 for(inti=0;i { intmin_index=i; for(intj=i+1;lines[j].low_point.y==lines[i].low_point.y;++j) { if(lines[j].low_point.x min_index=j; } swap(lines[i],lines[min_index]); if(i>0&&lines[i].low_point.x==lines[i-1].low_point.x) { if(lines[i].is_active==1&&lines[i-1].is_active==1) { if(lines[i].inverse_k swap(lines[i],lines[i-1]); } } } } //创建一个边表 EdgeTable*create_edge_table(Linelines[],intn) { EdgeTable*edge_table=(EdgeTable*)malloc(sizeof(EdgeTable)); edge_table->head=NULL; edge_table->next=NULL; sort(lines,n); Pointlowest_point=get_lowest_point(lines,n); Pointhighest_point=get_highest_point(lines,n); EdgeTable*s=edge_table; for(inti=lowest_point.y;i<=highest_point.y;++i) { Bucket*bucket=(Bucket*)malloc(sizeof(Bucket)); bucket->y=i; bucket->next=NULL; bucket->head=(EdgeNode*)malloc(sizeof(EdgeNode)); bucket->head->next=NULL; EdgeNode*p=bucket->head; for(intj=0;j { if(lines[j].is_active==0) continue; if(lines[j].low_point.y==i) { EdgeNode*q=(EdgeNode*)malloc(sizeof(EdgeNode)); q->x=lines[j].low_point.x; q->y_max=lines[j].high_point.y; q->inverse_k=lines[j].inverse_k; q->next=NULL; p->next=q; p=q; } } s->next=bucket; s=bucket; } returnedge_table; } //从边表中取出第一个不为空的桶初始化有效边表 ActiveEdgeTable*init_active_table(EdgeTable*edge_table) { ActiveEdgeTable*active_table=(ActiveEdgeTable*)malloc(sizeof(ActiveEdgeTable)); active_table->y=edge_table->next->y; active_table->head=(EdgeNode*)malloc(sizeof(EdgeNode)); active_table->head->next=NULL; EdgeNode*p=edge_table->next->head; EdgeNode*q=active_table->head; while(p->next! =NULL) { EdgeNode*s=(EdgeNode*)malloc(sizeof(EdgeNode)); s->x=p->next->x; s->y_max=p->next->y_max; s->inverse_k=p->next->inverse_k; s->next=NULL; q->next=s; q=s; p=p->next; } returnactive_table; } //从有效边表中删除指定y_max的边结点 voiddelete_edge(ActiveEdgeTable*active_table,inty_max) { EdgeNode*p=active_table->head; while(p->next! =NULL) { EdgeNode*q=p->next; if(q->y_max==y_max) { p->next=q->next; free(q); } else p=p->next; } } //将一个边结点按次序添加到有效边表中 voidadd_edge(ActiveEdgeTable*active_table,EdgeNodeedge) { EdgeNode*t=(EdgeNode*)malloc(sizeof(EdgeNode)); t->x=edge.x; t->y_max=edge.y_max; t->inverse_k=edge.inverse_k; t->next=NULL; EdgeNode*p=active_table->head; while(p->next! =NULL) { EdgeNode*q=p->next; if((edge
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 计算机 图形学 课程设计 有效 填充 算法 实现