本次实验项目
Bresenham画线算法实现缤纷放射线。
功能简单介绍
这是一个OpenGL程序,使用Bresenham算法在窗口中绘制放射状直线。程序初始化OpenGL环境,设置窗口和视图参数,然后进入主循环,在每个周期中清除屏幕,设置随机颜色,并绘制36条从中心点向外辐射的直线,每条直线的颜色随机。Bresenham算法用于高效地在像素网格中确定直线路径上的点,确保直线的精确绘制。
算法介绍
Bresenham算法,全称Bresenham's line algorithm,是一种在栅格系统(如计算机屏幕)上绘制直线的算法。这个算法由Jack E. Bresenham在1962年发表,它特别适用于计算机图形学中,因为它只使用整数运算,避免了浮点数的计算,从而提高了效率和速度。 Bresenham算法的核心思想是将直线的斜率(梯度)表示为一个分数,然后根据这个分数来决定在每一步中是只增加x坐标还是同时增加x和y坐标。算法通过计算一个决策参数来决定每一步的走向。
算法描述
假设我们有一个直线方程 y=mx+b,其中 m 是斜率, b 是y轴截距。如下图

考虑斜率 0<m<1 当我们向右移动一个像素(即x+1)时,我们希望确定 y 坐标是否也需要增加。这可以通过比较直线方程在 x+1 坐标处的 y 值和当前 x 时 y 坐标的差异来决定。
在上面光栅坐标系中,我们以任意点红点为观察对象分析,已知直线方程为 y=mx+b,所以在 的 y 坐标为 y = m(
+ 1) + b。
为方便填充像素点,要确定方程计算的 y 值与竖直方向最近的像素点的距离大小。
= y -
= m(
+ 1) + b -
= (
+1) - y =
+1-m(
+ 1) - b
-
=2m(
+ 1) - 2
+ 2b -1
若 -
>0, y 值靠近
+1,则填充
+1 像素点,反之填充
像素点。循环依次向x方向步进,即可描出直线。基于此,我们对给定的直线两点(x0,y0)与(
,
),令 m =
,
定义决策参数:
=
(
-
) = 2
- 2
+ 2
+ (2b -1)
因为与
-
符号相同,2
+ (2b -1)
为常数,及
= 2
- 2
+ 2
+ (2b -1)
。做差我们得到下一点的决策参数递推式:
-
=2
(
-
) - 2
(
-
)
因为 =
+ 1 ,得
=
+ 2
- 2
(
-
)
其中,( -
)取值为1或0,由
决定,
若 > 0 ,
=
+ 2
- 2
,填充(
+1,
+1);
否则 , =
+ 2
,填充(
+1,
).
对于起始点(x0,y0)的决策参数可由 y=mx+b 与 代入起始点(x0,y0)的 = 2
- 2
+ 2
+ (2b -1)
联立求得:
= 2
-
这样的决策过程确保了算法能够准确地沿着直线路径绘制像素点。从 k=0 重复 -1 次即可画出这(x0,y0)与(
,
)两点间直线 y=mx+b 。
对于其他斜率依法炮制,只需注意初始点和步进方向即可。
代码
#include <windows.h> // 包含Windows API函数
#include <GL/glut.h> // 包含OpenGL和GLUT库函数
#include <math.h> // 包含数学函数,如cos和sin
#define GLUT_DISABLE_ATEXIT_HACK // 禁用GLUT的atexit hack,避免在程序退出时产生警告
// 设置像素点的函数
void setPixel(int x, int y) {
glBegin(GL_POINTS); // 开始绘制点
glVertex2i(x, y); // 指定点的坐标
glEnd(); // 结束绘制
}
// 实现Bresenham直线算法的函数
void lineBresenham(int x0, int y0, int x1, int y1) {
int x, y, dx, dy, s1, s2, p, temp, interchange;
x = x0; // 当前x坐标
y = y0; // 当前y坐标
dx = abs(x1 - x0); // x方向的差值
dy = abs(y1 - y0); // y方向的差值
if (x1 > x0) s1 = 1; else s1 = -1; // x方向的步进值
if (y1 > y0) s2 = 1; else s2 = -1; // y方向的步进值
if (dy > dx) { // 如果dy大于dx,则交换dx和dy,并标记interchange
temp = dx;
dx = dy;
dy = temp;
interchange = 1;
} else interchange = 0;
p = 2 * dy - dx; // 初始化决策参数
for (int i = 1; i <= dx; i += 1) { // 循环dx次
setPixel(x, y); // 绘制当前像素点
if (p >= 0) { // 如果决策参数大于等于0
if (interchange == 0) y += s2; else x += s1; // 根据interchange决定是增加x还是y
p -= 2 * dx; // 更新决策参数
}
if (interchange == 0) x += s1; else y += s2; // 增加x或y
p += 2 * dy; // 更新决策参数
}
}
// 设置随机颜色的函数
void setRandomColor() {
float r = static_cast<float>(rand()) / static_cast<float>(RAND_MAX); // 生成随机红色值
float g = static_cast<float>(rand()) / static_cast<float>(RAND_MAX); // 生成随机绿色值
float b = static_cast<float>(rand()) / static_cast<float>(RAND_MAX); // 生成随机蓝色值
glColor3f(r, g, b); // 设置当前绘图颜色
}
// 绘制放射线的函数
void drawRadiatingLines(int centerX, int centerY, int radius, int numLines) {
const double angleIncrement = 2 * M_PI / numLines; // 每条线之间的角度增量
for (int i = 0; i < numLines; ++i) {
double angle = i * angleIncrement; // 计算当前线的角度
int x1 = static_cast<int>(centerX + radius * cos(angle)); // 计算终点X坐标
int y1 = static_cast<int>(centerY + radius * sin(angle)); // 计算终点Y坐标
setRandomColor(); // 设置随机颜色
lineBresenham(centerX, centerY, x1, y1); // 使用Bresenham算法绘制线段
}
}
// 绘制线段的函数
void lineSegment(void) {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // 清除屏幕和深度缓存区
glPointSize(6); // 设置点的大小
drawRadiatingLines(0, 0, 300, 36); // 绘制放射线
glFlush(); // 强制执行所有OpenGL命令
}
// 初始化OpenGL环境的函数
void init(void) {
glClearColor(0.0, 0.0, 0.0, 0.0); // 设置背景色为黑色
glMatrixMode(GL_PROJECTION); // 选择投影矩阵
gluOrtho2D(-500.0, 500.0, -500.0, 500.0); // 设置正交投影,定义坐标系
}
// 主函数
int main(int argc, char** argv) {
glutInit(&argc, argv); // 初始化GLUT库
glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB); // 设置显示模式为单缓冲和RGB颜色模式
glutInitWindowPosition(500, 500); // 设置窗口位置
glutInitWindowSize(1000, 1000); // 设置窗口大小
glutCreateWindow("Bresenham-OpenGL"); // 创建窗口
init(); // 初始化OpenGL环境
glutDisplayFunc(lineSegment); // 设置显示回调函数
glutMainLoop(); // 进入GLUT主事件循环
return 0;
}
运行结果
上手实践
各位学有余力的先生和女士,如有兴趣的话,不妨参考上面内容,动手绘制自己喜欢的直线吧!祝你成功。