计算机图形学学习记录(四) —— X扫描线算法与区域填充算法

为方便找代码的朋友直接看代码,先把代码和运行结果贴上:

OpenGL算法主要实现代码

point polypoint[POINTNUM]{ 250,50,550,150,550,400,250,250,100,350,100,100,120,30 };//多边形顶点  
void GlAreaFilled::PolyScanner(void)
{
    /******计算最高点的y坐标(扫描到此结束)****************************************/
    int MaxY = 0;
    int i;
    for (i = 0; i < POINTNUM; i++)
        if (polypoint[i].y > MaxY)
            MaxY = polypoint[i].y;

    /*******初始化AET表***********************************************************/
    AET *pAET = new AET;
    pAET->next = NULL;

    /******初始化NET表************************************************************/
    NET *pNET[1024];
    for (i = 0; i <= MaxY; i++)
    {
        pNET[i] = new NET;
        pNET[i]->next = NULL;
    }
    glClear(GL_COLOR_BUFFER_BIT);        //赋值的窗口显示.    
    glColor3f(0.0, 0.0, 0.0);             //设置直线的颜色红色  
    glBegin(GL_POINTS);
    /******扫描并建立NET表*********************************************************/
    for (i = 0; i <= MaxY; i++)
    {
        for (int j = 0; j < POINTNUM; j++)
            if (polypoint[j].y == i)
            {  //一个点跟前面的一个点形成一条线段,跟后面的点也形成线段     
                if (polypoint[(j - 1 + POINTNUM) % POINTNUM].y > polypoint[j].y)
                {
                    NET *p = new NET;
                    p->x = polypoint[j].x;
                    p->ymax = polypoint[(j - 1 + POINTNUM) % POINTNUM].y;
                    p->dx = (polypoint[(j - 1 + POINTNUM) % POINTNUM].x - polypoint[j].x) / (polypoint[(j - 1 + POINTNUM) % POINTNUM].y - polypoint[j].y);
                    p->next = pNET[i]->next;
                    pNET[i]->next = p;

                }
                if (polypoint[(j + 1 + POINTNUM) % POINTNUM].y > polypoint[j].y)
                {
                    NET *p = new NET;
                    p->x = polypoint[j].x;
                    p->ymax = polypoint[(j + 1 + POINTNUM) % POINTNUM].y;
                    p->dx = (polypoint[(j + 1 + POINTNUM) % POINTNUM].x - polypoint[j].x) / (polypoint[(j + 1 + POINTNUM) % POINTNUM].y - polypoint[j].y);
                    p->next = pNET[i]->next;
                    pNET[i]->next = p;
                }
            }
    }
    /******建立并更新活性边表AET*****************************************************/
    for (i = 0; i <= MaxY; i++)
    {
        //计算新的交点x,更新AET  
        NET *p = pAET->next;
        while (p)
        {
            p->x = p->x + p->dx;
            p = p->next;
        }
        //更新后新AET先排序*************************************************************/  
        //断表排序,不再开辟空间  
        AET *tq = pAET;
        p = pAET->next;
        tq->next = NULL;
        while (p)
        {
            while (tq->next && p->x >= tq->next->x)
                tq = tq->next;
            NET *s = p->next;
            p->next = tq->next;
            tq->next = p;
            p = s;
            tq = pAET;
        }
        //(改进算法)先从AET表中删除ymax==i的结点****************************************/  
        AET *q = pAET;
        p = q->next;
        while (p)
        {
            if (p->ymax == i)
            {
                q->next = p->next;
                delete p;
                p = q->next;
            }
            else
            {
                q = q->next;
                p = q->next;
            }
        }
        //将NET中的新点加入AET,并用插入法按X值递增排序**********************************/  
        p = pNET[i]->next;
        q = pAET;
        while (p)
        {
            while (q->next && p->x >= q->next->x)
                q = q->next;
            NET *s = p->next;
            p->next = q->next;
            q->next = p;
            p = s;
            q = pAET;
        }
        /******配对填充颜色***************************************************************/

        p = pAET->next;
        while (p && p->next)
        {
            for (float j = p->x; j <= p->next->x; j++)
                glVertex2i(static_cast<int>(j), i);
            p = p->next->next;//考虑端点情况  
        }


    }
    glEnd();
    glFlush();
}

void GlAreaFilled::Init(void)
{
    glClearColor(1.0, 1.0, 1.0, 0.0);
    // 窗口颜色设置为白色
    glMatrixMode(GL_PROJECTION);
    gluOrtho2D(0.0, 600.0, 0.0, 450.0);
}

