任何復雜的三維模型都是由基本的幾何圖元:點、線段和多邊形組成的,有了這些圖元,就可以建立比較復雜的模型。因此這部分內容是學習OpenGL編程的基礎。 一、基本圖元的描述及定義 OpenGL圖元是抽象的幾何概念,不是真實世界中的物體,因此須用相關的數學模型來描述。所有的圖元都是由一系列有順序的頂點集合來描述的。OpenGL中繪制幾何圖元,必須使用glBegain()和glEnd()這一對函數,傳遞給glBegain()函數的參數唯一確定了要繪制何種幾何圖元,同時,在該函數對中給出了幾何圖元的定義,函數glEnd()標志頂點列表的結束。例如,下面的代碼繪制了一個多邊形:
glBegin(GL_POLYGON); glVertex2f(0.0,0.0); glVertex2f(0.0,3.0); glVertex2f(3.0,3.0); glVertex2f(4.0,1.5); glVertex2f(3.0,0.0); glEnd(); |
函數glBegin(GLenum mode)標志描述一個幾何圖元的頂點列表的開始,其參數mode表示幾何圖元的描述類型,具體類型見表一:
類型 |
說明 |
GL_POINTS |
單個頂點集 |
GL_LINES |
多組雙頂點線段 |
GL_POLYGON |
單個簡單填充凸多邊形 |
GL_TRAINGLES |
多組獨立填充三角形 |
GL_QUADS |
多組獨立填充四邊形 |
GL_LINE_STRIP |
不閉合折線 |
GL_LINE_LOOP |
閉合折線 |
GL_TRAINGLE_STRIP |
線型連續填充三角形串 |
GL_TRAINGLE_FAN |
扇形連續填充三角形串 |
GL_QUAD_STRIP |
連續填充四邊形串 |
表一、幾何圖元類型說明 部分幾何圖元的示意圖:
 圖一、部分幾何圖元示意圖
|
在glBegin()和glEnd()之間最重要的信息就是由函數glVertex*()定義的頂點,必要時也可為每個頂點指定顏色(只對當前點或后續點有效)、法向、紋理坐標或其他,即調用相關的函數:
函數 |
函數意義 |
glColor*() |
設置當前顏色 |
glIndex*() |
設置當前顏色表 |
glNormal*() |
設置法向坐標 |
glEvalCoord*() |
產生坐標 |
glCallList(),glCallLists() |
顯示列表 |
glTexCoord*() |
設置紋理坐標 |
glEdgeFlag*() |
控制邊界繪制 |
glMaterial*() |
設置材質 |
表二、在glBegin()和glEnd()之間可調用的函數 需要指出的是:OpenGL所定義的點、線、多邊形等圖元與一般數學定義不太一樣,存在一定的差別。一種差別源于基于計算機計算的限制。OpenGL中所有浮點計算精度有限,故點、線 、多邊形的坐標值存在一定的誤差。另一種差別源于位圖顯示的限制。以這種方式顯示圖形,最小的顯示圖元是一個象素,盡管每個象素寬度很小,但它們仍然比數學上所定義的點或線寬要大得多。當用OpenGL進行計算時,雖然是用一系列浮點值定義點串,但每個點仍然是用單個象素顯示,只是近似擬合。 二、點(Point) 用浮點值表示的點稱為頂點(Vertex)。所有頂點在OpenGL內部計算時都使用三維坐標(x,y,z)來處理,用二維坐標(x,y)定義的點在OpenGL中默認z值為0。頂點坐標也可以用齊次坐標(x,y,z,w)來表示,如果w不為0.0,這些齊次坐標表示的頂點即為三維空間點(x/w,y/w,z/w),一般來說,w缺省為1.0。 可以用glVertex{234}{sifd}[V](TYPE cords)函數來定義一個頂點。例如:
glVertex2f(2.0f,3.0f);//二維坐標定義頂點; |
OpenGL中定義的點可以有不同的尺寸,其函數形式為:
void glPointSize(GLfloat size); |
參數size設置點的寬度(以象素為單位),必須大于0.0,缺省時為1.0。 三、線(Line) 在OpenGL中,線代表線段(Line Segment),它由一系列頂點順次連結而成。具體的講,線有獨立線段、條帶、封閉條帶三種,如圖二所示:
 圖二、線段的三種連結方式
