计算机图形学课程设计-有效边表填充算法的实现

更新时间:2023-11-17 12:03:01 阅读量: 教育文库 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

计算机图形学 课程设计

设计题目 改进的有效边表算法对多边形的填充

学院名称 信息科学与技术学院 专业名称 计算机科学与技术

学生姓名 刘 柯

学生学号 201213030112 任课教师 梅 占 勇

设计(论文)成绩

教务处 制 2015年 9 月 28 日

1

目录

一、设计内容与要求 .................................................................................................................................. 3

1.1 设计题目 .................................................................................................................................. 3 1.2 设计内容 ...................................................................................................................................... 3 1.3 设计目标 ...................................................................................................................................... 3 二、总体设计 .............................................................................................................................................. 3

2.1 多边形的表示 .............................................................................................................................. 3 2.2 x-扫描线算法 .............................................................................................................................. 4 2.3 改进的有效边表算法 .................................................................................................................. 4

2.3.1 改进的有效边表算法 ...................................................................................................... 4 2.3.2 有效边表 .......................................................................................................................... 5 2.3.3 边表 .................................................................................................................................. 6

三、详细设计 .............................................................................................................................................. 8

3.1 改进的有效边表算法的实现 ...................................................................................................... 8 3.2 有效边表算法程序流程图 .......................................................................................................... 9 四、测试结果 .............................................................................................................................................. 9 五、总结 .................................................................................................................................................... 15 六、源代码 ................................................................................................................................................ 15 参考文献 .................................................................................................................................................... 26

2

一、设计内容与要求

1.1 设计题目

用改进的有效边表算法实现多边形的填充

1.2 设计内容

使用OpenGL实现用改进的有效边表算法填充多边形

1.3 设计目标

参照课本上改进的有效边表算法的思想,实现该算法的C语言代码,并用该算法搭配OpenGL以像素点的方式绘制出给定顶点坐标的多边形。

二、总体设计

2.1 多边形的表示

在计算机图形学中,多边形有2种重要的表示方法:顶点表示和点阵表示。 顶点表示用多边形的顶点序列来刻画多边形,这种方法直观、几何意义强,占用内存少,应用普遍,但它没有明确指出哪些像素在多边形内,故不能直接用于面着色。

点阵表示用位于多边形内的像素的集合来刻画多边形。 这种表示法虽然失去了许多重要的几何信息,但便于运用帧缓存表示图形,是面着色所需要的图形表示形式。

大多数图形应用系统采用顶点序列表示多边形,而顶点表示又不能直接用于显示,那么就必须有从多边形的顶点表示到点阵表示的转换,这种转换称为多边形的扫描转

3

换或多边形的填充。即从多边形的顶点信息出发,求出位于其内部的各个像素,并将其颜色值写入帧缓存的相应单元中。

2.2 x-扫描线算法

x-扫描线算法的基本思想是,按照扫描线的顺序,计算扫描线与多边形的相交区间,

再用要求的颜色显示这些区间的像素,即完成填充工作。区间的端点可以通过计算扫描线与多边形边界线的交点获得。根据此原理,x-扫描线算法可以填充凸的、凹的或带有孔的多边形区域。

x-扫描线的算法步骤如下:

(1) 确定多边形顶点的最小和最大y值(ymin和ymax),得到多边形所占有的最大扫描线数。

(2) 从y=ymin到y=ymax,每次用一条扫描线填充。每一条扫描线填充的过程分为4个步骤:

① 求交。计算扫描线与多边形所有边的交点。 ② 排序。把所有交点按x坐标递增的顺序进行排序。

③ 交点配对。配对第一与第二、第三与第四个交点等,每对交点代表一个填充

区间。

④ 区间填色。把这些相交区间内的像素置成不同于背景色的填充色。 x-扫描线算法在处理每条扫描线时,需要与多边形的所有边求交,这样处理效率非常低。因为一条扫描线往往只与少数几条边相交,甚至与整个多边形都不相交。因此,有必要对算法进行改进。

2.3 改进的有效边表算法

