参考教材:《计算机图形学基础及应用教程》
ISBN:9787111224907
备注:这里我只是记录和阐述一些在我自己学习图形学过程中遇到的困惑和对已学知识的整理,如果有什么误导性的错误,还请见谅,如果读者愿
意,也可以给我留言向我指出问题。
三个常规直线算法中,传说Bresenham是最快的一个,真的吗?
这里先讨论 Bresenham 的优点在哪,它不要需要处理浮点数,没有乘法运算,有一个常数的乘法运算可以通过左移来实现,与DDA和MidPoint相比,
好像是先进不少,不过这就说是 “最快”,有点不合理,虽然我现在也还不知道是否有比它更优秀的直线算法,但是感觉还是有点偏颇,目前我只说它
是我学习过的直线算法中,确实做到了 “利于硬件实现” 的目标。暂且先这么想。
关键点:
d = d + k;
k = dy / dx;
if d >= 0.5 => 点亮相对于当前像素点右上方的点
if d < 0.5 => 点亮相对于当前像素点正右方的点
上面就这么几步已经可以完成这个算法了,但是说过了要利于硬件实现,所以需要考虑浮点数的去除等。
把 0.5 移动到上面两个判别式符号左边去,变成什么了?对,就是书上的 “令 e = d - 0.5” 这样一来可以让判别式与0作比较。
在我们进行整数处理中,有两个地方可能容易混淆,在将 0.5 × 2 之后我们便可以进行整数运算了,但是这里有个问题好多
书上讲得总是模糊,每次重复查看这个算法的步骤时,我总会觉得,作者实在太烂了,知识水平高,能不能把话也说得稍微
让人容易懂一些?(我觉得应该不只是我觉得图形学上一些算法步骤让人晦涩难懂)。操蛋的国内骗钱教材国内随处可见,
内容浅尝辄止,有些甚至错误连篇,这里我就不多说了。
书上其实给出的是 0.5 × 2 × dx,由于笔者没有明白 dx 是用来干嘛的。问题暂时搁这,待笔者查明真相,悟出真理再回来详
述。上述已经将一些细节问题用笔者自己的语言描述了一遍,下面我们对所有的整数及乘法处理的最终结果列表:
e = d - 0.5;
e = 2 * e * dx (这里 dx 的来历我们暂时不管)
x = START.x;
y = START.y;
dx = END.x - START.x;
dy = END.y - START.y;
e0 = -dx; ( 这里简单说一下 e = d - 0.5,那么当 d 为 0 时 e 的初始值就为 -0.5,那么做整数处理只需让 e × 2 即可,同样置 dx 的来历先不解释)
x++;
e = e + 2dy ( 在前一步将 d - 0.5 当作 e 之后,纵轴的增量表达式由 d = d + k 变化为 e = e + k ( e = d - 0.5 ),又因为之前将所有的 e 变化为 2 * e * dx
以做整数处理,所以这里的过程实际上是这样:e = 2 * dx ( e + k ),才会保持整个表达式在同一个因子作用范围类进行计算。
这里回来说一下关于 dx ,根据:http://en.wikipedia.org/wiki/Bresenham_line,著名维基百科上的诠释,实际后来我也明白了,几乎所有书本
上都假定用户输入的一定是整数,所以自然的 END.x - START.x 所得出的 dx 一定是整数,那么以它作为一个因子再去乘另一个因子(无论是小
数还是整数)其结果都是可以化整的,由于初始化的时候有一个0.5,我们在原有的算法上再多乘一个2,将0.5也化整,这样,整个bresenham
中就完全没有浮点数的运算了。
上述的步骤都有一些很特别的假设,例如,大多数的直线算法在书中所给出的多为 |slope| 小于等于1并且大于0的,所以仍然需要像之前修改该
中点直线算法一样,使其通用于任何两点的输入以及任何斜率。
下面的代码拥有了一定的通用性,但仍然可以在一些方面进行优化,需要说明的是,在笔者的OpenGL - Bresenham 直线算法中,由于融入了
一些看似冗余的步骤,但是我并没有打算删除,因为这些冗余的步骤正是笔者在思考的时候所留下的痕迹,所以如果您正在阅读代码,有不明白
之处,尽管留言询问。当然,这种文章的主要作用是帮助自己梳理算法思路,以及在实现程序上自己思考一些值得改进的地方。
OpenGL - Bresenham 直线算法完整代码如下:
#ifndef GLUT_DISABLE_ATEXIT_HACK
#define GLUT_DISABLE_ATEXIT_HACK
#endif
#include <GL\glut.h>
#include <stdio.h>
#include <math.h>
// 颜色
typedef struct _color
{
float r, g, b;
float alpha;
}ColorRGBA;
// 2D顶点
typedef struct _point
{
float x;
float y;
ColorRGBA color;
}Point2D;
Point2D gStart;
Point2D gEnd;
/*
* Now : Optimizing...
*
* Now this func can deal with line, which abs(slope) >= 1.0
* But it can't do the line, slope <= 0.
*/
void BresenhamLine(Point2D* start, Point2D* end)
{
int x, y;
float error;
float dx, dy;
float deltaError;
bool isSwap = (end->y - start->y) > (end->x - start->x); // |Slope| >= 1.0 solution.
bool isSlopeLE0 = start->y > end->y;
Point2D* START = new Point2D;
Point2D* END = new Point2D;
if( !START || !END )
{
return;
}
if( start->x > end->x )
{
START = end;
END = start;
if( isSwap )
{
START->x = start->y;
START->y = start->x;
END->x = end->y;
END->y = end->x;
}
}
else
{
START->x = start->x;
START->y = start->y;
END->x = end->x;
END->y = end->y;
if( isSwap )
{
START->x = start->y;
START->y = start->x;
END->x = end->y;
END->y = end->x;
}
}
dx = END->x - START->x;
dy = END->y - START->y;
x = START->x;
y = START->y;
error = 0;
deltaError = 2 * dy; // (dy/dx) * 2 * dx
float varForOptimize;
if( isSlopeLE0 )
varForOptimize = (error + 0.5) * 2 * dx;
else
varForOptimize = (error - 0.5) * 2 * dx;
for( int i = 0; i <= dx; i++ )
{
if( isSwap )
::glVertex3d(y, x, 0);
else
::glVertex3d(x, y, 0);
x++;
if( isSlopeLE0 )
{
varForOptimize += deltaError;
if( varForOptimize <= 0 )
{
y--;
varForOptimize += 2 * dx;
}
}
else
{
varForOptimize += deltaError;
if( varForOptimize >= 0 )
{
y++;
varForOptimize -= 2 * dx;
}
}
// Optimizing...
}
delete START;
delete END;
}
void init(void)
{
glClearColor(0.0, 0.0, 0.0, 0.0);
glOrtho(0.0, 500.0, 0.0, 500.0, -500.0, 500.0);
}
void display(void)
{
glClear(GL_COLOR_BUFFER_BIT);
glColor3f(1.0, 1.0, 1.0);
glBegin(GL_POINTS);
::BresenhamLine(&gStart, &gEnd);
glEnd();
glFlush();
}
int main(int argc, char **argv)
{
printf("Set start position & end position :");
scanf("%f%f%f%f", &gStart.x, &gStart.y, &gEnd.x, &gEnd.y);
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_SINGLE|GLUT_RGB);
glutInitWindowSize(800, 600);
glutCreateWindow("OpenGL Line Algorithms");
init();
glutDisplayFunc(display);
glutMainLoop();
return 0;
}
备注:已经修改为适应斜率小于0、大于等于1,任意顶点输入(我承认,我没有考虑到你可能会输入字符以表示顶点位置,例如 “一百 一百 四百 一百五”…)