Computer Graphics(3)

Draw Line

本节的主要目的是使用Bresenham算法完成基本图形元的绘制。

STEP 1使用Bresenham算法绘制三角形边框

在上一次作业的基础上进行处理。显然若要使用GLFW且仅适用GL_POINTS绘制该三角形边框需要边框上各个点的坐标,这里通过Bresenham算法来获取坐标矩阵。

  • 解题思路
    根据要求,我们需要根据两个点做出其间的线段。具体做法是将连续的无数多个点进行适当的采样变成离散的像素点,这些点的数量足够大,相隔间隙足够小,使得在计算机屏幕上显示时在视觉上时一条连续的直线。
    需要注意的是OpenGL中窗口内点的坐标在【-1,1】内,所以需要额外定义一个normalize函数:
// 假设输入[-500,500]范围内的数据
float normalize(int input) {
    return float(input) / 500;
}
  • Bresenham算法的具体步骤
    假设给定的两点坐标为(x0,y0)和(x1, y1),对应直线的斜率m绝对值小于1(按照x进行递增处理)。
    首先对坐标进行处理,保证以下几个条件:

    这里写图片描述

其中m为直线的斜率,设截距为B,那么可以得到:

这里写图片描述

接下来计算在x增加1后,所得到的y坐标距离离其最近的整数坐标之间的距离:

这里写图片描述

寻找规律:

这里写图片描述

这里写图片描述

至此可以看到可以通过对p符号的判断来确定应该选择upper 或者lower的点。

具体算法不再赘述。

// bresenham算法
// array 为两点间所有点的数组;i是用于计数的变量;length是i的限制量;dx, dy是两点之间x, y坐标的相对值
void bresenham(int array[], int p, int i, int length, int dx, int dy) {
    if (i == length - 1)
        return;
    int pnext;
    if (p <= 0) {
        array[i + 1] = array[i];
        pnext = p + 2 * dy;
    }
    else {
        array[i + 1] = array[i] + 1;
        pnext = p + 2 * dy - 2 * dx;
    }
    bresenham(array, pnext, i + 1, length, dx, dy);
}
  • Bresenham对直线进行分类处理(预处理)
    注意到上一个阶段中我们只考虑了斜率绝对值小于1的情况,显然实际情况需要分类进行讨论。这里定义pre_bresenham函数对我们可以直接获取的数据(两点的坐标)进行预处理。
    pre_bresenham函数原型如下:
void pre_bresenham(int arrx[], int arry[], int first_x, int first_y, int second_x, int second_y, int * length) {}

其中数组arrx与arry用于存储后续计算过程中获取的点的横纵坐标,其初始化在get_triangle_vertices函数中完成;length用于存储两点在x或y方向上的距离,具体该返回哪个值由斜率m决定,同样的初始化在get_triangle_vertices中完成。

对于线段的斜率绝对值小于1的情况,按x递增:

if (fabs(k) <= 1) {
            // 根据情况 调整两点的先后顺序
            if (first_x > second_x) {
                int tmpx = first_x;
                int tmpy = first_y;
                first_x = second_x;
                first_y = second_y;
                second_x = tmpx;
                second_y = tmpy;
            }
            /*注意到下面部分是取斜率绝对值考虑的,所以最后需要对斜率为负的情况进行处理*/
            // 计算length
            int width = second_x - first_x + 1;
            *length = width;
            // 按照x递增,所以点的横坐标获取较为简单
            for (int i = 0; i < width; i++)
                arrx[i] = first_x + i;
            // 将两个端点的纵坐标值存入数组
            arry[0] = first_y;
            arry[width - 1] = second_y;
            // 符号决定变量p
            int p = 2 * dy - dx;
            // 调用bresenham函数进行迭代
            bresenham(arry, p, 0, width, dx, dy);
            // 如果斜率小于0,根据上一阶段的推导,显然y的符号发生变化
            if (second_y < first_y) {
                for (int i = 0; i < width; i++)
                    arry[i] = -arry[i];
            }
        }

对于斜率绝对值大于1的情况,按y进行处理,实现时只需将上述代码片段中的x,y对应部分对调即可;
对于斜率不存在的情况其实现是十分简单的:

if (first_x == second_x) {
        if (first_y > second_y) {
            int tmp = first_y;
            first_y = second_y;
            second_y = tmp;
        }
        int height = second_y - first_y + 1;
        *length = height;
        for (int i = 0; i < height; i++) {
            arrx[i] = first_x;
            arry[i] = first_y + i;
        }
    }
  • 将点的坐标存入数组vertices中
    通过上一次的作业,当我们想要将上面得到的点绘制出来时,可以将其放入vertices数组中,然后在进行绑定、渲染等操作。这里需要我们将arrx和arry数组中的数据一一对应放入vertices数组中(保证从数组头开始每两个不交叉的相邻元素代表一个点的坐标),并将vertices数组中元素的总个数存为slength:
void get_triangle_vertices(float vertices[], int * slength) {
    // 设置三角形的三点
    int x0 = -250, y0 = 0, x1 = 250, y1 = 0, x2 = 250, y2 = 300;
    // arrx 与arry的初始化
    int x1array[1001], y1array[1001], x2array[1001], y2array[1001], x3array[1001], y3array[1001];
    // 注意要将数据normalize
    float fx1array[1001], fx2array[1001], fx3array[1001];
    float fy1array[1001], fy2array[1001], fy3array[1001];
    int length1;
    int length2;
    int length3;
    int count = 0;
    // 分三条边进行计算,一一写入
    pre_bresenham(x1array, y1array, x0, y0, x1, y1, &length1);
    for (int i = 0; i<length1; i++) {
        fx1array[i] = normalize(x1array[i]);
        fy1array[i] = normalize(y1array[i]);
        vertices[count++] = fx1array[i];
        vertices[count++] = fy1array[i];
    }
    // for x1, x2
    ...
    //for x0, x1
    ...
    *slength = 2 * (length1 + length2 + length3);
}
  • 使用GLFW绘制所有点
    方法与上次作业类似。这里将建立shaderProgram的过程单独写作函数unsigned int opengl_setting(),返回shaderProgram的值;将绑定顶点缓冲对象和顶点缓冲数组的过程放入函数void opengl_binding(int VAO, int VBO, int shaderProgram, float* vertices)中,其中VAO与VBO的在main函数中声明。