2.3.1 改进的有效边表算法

将x-扫描线算法加以改进可以得到改进的有效边表算法,也称y连贯算法。改进可以从三个方面进行:首先,在处理一条扫描线时,仅对与它相交的多边形的边(有效边)求交;其次,利用扫描线的连贯性,考虑到当前扫描线与各边的交点顺序与下一条扫描线与各边的交点顺序很可能相同或非常相似,因此在当前扫描线处理完毕之

4

后,不必为下一条扫描线从头开始构造交点信息;最后,利用多边形的连贯性,认为若某条边与当前扫描线相交,则它很可能也与下一条扫描线相交且其交点与上一次的交点相关。如下图所示,设直线的斜率为k,若y=yi时,x=xi;则当y=yi+1时,有xi+1=xi+1/k。

2.3.2 有效边表

有效边(Active Edge)是指与当前扫描线相交的多边形的边,也称活性边。把有

效边按与扫描线交点x坐标递增的顺序存放在一个链表中,此链表称为有效边表(Active Edge Table, 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)。

5

当y=8时的有效边表如下图所示:

2.3.3 边表

有效边表给出了扫描线和有效边交点坐标的计算方法,但是没有给出新边出现的

位置坐标。为了方便有效边表的建立与更新,就需要构造一个边表(Edge Table, ET),用以存放扫描线上多边形各条边的信息。由于水平边的1/k为∞,并且水平边本身就是扫描线,所以在建立边表时不予考虑。

边表的构造分为4个步骤:

6

① 首先构造一个纵向链表,链表的长度为多边形占有的最大扫描线数,链表的每

个结点,称为一个桶,对应多边形覆盖的每一条扫描线。

② 将每条边的信息装入与该边最小y坐标(ymin)相对应的桶中。也就是说,若某

边的较低端点为ymin,则该边就放在相应的y=ymin的扫描线桶中。

③ 每条边的数据形成一个结点,内容包括该扫描线与该边的初始交点x(即较低

端点的x坐标值),该边最大y坐标值ymax,以及斜率的倒数1/k和下一个边结点的指针next:

④ 同一桶中若干条边按x|ymin由小到大排列,若x|ymin相等,则按1/k由小到大排

序。

从上面可以看出,边表是有效边表的特例,有效边表和边表可以使用同一个数据

结构来表示。

对于多边形P0P1P2P3P4P5P6,它的边表结构如下图所示:

7

三、详细设计

3.1 改进的有效边表算法的实现

改进的有效边表算法具体实现过程如下: ① 初始化边表ET。

为了方便边表的建立,可以定义sort()函数对多边形的边进行排序,保证边表中每个桶中的边是有序的。同时定义一个creat_edge_table()函数,该函数需要多边形的顶点信息作为参数传入,并返回一个边表指针。

② 初始化有效边表AET。从ET表中取出第一个不为空的桶与AET表合并。

为了初始化AET表,可以定义一个init_active_table()函数,该函数传入边表指针作为形参,返回一个有效边表指针。 ③ 从AET表中取出交点进行填充。

填充时设置一个布尔值b(初值为假),令指针从有效边表的第一个结点(也就是扫描线与有效边的交点)开始遍历。每访问一个结点,把b取反一次。若b为真,则把从当前结点的x值开始(设为x1)到下一结点的x值结束(设为x2)的区间[x1, x2]用多边形色填充。填充时为了避免多边形区域扩大化,需要对区间边界进行如下处理:

若x是整数,则按“左闭右开”的原则处理,即左边界上的x(x1)不变,右边界上的x(x2)减1;若x不是整数,左边界上的x(x1)向右取整,右边界上的x(x2)向左取整。 ④ 更新AET表。

设当前AET表中扫描线为y,首先更新扫描线为y=y+1,然后删除AET表中所有ymax=y的边结点;再根据xi+1=xi+1/k计算并修改AET表中各边结点的x坐标,同时合并ET表对应于扫描线y的桶中的边,按次序插入到AET表中,形成新AET表。此步骤满足多边形的“下闭上开”原则。

