OpenGL-直线的扫描转换

发布于 2026/04/12 技术 总字数:1.1k 阅读时长:3 分钟

这篇文章介绍了三种直线扫描转换算法:DDA算法、中点画线算法和Bresenham算法。 DDA算法通过计算直线起点和终点的坐标差值来确定步数和增量,依次绘制直线上的点。算法实现简单,适用于任意直线,但精度较低。 中点画线算法仅适用于斜率为0或1的直线,通过计算中点和斜率来确定直线上的像素点,效率较高。 Bresenham算法根据直线的斜率和误差项来确定像素点的位置,适用于任意直线,且精度高,但实现稍复杂。

AI摘要 QWEN

这篇文章来介绍直线扫描转换算法

DDA数值微分线段算法

算法简介

数值微分法即DDA法(Digital Differential Analyzer),是一种基于微分方程来生成直线的方法。在计算机图形学中,并没有线段的概念,而是一个个像素点组成了线段。

DDA法生成线段的步骤一般如下:

  1. 有了起始点()和终点();
  2. 比较的大小; steps=中较大者;

算法实现

DDA算法实现如下:

#include <cmath>
#include <iostream>
#include <GL/glut.h>

// 窗口宽度和高度
const int WIDTH = 640;
const int HEIGHT = 480;

void drawDDALine(int x1, int y1, int x2, int y2) {
    float x = x1;
    float y = y1;

    // 计算差值
    int dx = x2 - x1;
    int dy = y2 - y1;

    // 确定步数,取 dx 和 dy 中绝对值较大的那个
    int steps = std::abs(dx) > std::abs(dy) ? std::abs(dx) : std::abs(dy);  //三元表达式

    // 计算每一步的增量
    float xIncrement = (float)dx / steps;
    float yIncrement = (float)dy / steps;

    // 开始绘制点
    glBegin(GL_POINTS);
    glVertex2i((int)round(x), (int)round(y)); // 绘制起点

    for (int k = 0; k < steps; k++) {
        x += xIncrement;
        y += yIncrement;
        // 将浮点坐标四舍五入取整转换为整数像素坐标
		std::cout << (int)round(x) << ", " << (int)round(y)<<"\n";
        glVertex2i((int)round(x), (int)round(y));
    }
    glEnd();
}

// 显示回调函数
void display() {
    drawDDALine(0, 0, 50, 20);
    glFlush();
}

// 初始化 OpenGL 设置
void init() {
    // 设置背景颜色为白色
    glClearColor(1.0f, 1.0f, 1.0f, 1.0f);

    // 设置投影矩阵为 2D 正交投影
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    // 定义可视区域,左下角(0,0),右上角(WIDTH, HEIGHT)
    gluOrtho2D(0.0, WIDTH, 0.0, HEIGHT);
}

int main(int argc, char** argv) {
    // 初始化 GLUT
    glutInit(&argc, argv);

    // 设置显示模式:单缓冲、RGB 颜色模式
    glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);

    // 设置窗口大小和位置
    glutInitWindowSize(WIDTH, HEIGHT);
    glutInitWindowPosition(100, 100);

    // 创建窗口
    glutCreateWindow("DDA算法");

    // 注册回调函数
    glutDisplayFunc(display);

    // 初始化设置
    init();

    // 进入主循环
    glutMainLoop();

    return 0;
}

中点画线算法

算法简介

只考虑当直线的斜率时的情况,假设现在有一条直线,那么第一个点一定是无疑,下一个点的坐标为,坐标要么为要么为。关键在于每次取下一个点时,是取前一个的呢,还是,这时一定是取直线上点最靠近的那个了,而判断取哪个点就用到了中点,我们将中点代入直线中

  1. 如果直线,则取下边的点也就是
  2. 如果直线,则取上边的点也就是

它的实际过程就是这样每次根据前边的点判断下一个点在哪,然后进行打亮,但这样每次判断的时候都得代入直线方程计算太麻烦了,我们将这俩种情况分别代入直线方程中可以找出规律:

  1. 当直线时,经过化解得;
  2. 当直线时,经过化解得;
  3. 初始值

也就是说每次的增量要么为,要么为,那么这样判断的时候就简单多了,因为我们每次只是判断它的正负。所以给等式同时乘2,将其中浮点数0.5化为整数,这样硬件操作时无疑更快了。

算法实现

#include <GL/freeglut.h>
#include <iostream>
#include <cmath>

// 中点画线算法函数 (仅针对斜率 0 <= k <= 1 的情况进行演示,其它象限需类比处理)
void drawLineMidpoint(int x0, int y0, int x1, int y1) {
    int a = y0 - y1;
    int b = x1 - x0;
    int d = 2 * a + b;
    int d1 = 2 * a;
    int d2 = 2 * (a + b);

    int x = x0, y = y0;

    glBegin(GL_POINTS);
    glVertex2i(x, y); // 画起点

    while (x < x1) {
        if (d < 0) {
            x++;
            y++;
            d += d2;
        } else {
            x++;
            d += d1;
        }
        glVertex2i(x, y);
    }
    glEnd();
}