void GlAreaFilled::PolyGLProEnter(int argc, char* argv)
{
    glutInit(&argc, &argv); // 初始化Glut
    glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB); //设置显示模式:单个缓存和使用RGB模型 
    glutInitWindowPosition(50, 100); //设置窗口的顶部和左边位置
    glutInitWindowSize(400, 300); //设置窗口的高度和宽度 
    glutCreateWindow("An Example OpenGL Program"); //创建显示窗口

    Init(); //调用初始化过程  
    glutDisplayFunc(PolyScan); //图形的定义传递给我window.
    glutMainLoop(); //显示所有的图形并等待  
}

运行结果

![运行结果](https://img-blog.csdn.net/2018040310313021?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3poYW90aWFueXU5NTAzMjM=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)

多边形扫描转换

多边形的扫描转换和区域填充这个问题是怎么样在离散的像素集上,表示一个连续的二维图形。

多边形有两种重要的表示形式:顶点表示与点阵表示,具体如下:

计算机图形学学习记录(四) —— X扫描线算法与区域填充算法_第1张图片

顶点表示是用多边形的顶点序列来表示多边形。这种表示直观、几何意义强、占内存少,易于进行几何变换。
但由于它没有明确指出哪些象素在多边形内,故不能直接用于面着色。

点阵表示是用位于多边形内的象素集合来刻画多边形。这种表示丢失了许多几何信息(如边界、顶点等),
但它却是光栅显示系统显示时所需的表示形式。因为需要通过记录每个点的像素具体设置的值来点亮显示器。

我们就可以思考两个问题:

  1. 是如果知道边界,能否求出哪些像素在多边形内?
  2. 是知道多边形内部的像素,反过来如何求多边形的边界?

光栅图形的一个基本问题是把多边形的顶点表示转换为点阵表示。这种转换称为多边形的扫描转换

计算机图形学学习记录(四) —— X扫描线算法与区域填充算法_第2张图片

X - 扫描线算法

X-扫描线算法填充多边形的基本思想是按扫描线顺序,计算扫描线与多边形的相交区间。
再用要求的颜色显示这些区间的像素,即完成填充工作。

区间的端点可以通过计算扫描线与多边形边界线的交点获得。

![X扫描线算法](https://img-blog.csdn.net/20180403084021235?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3poYW90aWFueXU5NTAzMjM=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) 如扫描线y=3与多边形的边界相交于4点:
23437393 ( 2 , 3 ) 、 ( 4 , 3 ) 、 ( 7 , 3 ) 、 ( 9 , 3 ) 。
这四点定义了扫描线从 X=2 X = 2 X=4 X = 4 ,从 X=7 X = 7 X=9 X = 9 两个落在多边形内的区间,该区间内的像素应取填充色。 算法的核心是按X递增顺序排列交点的X坐标序列。由此,可得到X-扫描线算法步骤如下: 1. 确定多边形所占有的最大扫描线数,得到多边形顶点的最小和最大y值( ymin y m i n ymax y m a x )。 2. 从 y=ymin y = y m i n y=ymax y = y m a x ,每次用一条扫描线进行填充。 3. 对一条扫描线填充的过程可分为四个步骤: - 求交:计算扫描线与多边形各边的交点。 - 排序:把所有交点按递增顺序进行排序。 - 交点配对:第一个与第二个,第三个与第四个。 - 区间填色:把这些相交区间内的像素置成不同于背景色的填充色。 当扫描线与多边形顶点相交时,交点的取舍问题( **交点的个数应保证为偶数个**)
![这里写图片描述](https://img-blog.csdn.net/20180403090415916?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3poYW90aWFueXU5NTAzMjM=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) 解决方案为: 1. 若共享顶点的两条边分别落在扫描线的两边,交点只算一个。 2. 若共享顶点的两条边在扫描线的同一边,这时交点作为零个或两个。 **这个算法效率低,为什么?** 关键问题是求交!而求交是很可怕的,求交的计算量是非常大的。

X- 扫描线改进算法

排序、配对、填色总是要的!最理想的算法是不求交!

扫描转换算法重要意义是提出了图形学里两个重要的思想:

  1. 扫描线:当处理图形图像时按一条条扫描线处理。
  2. 增量的思想。

那么求交点的时候能不能也采取增量的方法?每条扫描线的 y y 值都知道,关键是求 x x 的值。 x x 是什么?

我们可以从三个方面来考虑如何改进该算法:

  1. 在处理一条扫描线时,仅对与它相交的多边形的边( 有效边)进行求交运算。
  2. 考虑扫描线的连贯性。即当前扫描线与各边的交点顺序与下一条扫描线与各边的交点顺序很可能相同或非常相似。
  3. 最后考虑多边形的连贯性。即当某条边与当前扫描线相交时,它很可能也与下一条扫描线相交。

为了避免求交运算,需要引进一套特殊的数据结构

  1. 活性边表(AET):把与当前扫描线相交的边称为活性边,并把它们按与扫描线交点x坐标递增的顺序存放在一个链表中。
  2. 结点内容(一个结点在数据结构里可用结构来表示):
    • x x : 当前扫描线与边的交点坐标。
    • x △ x : 从当前扫描线到下一条扫描线间x的增量。
    • ymax y m a x : 该边所交的最高扫描线的坐标值 ymax y m a x
![节点数据结构](https://img-blog.csdn.net/20180403093309934?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3poYW90aWFueXU5NTAzMjM=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) 随着扫描线的移动,扫描线与多边形的交点和上一次交点相关: 设边的直线斜率为 k k :
k=ΔyΔx=yi+1yixi+1xi k = Δ y Δ x = y i + 1 − y i x i + 1 − x i
xi+1xi=1kxi+1=xi+1k x i + 1 − x i = 1 k ⇒ x i + 1 = x i + 1 k
所以 Δx=1k Δ x = 1 k 另外,需要知道一条边何时不再与下一条扫描线相交,以便及时把它从有效边表中删除出去,避免下一步进行无谓的计算。
![节点数据结构](https://img-blog.csdn.net/20180403093309934?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3poYW90aWFueXU5NTAzMjM=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) 其中 x x 为当前扫描线与边的交点, ymax y m a x 是边所在的最大扫描线值,通过它可以知道何时才能“ 抛弃”该边, x △ x 表示从当前扫描线到下一条扫描线之间的 x x 增量即斜率的倒数。 next n e x t 为指向下一条边的指针。 为了方便活性边表的建立与更新,需构造一个新边表(**NET**),用来存放多边形的边的信息,分为4个步骤: 1. 首先构造一个纵向链表,链表的长度为多边形所占有的最大扫描线数,链表的每个结点,称为一个**吊桶**,对应多边形覆盖的每一条扫描线。 2. NET挂在与该边低端 y y 值相同的扫描线桶中。也就是说,存放在该扫描线第一次出现的边。
![NET头结点](https://img-blog.csdn.net/20180403100748422?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3poYW90aWFueXU5NTAzMjM=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) - 该边的 ymax y m a x - 该边较低点的 x x 坐标值 xmin x m i n - 该边的斜率 1/k 1 / k - 指向下一条具有相同较低端 y y 坐标的边的指针 3. 建立新边表,可以拿来给活性边表使用。
![新边表举例](https://img-blog.csdn.net/20180403101244729?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3poYW90aWFueXU5NTAzMjM=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) 4. 在活性边表中加入边,并进行相关的扫描运算。每做一次新的扫描线时,要对已有的边进行三个处理: - 是否被去除掉; - 如果不被去除,第二就要对它的数据进行更新。所谓更新数据就是要更新它的x值,即: x+1k x + 1 k 。 - 看有没有新的边进来,新的边在NET里,可以插入排序插进来。 ### 伪代码描述
void polyfill (polygon, color)
int color; 多边形 polygon;
{ 
    for (各条扫描线i )
    {
        初始化新边表头指针NET[i];
        把ymin = i 的边放进边表NET[i];
    }
    y = 最低扫描线号;
    初始化活性边表AET为空;
    for (各条扫描线i )
    {
        把新边表NET[i] 中的边结点用插入排序法插入AET表,使之按x坐标递增顺序排列;
        遍历AET表,把配对交点区间(左闭右开)上的象素(x,y),用putpixel(x,y,color) 改写象素颜色值;
        遍历AET表,把ymax= i 的结点从AET表中删除,并把ymax>i结点的x值递增x;
        若允许多边形的边自相交,则用冒泡排序法对AET表重新排序;
    }
} /* polyfill */

OpenGL算法主要实现代码

point polypoint[POINTNUM]{ 250,50,550,150,550,400,250,250,100,350,100,100,120,30 };//多边形顶点  
void GlAreaFilled::PolyScanner(void)
{
    /******计算最高点的y坐标(扫描到此结束)****************************************/
    int MaxY = 0;
    int i;
    for (i = 0; i < POINTNUM; i++)
        if (polypoint[i].y > MaxY)
            MaxY = polypoint[i].y;

    /*******初始化AET表***********************************************************/
    AET *pAET = new AET;
    pAET->next = NULL;

    /******初始化NET表************************************************************/
    NET *pNET[1024];
    for (i = 0; i <= MaxY; i++)
    {
        pNET[i] = new NET;
        pNET[i]->next = NULL;
    }
    glClear(GL_COLOR_BUFFER_BIT);        //赋值的窗口显示.    
    glColor3f(0.0, 0.0, 0.0);             //设置直线的颜色红色  
    glBegin(GL_POINTS);
    /******扫描并建立NET表*********************************************************/
    for (i = 0; i <= MaxY; i++)
    {
        for (int j = 0; j < POINTNUM; j++)
            if (polypoint[j].y == i)
            {  //一个点跟前面的一个点形成一条线段,跟后面的点也形成线段     
                if (polypoint[(j - 1 + POINTNUM) % POINTNUM].y > polypoint[j].y)
                {
                    NET *p = new NET;
                    p->x = polypoint[j].x;
                    p->ymax = polypoint[(j - 1 + POINTNUM) % POINTNUM].y;
                    p->dx = (polypoint[(j - 1 + POINTNUM) % POINTNUM].x - polypoint[j].x) / (polypoint[(j - 1 + POINTNUM) % POINTNUM].y - polypoint[j].y);
                    p->next = pNET[i]->next;
                    pNET[i]->next = p;

                }
                if (polypoint[(j + 1 + POINTNUM) % POINTNUM].y > polypoint[j].y)
                {
                    NET *p = new NET;
                    p->x = polypoint[j].x;
                    p->ymax = polypoint[(j + 1 + POINTNUM) % POINTNUM].y;
                    p->dx = (polypoint[(j + 1 + POINTNUM) % POINTNUM].x - polypoint[j].x) / (polypoint[(j + 1 + POINTNUM) % POINTNUM].y - polypoint[j].y);
                    p->next = pNET[i]->next;
                    pNET[i]->next = p;
                }
            }
    }
    /******建立并更新活性边表AET*****************************************************/
    for (i = 0; i <= MaxY; i++)
    {
        //计算新的交点x,更新AET  
        NET *p = pAET->next;
        while (p)
        {
            p->x = p->x + p->dx;
            p = p->next;
        }
        //更新后新AET先排序*************************************************************/  
        //断表排序,不再开辟空间  
        AET *tq = pAET;
        p = pAET->next;
        tq->next = NULL;
        while (p)
        {
            while (tq->next && p->x >= tq->next->x)
                tq = tq->next;
            NET *s = p->next;
            p->next = tq->next;
            tq->next = p;
            p = s;
            tq = pAET;
        }
        //(改进算法)先从AET表中删除ymax==i的结点****************************************/  
        AET *q = pAET;
        p = q->next;
        while (p)
        {
            if (p->ymax == i)
            {
                q->next = p->next;
                delete p;
                p = q->next;
            }
            else
            {
                q = q->next;
                p = q->next;
            }
        }
        //将NET中的新点加入AET,并用插入法按X值递增排序**********************************/  
        p = pNET[i]->next;
        q = pAET;
        while (p)
        {
            while (q->next && p->x >= q->next->x)
                q = q->next;
            NET *s = p->next;
            p->next = q->next;
            q->next = p;
            p = s;
            q = pAET;
        }
        /******配对填充颜色***************************************************************/

        p = pAET->next;
        while (p && p->next)
        {
            for (float j = p->x; j <= p->next->x; j++)
                glVertex2i(static_cast<int>(j), i);
            p = p->next->next;//考虑端点情况  
        }


    }
    glEnd();
    glFlush();
}

void GlAreaFilled::Init(void)
{
    glClearColor(1.0, 1.0, 1.0, 0.0);
    // 窗口颜色设置为白色
    glMatrixMode(GL_PROJECTION);
    gluOrtho2D(0.0, 600.0, 0.0, 450.0);
}

void GlAreaFilled::PolyGLProEnter(int argc, char* argv)
{
    glutInit(&argc, &argv); // 初始化Glut
    glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB); //设置显示模式:单个缓存和使用RGB模型 
    glutInitWindowPosition(50, 100); //设置窗口的顶部和左边位置
    glutInitWindowSize(400, 300); //设置窗口的高度和宽度 
    glutCreateWindow("An Example OpenGL Program"); //创建显示窗口

    Init(); //调用初始化过程  
    glutDisplayFunc(PolyScan); //图形的定义传递给我window.
    glutMainLoop(); //显示所有的图形并等待  
}

运行结果


计算机图形学学习记录(四) —— X扫描线算法与区域填充算法_第3张图片

你可能感兴趣的:(计算机图形学,C++,数据结构)