像素网格坐标:每个像素区域标识为每个像素点的中心
屏幕网格坐标:每个像素区域由左下角整数网格坐标来指定
直线
基本原则:当斜率绝对值|m|<1时通过 Δ x = > Δ \Delta x=>\Delta Δx=>Δ y,|m|>1时通过 Δ y = > Δ x \Delta y=>\Delta x Δy=>Δx
DDA算法(数值差分分析)
以|m|<1时为例,设置,,
Δ
x
=
1
,
Δ
y
=
m
\Delta x=1,\Delta y=m
Δx=1,Δy=m,则
x
k
+
1
=
x
k
+
Δ
x
=
x
k
+
1
x_{k+1}=x_{k}+\Delta x=x_{k}+1
xk+1=xk+Δx=xk+1
y k + 1 = y k + Δ y = y k + m y_{k+1}=y_{k}+\Delta y=y_{k}+m yk+1=yk+Δy=yk+m
- 优点:消除直线方程中的乘法,比直接使用直线方程快。
- 缺点:
- 浮点增量的连续迭加中取整误差的积累会使长线段所计算的像素位置偏离实际线段。
- 取整操作和浮点运算仍十分耗时
- 可通过将增量m和1/m分离成整数和小数部分而使所有的计算都简化为整数操作来改善。
中点画线法
以0<m<1时为例,设直线方程为
F
(
x
,
y
)
=
a
x
+
b
y
+
c
,
a
=
y
0
−
y
1
,
b
=
x
1
−
x
0
,
c
=
x
0
y
1
−
x
1
y
0
F(x,y)=ax+by+c, a=y_{0}-y_{1}, b=x_{1}-x_{0}, c=x_{0}y{1}-x{1}y{0}
F(x,y)=ax+by+c,a=y0−y1,b=x1−x0,c=x0y1−x1y0
则直线上点满足F(x,y)=0,F<0为直线下方,F>0为直线上方。
由于0<m<1,因此下一个顶点的取值范围为 P 0 ( x + 1 , y ) 或 P 1 ( x + 1 , y + 1 ) P_{0}(x+1, y)或P_{1}(x+1, y+1) P0(x+1,y)或P1(x+1,y+1),二者中点为,二者中点为P*(x+1, y+0.5)
通过判断中点P*(x+1, y+0.5)与直线的位置关系选点。
- 若F(P*)>0,则中点在直线上,选择直线下的点P
- 若F(P*)<0,则中点在直线上,选择直线上的点P‘
在计算F是同样可以采用增量运算:
- 初始时 P ( x 0 , y 0 ) P(x_{0}, y_{0}) P(x0,y0)一定在直线上,因此F第一次增量为a+0.5b
- 若选择直线下方点 P 0 P_{0} P0则下一个中点参数为则下一个中点参数为则下一个中点参数为则下一个中点参数为 F ∗ ( x 0 + 2 , y 0 + 0.5 ) F*(x_{0}+2, y{0}+0.5) F∗(x0+2,y0+0.5),相比此时此时增量为a
- 若选择直线上方点 P 1 P_{1} P1则下一个中点参数为则下一个中点参数为则下一个中点参数为则下一个中点参数为 F ∗ ( x 0 + 2 , y 0 + 1.5 ) F*(x_{0}+2, y{0}+1.5) F∗(x0+2,y0+1.5),相比此时增量为a+b
- 为避免浮点运算同2F代替F避免0.5小数
Bresenham画线算法
设直线方程为 y = m x + b y=mx+b y=mx+b,以0<m<1时为例
Bresenham算法在原理上和中点画线法一致,只不过通过恒等变形优化了计算开销。
同中点生成算法,下一个候选点为 P 1 ( x + 1 , y ) P_{1}(x+1, y) P1(x+1,y)和 P 2 ( x + 1 , y + 1 ) P_{2}(x+1, y+1) P2(x+1,y+1),因此需要判断直线上点 P ∗ ( x ∗ , y ∗ ) P^*(x^*, y^*) P∗(x∗,y∗)距离哪个候选点更近,即比较 d 1 = y ∗ − y 1 , d 2 = y 2 − y ∗ d_{1}=y^*-y_{1}, d_{2}=y_{2}-y^* d1=y∗−y1,d2=y2−y∗的大小。 d 1 − d 2 = 2 y ∗ − y 1 − y 2 = 2 m ( x + 1 ) + 2 b − 2 y − 1 d_{1}-d{2}=2y^*-y_{1}-y_{2}=2m(x+1)+2b-2y-1 d1−d2=2y∗−y1−y2=2m(x+1)+2b−2y−1
Δ
y
,
Δ
x
\Delta y, \Delta x
Δy,Δx为直线段在横、纵轴的投影长度,则:
m
=
Δ
y
Δ
x
,
p
=
Δ
x
(
d
1
−
d
2
)
=
2
Δ
y
x
−
2
Δ
x
y
+
c
,
其
中
c
=
2
Δ
y
+
Δ
x
(
2
b
−
1
)
为
常
数
m=\frac{\Delta y}{\Delta x},p=\Delta x(d_{1}-d_{2})=2\Delta yx-2\Delta xy+c,其中c=2\Delta y+\Delta x(2b-1)为常数
m=ΔxΔy,p=Δx(d1−d2)=2Δyx−2Δxy+c,其中c=2Δy+Δx(2b−1)为常数
当p>0时上方候选点离直线更近,选择
P
2
P_{2}
P2,p<0时选择
P
1
P_{1}
P1。
和中点画线法一样,p也可以通过类继增量计算:
p
k
+
1
−
p
k
=
2
Δ
y
(
x
k
+
1
−
x
k
)
−
2
Δ
x
(
y
k
+
1
−
y
k
)
=
2
Δ
y
−
2
Δ
x
(
y
k
+
1
−
y
k
)
p_{k+1}-p_{k}=2\Delta y(x_{k+1}-x_{k})-2\Delta x(y_{k+1}-y_{k})=2\Delta y-2\Delta x(y_{k+1}-y_{k})
pk+1−pk=2Δy(xk+1−xk)−2Δx(yk+1−yk)=2Δy−2Δx(yk+1−yk)
- 当 p k > 0 p_{k}>0 pk>0时, y k + 1 − y k = 1 , p k + 1 − p k = 2 Δ y − 2 Δ x y_{k+1}-y_{k}=1,p_{k+1}-p_{k}=2\Delta y-2\Delta x yk+1−yk=1,pk+1−pk=2Δy−2Δx
- 当 p k < 0 p_{k}<0 pk<0时, y k + 1 − y k = 0 , p k + 1 − p k = 2 Δ y y_{k+1}-y_{k}=0,p_{k+1}-p_{k}=2\Delta y yk+1−yk=0,pk+1−pk=2Δy
反走样
按照上述方法生成的直线不可避免为一个锯齿形路径。因此需要一些反走样技术提高图元的视觉体验。
- 抗锯齿
- 区间取样:将线元看作矩形,相交的像素区域亮度为被覆盖面积占比*最大亮度值。
- 加权区域采样:通过像素到直线距离,使用掩膜函数如高斯函数计算亮度值。
- 亮度矫正:由于相同像素数目的斜线比水平/竖直线长,因此两个采样点间距更大,看起来更暗。因此按照斜率对线段重新分配亮度,45°直线亮度最高,水平和垂直线亮度最低。
圆和椭圆
中点圆算法
圆具有高度对称性,因此只需绘制第一象限中 θ ∈ [ π / 4 , π / 2 ] \theta\in[\pi/4, \pi/2] θ∈[π/4,π/2]的部分即可通过对称性绘制其余的7个部分。
和Bresenham直线算法类似,定义方程 f ( x , y ) = x 2 + y 2 − r 2 f(x, y)=x^{2}+y^{2}-r^{2} f(x,y)=x2+y2−r2,当f=0时点在圆上,f<0时在圆内部,f>0时在圆外部。
对于 θ ∈ [ π / 4 , π / 2 ] \theta\in[\pi/4, \pi/2] θ∈[π/4,π/2]部分,圆弧的切线斜率 k ∈ [ 0 , π / 4 ] k\in[0, \pi/4] k∈[0,π/4],因此在从 ( 0 , r ) (0,r) (0,r)到 ( r , 0 ) (r,0) (r,0)的顺时针绘制过程中,下一个候选点为 P 1 ( x + 1 , y ) P_{1}(x+1, y) P1(x+1,y)和 P 2 ( x + 1 , y − 1 ) P_{2}(x+1, y-1) P2(x+1,y−1),其中点为 P ∗ ( x + 1 , y − 0.5 ) P^{*}(x+1, y-0.5) P∗(x+1,y−0.5)
- 若 f ( x + 1 , y − 0.5 ) > 0 f(x+1,y-0.5)>0 f(x+1,y−0.5)>0,则中点在圆外,选择圆内点(下方点) P 2 P_{2} P2
- 若 f ( x + 1 , y − 0.5 ) < 0 f(x+1,y-0.5)<0 f(x+1,y−0.5)<0,则中点在圆内,选择圆外点(上方点) P 1 P_{1} P1
这里同样可以采用增量计算,令中点纵坐标为
p
k
=
f
(
x
k
,
y
k
−
0.5
)
p_{k}=f(x_{k},y_{k}-0.5)
pk=f(xk,yk−0.5),则:
Δ
p
=
p
k
+
1
−
p
k
=
x
k
+
1
2
−
x
k
2
+
y
k
+
1
2
−
y
k
2
=
(
x
k
+
1
+
x
k
)
(
x
k
+
1
−
x
k
)
+
(
y
k
+
1
+
y
k
)
(
y
k
+
1
−
y
k
)
\Delta p=p_{k+1}-p_{k}=x_{k+1}^2-x_{k}^2+y_{k+1}^2-y_{k}^2=(x_{k+1}+x_{k})(x_{k+1}-x_{k})+(y_{k+1}+y_{k})(y_{k+1}-y_{k})
Δp=pk+1−pk=xk+12−xk2+yk+12−yk2=(xk+1+xk)(xk+1−xk)+(yk+1+yk)(yk+1−yk)
= 2 x k + 1 + ( y k + 1 + y k ) ( y k + 1 − y k ) =2x_{k}+1+(y_{k+1}+y_{k})(y_{k+1}-y_{k}) =2xk+1+(yk+1+yk)(yk+1−yk)
- 当选择圆内点时,即 p k > 0 时 , y k + 1 = y k − 1 , Δ p = 2 x k − 2 y k + 1 p_{k}>0时,y_{k+1}=y_{k}-1,\Delta p=2x_{k}-2y_{k}+1 pk>0时,yk+1=yk−1,Δp=2xk−2yk+1
- 当选择圆外点时,即 p k < 0 时 , y k + 1 = y k , Δ p = 2 x k + 1 p_{k}<0时,y_{k+1}=y_{k},\Delta p=2x_{k}+1 pk<0时,yk+1=yk,Δp=2xk+1
- 初始时为(0, r)在圆上,中点为(1, r-1/2),因此增量为5/4-r。
改进1
为应对起点的浮点数5/4,采用e=p-1/4作为判断条件, p < 0 ⇔ e < − 0.25 , p > 0 ⇔ e > − 0.25 p<0 \Leftrightarrow e<-0.25, p>0 \Leftrightarrow e>-0.25 p<0⇔e<−0.25,p>0⇔e>−0.25由于p的步长为整数,则: p < 0 ⇔ e < 0 , p > 0 ⇔ e > 0 p<0 \Leftrightarrow e<0, p>0 \Leftrightarrow e>0 p<0⇔e<0,p>0⇔e>0
改进2
由于p为 Δ x , Δ y \Delta x, \Delta y Δx,Δy的线性函数,因此可以使用两个变量 Δ x , Δ y 代 替 Δ p \Delta x, \Delta y代替\Delta p Δx,Δy代替Δp,且 Δ x 初 值 为 3 , Δ y 初 值 为 − 2 r + 5 \Delta x初值为3, \Delta y初值为-2r+5 Δx初值为3,Δy初值为−2r+5
椭圆生成算法
原理和圆类似,不过要注意划分时要找到第一象限内斜率为-1的划分点,利用椭圆性质此点与原点连线斜率为-1,解得 x = a 2 ( a 2 + b 2 ) x=\frac{a^{2}}{\sqrt{(a^{2}+b^{2})}} x=(a2+b2)a2。