|
OpenGL能指定線的寬度并繪制不同的虛點線,如點線、虛線等。相應的函數形式如下: 1、void glLineWidth(GLfloat width); 設置線寬(以象素為單位)。參數width必須大于0.0,缺省時為1.0。 2、void glLineStipple(GLint factor,GLushort pattern); 設置當前線為虛點模式。參數pattern是一系列的16位二進制數(0或1),它重復地賦給所指定的線,從低位開始,每一個二進制位代表一個象素, 1表示用當前顏色繪制一個象素(或比例因子指定的個數),0表示當前不繪制,只移動一個象素位(或比例因子指定的個數)。參數factor是個比例因子,它用來拉伸pattern中的元素,即重復繪制1或移動0,比如,factor為2,則碰到1時就連續繪制2次,碰到0時連續移動2個單元。factor的大小范圍限制在1到255之間。 在繪制虛點線之前必須先啟動虛點模式,即調用函數glEnable(GL_LINE_STIPPLE);結束時,調用glDisable(GL_LINE_STIPPLE)關閉。下面代碼繪制了一個點線:
void line2i(GLint x1,GLint y1,GLint x2,GLint y2) { glBegin(GL_LINES); glVertex2f(x1,y1); glVertex2f(x2,y2); glEnd(); } glLineStipple (1, 0x1C47); /* 虛點線 */ glEnable(GL_LINE_STIPPLE); glColor3f(0.0,1.0,0.0); line2i (450 , 250 , 600 , 250 ); |
三、多邊形(Polygon)
(一)凸、凹多邊形。 OpenGL定義的多邊形是由一系列線段依次連結而成的封閉區域,多邊形可以是平面多邊形,即所有頂點在一個平面上,也可以是空間多邊形。OpenGL規定多邊形中的線段不能交叉,區域內不能有空洞,也即多邊形必須是凸多邊形(指多邊形任意非相鄰的兩點的連線位于多邊形的內部),不能是凹多邊形,否則不能被OpenGL函數接受。凸多邊形和凹多邊形見圖三。
 圖三、凸凹多邊形
