首先來看一下題目要求:
2.2 Draw a Line
Implement your line rasterization algorithm in OpenGL. You can only use integer arithmetic in your code.
- Input: 2 2D points, that makes 4 integers, as a start point and an end point
- Output: A straight line in a window
- You can only use the
GL_POINTS
as primitives. Other (i.e.GL_LINES
orGL_LINE_STRIP
) primitives is not allowed to use.
-
Language: Only C/C++ is accepted in this homework.
-
Libraries: OpenGL and freeGLUT/GLFW
-
IDE: Any IDE can be used.
這里我的的開發環境是mac OS Sierra 10.2.3,使用Sublime做C++語言編寫,直接在terminal使用g++編譯。使用的library是GLFW,由於編譯時需要鏈接的庫文件還是蠻多的,這里也可以用Xcode配置好環境再編寫,或者直接用makefile。這個環境配置的問題就下次再說了(其實也不知道下次會拖到什么時候)。
首先來審題,要求只用GL_POINTS做primitives,那么從幾何數學的角度來看就是用連續的無數個點來集成一條線段。而且考察的是bresenham的光柵化算法,也就是把連續圖形轉換為計算機可以顯示離散的像素點的一種做法,那么題意應該是要將我們理想中的一條【連續】的線段,【光柵化】處理后變成一系列有限的【離散】的點的集合,這些點的數量足夠大,相隔間隙足夠小,使得在計算機屏幕顯示時在視覺上形成一條【看似連續】的線段。
然后來談談在審完題之后思考的一些問題。
第一,要求我們輸入的是兩個整數對(代表線段兩個端點的坐標),但OpenGL的默認顯示坐標范圍是[-1,1]區間內的浮點數,而bresenham算法所做的是將連續的線段變為離散的點的坐標(坐標值也為整數對)。所以輸入和輸出到底要作些什么轉化?
輸入的是兩個整數對,我們在數學上可以通過計算出直線的表達式,知道直線上的無數個點的X、Y坐標的范圍,以及它們所遵循的一種關系。光柵化所做的就是對這些點進行一個采樣量化的操作,在這連續的無數多個點中適當地采樣出足夠的點的值(比如在橫坐標上進行整數采樣),再對另一項坐標值進行量化(比如Y坐標量化得到的值也都是整數)。然后再將我們光柵化后得到的有限的一系列的點(坐標值為整數),將它們的坐標值規格化到[-1,1]。得到合適的浮點數,用OpenGL進行繪制。
規格化這方面就寫了個normalize()函數,輸入-500到500之間的整數,返回-1到1之間的浮點數。
想好了步驟就分部地研究這些問題。
首先確定好輸入的范圍,不確定好輸入范圍的話用戶也不知道你畫出的線段是不是他需要的大小比例。由於我們要使屏幕上展示的點的數量足夠多以在視覺上形成連續的線段的效果,所以設定的整數范圍應該要比較大,我這里默認設置的輸入范圍是[-500,500]之間的整數,當然如果要實現更加逼近視覺上連續的效果的話,可以設置更大的輸入范圍,依次類推。 此外,有朋友跟我交流的一種想法是以用戶輸入整數中的最大絕對值為輸入邊界,我想了一下,這樣線段的形狀當然是用戶想要的形狀,大小的話線段肯定有一端處在窗口的邊界上,這么做是不是要涉及到什么動態的設置我就不清楚了,因為我寫的不是這個想法(……)。
然后不管其他,先只考慮位於線段所在直線經過第一象限與x軸夾角小於45度,也就是0<m<1的情況。bresenham算法推導過程不再贅述,直接上結論。因為m=0或者1時畫出來的線段也是差不多的,就直接算作0<=m<=1的情況了。
插播一下一下在OpenGL中怎么實現畫多個點連成線。我的想法是用分別用兩個長度相等的浮點數數組fxarray[]和fyarray[]存儲規格化后的xi和yi的值,(其實對就是在光柵化計算出的兩個整數數組做一下規格化運算得到的),然后在繪制點的時候循環調用。
for(int i=0;i<length;i++) { glVertex2f(xarray[i],yarray[i]); }
因為在這種情況下,我們選擇在x方向上采樣,並在y方向上量化。x0到x1之間每個xi的值是固定的,xi=x0+i,而bresenham算法是根據以上過程遞推出yi的值。所以我用一個整數數組array[]存儲y0到y1之間每個yi的值,array[0]和array[n-1]分別最初賦值y0和y1的值,n為從x0到x1之間的距離。輸入dx,dy,和p,array[]的地址及長度到一個遞推函數。最初傳入的p為p0=2dy-dx,遞推結束后數組array就存儲了計算出的y0到yn之間的值。
void bresenham(int array[], int p, int i, int length, int dx, int dy) { if(i==length-1) return; int pnext; if(p<=0) { array[i+1]=array[i]; pnext=p+2*dy; } else { array[i+1]=array[i]+1; pnext=p+2*dy-2*dx; } bresenham(array,pnext,i+1,length,dx, dy); }
然后就是考慮其他幾種情況的問題了。
(1)m不存在(線段與y軸平行或重合)
(2)-1<=m<0
(3)m>1
(4)m<-1。
情況(1)比較簡單,xarray[]中的值一直不變,存儲y的數列直接從最低點一直遞增最高點。(這里默認y0<=y1,同樣在|m|<1的情況下默認x0<x1,|m|>1的情況下默認y0<y1,方便分析。控制這個在前面寫個條件判斷的交換函數也很容易。)
if(x0==x1) { if(y0>y1) { int tempy=y0; y0=y1; y1=tempy; } length=y1-y0+1; for(int i=0;i<length;i++) { xarray[i]=x0; yarray[i]=y0+i; }
因為數學差(……)不想做其他情況的bresenham算法推導,所以直接采用了對稱變換的思想。直接改變傳入bresenham()中的參數並作一些相應的變換來求結果。沒有去網上參考其他人是什么樣的做法,也不知道這種對稱變換+數組記錄的方式會不會更復雜了吧(……)。
情況(2)其實和最開始討論的0<=m<=1完全關於y軸對稱,只需要先把既定的y0和y1的值關於y軸做一下軸對稱變換(也就是*-1),用bresenham計算出來的yarray[]一定與我們要求的yarray[]關於y軸對稱,再*-1即可求得。當然,由於y的值做了變換,所以傳入bresenham算法中的與y有關的參數也有了相應的改變。以下是|m|<=1時的兩種情況的計算方式。
if(fabs(m)<=1) { length=x1-x0+1; for(int i=0;i<length;i++) xarray[i]=x0+i; //0<m<=1 if(dy>=0) { yarray[0]=y0; yarray[length-1]=y1; int p0=2*dy-dx; bresenham(yarray,p0, 0, length,dx,dy); } else { //-1<=m<0 yarray[0]=-y0; yarray[length-1]=-y1; int p0=2*(-dy)-dx; bresenham(yarray,p0,0,length,dx,-dy); for(int i=0;i<length;i++) yarray[i]=-yarray[i]; }
然后到了情況(3)和(4)。先考慮情況(3)。情況(4)與上同理,只需要做一個關於y軸的軸對稱變換。
由於|m|>1,y軸上的變化尺度要比x軸上的大,所以應該在y軸上取樣,在x軸上量化。這里相當於是把水平直角坐標系逆時針方向旋轉了90度來思考問題,m>1的情況與y軸正方向的夾角為0~45度,就相當於最初討論的最基本0<=m<=1的情況。length改為從y0到y1的距離,向bresenham算法中傳入的數組也變為了xarray[],傳入參數變成了p0=2*dx-dy。這個旋轉坐標系的想法吧說難也不難,不過剛開始想的時候翻車了挺多次的(……),想明白了就好。底下是|m|>1的情況(3)(4)。
} else { length=y1-y0+1; for(int i=0;i<length;i++) yarray[i]=y0+i; if(dx>=0) { //m>1 xarray[0]=x0; xarray[length-1]=x1; int p0=2*dx-dy; bresenham(xarray,p0,0,length,dy,dx); } else { //m<-1 xarray[0]=-x0; xarray[length-1]=-x1; int p0 = 2*(-dx)-dy; bresenham(xarray,p0,0,length,dy,-dx); for(int i=0;i<length;i++) xarray[i]=-xarray[i]; }
然后在進行對輸入(x0,y0), (y0, y1)的處理就是在進行bresenham算法處理之前,保證當|m|<=1時,x0<=x1,|m|>1時,y0<=y1,|m|不存在時,y0<=y1。這里就不詳細解釋了。直接看完全代碼。
1 #include <iostream> 2 #include <math.h> 3 #include <GLFW/glfw3.h> 4 using namespace std; 5 //g++ -o line line.cpp -lglfw3 -framework Cocoa -framework OpenGL -framework IOKit -framework CoreVideo 6 void show(float xarray[],float yarray[],int length) { 7 bool init = true; 8 if(!glfwInit()) { 9 cout << "Initialization failed" << endl; 10 init = false; 11 //return? 12 } 13 14 //create a window with its OpenGL context 15 GLFWwindow* window1 = glfwCreateWindow(640, 640, "line", NULL, NULL); 16 17 if (!window1) { 18 cout << "Window or OpenGL context creation failed" << endl; 19 glfwTerminate(); 20 //return? 21 } 22 if(init&&window1) { 23 // Make the window's context current */ 24 glfwMakeContextCurrent(window1); 25 26 while (!glfwWindowShouldClose(window1)) { 27 // Keep running 28 /* Draw a triangle */ 29 glBegin(GL_POINTS); 30 31 glColor3f(1, 0.52, 0.0); // Orange 32 for(int i=0;i<length;i++) { 33 glVertex2f(xarray[i],yarray[i]); 34 } 35 36 glEnd(); 37 38 //When the frame is rendered, swap the buffer with one another 39 glfwSwapBuffers(window1); 40 41 /* Poll for and process events */ 42 glfwPollEvents(); 43 } 44 45 if(glfwWindowShouldClose(window1)) 46 glfwDestroyWindow(window1); 47 48 //done before exit 49 glfwTerminate(); 50 } 51 } 52 53 54 float normalize(int input) { 55 return float(input)/500; 56 } 57 void bresenham(int array[], int p, int i, int length, int dx, int dy) { 58 if(i==length-1) 59 return; 60 int pnext; 61 if(p<=0) { 62 array[i+1]=array[i]; 63 pnext=p+2*dy; 64 } 65 else { 66 array[i+1]=array[i]+1; 67 pnext=p+2*dy-2*dx; 68 } 69 bresenham(array,pnext,i+1,length,dx, dy); 70 } 71 72 int main() { 73 int x0, x1, y0, y1; 74 cout << "Please gives the x and y value of two points" << endl; 75 cout << "with one space between them (like: 'x y')" << endl; 76 cout << "Notice: the x and y value should be integers between -500 and 500" << endl << endl; 77 78 cout << "the x and y value of the first point?" << endl; 79 cin >> x0 >> y0; 80 cout << "the coordinate of the first point: (" << x0 << ", " << y0 <<")." << endl; 81 82 cout << "the x and y value of the second point?" << endl; 83 cin >> x1 >> y1; 84 cout << "the coordinate of the second point: (" << x1 << ", " << y1 <<")." << endl; 85 86 int length; 87 int xarray[1001], yarray[1001]; 88 int dx; 89 int dy; 90 91 if(x0==x1) { 92 if(y0>y1) { 93 int tempy=y0; 94 y0=y1; 95 y1=tempy; 96 } 97 length=y1-y0+1; 98 for(int i=0;i<length;i++) { 99 xarray[i]=x0; 100 yarray[i]=y0+i; 101 } 102 } else { 103 //|m|<=1,let point 1 be on the right of point 0 104 float m=float(y1-y0)/float(x1-x0); 105 if(fabs(m)<=1&&x0>x1) { 106 int tempx=x0; 107 int tempy=y0; 108 x0=x1; 109 y0=y1; 110 x1=tempx; 111 y1=tempy; 112 } 113 //|m|>1, let point 1 be on the top of point 0 114 if(fabs(m)>1&&y0>y1) { 115 int tempy=y0; 116 int tempx=x0; 117 y0=y1; 118 x0=x1; 119 y1=tempy; 120 x1=tempx; 121 } 122 dx=x1-x0; 123 dy=y1-y0; 124 m=float(dy)/float(dx); 125 126 if(fabs(m)<=1) { 127 length=x1-x0+1; 128 for(int i=0;i<length;i++) 129 xarray[i]=x0+i; 130 //0<m<=1 131 if(dy>=0) { 132 yarray[0]=y0; 133 yarray[length-1]=y1; 134 int p0=2*dy-dx; 135 bresenham(yarray,p0, 0, length,dx,dy); 136 } else { 137 //-1<=m<0 138 yarray[0]=-y0; 139 yarray[length-1]=-y1; 140 int p0=2*(-dy)-dx; 141 bresenham(yarray,p0,0,length,dx,-dy); 142 for(int i=0;i<length;i++) 143 yarray[i]=-yarray[i]; 144 } 145 } else { 146 length=y1-y0+1; 147 for(int i=0;i<length;i++) 148 yarray[i]=y0+i; 149 if(dx>=0) { 150 //m>1 151 xarray[0]=x0; 152 xarray[length-1]=x1; 153 int p0=2*dx-dy; 154 bresenham(xarray,p0,0,length,dy,dx); 155 } else { 156 //m<-1 157 xarray[0]=-x0; 158 xarray[length-1]=-x1; 159 int p0 = 2*(-dx)-dy; 160 bresenham(xarray,p0,0,length,dy,-dx); 161 for(int i=0;i<length;i++) 162 xarray[i]=-xarray[i]; 163 } 164 } 165 } 166 167 float fxarray[1001]; 168 float fyarray[1001]; 169 for(int i=0;i<length;i++) { 170 fxarray[i]=normalize(xarray[i]); 171 fyarray[i]=normalize(yarray[i]); 172 } 173 174 show(fxarray,fyarray,length); 175 176 177 178 return 0; 179 }
結果展示
m不存在的情況
m=0的情況
0<m<=1
-1<=m<0
m>1
m<-1