// 渲染回调函数
void display() {
    glClear(GL_COLOR_BUFFER_BIT);
    glColor3f(1.0, 1.0, 1.0); // 设置画笔颜色为白色

    // 调用算法画一条直线 (x0, y0) 到 (x1, y1)
    // 注意:此处的坐标对应屏幕像素坐标
    drawLineMidpoint(50, 50, 450, 300);

    glFlush();
}

// 初始化设置
void init() {
    glClearColor(0.0, 0.0, 0.0, 1.0); // 背景设为黑色
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    // 设置正交投影矩阵,使坐标系与窗口像素对应
    gluOrtho2D(0, 500, 0, 500); 
}

int main(int argc, char** argv) {
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);
    glutInitWindowSize(500, 500);
    glutInitWindowPosition(100, 100);
    glutCreateWindow("Midpoint Line Algorithm - FreeGLUT");

    init();
    glutDisplayFunc(display);
    glutMainLoop();

    return 0;
}

Bresenham算法

算法简介

假设我们需要由这一点,绘画一直线至右下角的另一点,x,y分别代表其水平及垂直坐标,并且。在此我们使用电脑系统常用的坐标系,即坐标值沿轴向右增长,坐标值沿轴向下增长。

因此x及y之值分别向右及向下增加,而两点之水平距离为且垂直距离为。由此得之,该线的斜率必定介乎于之间。而此算法之目的,就是找出在之间,第行相对应的第列,从而得出一像素点,使得该像素点的位置最接近原本的线。

对于由两点所组成之直线,公式如下: 因此,对于每一点的x,其y的值是 因为皆为整数,但并非每一点所对应的皆为整数,故此没有必要去计算每一点x所对应之值。反之由于此线之斜率介乎于之间,故此我们只需要找出当到达那一个数值时,会使上升,若尚未到此值,则不变。至于如何找出相关的值,则需依靠斜率。斜率之计算方法为。由于此值不变,故可于运算前预先计算,减少运算次数。

要实行此算法,我们需计算每一像素点与该线之间的误差。于上述例子中,误差应为每一点中,其相对的像素点之值与该线实际之值的差距。每当的值增加,误差的值就会增加。每当误差的值超出,线就会比较靠近下一个映像点,因此的值便会加,且误差减

算法实现

#include <GL/freeglut.h>
#include <iostream>
#include <cmath>

// 通用 Bresenham 画线算法
void drawLineBresenham(int x0, int y0, int x1, int y1) {
    int dx = abs(x1 - x0);
    int dy = abs(y1 - y0);
    int sx = (x0 < x1) ? 1 : -1; // X 方向步进
    int sy = (y0 < y1) ? 1 : -1; // Y 方向步进
    int err = dx - dy;           // 初始误差项

    glBegin(GL_POINTS);
    while (true) {
        glVertex2i(x0, y0); // 绘制当前点

        if (x0 == x1 && y0 == y1) break; // 到达终点

        int e2 = 2 * err;
        // 判断是否在 X 方向步进
        if (e2 > -dy) {
            err -= dy;
            x0 += sx;
        }
        // 判断是否在 Y 方向步进
        if (e2 < dx) {
            err += dx;
            y0 += sy;
        }
    }
    glEnd();
}

// 渲染回调
void display() {
    glClear(GL_COLOR_BUFFER_BIT);
    glColor3f(0.0f, 0.8f, 0.4f); // 设置一个好看的绿色(类似你图中的图标颜色)

    // 绘制几条不同方向的线来测试算法的健壮性
    drawLineBresenham(50, 50, 450, 400);  // 第一象限
    drawLineBresenham(50, 400, 450, 50);  // 第四象限
    drawLineBresenham(250, 50, 250, 450); // 垂直线
    drawLineBresenham(50, 250, 450, 250); // 水平线

    glFlush();
}

void init() {
    glClearColor(0.1f, 0.1f, 0.1f, 1.0f); // 深色背景
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluOrtho2D(0, 500, 0, 500); // 建立 500x500 的直角坐标系
}

int main(int argc, char** argv) {
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);
    glutInitWindowSize(600, 600);
    glutCreateWindow("Bresenham Line Algorithm");
    init();
    glutDisplayFunc(display);
    glutMainLoop();
    return 0;
}

OpenGL-直线的扫描转换

/posts/7207243b/
作者
biss
发布于
许可协议
CC BY-NC-SA 4.0
OpenGL
订阅 打赏

Comments

OpenGL-直线的扫描转换