|
(二)邊界標志問題。 實際應用中,往往需要繪制一些凹多邊形,通常解決的辦法是對它們進行分割,用多個三角形來替代。顯然,繪制這些三角形時,有些邊不應該進行繪制,否則,多邊形內部就會出現多余的線框。OpenGL提供的解決辦法是通過設置邊標志命令glEdgeFlag()來控制某些邊產生繪制,而另外一些邊不產生繪制,這也稱為邊界標志線或非邊界線。這個命令的定義如下;
void glEdgeFlag(GLboolean flag); void glEdgeFlag(PGLboolean pflag); |
(三)多邊形繪制模式。 多邊形的繪制模式包含有:全填充式、輪廓點式、輪廓線式、圖案填充式及指定正反面等。下面分別介紹相應的OpenGL函數形式。 1)多邊形模式設置。其函數為:
void glPolygonMode(GLenum face,GLenum mode); |
參數face為GL_FRONT、GL_BACK或GL_FRONT_AND_BACK;參數mode為GL_POINT、GL_LINE或GL_FILL,分別表示繪制輪廓點式多邊形、輪廓線式多邊形或全填充式多邊形。在OpenGL中,多邊形分為正面和反面,對這兩個面都可以進行操作,在缺省狀況下,OpenGL對多邊形正反面是以相同的方式繪制的,要改變繪制狀態,必須調用PolygonMode()函數, 2)設置圖案填充式多邊形。其函數為:
void glPolygonStipple(const GLubyte *mask); |
參數mask是一個指向32x32位圖的指針。與虛點線繪制的道理一樣,某位為1時繪制,為0時什么也不繪。注意,在調用這個函數前,必須先啟動glEnable(GL_POLYGON_STIPPLE);不用時用glDisable(GL_POLYGON_STIPPLE)關閉。下面舉出一個多邊形擴展繪制實例:
void CALLBACK display(void) { /* 填充模式定義 (32x32) */ GLubyte pattern[]= { 0x00, 0x01, 0x80, 0x00, 0x00, 0x03, 0xc0, 0x00, 0x00, 0x07, 0xe0, 0x00, 0x00, 0x0f, 0xf0, 0x00, 0x00, 0x1f, 0xf8, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x00, 0x7f, 0xfe, 0x00, 0x00, 0xff, 0xff, 0x00, 0x01, 0xff, 0xff, 0x80, 0x03, 0xff, 0xff, 0xc0, 0x07, 0xff, 0xff, 0xe0, 0x0f, 0xff, 0xff, 0xf0, 0x1f, 0xff, 0xff, 0xf8, 0x3f, 0xff, 0xff, 0xfc, 0x7f, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xff, 0xff, 0xfe, 0x3f, 0xff, 0xff, 0xfc, 0x1f, 0xff, 0xff, 0xf8, 0x0f, 0xff, 0xff, 0xf0, 0x07, 0xff, 0xff, 0xe0, 0x03, 0xff, 0xff, 0xc0, 0x01, 0xff, 0xff, 0x80, 0x00, 0xff, 0xff, 0x00, 0x00, 0x7f, 0xfe, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x00, 0x1f, 0xf8, 0x00, 0x00, 0x0f, 0xf0, 0x00, 0x00, 0x07, 0xe0, 0x00, 0x00, 0x03, 0xc0, 0x00, 0x00, 0x01, 0x80, 0x00 }; glClear (GL_COLOR_BUFFER_BIT); /* 繪制一個指定圖案填充的三角形 */ glColor3f(0.9,0.86,0.4); glPolygonStipple (pattern); glBegin(GL_TRIANGLES); glVertex2i(310,310); glVertex2i(220,80); glVertex2i(405,80); glEnd(); glDisable (GL_POLYGON_STIPPLE); glFlush (); } |
(三)指定多邊形的正反面。 其函數為:
void glFrontFace(GLenum mode); |
在正常情況下,OpenGL中的多邊形的正面和反面是由繪制的多邊形的頂點順序決定的,逆時針繪制的面是多邊形的正面,但是,在OpenGL中使用該函數可以自定義多邊形的正面。該函數的參數mode指定了正面的方向。它可以是CL_CCW和CL_CW,分別指定逆時針和順時針方向為多邊形的正方向。 四、法向量的計算及指定
法向量是幾何圖元的重要屬性之一。幾何對象的法向量是垂直與曲面切面的單位向量,它定義了幾何對象的空間方向,特別定義了它相對于光源的方向,決定了在該點上可接受多少光照。 OpenGL本身沒有提供計算法向量的函數(計算法向量的任務由程序員自己去完成),但它提供了賦予當前頂點法向的函數。 (一)平面法向的計算方法。 在一個平面內,有兩條相交的線段,假設其中一條為矢量W,另一條為矢量V,平面法向為N,則平面法向就等于兩個矢量的叉積(遵循右手定則),即N=WxV。例如:一個三角形平面三個頂點分別為P0、P1、P2,相應兩個向量為W、V,則三角平面法向的計算方式如下列代碼所示:
void getNormal(GLfloat gx[3],GLfloat gy[3], GLfloat gz[3],GLfloat *ddnv) { GLfloat w0,w1,w2,v0,v1,v2,nr,nx,ny,nz; w0=gx[0]-gx[1]; w1=gy[0]-gy[1];w2=gz[0]-gz[1]; v0=gx[2]-gx[1]; v1=gy[2]-gy[1];v2=gz[2]-gz[1]; nx=(w1*v2-w2*v1);ny=(w2*v0-w0*v2);nz=(w0*v1-w1*v0); nr=sqrt(nx*nx+ny*ny+nz*nz); //向量單位化。 ddnv[0]=nx/nr; ddnv[1]=ny/nr;ddnv[2]=nz/nr; } |
以上函數的輸出參數為指針ddnv,它指向法向的三個分量,并且程序中已經將法向單位化(或歸一化)了。 (二)曲面法向量的計算。 對于曲面各頂點的法向計算有很多種,如根據函數表達式求偏導的方法等。但是,在大多數情況,OpenGL中的多邊形并不是由曲面方程建立起來的,而是由模型數組構成,這時候求取法向量的辦法是將曲面細分成多個小多邊形,然后選取小多邊形上相鄰的三個點v1、v2、v3(當然三個點不能在同一直線上),按照平面法向量的求取方法就可以了。 (三)法向量的定義。 OpenGL法向量定義函數為:
void glNormal3{bsifd}(TYPE nx,TYPE ny,TYPE nz); void glNormal3{bsifd}v(const TYPE *v); |
非向量形式定義法向采用第一種方式,即在函數中分別給出法向三個分量值nx、ny和nz;向量形式定義采用第二種,即將v設置為一個指向擁有三個元素的指針,例如v[3]={nx,ny,nz}。 五、顯示列表 (一)定義顯示列表。 前面所舉出的例子都是瞬時給出函數命令,OpenGL瞬時執行相應的命令,這種繪圖方式叫做立即或瞬時方式(immediate mode)。OpenGL顯示列表(Display List)是由一組預先存儲起來的留待以后調用的OpenGL函數語句組成的,當調用顯示列表時就依次執行表中所列出的函數語句。顯示列表可以用在以下場合: 1)矩陣操作 大部分矩陣操作需要OpenGL計算逆矩陣,矩陣及其逆矩陣都可以保存在顯示列表中。 2)光柵位圖和圖像 程序定義的光柵數據不一定是適合硬件處理的理想格式。當編譯組織一個顯示列表時,OpenGL可能把數據轉換成硬件能夠接受的數據,這可以有效地提高畫位圖的速度。 3)光、材質和光照模型 當用一個比較復雜的光照環境繪制場景時,因為材質計算可能比較慢。若把材質定義放在顯示列表中,則每次改換材質時就不必重新計算了,因此能更快地繪制光照場景。 4)紋理 因為硬件的紋理格式可能與OpenGL格式不一致,若把紋理定義放在顯示列表中,則在編譯顯示列表時就能對格式進行轉換,而不是在執行中進行,這樣就能大大提高效率。 5)多邊形的圖案填充模式,即可將定義的圖案放在顯示列表中。 OpenGL提供類似于繪制圖元的結構即類似于glBegin()與glEnd()的形式創建顯示列表,其相應的函數為:
void glNewList(GLuint list,GLenum mode); void glEndList(void); |
glNewList()函數說明一個顯示列表的開始,其后的OpenGL函數存入顯示列表中,直至調用結束表的函數glEndList(void)。glNewList()函數中的參數list是一個正整數,它標志唯一的顯示列表;參數mode的可能值有GL_COMPILE和GL_COMPILE_AND_EXECUTE;若要使列表中函數語句只存入而不執行,則用GL_COMPILE;若要使列表中的函數語句存入表中且按瞬時方式執行一次,則用GL_COMPILE_AND_EXECUTE。 注意:并不是所有的OpenGL函數都可以在顯示列表中存儲且通過顯示列表執行。一般來說,用于傳遞參數或返回數值的函數語句不能存入顯示列表,因為這張表有可能在參數的作用域之外被調用;如果在定義顯示列表時調用了這樣的函數,則它們將按瞬時方式執行并且不保存在顯示列表中,有時在調用執行顯示列表函數時會產生錯誤。以下列出的是不能存入顯示列表的OpenGL函數:
glDeleteLists() glIsEnable() glFeedbackBuffer() glIsList() glFinish() glPixelStore() glGenLists() glRenderMode() glGet*() glSelectBuffer() |
在建立顯示列表以后就可以調用執行顯示列表的函數來執行它,并且允許在程序中多次執行同一顯示列表,同時也可以與其它函數的瞬時方式混合使用。顯示列表執行的函數形式如下: void glCallList(GLuint list); 參數list指定被執行的顯示列表。顯示列表中的函數語句按它們被存放的順序依次執行;若list沒有定義,則不會產生任何事情。 (二)管理顯示列表 在實際應用中,一般調用函數glGenList()來創建多個顯示列表,這樣可以避免意外刪除,產生一個沒有用過的顯示列表。此外,在管理顯示列表的過程中,還可調用函數glDeleteLists()來刪除一個或一個范圍內的顯示列表。 1)GLuint glGenList(GLsizei range) 該函數分配range個相鄰的未被占用的顯示列表索引。這個函數返回的是一個正整數索引值,它是一組連續空索引的第一個值。返回的索引都標志為空且已被占用,以后再調用這個函數時不再返回這些索引。若申請索引的指定數目不能滿足或range為0則函數返回0。 2)GLboolean glIsList(GLuint list) 該函數詢問顯示列表是否已被占用的情況,若索引list已被占用,則函數返回TURE;反之,返回FAULSE。 3)void glDeleteLists(GLuint list,GLsizei range) 該函數刪除一組連續的顯示列表,即從參數list所指示的顯示列表開始,刪除range個顯示列表,并且刪除后的這些索引重新有效。 (三)多級顯示列表 多級顯示列表的建立就是在一個顯示列表中調用另一個顯示列表,也就是說,在函數glNewList()與glEndList()之間調用glCallList()。多級顯示列表對于構造由多個元件組成的物體十分有用,尤其是某些元件需要重復使用的情況。但為了避免無窮遞歸,顯示列表的嵌套深度最大為64(也許更高些,這依賴于不同的OpenGL實現),當然也可調用函數glGetIntegerv()來獲得這個最大嵌套深度值。OpenGL也允許用一個顯示列表包含幾個低級的顯示列表來模擬建立一個可編輯的顯示列表。 下面的一段代碼使用了列表嵌套來顯示一個三角形:
glNewList(1,GL_COMPILE); glVertex3fv(v1); glEndList(); glNewList(2,GL_COMPILE); glVertex3fv(v2); glEndList();
glNewList(3,GL_COMPILE); glVertex3fv(v3); glEndList();
glNewList(4,GL_COMPILE); glBegin(GL_POLYGON); glCallList(1); glCallList(2); glCallList(3); glEnd(); glEndList(); |
|