计算机图形学笔记——画直线算法
画线算法主要用来解决如下问题:对于某个直线方程,已知起点重点,计算机如何绘制出这个线段。计算机绘制线段其实就是绘制一系列的像素点。
DDA算法
思路: 对于直线方程
y=kx+b
, 在一个坐标轴以单位长度步进,并计算得到另一坐标轴的步进,之后根据起点+步进得到一个点的坐标,这个点就是需要绘制的点。为了保证绘制的精度,选择步进较大的坐标轴为基础坐标轴,例如每次步进Δx>Δy
, 那么以x轴为单位长度步进的坐标轴。
实现代码:
//四舍五入
inline int round(float a) {return int(a+0.5f)}
void lineDDA(int startX, int startY, int endX, int endY)
{
float dx = endX - startX;
float dy = endY - startY;
float steps = fabs(dx) > fabs(dy) ? fabs(dx) : fabs(dy);
float incX = dx / steps;
float incY = dy / steps;
for (int i = 0; i <= steps; i++)
{
float newX = (float)startX + (float)(i) * incX;
float newY = (float)startY + (float)(i) * incY;
drawPixel(round(newX), round(newY)); //画像素点
}
}
通过代码也可以看到,运算使用了浮点运算,取整,不利于用硬件实现。
Bresenham算法
同DDA算法一样选择步进坐标轴,我们假设步进坐标轴为
x
轴,即斜率 ∣k∣<1
, 因为绘制的像素点坐标必须是整数, 那么对于(xn,yn)
的下一个点(xn+1,yn+1)
,有xn+1=xn+1
, yn+1<1
, 该点绘制的像素坐标会是(xn+1,yn)
, (xn+1,yn+1)
中的一个。
每次绘制下一个点时,我们期望有这样一个变量
pn
, 如果pn>0
下一个点的坐标为 (xn+1,yn+1)
,否则下一个点的坐标为(xn+1,yn)
。
我们期望绘制的像素点离实际的坐标点距离越小越好,那么两个候选的像素坐标到实际坐标的距离分别为:
dupper
= yn+1−yn+1
dlower
= yn+1−yn
对于直线方程
y=kx+b
:
Δd=dlower−dupper=2yn+1−2yn−1=2(kxn+1+b)−2yn−1
其中
xn+1=xn+1
, 推导可得:
Δd=2(kxn−yn)+2k+2b−1
注意:不能再继续用yn=kxn+b
代如上式推导下去了,因为点(xn,yn)
不一定精确地在直线上。
如果
Δd>0
, 那么下个点取(xn+1,yn+1)
会准确一些,反之亦然,但Δd
还不能作为变量pn
使用,因为计算还存在浮点运算和除法。
到这一步的式子中,为了消除
k
这个存在除法运算的变量,我们可以用k=Yend−YstartXend−Xstart=ΔxΔy
代如上式, 然后等式两边同时乘以Δx
可得:
ΔxΔd=2(Δyxn−Δxyn)+2Δy+2bΔx−Δx=2(Δyxn−Δxyn)+c
因为
Δx
在我们的设定中始终大于0, 这样我们就得到了需要的变量pn=ΔxΔd
,计算该变量无需浮点运算和除法。
进一步,我们可以通过
pn
和pn+1
的关系来继续简化运算:
pn+1−pn=2(Δy(xn+1−xn)−Δx(yn+1−yn))=2(Δy−Δx(yn+1−yn))
其中
yn+1
, yn
是我们要话的两个像素点,它们的差为0
或者−1
, 得到:
pn+1={pn+2Δypn+2(Δy−Δx)\mboxpn≤0\mboxpn>0
其中:
p0=2(Δyxn−Δxyn)+c=2(Δyxn−Δx(ΔxΔyx0+b))+c=2Δy−Δx
下面给出斜率
∣k∣<1
的代码:
void lineBresenham(int startX, int startY, int endX, int endY)
{
int x = startX;
int y = startY;
if (x > endX) //保证dx > 0
{
x = endX;
endX = startX;
y = endY;
endY = startY;
}
int dx = endX - x;
int dy = endY - y;
int p = 2*dy - dx;
drawPixel(x, y);
while(x < endX)
{
x++;
if (p > 0)
{
y++;
p += 2 * (dy - dx);
}
else
{
p += 2 * dy;
}
drawPixel(x, y);
}
}
当
∣k∣>1
时,选择y方向的步进即可,对于一些特殊斜率,如0, 1, 无穷大,直接使用特殊处理会更简单。