開一個新坑,記錄從零開始學習圖形學的過程,如今仍是個正在學習的萌新,寫的很差請見諒。ios
首先從最基礎的直線生成算法開始,當咱們要在屏幕上畫一條直線時,因爲屏幕由一個個像素組成,因此實際上計算機顯示的直線是由一些像素點近似組成的,直線生成算法解決的是如何選擇最佳的一組像素來顯示直線的問題。算法
對這個問題,首先想到的最暴力的方法固然是從直線起點開始令x或y每次增長1直到終點,每次根據直線方程計算對應的函數值再四捨五入取整,便可找到一個對應的像素,但這樣作每一步都要進行浮點數乘法運算,效率極低,因此出現了DDA和Bresenham兩種直線生成算法。編程
DDA算法主要是利用了增量的思想,經過同時對x和y各增長一個小增量,計算下一步的x和y值。函數
根據上式可知$\bigtriangleup x$=1時,x每遞增1,y就遞增k,因此只須要對x和y不斷遞增就能夠獲得下一點的函數值,這樣避免了對每個像素都使用直線方程來計算,消除了浮點數乘法運算。學習
代碼實現:ui
#include<Windows.h> #include<iostream> #include<cmath> using namespace std; const int ScreenWidth = 500; const int ScreenHeight = 500; LRESULT CALLBACK WinProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_CLOSE: DestroyWindow(hWnd); break; case WM_DESTROY: PostQuitMessage(0); break; default: return DefWindowProc(hWnd, message, wParam, lParam); break; } return 0; } void DDALine(int x0,int y0,int x1,int y1,HDC hdc) { int i=1; float dx, dy, length, x, y; if (fabs(x1 - x0) >= fabs(y1 - y0)) length = fabs(x1 - x0); else length = fabs(y1 - y0); dx = (x1 - x0) / length; dy = (y1 - y0) / length; x = x0; y = y0; while (i<=length) { SetPixel(hdc,int(x + 0.5), ScreenHeight-40-int(y + 0.5), RGB(0, 0, 0)); x = x + dx; y = y + dy; i++; } } int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int nShowCmd) { WNDCLASS wcs; wcs.cbClsExtra = 0; // 窗口類附加參數 wcs.cbWndExtra = 0; // 窗口附加參數 wcs.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); // 窗口DC背景 wcs.hCursor = LoadCursor(hInstance, IDC_CROSS); // 鼠標樣式 wcs.hIcon = LoadIcon(NULL, IDI_WINLOGO); // 窗口icon wcs.hInstance = hInstance; // 應用程序實例 wcs.lpfnWndProc = (WNDPROC)WinProc; wcs.lpszClassName = "CG"; wcs.lpszMenuName = NULL; wcs.style = CS_VREDRAW | CS_HREDRAW; RegisterClass(&wcs); HWND hWnd; hWnd = CreateWindow("CG","DrawLine", WS_OVERLAPPEDWINDOW, 200, 200, ScreenWidth, ScreenHeight, NULL, NULL, hInstance, NULL); ShowWindow(hWnd, nShowCmd); UpdateWindow(hWnd); MSG msg; // hdc init HDC hdc = GetDC(hWnd); // 繪圖,畫一條從點(0,0)到(100,350)的直線 DDALine(0, 0, 100, 350, hdc);// 消息循環 while (GetMessage(&msg, 0, NULL, NULL)) { TranslateMessage(&msg); DispatchMessage(&msg); } // release ReleaseDC(hWnd, hdc); return 0; }
以上是DDA算法的實現,WinMain和WinProc函數是Windows API編程特有的,咱們只須要關注DDALine這個繪圖函數,該函數傳入兩個點的座標畫出一條直線。spa
首先判斷起點和終點間x軸和y軸哪一個軸向的跨度更大(斜率範圍),爲了防止丟失像素,應讓跨度更大的軸向每次自增1,這樣能得到更精確的結果。3d
接下來就沒什麼好說的,依次讓x和y加上增量而後四捨五入就好了,浮點數四捨五入能夠直接用int(x+0.5)計算,setPixel函數用於設置像素的顏色值(須要傳入窗口的hdc句柄),因爲Windows窗口座標的原點在左上角,因此拿窗口高度減去y值就能夠轉換成日常習慣的左下角座標系了。code
運行結果:blog
DDA算法儘管消除了浮點數乘法運算,但仍存在浮點數加法和取整操做,效率仍有待提升,1965年Bresenham提出了更好的直線生成算法,成爲了時至今日圖形學領域使用最普遍的直線生成算法,該算法採用增量計算,藉助一個偏差量的符號肯定下一個像素點的位置,該算法中不存在浮點數,只有整數運算,大大提升了運行效率。
咱們先只考慮斜率在0-1之間的狀況,從線段左端點開始處理,並逐步處理每一個後續列,每肯定當前列的像素座標$(x_{i},y_{i})$後,那麼下一步須要在列$x_{i+1}$上肯定y的值,此時y值要麼不變,要麼增長1,這是由於斜率在0-1之間,x增加比y快,因此x每增長1,y的增量是小於1的。
對於左端點默認爲其像素座標,下一列要麼是右方的像素,要麼是右上方的像素,設右上方像素到直線的距離爲d2,右方像素到直線的距離爲d1,顯然只須要判斷直線離哪一個像素點更近也就是d1-d2的符號便可找到最佳像素。
因此能夠推出如下式子:
其中$\bigtriangleup x$起點到終點x軸上距離,$\bigtriangleup y$爲y軸上距離,k=$\bigtriangleup y$/$\bigtriangleup x$,c是常量,與像素位置無關。
令$e_{i}$=$\bigtriangleup x$(d1-d2),則$e_{i}$的計算僅包括整數運算,符號與d1-d2一致,稱爲偏差量參數,當它小於0時,直線更接近右方像素,大於0時直線更接近右上方像素。
可利用遞增整數運算獲得後繼偏差量參數,計算以下:
因此選擇右上方像素時($y_{i+1}$-$y_{i}$=1):
選擇右方像素時($y_{i+1}$-$y_{i}$=0):
初始時,將k=$\bigtriangleup y$/$\bigtriangleup x$代入$\bigtriangleup x$(d1-d2)中可獲得起始像素的第一個參數:
斜率在0-1之間的Bresenham算法代碼實現(替換上面程序中DDALine便可):
void Bresenham_Line(int x0, int y0, int x1, int y1, HDC hdc) { int dx, dy, e, x=x0, y=y0; dx = x1 - x0; dy = y1 - y0; e = 2 * dy - dx; while (x<=x1) { SetPixel(hdc, x, ScreenHeight-40-y, RGB(0, 0, 0)); if (e >= 0)//選右上方像素 { e = e + 2 * dy - 2 * dx; y++; } else//選右方像素 { e = e + 2 * dy; } x++; } }
運行結果:
要實現任意方向的Bresenham算法也很容易,斜率在0-1之間意味着直線位於座標系八象限中的第一象限,若是要繪製第二象限的直線,只須要利用這兩個象限關於直線x=y對稱的性質便可,能夠先將x和y值互換先在第一象限進行計算,而後調用SetPixel時再將x和y值反過來,在第二象限中繪製,其餘象限也是相似的思路。
繪製任意方向直線的Bresenham算法代碼實現:
void Bresenham_Line(int x0, int y0, int x1, int y1, HDC hdc) { int flag = 0; int dx = abs(x1 - x0); int dy = abs(y1 - y0); if (dx == 0 && dy == 0) return; if (abs(x1 - x0) < abs(y1 - y0)) { flag = 1; swap(x0, y0); swap(x1, y1); swap(dx, dy); } int tx = (x1 - x0) > 0 ? 1 : -1; int ty = (y1 - y0) > 0 ? 1 : -1; int x = x0; int y = y0; int dS = 2 * dy; int dT = 2 * (dy - dx); int e = dS - dx; SetPixel(hdc, x0, y0, RGB(0,0,0)); while (x != x1) { if (e < 0) e += dS; else { y += ty; e += dT; } x += tx; if (flag) SetPixel(hdc, y, ScreenHeight - 40 - x, RGB(0, 0, 0)); else SetPixel(hdc, x, ScreenHeight - 40 - y, RGB(0, 0, 0)); } }
直線生成算法就到這裏啦,接下來也要加油學習圖形學~