此过程用到自定义的函数update_active_table()。

⑤ 判断AET表是否为空。若为空,则结束;否则转到③

8

delete_edge()、add_edge()、

3.2 有效边表算法程序流程图

开始输入多边形顶点信息AET是否为空构造边表是结束否ET取出交点进行填充初始化有效边表AET更新AET表

四、测试结果

为了便于观察多边形的填充,本程序将像素点进行放大处理,400 x 300的分辨率投影到20 x 15,并绘制出网格,使用OpenGL画出多边形的边框。使用了Sleep()函数来延时生成像素点,并且每填充一个像素点刷新一次,给人动态直观的感受。

① 在不处理边界的情况下,如下图所示,多边形的区域可能会扩大化。

9

② 对边界进行处理后,结果如下:

10

③ 为了验证改进的有效边表填充算法,现将本程序的像素大小恢复为1:1,按

比例扩大多边形的顶点坐标,测试结果如下:

从上图可以看出填充过后的多边形与原多边形P0P1P2P3P4P5P6的形状一致,

该算法验证通过。

11

④ 测试其他多边形

长方形:

三角形:

12

五角星:

13

从以上结果来看,该算法基本得到完美实现。而程序的执行时间来看,生成边表的时间(包括分配内存、对桶中的结点进行排序等)远比填充像素点的时间要长。因此,该算法的效率虽然很高,但对于表的维护和排序开销太大,它适合软件实现而不适合硬件实现。

14

五、总结

通过本次课程设计,我掌握了多边形的填充算法,了解了OpenGL的运行结构,

熟悉了很多OpenGL的函数,对OpenGL编程也产生的浓厚的兴趣。同时也巩固了对计算机图形学知识的掌握,锻炼了我对手实验的能力,看清了自己的水平,认识到了自己的不足。

在本次课程设计中,存在一些不足之处。比如对边界的处理,课本上的说法模糊

不清,在网上也没有找到相应的解答,我只能根据自己的理解来试着解决;这方面也存在没有及时和老师沟通的原因。从这一点上,我认识到理论和实践的差别,我们应该多实践才能真正掌握理论。

还有就是完全填充后的多边形,边缘有“锯齿”现象,不知道是我程序的原因还

是算法的问题。或许对于多边形的填充算法还应该处理“锯齿”。

六、源代码

//源代码仅包含文件PolygonFilling.cpp

#include #include #include #include

#define EPSILON 0.000001 //最小浮点数

//点结构体 struct Point {

int x; //x坐标 int y; //y坐标 };

//线结构体 struct Line {

Point high_point; //高端点

15

Point low_point; //低端点

int is_active; //是否为有效边,水平边(0),非水平边(1) double inverse_k; //斜率k的倒数 };

//边结点 struct EdgeNode {

double x; //扫描线与边交点的x坐标(边的低端点的x坐标) int y_max; //边的高端点的y坐标ymax double inverse_k; //斜率k的倒数 EdgeNode *next; //下一个边结点的指针 };

//有效边表

struct ActiveEdgeTable {

int y; //扫描线y EdgeNode *head; //边链表的头指针 };

//桶结点

typedef struct Bucket {

int y; //扫描线y EdgeNode *head; //边链表的头指针 Bucket *next; //下一个桶的指针 } EdgeTable;

int compare(Point p1, Point p2);

Line* create_lines(Point points[], int n); Point get_lowest_point(Line lines[], int n); Point get_highest_point(Line lines[], int n); void swap(Line &l1, Line &l2); void sort(Line lines[], int n);

EdgeTable* create_edge_table(Line lines[], int n);

ActiveEdgeTable* init_active_table(EdgeTable *edge_table); void delete_edge(ActiveEdgeTable *active_table, int y_max); void add_edge(ActiveEdgeTable *active_table, EdgeNode edge);

ActiveEdgeTable* update_active_table(ActiveEdgeTable *active_table, EdgeTable *edge_table);

void DrawPolygon(Point points, int n); void DrawGrid(int x, int y);

16

void Fill(Point points[], int n); void Initial(); void Display();

int main(int argc, char* argv[]) {

return 0; }

//比较2个点的高度

int compare(Point p1, Point p2) {

if (p1.y > p2.y) return 1;

else if (p1.y == p2.y) return 0; return -1; }

//由点数组生成线段数组

Line* create_lines(Point points[], int n) {

17

glutInit(&argc, argv);

glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB); glutInitWindowSize(400, 300); glutInitWindowPosition(100, 120); glutCreateWindow(\); glutDisplayFunc(Display); Initial(); glutMainLoop();

Line *lines = (Line*)malloc(n * sizeof(Line)); for (int i = 0; i < n; ++i) {

Point p1 = points[i];

Point p2 = points[(i + 1) % n]; int result = compare(p1, p2); if (result == 0)

lines[i].is_active = 0; lines[i].is_active = 1; else

}

}

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);

