写这篇文章的原因是因为发觉网络上太少关于计算机图形学算法的资料了,所以我希望从我这次完成计算机图形学大作业的例子给一些也正在学的人一些小小的帮助,即使不是些很高深的问题,但我更觉得我需要做的是扫盲。当然我写的也不是教程,只是针对一道题目而讨论。
题目:图元扫描转换算法改进:实现改进的画线算法(DDA或Bresenham),使得线段无论从哪个端点开始画,算法求出的象素点都是相同的(即与方向无关)。
(要求:交算法说明;源程序及详细注释;程序输出每一个象素点坐标)另外一个题目可能没有明确说到但是我们做的时候要按照的规定是:如果开始是起点A,终点B,你的改进算法是应该以B为起点,A为终点,但不能在程序中又把A变为起点,B为终点。
我手头上得到的一个Bresenham算法如下:
Bresenham_line(int x1 ,int y1,int x2 ,int y)
{ int dx,dy,s1,s2,temp,interchange=0,p,i;
float x,y;
dx = abs(x2 – x1); dy = abs(y2 – y1);
s1 = sign(x2 – x1); s2 = sign(y2 – y1); /*决定方向*/
x = x1 + 0.5*s1; y = y1 + 0.5*s2;
if(dy > dx){ /*决定m值*/
temp = dx; dx = dy; dy = temp; /*dx为增长快的边*/
interchange = 1;} /*在2,3,6,7区间*/
p = 2 * dy – dx;
for( i = 1; i <= dx; i++) {
setPixel(int(x), int(y));
if( p>0 ){
if(interchange)
x = x + s1; /*把xi当成yI */
else
y = y +s2;
p = p – 2 * dx;
}
if(interchange) /*当pi<=0,yi不变*/
y = y + s2; /*把yi当成xi*/
else
x = x + s1;
p = p + 2 * dy;
}/*for*/
}/* Bresenham_line */
(因为以上是从PPT上直接copy下来的代码,在编译的时候会因为很多符号问题而产生错误,如果有意想用者请将错误的符号先改正,因为此算法只作原理参考,所以我就不再改正了,呵呵,我还是很懒的)
从上面的算法看,可自己验证出,从(0,0)点画直线到(3,5)点和从(3,5)点画直线到(0,0)中间的像素点坐标值是有区别的,所以上面的Bresenham算法是跟方向有关的。好的,如果明白这里说的那么我就可以正式开始说Bresenham算法的改进了。
因为Bresenham画线算法使用了最小的计算量,是最高效的单步画线算法,所以值得去研究这算法的改进,使得其与画线方向无关。
不过实际上当我们每一次画线肯定会与方向相关,那如何做得像是与方向无关呢?我的解决办法就是对称直线生成算法。这种算法是基于这样一个事实:直线以中点为界,其两边是对称的。因而可以利用这个对称性,对Bresenham算法进行改进,使得每进行一次判断就可以生成相对于直线中点的两个对称点。如此以来,直线就由两端向中间生成。那就是说,当我们知道要画线的任何两点,无论它们那个是起点或者终点,本质上都只是以一个方向去判断所有画线点的生成,所以就不存在与画线方向相关的问题了。
因为OpenGL没怎么学过,没有图形化输出,所以本程序只采用控制台方式输出所有将被描画的像素点位置,以下是源程序代码(C++):
#include<iostream.h>
#include<stdlib.h>
#include<math.h>
void setPixel(int m,int n)
{
cout<<"x="<<m<<"; "<<"y="<<n<<endl;
}
void Bresenham_line(int x1,int y1,int x2,int y2) //x1
为起点
x,y1
为起点
y,x2
为终点
x,y2
为终点
y
{
int dx,dy,half,s1,s2,temp,interchange=0,p,i; //half
为
x
方向中点值
double xa,ya,xb,yb;
dx=abs(x2-x1);
dy=abs(y2-y1);
half=(dx+1)>>1; //
移位,与除
2
效果相同
s1=(x2-x1)/abs(x2-x1); /*
决定方向
*/
s2=(y2-y1)/abs(y2-y1);
xa=x1+0.5*s1; //
根据不同方向
(
符号
s1,s2)
判断生成像素点的位置
xb=x2-0.5*s1;
ya=y1+0.5*s2;
yb=y2-0.5*s2;
if(dy>dx){ /*
决定
m
值
*/
temp=dx; dx=dy; dy=temp; /*dx
为增长快的边
*/
interchange=1;} /*
在
2,3,6,7
区间
*/
p=2*dy-dx;
for(i=0;i<=half;i++) {
setPixel(int(xa),int(ya)); //
输出由起点向中点靠的各点
setPixel(int(xb),int(yb)); //
输出由终点向终点靠的各点
if(p>0){
if(interchange)
{
xa=xa+s1; /*
把
xi
当成
yi*/
xb=xb-s1; //
对称输出
}
else
{
ya=ya+s2;
yb=yb-s2; //
对称输出
}
p=p-2*dx;
}
if(interchange) /*
当
pi<=0
,
yi
不变
*/
{
ya=ya+s2; /*
把
yi
当成
xi*/
yb=yb-s2; //
对称输出
}
else
{
xa=xa+s1;
xb=xb-s1; //
对称输出
}
p=p+2*dy;
}/*for*/
}/* Bresenham_line */
void main()
{
int x1,y1,x2,y2;
cout<<"x1:";
cin>>x1; //
输入起点
x
cout<<"y1:";
cin>>y1; //
输入起点
y
cout<<"x2:";
cin>>x2; //
输入终点
x
cout<<"y2:";
cin>>y2; //
输入终点
y
Bresenham_line(x1,y1,x2,y2);
}
以下是输出结果验证:
(1)
分别输入x1:0 y1:0 x2:3 y2:5 ,得到一下输出:
点是从两端点逐渐靠近中点输出,中点值会输出两次,当然有必要的时候,如重画会加深颜色的情况,可以通过一定的判断语句改成一次输出,但这里的简单实现就不用太在乎最终画一次还是两次,结果一样,效率也基本没有差别,考虑到尽量简单化程序就不再加入太多条件控制语句了。
(2) 分别输入x1:3 y1:5 x2:0 y2:0 ,得到一下输出:
可见,只是连续输出的两个点的前后顺序不同,而所有点的值都相等,所以可以证明该算法与方向无关!