前几章介绍了几何处理和裁剪变换,接下来的步骤就是光栅化
光栅化是将形式表示的几何图元转换为阵列表示的数据片元的过程,片元中每一个像素对应帧缓冲区中的每一个像素
设直线表达式为 y=mx+b ,输入直线两端点坐标 (x0,y0) 和 (xend,yend) ,可以计算出 m=yend−y0xend−x0 和 b=y0−m⋅x0
DAA是基于微分运算的线段生成算法,其主要计算式便是 δy=mδx :
为了有效的避免了斜率为正无穷时 xend−x0=0 的除零计算,我们将不直接计算m而是直接比较 Δy=|yend−y0| 和 Δx=|xend−x0| 的大小确定步长,计算出步长后每一步从的 (x,y) 更新到 (x+xstep,y+ystep) 并计算取整即可(注意,像素永远是整数点)
下面是DDA算法C语言版本代码:
void lineDDA(int x0, int y0, int xend, int yend){
int steps, k;
float xstep, ystep;
float x = x0, y = y0;
int dx = xend - x0;
int dy = yend - y0;
if (fabs(dx) >= fabs(dy))
steps = dx;
else
steps = dy;
xstep = (float)dx / (float)steps;
ystep = (float)dy / (float)steps;
setPixel(round(x), round(y));
for (k = 0; k < steps; k++) {
x += xstep;
y += ystep;
setPixel(round(x), round(y));
}
}
DDA算法避免的迭代时的乘积运算因此比直接用直线表达式求点坐标效率更高,但是,每一步骤中的浮点操作和取整运算开销仍然较大(体系结构告诉我们整数运算和浮点运算效率可以相差几十倍)
Bresenham画线算法是一种精确而有效的线段生成算法,它运用DDA的思想并通过邻近点的比较避免了浮点操作和取整运算,下面讨论斜率小于1的线段的Bresenham画线算法(斜率大于1只需要类似DDA中的比较、交换即可)
再进一步方便讨论,限定 0≤m≤1 ,我们需要确定的是当前画了点 (xk,yk) 之后下一步要画的点 (xk+1,yk+1) 的位置,由于x是主方向所以取 xk+1=xk+1 毋庸置疑,而x是主方向说明了 ystep≤xstep=1 ,则 yk≤yk+1≤yk+1 ,由于只能去整数点那么 yk+1 就只能是 yk、yk+1 二者其中之一,很显然我们只用选一个离 yk+1 近的就可以了
令 dlower=yk+1−yk=f(xk+1)−yk=m(xk+1)+b−yk>0
令 dupper=(yk+1)−yk+1=(yk+1)−f(xk+1)=(yk+1)−[m(xk+1)+b]>0
则要比较的就是 dlower 和 dupper ,选更小的那个即可,做差有:
那么每次我们可以带入上式计算大于0( dlower 更大更靠近上者) yk+1 选 yk+1 ,否则选 yk ,但是上式计算仍然有乘法计算,我们想避免乘法计算获取更高的效率,那么需要引入决策参数 Δp (Bresenham算法核心),令:
这样仍然存在乘法计算,考虑 pk 的迭代计算有:
p0=2Δy−Δx , pk 决定 yk+1−yk 取0还是1,因此只用一个if语句即可避免每次迭代中的乘法运算(把 2Δy、2Δx 存在临时变量中)
上述即是Bresenham画线算法的思想,下面给出二维全空间的**Bresenham画线算法**C语言源代码:
void line_Bresenham(int x1, int y1, int x2, int y2){
int dx = abs(x2 - x1);
int dy = abs(y2 - y1);
bool morethan_45 = dy > dx;
if (morethan_45) {
swap(x1, y1);
swap(x2, y2);
dx = abs(x2 - x1);
dy = abs(y2 - y1);
}
if (x1 > x2) {
swap(x1, x2);
swap(y1, y2);
}
int ystep = y2 > y1 ? 1 : -1;
int y = y1;
int x = x1;
int twody = 2 * dy;
int twody_minus_twodx = 2 * dy - 2 * dx;
int p = twody - dx;
while (x <= x2) {
if (morethan_45) {
plot_point(y + size, x);
} else {
plot_point(x, y + size);
}
if (p < 0) {
p += twody;
} else {
p += twody_minus_twodx;
y += ystep;
}
x++;
}
}
对称说明:
我们在1.Bresenham画线算法中给出的算法是在0 <= m <= 1的情况下的,所以对于全二维空间的直线我们需要做两次对称。
(1)x轴对称
做x轴对称将m取值范围扩展到|m| <= 1:
其他不变,引入变量ystep表示y的运动方向和步长。
0 <= m <= 1时,ystep = 1;-1 <= m < 0时,ystep = -1。
那么算法的第四步迭代y坐标时将1换成ystep即可。
(2)y=x对称
处理完|m| <= 1情况后,再处理|m| > 1的情况:
任意一条|m| > 1的直线都和一条|m| <= 1的直线关于y=x对称,那么则没必要写一次y方向为主方向的画线算法。直接在输入坐标后将端点坐标做一次y=x对称变换,当作一条|m| <= 1的直线来完成Bresenham画线算法的相应计算。最后在画点时再做一次y=x对称变换得到正确位置即可。
中点画圆算法和Bresenham画线算法一样,通过引入决策参数来消除浮点和乘法运算
把圆划分成8个 18 圆(弧),可以通过做对称变换画出整个圆,下面以一象限内 0<x≤y 的 18 圆(弧)A举例
从 x=0 到 x=y 枚举点,在弧A上x的变化大于y的变换(x是主方向),那么画了 (xk,yk) 之后下一步要画的点 (xk+1,yk+1) 只能取 (xk+1,yk) 或 (xk+1,yk−1)
中点画圆的思想就是:判断如果 yk+1 取 yk 和 yk−1 的中点 yk−12 ,改点在圆外还是圆内
取决策参数 pk=fcircle(xk+1,yk−12)=(xk+1)2+(yk−12)2−r2 ,则
同样有迭代思想求 pk :
注意上述描述的是以原点为中心点的画圆算法,若中心点需要在 (xc,yc) 的话,所有画的点均平移到 (x+xc,y+yc) 即可
中点画椭圆算法和中点画圆算法基本一样,只是需要在一象限分两个区域1、2讨论,令 fellipse=r2yx2+r2xy2−r2yr2x
区域1中:
区域2中:
注意区域2中小于0选 (xk,yk−1) ,否则选 (xk+1,yk−1)
多边形扫描线填充的方法是沿多条平行于x轴的直线 y=c 逐像素扫描,若像素点落在多边形内部则填充为指定颜色
扫面线算法利用以下相邻像素连贯性,提高扫描效率:
扫描线算法的一般步骤为:
有序边表算法就是一种经典的扫描线算法
定义活性边是与当前扫描线相交的边,边结构如下:
typedef struct {
int ymax; //边最大y值,即与相交的最大扫面线号
float x; //当前扫描线与边相交的x坐标
float dx; //边斜率的倒数
Edge *next; //指向下一条边的指针
} Edge;
那么活性边表AEL就是活性边按x递增的顺序构成的链表,和活动边表相关的是当前的扫描线,随着扫描线的移动按照如下的规则维护AEL:
x=x+dx
上述新边的定义是:若某条(非水平)边的下端点是y,那么称之为扫面线y的新边;很容易发现要构造AEL就先要构造定义新边表NET,NEL无序排列因此构造NET枚举一遍边即可
下面给出有序边表算法的算法描述:
y=y+1
ymax=y
的边x=x+dx
更详细的有序边表算法可以查看:http://blog.csdn.net/orbit/article/details/7368996
走样是指:用离散的像素来连续的图形时引起的失真,常见的走样有:
反走样有一下几种策略:
提高分辨率可取但成本高,显示器首先要重新设计(提高1倍的分辨率需要4倍的像素点阵和帧缓存容量)其次图元生成、片元生成等算法开销均要增大
区域采样是把直线段看作具有一定宽度的狭长矩形,当直线段与某象素有交时,求出两者相交区域的面积,根据相交区域的面积,加权或不加权地确定该象素的亮度值,如下图展示:
非加权区域采样需要直接计算相交的三角形或梯形或其他多边形的区域的面积,运算量大(设计乘法运算),一种近似策略是分割像素点为更小的子像素,计算子像素落在直线内部的比例从而得到近似面积
非加权区域采样有一个问题就是像素中靠外面的子像素对近似面积的贡献和靠里面的子像素对近似面积的贡献相同,这样的得到的反走样图像不是很平顺,一般采用加权区域采样
我们可以仿照图像处理中滤波器的思想来设计反走样立方体滤波器以及圆锥体滤波器等等来确定权重
直线反走样算法中应用最广泛的是Wu直线反走样算法,Wu反走样的算法和Bresenham画线算法思想很类似,同样是在比较 dupper 和 dlower 大小:
设背景色是 C1 ,线条颜色是 C2 ,那么如图的H点和L点的颜色分别是 C1dupper+C2dlowerdupper+dlower、C1dlower+C2dupperdupper+dlower
对于黑线白背景我们用RGBA颜色的不透明度alpha表示灰度颜色,且栅格距离为1,则有H、L点不透明度分别为 dlower、dupper
因此Wu反走样计算十分简单,直观上也能理解:线条离H点更近因此H点有更大的不透明度( dlower ),L点反之;当线条过HL中点时H、L两个像素点的颜色应该具有同样的不透明度,符合常理
dlower、dupper 的计算我们通过维护error——直线上坐标与画点坐标偏差来完成:
当0 < error < 0.5时,y(xk+1) > yk 且主点是(xk+1, yk)在下,dupper=1 - error,dlower=error,即主点不透明度1 - error,副点不透明度error。
当-0.5 <= error <= 0时,y(xk+1) < yk 且主点是(xk+1, yk)在上,dupper=1 + error,dlower=-error,即主点不透明度1 + error,副点不透明度-error。
Bresenham画线算法稍加修改即可得到Wu直线反走样算法,略
多边形填充区域边界反走样算法和直线反走样算法类似,只是它只考虑多边形外部边界的反走样,内部均填充为多边形指定颜色
或者直接按直线反走样算法,然后做扫描填充时不考虑边界像素点,只按最大亮度/不透明度填充内部像素点即可