return lines;

//获取线数组中最低的端点

Point get_lowest_point(Line lines[], int n) {

Point lowest_point = lines[0].low_point;

for (int i = 1; i < n; ++i) {

Point low_point = lines[i].low_point; if (compare(lowest_point, low_point) > 0) lowest_point = low_point; }

return lowest_point; }

//获取线数组中最高的端点

Point get_highest_point(Line lines[], int n) {

Point highest_point = lines[0].high_point;

for (int i = 1; i < n; ++i) {

Point high_point = lines[i].high_point; if (compare(highest_point, high_point) < 0) highest_point = high_point; }

return highest_point; }

//交换2个Line对象

void swap(Line &l1, Line &l2) {

Line temp = l1; l1 = l2;

18

l2 = temp; }

//对线数组进行排序

void sort(Line lines[], int n) {

//先按低端点的y坐标进行升序排序 for (int i = 0; i < n; ++i) {

int min_index = i;

for (int j = i + 1; j < n; ++j) {

if (lines[j].low_point.y < lines[min_index].low_point.y) min_index = j; }

swap(lines[i], lines[min_index]); }

//再将有序数组按低端点的x坐标升序排列,若x坐标相等,按inverse_k升序 for (int i = 0; i < n; ++i) {

int min_index = i;

for (int j = i + 1; lines[j].low_point.y == lines[i].low_point.y; ++j) {

if (lines[j].low_point.x < lines[min_index].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 < lines[i - 1].inverse_k) swap(lines[i], lines[i - 1]); } } } }

//创建一个边表

19

EdgeTable* create_edge_table(Line lines[], int n) {

20

EdgeTable *edge_table = (EdgeTable*)malloc(sizeof(EdgeTable)); edge_table->head = NULL; edge_table->next = NULL; sort(lines, n);

Point lowest_point = get_lowest_point(lines, n); Point highest_point = get_highest_point(lines, n); EdgeTable *s = edge_table;

for (int i = 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 (int j = 0; j < n; ++j) { }

s->next = bucket; s = bucket;

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;

}

glPointSize(6.0f); //使用红色填充多边形

glColor3f(1.0f, 0.0f, 1.0f); Fill(points, n); glFlush();

参考文献

[1] 陆枫,何云峰. 计算机图形学(第二版). 北京:电子工业出版社,2008.10

26

通过本次课程设计,我掌握了多边形的填充算法,了解了OpenGL的运行结构,熟悉了很多OpenGL的函数,对OpenGL编程也产生的浓厚的兴趣。同时也巩固了学生对计算机图形学知识的掌握,锻炼了我对手实验的能力,看清了自己的水平,认学习识到了自己的不足。 心得 学生(签名): 年 月 日 本人郑重声明所呈交的课程报告是本人在指导教师指导下进行的研究工作及诚信取得的研究成果。据我所知,除了文中特别加以标注的地方外,论文中不包含其他人已经发表或撰写过的研究成果。与我一同工作的同学对本文研究所做的贡献承诺 均已在报告中作了明确的说明并表示谢意。 学生(签名): 任课 教师 评语 成绩评定: 任课教师(签名): 年 月 日 27

}

