圖形是怎么生成的?
視頻控制器通過訪問幀緩存來刷新屏幕
幀緩存中的保存的是點陣數據,而我們將要討論的是
如何將圖形的幾何參數來得到點陣數據,本文主要介紹最簡單的直線生成算法
通過兩個點\(p_0\),\(p_1\),如何轉化成幀緩存中的點陣數據
圖元的生成
-
概念:圖元的參數表示形式到點陣表示形式的轉換
-
參數表示形式由不同種類圖形的性質決定
-
點陣表示形式是光柵顯示系統刷新時所需的表示形式。
在光柵顯示器的熒光屏上生成一個對象,實質上是往幀暫存寄存器的相應單元中填入數據
-
像素操作函數:最基本的繪圖函數,等同於寫讀幀緩存
- 寫像素:
SetPixel ( int x, int y, int color )
- 讀像素:
int GetPixel ( int x, int y)
- 寫像素:
-
單緩存與雙緩存:
直線生成的基本思路
畫一條從\(p_0\)到$p_1 $的直線,實質上是一個發現最佳逼近直線的像素序列、並填入色彩數據的過程,這個過程也稱為直線光柵化。
- 概念:求與直線段充分接近的像素集,並以此像素集替代原連續直線段在屏幕上顯示。
說到直線,肯定想到最基礎的直線方程\(y=kx+b\)。如果用這種方法,會用到乘法,加法,還有取整。最簡單的找到充分接近的像素的方法就是取整。
這在計算機中效率是比較低的。
我們做一次改進:變乘法為加法
又因為\(y_i=kx_i+b\),所以
這樣我們就可以通過遞推式的方法解決問題。
基本增量算法(DDA)
-
基本思想:舍入法求解最佳逼近;利用微分思想,即每一個點坐標都可以由前一個坐標變化一個增量得到。
-
乘法用加法實現,每一個點坐標都可以由前一個坐標變化一個增量得到。
\[x_{i+1}=x_i+\Delta x \]\[y_{i+1}=y_i+\Delta y \]\[\Delta=t_{i+1}-t_i \] -
該算法在x或y變化比較大的方向的增量絕對值為1,而另一方向上的增量絕對值小於等於1。
-
為了方便,我們設置:\(\Delta x=1,\Delta y=0.5\)
-
實現代碼,下面代碼有多處錯誤:
//k在0到1之間
void LineDDA(int x0,int y0,int x1,int y1,int color){
int x, dx, dy, y; float k;
dx = x1 - x0; dy = y1 - y0; k = dy / dx; y = y0;
for(x = x0; x <= x1; x++) {
y = (int)(y + 0.5); SetPixel(x, y, color); y += k;
}
}
- 改錯:
//k在0到1之間
void LineDDA(int x0,int y0,int x1,int y1,int color){
int x, dx, dy;
//y應該是浮點型
float k,y;
dx = x1 - x0; dy = y1 - y0;
//需要強制轉換
k =(float)dy / dx;
y = y0;
for(x = x0; x <= x1; x++) {
// y = (int)(y + 0.5); SetPixel(x, y, color);
// 不應該先取整,這樣會有很大誤差。
SetPixel(x, (int)(y + 0.5), color);
y += k;
}
}
中點算法
-
目標:消除DDA算法中的浮點運算,浮點數取整運算,不利於硬件實現; DDA算法效率低。會不會有另外一種遞推的方法?
-
直線隱式方程
\[F(x,y)=ax+by+c=0 \]其中: \(a = y_0-y_1= -\Delta y, b = x_1-x_0= \Delta x, c = x_0*y_1- x_1*y_0\)
-
直線的正負划分性
- 直線上方的點:\(F(x, y) >0\)
- 直線下方的點:\(F(x, y) <0\)
- 直線上的點: \(F(x, y) =0\)
-
設:\(y_i\) 為實際坐標; \(y_{i, r}\) 表示取整后的坐標; \(M\)是中點
一、已計算出像素\((x_i , y_{i,r})\)如何判斷距直線最近的下一個像素點
答案是根據可能所取點間的中點\(M\)與直線的位置
如果\(M\)在直線下方,那就選擇像素點\(NE\),否則選擇\(E\)
通過構造判別式:\(d = F(M) = F(x_{i}+1, y_{i,r}+0.5)\) 由\(d\) 的正和負可判定下一個像素
二、如何判定再下一個像素\((x_i+2,??)\)
- 若\(d≥0\),取正右方像素\(E\),則判定再下一個像素的\(d\)為 \(d_1= F(x_i+2, y_{i,r}+0.5) = a(x_i+2) + b(y_{i,r}+0.5) + c = d + a\), \(d\)的增量是\(a\)(即\(-\Delta y\))
- 若\(d<0\),取右上方像素\(E\),則判定再下一個像素的\(d\)為 \(d_2= F(x_i+2, y_{i,r}+1.5) = d + a+b\) , \(d\)的增量為\(a+b\) (即\(-(\Delta y-\Delta x)\))
三、增量\(d_0\)的初始值:
但是\(F(x_0,y_0)=0\),所以:
四、增量\(d\)的遞推公式
五、優化:增量都是整數,只有初始值包含小數,可以用\(2d\)代替 \(d\), \(2a\)改寫成\(a + a\)
/*x0<x1,y0<y1,0<=k<=1*/
void MidpointLine(int x0,int y0, int x1,int y1, int color){
int a,b,d1,d2,d,x,y;
a = y0 - y1;
b = x1 - x0;
d = a + a + b;
d1 = a + a;
d2 = (a + b) + (a + b);
x = x0;
y = y0;
SetPixel(x, y, color);
while (x<x1){
if (d<0){
y++;
d += d2;
}
else
d += d1;
x++ ;
SetPixel(x,y, color);
}
}
我們可以直接通過\(d=F(M)\)的增量來構造遞推式,從而:
1、不必計算直線之斜率,因此不做除法;
2、不用浮點數,只用整數;
3、只做整數加減法和乘2運算,而乘2運算可以用硬件移位實現。
其他情況
上文的遞推式子只是當\(0 \leq m \leq 1\) 的時候會滿足
這是因為\(x\)每次的變化量比\(y\)大,所以\(y\)每次最多增加\(1\)
例如:\(p_0(0,0)\)和\(p_1(6,12)\),如果按照上面的遞推式,得到的結果如下圖綠線是:
紅線才應該是正確的。
- 當\(0 \leq m \leq 1\) :
\(d_i < 0\) 時\(y\)增加\(1\)
- 當\(m\geq 1\):
\(d_i > 0\) 時\(x\)增加\(1\)
- 當\(-1 \leq m \leq 0\):
\(d_i>0\)時\(y\)減少\(1\)
- 當\(m\leq 0\):
\(d_i<0\)時\(x\)增加1
記住了第一個,其他都是同理
參考
[1] https://www.cnblogs.com/wkfvawl/p/11621653.html
[2] 老師課件