【计算机图形学 OpenGL】活性表算法的三角性光栅化(多边形的扫描转换算法)

        学校计算机图形学的课程实验,第一次尝试OpenGL,实现大概花了8个小时(主要花在找各种bug上了)

作业需求:

  1. 三角形区域的扫描转换。设计函数ScanTriangle(int x1, int y1, int x2, int y2, int x3, int y3, int c1[], int c2[], int c3[])能在指定窗口中对由三个顶点(x1, y1), (x2, y2), (x3, y3)构成的三角形进行扫描转换,并按照如下颜色计算方法,将三角形区域的像素绘制出来。
  1. 数组c1, c2, c3分别存放顶点(x1, y1), (x2, y2), (x3, y3)的RGB颜色, 数组中每个颜色的取值再0-255之间;
  2. 三角形边上的颜色用两个短点的颜色作线性插值得到;
  3. 对与三角形内部点p=(px, py),计算直线y = py与三角形的两条边的交点i1, i2, p的颜色由 i1, i2的颜色线性插值得到
  4. 可以采用简化的活性表算法对三角形进行光栅化。

实现效果:

设计思路

        因为本次实验题目要求是三角形的光栅化,所以一些循环次数和点的个数限制成3个,修改成n个变成正常的多边形扫描转换也是可以的。

         首先定义一些基础的数据结构,例如点、边、颜色的struct

struct Color3
{
    float r;
    float g;
    float b;
    Color3(float r = 0, float g = 0, float b = 0)
    {
        this->r = r;
        this->g = g;
        this->b = b;
    }
};
struct Vector2
{
    int x;
    int y;
    Vector2(int _initX = 0, int _initY = 0)
    {
        x = _initX;
        y = _initY;
    }
    Color3* color = nullptr;
    
};
//三角形的边
Vector2 trianglePoints[3];
Color3 triangleColor[3];

        然后是边表的结构,在多边形扫描转换算法中,一个边的数据结构存了4个信息ymax,x,dx,*next

typedef struct XET
{
    float ymax;
    float x;
    float dx;
    
    XET* next;

    XET(float _ymax,float _x,float _dx)
    {
        x = _x;
        dx = _dx;
        ymax = _ymax;
        next = nullptr;
    }
    XET():XET(0,0,0) {};

    Vector2* pos1 = nullptr;
    Vector2* pos2 = nullptr;
};

        这里下边用到了Vector2的指针,是为了方便记录两个点的位置,方便后面的计算颜色的线性插值(不影响活性边表算法的光栅化过程)除了数据结构中的四个成员变量,边表存储还利用了桶排序的思想存储了其他数据,下标为ymin

const int YMIN = 0;
const int YMAX = 600;

//ET 有序边表(ET)-桶分类,窗口像素600*600,第一个指针是空的头节点,不使用

XET ET[YMAX];
XET* AET = nullptr;

        首先是静态边表的建立,静态边表只会在算法过程中执行一次,利用桶的思想,将对应的数据添加到对应数组下表即可。