return edge_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; }

return active_table; }

//从有效边表中删除指定y_max的边结点

void delete_edge(ActiveEdgeTable *active_table, int y_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

21

p = p->next; } }

//将一个边结点按次序添加到有效边表中

void add_edge(ActiveEdgeTable *active_table, EdgeNode edge) {

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.x < q->x) || (edge.x == q->x && edge.inverse_k < q->inverse_k)) {

p->next = t; t->next = q; return; }

p = p->next; }

p->next = t; }

//更新有效边表,并与边表中对应的桶合并

ActiveEdgeTable* update_active_table(ActiveEdgeTable *active_table, EdgeTable *edge_table) {

//删除y=ymax的边

delete_edge(active_table, active_table->y);

//更新边结点的数据 //更新扫描线y ++active_table->y;

EdgeNode *p = active_table->head->next; while (p != NULL) {

p->x += p->inverse_k;

22

p = p->next; }

//找到边表中对应的桶 EdgeTable *q = edge_table;

while ((q = q->next) != NULL && q->y != active_table->y);

//如果找到,则进行合并 if (q != NULL) {

EdgeNode *s = q->head; while ((s = s->next) != NULL) {

add_edge(active_table, *s); } }

return active_table; }

//画出多边形的边框

void DrawPolygon(Point points[], int n) { }

//画出x * y的网格

void DrawGrid(int x, int y) {

glBegin(GL_LINE_LOOP); for (int i = 0; i < n; ++i)

glVertex2i(points[i].x, points[i].y); glEnd();

glBegin(GL_LINES); //横线

for (int i = 0; i <= y; ++i) { } //竖线

for (int i = 0; i <= x; ++i)

23

glVertex2d(0, i); glVertex2d(x, i);

}

{ } glEnd();

glVertex2d(i, 0); glVertex2d(i, y);

//用指定的像素大小填充多边形 void Fill(Point points[], int n) {

Line *lines = create_lines(points, n);

EdgeTable *edge_table = create_edge_table(lines, n);

ActiveEdgeTable *active_table = init_active_table(edge_table); while (active_table->head->next != NULL) {

EdgeNode *p = active_table->head;

int b = -1;

while (p->next != NULL) {

if (b > 0) {

int left = p->x; int right = p->next->x;

//如果不是局部最低点,则进行边界处理

if (!(p->x - p->next->x >= -EPSILON && p->x - p->next->x <= EPSILON)) { }

for (int i = left; i <= right; ++i) {

glBegin(GL_POINTS);

glVertex2d(i, active_table->y); glEnd();

24

//处理左边界

if (!(p->x - left >= -EPSILON && p->x - left <= EPSILON))

left += 1;

//处理右边界

if (p->next->x - right >= -EPSILON && p->next->x - right <= EPSILON)

right -= 1;

}

}

}

glFlush(); Sleep(50);

p = p->next; b = -b;

}

active_table = update_active_table(active_table, edge_table); }

//初始化窗口,x和y指定窗口的坐标大小 void Initial() { }

//窗口的显示回调函数 void Display() {

glClearColor(1.0f, 1.0f, 1.0f, 1.0f); glMatrixMode(GL_PROJECTION); gluOrtho2D(0.0, 20.0, 0.0, 15.0);

//使用当前背景色填充窗口 glClear(GL_COLOR_BUFFER_BIT);

//使用灰色画出网格线

glColor3f(0.75f, 0.75f, 0.75f); DrawGrid(20, 14); glFlush();

//多边形的顶点坐标

Point points[] = { { 3, 1 },{ 6, 5 },{ 8, 1 },{ 12, 9 },{ 7, 8 },{ 3, 12 },{ 1, 7 } }; //计算顶点个数

int n = sizeof(points) / sizeof(Point); //使用黑色画出多边形的边框 glColor3f(0.0f, 0.0f, 0.0f); DrawPolygon(points, n); glFlush(); //指定点大小

25

本文来源:https://www.bwwdw.com/article/je2v.html

Top