其余细节需要改动的地方:

着色器的源码设置:

const char *vertexShaderSource = "#version 330 core\n"
"layout (location = 0) in vec2 aPos;\n"   // 位置只用了x,y两个坐标表示
"void main()\n"
"{\n"
"   gl_Position = vec4(aPos.x, aPos.y, 0.0, 1.0);\n"
"}\0";
const char* fragmentShaderSource = "#version 330 core\n"
"out vec4 FragColor;\n"
"void main()\n"
"{\n"
"    FragColor = vec4(1.0f, 1.0f, 1.0f, 0.0f);\n"   // 设置画出的线框颜色为白色
"}\n\0";

以及在绑定时需要注意glVertexAttribPointer函数参数的变化:
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void*)0);

  • 绘制三角形
    gui中选中show_triangle_line时,初始化vertices数组,然后调用三角形的生成函数即可:
if (show_triangle_line) {
                for (int i = 0; i < 10000; i++)
                    vertices[i] = 0;
                get_triangle_vertices(vertices, &slength);
                show_circle = false;
            }

STEP2 使用Bresenham绘制圆

这里使用八分法完成圆的绘制。具体参考了以下博客:
Bresenham八分法绘制圆

思想比较简单,见下图:
这里写图片描述

用类似对直线处理时的方法进行推导,得到:
这里写图片描述

代码实现如下:

// 注意这里直接将所有点写入vertices数组中了
void circle_bresenham(float vertices[],int r, int* length) {
    int x = 0, y = r;
    GLint dy = (int)(r*1.0 / (sqrt(2)));
    int d = 1.25 - r;
    while (dy >= x) {
    //直接normalize
        float fx = normalize(x);
        float fy = normalize(y);
        vertices[++(*length)] = fx;
        vertices[++(*length)] = fy;
        //对剩下七个点用类似的方法赋值
        ...

        if (d < 0) {
            d = d + 2 * x + 3;
        }
        else {
            d = d + 2 * (x - y) + 5;
            y = y-1;
        }
        x++;
    }
}

然后在gui中选中show_circle时调用该函数即可。

STEP3 gui中设置圆半径可以调整

这一步的实现较为简单,依旧参考opengl3的案例,为gui添加一个滑块用于选定半径大小,并将该大小记为circle_bresenham的参数radius:

ImGui::SliderInt("radius", &r, 0, 500);

STEP4 使用三角形光栅转换算法对三角形进行填充

这里选择的是edge_equation方法,即先根据三个点确定一个较小的范围,然后对该范围内的点依次带入三边的标准式Ax+By+C,通过所得值的正负判断该点是否在三角形三边的内侧(这里内侧定义为在三角形内部的一侧)。
要进行这样的处理显然在求标准式时需要保证满足Ax1+By1+C>0的点必然在三角形内部,因此必要情况下所求得的系数需要取反。

void get_line_equation(int x0, int y0, int x1, int y1, float coordiate[]) {
    float k = float(y1 - y0) / float(x1 - x0);
    float b = y1 - k *x1;
    coordiate[0] = k;
    coordiate[1] = -1;
    coordiate[2] = b;
    return;
}
void get_render_triangle(int x0, int y0, int x1, int y1, int x2, int y2, float vertices[], int * length) {
    int maxx = max(x0, (max(x1, x2)));
    int minx = min(x0, (min(x1, x2)));
    int maxy = max(y0, (max(y1, y2)));
    int miny = min(y0, (min(y1, y2)));
    float coordiate1[3];
    float coordiate2[3];
    float coordiate3[3];
    get_line_equation(x0, y0, x1, y1, coordiate1);
    if (coordiate1[0] * x2 - y2 + coordiate1[2] < 0) {
        coordiate1[0] = -coordiate1[0];
        coordiate1[1] = -coordiate1[1];
        coordiate1[2] = -coordiate1[2];
    }
    get_line_equation(x0, y0, x2, y2, coordiate2);
    if (coordiate2[0] * x1 - y1 + coordiate2[2] < 0) {
        coordiate2[0] = -coordiate2[0];
        coordiate2[1] = -coordiate2[1];
        coordiate2[2] = -coordiate2[2];
    }
    get_line_equation(x2, y2, x1, y1, coordiate3);
    if (coordiate3[0] * x0 - y0 + coordiate3[2] < 0) {
        coordiate3[0] = -coordiate3[0];
        coordiate3[1] = -coordiate3[1];
        coordiate3[2] = -coordiate3[2];
    }
    int count = 0;
    cout << "minx" << minx << "    " << "maxx" << maxx << endl;
    cout << "miny" << miny << "    " << "maxy" << maxy << endl;

    for (int i = minx; i <= maxx; i++) {
        for (int j = miny; j <= maxy; j++) {
            if (coordiate1[0] * i - j + coordiate1[2] > 0 && (coordiate2[0] * i - j + coordiate2[2] < 0)
                && (coordiate2[0] * i - j + coordiate2[2] < 0)) {
                vertices[count++] = float(i)/50;
                vertices[count++] = float(j)/50;
                length = length + 1;
                //cout << i << endl << j << endl;
            }
            //cout << "infacet: " << i << "    " << j << endl;
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值