void RegisterET()
{
    //min(trianglePoints[0].y,trianglePoints[1].y)
    //构建静态边表,遍历所有点,这里直接从第二个点开始
    for (int i = 1; i < 3; i++)
    {
        //选出左下角的那个点
        Vector2* minPos = nullptr;
        Vector2* maxPos = nullptr;
        if (trianglePoints[i - 1].y <= trianglePoints[i].y)
        {
            minPos = &trianglePoints[i - 1];
            maxPos = &trianglePoints[i];
        }
        else
        {
            minPos = &trianglePoints[i];
            maxPos = &trianglePoints[i - 1];
        }

        XET* et = new XET(maxPos->y, minPos->x, float(maxPos->x - minPos->x) / (maxPos->y - minPos->y));
        et->next = nullptr;

        XET* thisPtr = &ET[minPos->y];
        //比较x和dx,递增插入
        while (thisPtr->next != nullptr)
        {
            if (thisPtr->next->x > et->x)
                break;
            else if (thisPtr->next->x == et->x && thisPtr->next->dx > et->dx)
                break;
            thisPtr = thisPtr->next;
        }
        //插入
        et->next = thisPtr->next;
        thisPtr->next = et;
        //关联节点
        et->pos1 = minPos;
        et->pos2 = maxPos;

        //首位闭环
        if (i == 2)
        {
            //选出左下角的那个点
            Vector2* minPos = nullptr;
            Vector2* maxPos = nullptr;
            if (trianglePoints[0].y <= trianglePoints[i].y)
            {
                minPos = &trianglePoints[0];
                maxPos = &trianglePoints[i];
            }
            else
            {
                minPos = &trianglePoints[i];
                maxPos = &trianglePoints[0];
            }

            XET* et = new XET(maxPos->y, minPos->x, float(maxPos->x - minPos->x) / (maxPos->y - minPos->y));
            et->next = nullptr;

            XET* thisPtr = &ET[minPos->y];
            //比较x和dx,递增插入
            while (thisPtr->next != nullptr)
            {
                if (thisPtr->next->x > et->x)
                    break;
                else if (thisPtr->next->x == et->x && thisPtr->next->dx > et->dx)
                    break;

                thisPtr = thisPtr->next;
            }
            //插入
            et->next = thisPtr->next;
            thisPtr->next = et;

            et->pos1 = minPos;
            et->pos2 = maxPos;
        }
    }
    
}

        接着就是活性边表的建立了,y=0到最高点y轴以此遍历循环,动态更新活性边表中AET的值,虽然AET中和静态的ET用的是同一个struct,但是里面的x还是不同的。一个是xmin,一个是当前轴的currx,这里可以用相加dx计算即可(这就是图形学中的利用前后数据的连贯性优化计算的方法了)。代码流程如下:

/// <summary>
/// 三角形光栅化
/// </summary>
void RasterizationTriangle()
{
    // attributes
    glClearColor(1, 1, 1, 1);
    //设置绘制窗口颜色为白色
    glClearColor(1.0,1.0, 1.0,1.0);
    glClear(GL_COLOR_BUFFER_BIT);
    glColor3f(1, 0, 0);

    // set up viewing
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluOrtho2D(0.0, 600.0, 0.0, 600.0);
    glMatrixMode(GL_MODELVIEW);

    glBegin(GL_POINTS);
    //glColor3f(1, 0, 0);

    int count = 0;
    //AET扫描
    for (int y = YMIN; y < YMAX; y++)
    {
        //更新AET中的x值
        if (AET != nullptr)
        {
            XET* headET = new XET();
            headET->next = AET;
            XET* p1 = headET;
            while (p1->next != nullptr)
            {
                p1->next->x += p1->next->dx; //更新AET中的x值

                //如果已经超出了扫描线
                if (p1->next->ymax < y)
                {
                    auto gc = p1->next;
                    p1->next = p1->next->next;
                    //标记该节点应该被抹除
                    if (gc == AET)
                    {
                        AET = AET->next;
                    }
                    delete gc;
                        
                }
                p1 = p1->next; //步进
                if (p1 == nullptr)
                    break;
            }
            delete headET;
        }
        
        //将ET[y]加入AET,并排序
        if (ET[y].next != nullptr)
        {
            XET* p2 = ET[y].next;
            //遍历这里的静态边表
            while (p2 != nullptr)
            {
                XET* et = new XET(*p2);
                //这里深复制完要把next的东西抹掉
                et->next = nullptr;
                //插入活性边表
                XET* thisPtr = AET;
                //比较x和dx,递增插入
                while (thisPtr != nullptr && thisPtr->next != nullptr)
                {
                    //找到第一个x大于它的值,保证升序
                    if (thisPtr->next->x > et->x)
                        break;
                    else if (thisPtr->next->x == et->x && thisPtr->next->dx > et->dx)
                        break;
                    thisPtr = thisPtr->next;
                }
                //插入
                if (thisPtr == nullptr)
                {
                    AET = thisPtr = et;
                }
                else
                {
                    et->next = thisPtr->next;
                    thisPtr->next = et;
                }
                
                p2 = p2->next;

            }
        }

        //根据AET填充区间
        if (AET != nullptr)
        {
            XET* p1 = AET;
            while (p1 != nullptr && p1->next != nullptr)
            {
                for (int x = p1->x; x <= p1->next->x; x++)
                {
                    Color3 c = GetPixelColor(x, y, p1,p1->next);
                    glColor3f(c.r, c.g, c.b);
                    //glColor3f(1,0, 0);
                    GLfloat p[3] = { x, y, 0.0 };
                    glVertex3fv(p);
                    count++;
                }
                p1 = p1->next;
                p1 = p1->next;
            }
            
        }
    }
    std::cout << "像素点个数: " << count << "\n";

    glEnd();
    glFlush();
}

        最后是颜色插值计算,这里根据y轴在上下两个点之间计算出比例t(0-1),x轴方向也是同理,然后再lerp(a,b) = (1-t)* a + t * b的思想的思想就可以了,不过写出来挺麻烦的(好想像C#一下直接Vector3.Lerp啊,自己写确实麻烦)

Color3 GetPixelColor(int x, int y,const XET* left,const XET* right)
{
    
    //计算插值
    Vector2* ymax = left->pos1->y > left->pos2->y ? left->pos1 : left->pos2;
    Vector2* ymin = left->pos1->y <= left->pos2->y ? left->pos1 : left->pos2;
    float t = float(ymax->y - y) / (ymax->y - ymin->y);
    Color3 leftC = Color3(ymax->color->r * (1 - t) + ymin->color->r * t,
        ymax->color->g * (1 - t) + ymin->color->g * t,
        ymax->color->b * (1 - t) + ymin->color->b * t);

    ymax = right->pos1->y > right->pos2->y ? right->pos1 : right->pos2;
    ymin = right->pos1->y <= right->pos2->y ? right->pos1 : right->pos2;
    t = float(ymax->y - y) / (ymax->y - ymin->y);
    Color3 rightC = Color3(ymax->color->r * (1 - t) + ymin->color->r * t,
        ymax->color->g * (1 - t) + ymin->color->g * t,
        ymax->color->b * (1 - t) + ymin->color->b * t);

    if (left->x == right->x)
        return rightC;

    t = float(right->x - x) / (right->x - left->x);

    return Color3(rightC.r * (1 - t) + leftC.r * t,
        rightC.g * (1 - t) + leftC.g * t,
        rightC.b * (1 - t) + leftC.b * t);
}

        一个彩色渐变三角形就实现了.

完整代码

        已知缺陷:代码无法处理斜率为0或者正无穷的情况(没做除0的特殊判断),可以自行修改一下,受限于实验时间,有些地方执行效率还待优化。

#include <GL/glut.h>
#include<queue>
#include <iostream>
#include<cmath>
//1 1 255 500 500 100 255 0 0 0 0 255 0 255 0
const int YMIN = 0;
const int YMAX = 600;
struct Color3
{
    float r;
    float g;
    float b;
    Color3(float r = 0, float g = 0, float b = 0)
    {
        this->r = r;
        this->g = g;
        this->b = b;
    }
};
struct Vector2
{
    int x;
    int y;
    Vector2(int _initX = 0, int _initY = 0)
    {
        x = _initX;
        y = _initY;
    }
    Color3* color = nullptr;
    
};

#pragma region T3
//简易活性边表实现
typedef struct XET
{
    float ymax;
    float x;
    float dx;
    
    XET* next;

    XET(float _ymax,float _x,float _dx)
    {
        x = _x;
        dx = _dx;
        ymax = _ymax;
        next = nullptr;
    }
    XET():XET(0,0,0) {};

    Vector2* pos1 = nullptr;
    Vector2* pos2 = nullptr;
};
//ET 有序边表(ET)-桶分类,窗口像素600*600,第一个指针是空的头节点,不使用
XET ET[YMAX];
XET* AET = nullptr;

Vector2 trianglePoints[3];
Color3 triangleColor[3];


///ReadInputPosition
void InPutPosition()
{
    std::cout << "三角形位置输入(x,y) * 3:\n";
    std::cout << "可用测试数据: 1 1 255 500 500 100\n";
    std::cin >> trianglePoints[0].x
        >> trianglePoints[0].y
        >> trianglePoints[1].x
        >> trianglePoints[1].y
        >> trianglePoints[2].x
        >> trianglePoints[2].y;

    for (int i = 0; i < 3; i++)
    {
        trianglePoints[i].x = std::max(0, trianglePoints[i].x);
        trianglePoints[i].x = std::min(600, trianglePoints[i].x);
        trianglePoints[i].y = std::max(0, trianglePoints[i].y);
        trianglePoints[i].y = std::min(600, trianglePoints[i].y);

    }
}
///ReadInputPosition
void InPutColor()
{
    std::cout << "三角形颜色输入(r,g,b) * 3 (0 - 255):\n";
    std::cout << "可用测试数据: 255 0 0 0 0 255 0 255 0\n";
    std::cin >> triangleColor[0].r
        >> triangleColor[0].g
        >> triangleColor[0].b
        >> triangleColor[1].r
        >> triangleColor[1].g
        >> triangleColor[1].b
        >> triangleColor[2].r
        >> triangleColor[2].g
        >> triangleColor[2].b;

    for (int i = 0; i < 3; i++)
    {
        triangleColor[i].r = std::min(255.0f, triangleColor[i].r) / 255.0;
        triangleColor[i].g = std::min(255.0f, triangleColor[i].g) / 255.0;
        triangleColor[i].b = std::min(255.0f, triangleColor[i].b) / 255.0;
        triangleColor[i].r = std::max(0.0f, triangleColor[i].r);
        triangleColor[i].g = std::max(0.0f, triangleColor[i].g);
        triangleColor[i].b = std::max(0.0f, triangleColor[i].b);

        trianglePoints[i].color = &triangleColor[i];
    }
}

void RegisterET()
{
    //min(trianglePoints[0].y,trianglePoints[1].y)
    //构建静态边表,遍历所有点,这里直接从第二个点开始
    for (int i = 1; i < 3; i++)
    {
        //选出左下角的那个点
        Vector2* minPos = nullptr;
        Vector2* maxPos = nullptr;
        if (trianglePoints[i - 1].y <= trianglePoints[i].y)
        {
            minPos = &trianglePoints[i - 1];
            maxPos = &trianglePoints[i];
        }
        else
        {
            minPos = &trianglePoints[i];
            maxPos = &trianglePoints[i - 1];
        }

        XET* et = new XET(maxPos->y, minPos->x, float(maxPos->x - minPos->x) / (maxPos->y - minPos->y));
        et->next = nullptr;

        XET* thisPtr = &ET[minPos->y];
        //比较x和dx,递增插入
        while (thisPtr->next != nullptr)
        {
            if (thisPtr->next->x > et->x)
                break;
            else if (thisPtr->next->x == et->x && thisPtr->next->dx > et->dx)
                break;
            thisPtr = thisPtr->next;
        }
        //插入
        et->next = thisPtr->next;
        thisPtr->next = et;
        //关联节点
        et->pos1 = minPos;
        et->pos2 = maxPos;

        //首位闭环
        if (i == 2)
        {
            //选出左下角的那个点
            Vector2* minPos = nullptr;
            Vector2* maxPos = nullptr;
            if (trianglePoints[0].y <= trianglePoints[i].y)
            {
                minPos = &trianglePoints[0];
                maxPos = &trianglePoints[i];
            }
            else
            {
                minPos = &trianglePoints[i];
                maxPos = &trianglePoints[0];
            }

            XET* et = new XET(maxPos->y, minPos->x, float(maxPos->x - minPos->x) / (maxPos->y - minPos->y));
            et->next = nullptr;

            XET* thisPtr = &ET[minPos->y];
            //比较x和dx,递增插入
            while (thisPtr->next != nullptr)
            {
                if (thisPtr->next->x > et->x)
                    break;
                else if (thisPtr->next->x == et->x && thisPtr->next->dx > et->dx)
                    break;

                thisPtr = thisPtr->next;
            }
            //插入
            et->next = thisPtr->next;
            thisPtr->next = et;

            et->pos1 = minPos;
            et->pos2 = maxPos;
        }
    }
    
}
Color3 GetPixelColor(int x, int y,const XET* left,const XET* right)
{
    
    //计算插值
    Vector2* ymax = left->pos1->y > left->pos2->y ? left->pos1 : left->pos2;
    Vector2* ymin = left->pos1->y <= left->pos2->y ? left->pos1 : left->pos2;
    float t = float(ymax->y - y) / (ymax->y - ymin->y);
    Color3 leftC = Color3(ymax->color->r * (1 - t) + ymin->color->r * t,
        ymax->color->g * (1 - t) + ymin->color->g * t,
        ymax->color->b * (1 - t) + ymin->color->b * t);

    ymax = right->pos1->y > right->pos2->y ? right->pos1 : right->pos2;
    ymin = right->pos1->y <= right->pos2->y ? right->pos1 : right->pos2;
    t = float(ymax->y - y) / (ymax->y - ymin->y);
    Color3 rightC = Color3(ymax->color->r * (1 - t) + ymin->color->r * t,
        ymax->color->g * (1 - t) + ymin->color->g * t,
        ymax->color->b * (1 - t) + ymin->color->b * t);

    if (left->x == right->x)
        return rightC;

    t = float(right->x - x) / (right->x - left->x);

    return Color3(rightC.r * (1 - t) + leftC.r * t,
        rightC.g * (1 - t) + leftC.g * t,
        rightC.b * (1 - t) + leftC.b * t);
}

/// <summary>
/// 三角形光栅化
/// </summary>
void RasterizationTriangle()
{
    // attributes
    glClearColor(1, 1, 1, 1);
    //设置绘制窗口颜色为白色
    glClearColor(1.0,1.0, 1.0,1.0);
    glClear(GL_COLOR_BUFFER_BIT);
    glColor3f(1, 0, 0);

    // set up viewing
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluOrtho2D(0.0, 600.0, 0.0, 600.0);
    glMatrixMode(GL_MODELVIEW);

    glBegin(GL_POINTS);
    //glColor3f(1, 0, 0);

    int count = 0;
    //AET扫描
    for (int y = YMIN; y < YMAX; y++)
    {
        //更新AET中的x值
        if (AET != nullptr)
        {
            XET* headET = new XET();
            headET->next = AET;
            XET* p1 = headET;
            while (p1->next != nullptr)
            {
                p1->next->x += p1->next->dx; //更新AET中的x值

                //如果已经超出了扫描线
                if (p1->next->ymax < y)
                {
                    auto gc = p1->next;
                    p1->next = p1->next->next;
                    //标记该节点应该被抹除
                    if (gc == AET)
                    {
                        AET = AET->next;
                    }
                    delete gc;
                        
                }
                p1 = p1->next; //步进
                if (p1 == nullptr)
                    break;
            }
            delete headET;
        }
        
        //将ET[y]加入AET,并排序
        if (ET[y].next != nullptr)
        {
            XET* p2 = ET[y].next;
            //遍历这里的静态边表
            while (p2 != nullptr)
            {
                XET* et = new XET(*p2);
                //这里深复制完要把next的东西抹掉
                et->next = nullptr;
                //插入活性边表
                XET* thisPtr = AET;
                //比较x和dx,递增插入
                while (thisPtr != nullptr && thisPtr->next != nullptr)
                {
                    //找到第一个x大于它的值,保证升序
                    if (thisPtr->next->x > et->x)
                        break;
                    else if (thisPtr->next->x == et->x && thisPtr->next->dx > et->dx)
                        break;
                    thisPtr = thisPtr->next;
                }
                //插入
                if (thisPtr == nullptr)
                {
                    AET = thisPtr = et;
                }
                else
                {
                    et->next = thisPtr->next;
                    thisPtr->next = et;
                }
                
                p2 = p2->next;

            }
        }

        //根据AET填充区间
        if (AET != nullptr)
        {
            XET* p1 = AET;
            while (p1 != nullptr && p1->next != nullptr)
            {
                for (int x = p1->x; x <= p1->next->x; x++)
                {
                    Color3 c = GetPixelColor(x, y, p1,p1->next);
                    glColor3f(c.r, c.g, c.b);
                    //glColor3f(1,0, 0);
                    GLfloat p[3] = { x, y, 0.0 };
                    glVertex3fv(p);
                    count++;
                }
                p1 = p1->next;
                p1 = p1->next;
            }
            
        }
    }
    std::cout << "像素点个数: " << count << "\n";

    glEnd();
    glFlush();
}

#pragma endregion

void  main(int argc, char** argv)
{
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);
    glutInitWindowSize(600, 600);
    glutInitWindowPosition(50, 50);
    glutCreateWindow("Triangle");

    InPutPosition();
    InPutColor();
    RegisterET();

    glutDisplayFunc(RasterizationTriangle);

    glutMainLoop();
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值