任何復(fù)雜的三維模型都是由基本的幾何圖元:點(diǎn)、線段和多邊形組成的,有了這些圖元,就可以建立比較復(fù)雜的模型。因此這部分內(nèi)容是學(xué)習(xí)OpenGL編程的基礎(chǔ)。

  一、基本圖元的描述及定義

  OpenGL圖元是抽象的幾何概念,不是真實(shí)世界中的物體,因此須用相關(guān)的數(shù)學(xué)模型來(lái)描述。所有的圖元都是由一系列有順序的頂點(diǎn)集合來(lái)描述的。OpenGL中繪制幾何圖元,必須使用glBegain()和glEnd()這一對(duì)函數(shù),傳遞給glBegain()函數(shù)的參數(shù)唯一確定了要繪制何種幾何圖元,同時(shí),在該函數(shù)對(duì)中給出了幾何圖元的定義,函數(shù)glEnd()標(biāo)志頂點(diǎn)列表的結(jié)束。例如,下面的代碼繪制了一個(gè)多邊形:

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();

  函數(shù)glBegin(GLenum mode)標(biāo)志描述一個(gè)幾何圖元的頂點(diǎn)列表的開(kāi)始,其參數(shù)mode表示幾何圖元的描述類(lèi)型,具體類(lèi)型見(jiàn)表一:

類(lèi)型 說(shuō)明
GL_POINTS 單個(gè)頂點(diǎn)集
GL_LINES 多組雙頂點(diǎn)線段
GL_POLYGON 單個(gè)簡(jiǎn)單填充凸多邊形
GL_TRAINGLES 多組獨(dú)立填充三角形
GL_QUADS 多組獨(dú)立填充四邊形
GL_LINE_STRIP 不閉合折線
GL_LINE_LOOP 閉合折線
GL_TRAINGLE_STRIP 線型連續(xù)填充三角形串
GL_TRAINGLE_FAN 扇形連續(xù)填充三角形串
GL_QUAD_STRIP 連續(xù)填充四邊形串
                  表一、幾何圖元類(lèi)型說(shuō)明

  部分幾何圖元的示意圖:


圖一、部分幾何圖元示意圖

  在glBegin()和glEnd()之間最重要的信息就是由函數(shù)glVertex*()定義的頂點(diǎn),必要時(shí)也可為每個(gè)頂點(diǎn)指定顏色(只對(duì)當(dāng)前點(diǎn)或后續(xù)點(diǎn)有效)、法向、紋理坐標(biāo)或其他,即調(diào)用相關(guān)的函數(shù):

函數(shù) 函數(shù)意義
glColor*() 設(shè)置當(dāng)前顏色
glIndex*() 設(shè)置當(dāng)前顏色表
glNormal*() 設(shè)置法向坐標(biāo)
glEvalCoord*() 產(chǎn)生坐標(biāo)
glCallList(),glCallLists() 顯示列表
glTexCoord*() 設(shè)置紋理坐標(biāo)
glEdgeFlag*() 控制邊界繪制
glMaterial*() 設(shè)置材質(zhì)
              表二、在glBegin()和glEnd()之間可調(diào)用的函數(shù)

  需要指出的是:OpenGL所定義的點(diǎn)、線、多邊形等圖元與一般數(shù)學(xué)定義不太一樣,存在一定的差別。一種差別源于基于計(jì)算機(jī)計(jì)算的限制。OpenGL中所有浮點(diǎn)計(jì)算精度有限,故點(diǎn)、線 、多邊形的坐標(biāo)值存在一定的誤差。另一種差別源于位圖顯示的限制。以這種方式顯示圖形,最小的顯示圖元是一個(gè)象素,盡管每個(gè)象素寬度很小,但它們?nèi)匀槐葦?shù)學(xué)上所定義的點(diǎn)或線寬要大得多。當(dāng)用OpenGL進(jìn)行計(jì)算時(shí),雖然是用一系列浮點(diǎn)值定義點(diǎn)串,但每個(gè)點(diǎn)仍然是用單個(gè)象素顯示,只是近似擬合。

  二、點(diǎn)(Point)

  用浮點(diǎn)值表示的點(diǎn)稱為頂點(diǎn)(Vertex)。所有頂點(diǎn)在OpenGL內(nèi)部計(jì)算時(shí)都使用三維坐標(biāo)(x,y,z)來(lái)處理,用二維坐標(biāo)(x,y)定義的點(diǎn)在OpenGL中默認(rèn)z值為0。頂點(diǎn)坐標(biāo)也可以用齊次坐標(biāo)(x,y,z,w)來(lái)表示,如果w不為0.0,這些齊次坐標(biāo)表示的頂點(diǎn)即為三維空間點(diǎn)(x/w,y/w,z/w),一般來(lái)說(shuō),w缺省為1.0。

  可以用glVertex{234}{sifd}[V](TYPE cords)函數(shù)來(lái)定義一個(gè)頂點(diǎn)。例如:

glVertex2f(2.0f,3.0f);//二維坐標(biāo)定義頂點(diǎn);

  OpenGL中定義的點(diǎn)可以有不同的尺寸,其函數(shù)形式為:

void glPointSize(GLfloat size);

  參數(shù)size設(shè)置點(diǎn)的寬度(以象素為單位),必須大于0.0,缺省時(shí)為1.0。

  三、線(Line)

  在OpenGL中,線代表線段(Line Segment),它由一系列頂點(diǎn)順次連結(jié)而成。具體的講,線有獨(dú)立線段、條帶、封閉條帶三種,如圖二所示:


圖二、線段的三種連結(jié)方式

  OpenGL能指定線的寬度并繪制不同的虛點(diǎn)線,如點(diǎn)線、虛線等。相應(yīng)的函數(shù)形式如下:

  1、void glLineWidth(GLfloat width);

  設(shè)置線寬(以象素為單位)。參數(shù)width必須大于0.0,缺省時(shí)為1.0。

  2、void glLineStipple(GLint factor,GLushort pattern);

  設(shè)置當(dāng)前線為虛點(diǎn)模式。參數(shù)pattern是一系列的16位二進(jìn)制數(shù)(0或1),它重復(fù)地賦給所指定的線,從低位開(kāi)始,每一個(gè)二進(jìn)制位代表一個(gè)象素, 1表示用當(dāng)前顏色繪制一個(gè)象素(或比例因子指定的個(gè)數(shù)),0表示當(dāng)前不繪制,只移動(dòng)一個(gè)象素位(或比例因子指定的個(gè)數(shù))。參數(shù)factor是個(gè)比例因子,它用來(lái)拉伸pattern中的元素,即重復(fù)繪制1或移動(dòng)0,比如,factor為2,則碰到1時(shí)就連續(xù)繪制2次,碰到0時(shí)連續(xù)移動(dòng)2個(gè)單元。factor的大小范圍限制在1到255之間。

  在繪制虛點(diǎn)線之前必須先啟動(dòng)虛點(diǎn)模式,即調(diào)用函數(shù)glEnable(GL_LINE_STIPPLE);結(jié)束時(shí),調(diào)用glDisable(GL_LINE_STIPPLE)關(guān)閉。下面代碼繪制了一個(gè)點(diǎn)線:

void line2i(GLint x1,GLint y1,GLint x2,GLint y2)
{
  glBegin(GL_LINES);
  glVertex2f(x1,y1);
  glVertex2f(x2,y2);
  glEnd();
}
glLineStipple (1, 0x1C47); /* 虛點(diǎn)線 */
glEnable(GL_LINE_STIPPLE);
glColor3f(0.0,1.0,0.0);
line2i (450 , 250 , 600 , 250 );
三、多邊形(Polygon)

  (一)凸、凹多邊形。

  OpenGL定義的多邊形是由一系列線段依次連結(jié)而成的封閉區(qū)域,多邊形可以是平面多邊形,即所有頂點(diǎn)在一個(gè)平面上,也可以是空間多邊形。OpenGL規(guī)定多邊形中的線段不能交叉,區(qū)域內(nèi)不能有空洞,也即多邊形必須是凸多邊形(指多邊形任意非相鄰的兩點(diǎn)的連線位于多邊形的內(nèi)部),不能是凹多邊形,否則不能被OpenGL函數(shù)接受。凸多邊形和凹多邊形見(jiàn)圖三。


圖三、凸凹多邊形

  (二)邊界標(biāo)志問(wèn)題。

  實(shí)際應(yīng)用中,往往需要繪制一些凹多邊形,通常解決的辦法是對(duì)它們進(jìn)行分割,用多個(gè)三角形來(lái)替代。顯然,繪制這些三角形時(shí),有些邊不應(yīng)該進(jìn)行繪制,否則,多邊形內(nèi)部就會(huì)出現(xiàn)多余的線框。OpenGL提供的解決辦法是通過(guò)設(shè)置邊標(biāo)志命令glEdgeFlag()來(lái)控制某些邊產(chǎn)生繪制,而另外一些邊不產(chǎn)生繪制,這也稱為邊界標(biāo)志線或非邊界線。這個(gè)命令的定義如下;

void glEdgeFlag(GLboolean flag);
void glEdgeFlag(PGLboolean pflag);

  (三)多邊形繪制模式。

  多邊形的繪制模式包含有:全填充式、輪廓點(diǎn)式、輪廓線式、圖案填充式及指定正反面等。下面分別介紹相應(yīng)的OpenGL函數(shù)形式。

  1)多邊形模式設(shè)置。其函數(shù)為:

void glPolygonMode(GLenum face,GLenum mode);

  參數(shù)face為GL_FRONT、GL_BACK或GL_FRONT_AND_BACK;參數(shù)mode為GL_POINT、GL_LINE或GL_FILL,分別表示繪制輪廓點(diǎn)式多邊形、輪廓線式多邊形或全填充式多邊形。在OpenGL中,多邊形分為正面和反面,對(duì)這兩個(gè)面都可以進(jìn)行操作,在缺省狀況下,OpenGL對(duì)多邊形正反面是以相同的方式繪制的,要改變繪制狀態(tài),必須調(diào)用PolygonMode()函數(shù),

  2)設(shè)置圖案填充式多邊形。其函數(shù)為:

void glPolygonStipple(const GLubyte *mask);

  參數(shù)mask是一個(gè)指向32x32位圖的指針。與虛點(diǎn)線繪制的道理一樣,某位為1時(shí)繪制,為0時(shí)什么也不繪。注意,在調(diào)用這個(gè)函數(shù)前,必須先啟動(dòng)glEnable(GL_POLYGON_STIPPLE);不用時(shí)用glDisable(GL_POLYGON_STIPPLE)關(guān)閉。下面舉出一個(gè)多邊形擴(kuò)展繪制實(shí)例:

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);
   /* 繪制一個(gè)指定圖案填充的三角形 */
    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 ();
  }

  (三)指定多邊形的正反面。

  其函數(shù)為:

void glFrontFace(GLenum mode);

  在正常情況下,OpenGL中的多邊形的正面和反面是由繪制的多邊形的頂點(diǎn)順序決定的,逆時(shí)針繪制的面是多邊形的正面,但是,在OpenGL中使用該函數(shù)可以自定義多邊形的正面。該函數(shù)的參數(shù)mode指定了正面的方向。它可以是CL_CCW和CL_CW,分別指定逆時(shí)針和順時(shí)針?lè)较驗(yàn)槎噙呅蔚恼较颉?br>四、法向量的計(jì)算及指定

  法向量是幾何圖元的重要屬性之一。幾何對(duì)象的法向量是垂直與曲面切面的單位向量,它定義了幾何對(duì)象的空間方向,特別定義了它相對(duì)于光源的方向,決定了在該點(diǎn)上可接受多少光照。

  OpenGL本身沒(méi)有提供計(jì)算法向量的函數(shù)(計(jì)算法向量的任務(wù)由程序員自己去完成),但它提供了賦予當(dāng)前頂點(diǎn)法向的函數(shù)。

  (一)平面法向的計(jì)算方法。

  在一個(gè)平面內(nèi),有兩條相交的線段,假設(shè)其中一條為矢量W,另一條為矢量V,平面法向?yàn)镹,則平面法向就等于兩個(gè)矢量的叉積(遵循右手定則),即N=WxV。例如:一個(gè)三角形平面三個(gè)頂點(diǎn)分別為P0、P1、P2,相應(yīng)兩個(gè)向量為W、V,則三角平面法向的計(jì)算方式如下列代碼所示:

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;
}

  以上函數(shù)的輸出參數(shù)為指針ddnv,它指向法向的三個(gè)分量,并且程序中已經(jīng)將法向單位化(或歸一化)了。

  (二)曲面法向量的計(jì)算。

  對(duì)于曲面各頂點(diǎn)的法向計(jì)算有很多種,如根據(jù)函數(shù)表達(dá)式求偏導(dǎo)的方法等。但是,在大多數(shù)情況,OpenGL中的多邊形并不是由曲面方程建立起來(lái)的,而是由模型數(shù)組構(gòu)成,這時(shí)候求取法向量的辦法是將曲面細(xì)分成多個(gè)小多邊形,然后選取小多邊形上相鄰的三個(gè)點(diǎn)v1、v2、v3(當(dāng)然三個(gè)點(diǎn)不能在同一直線上),按照平面法向量的求取方法就可以了。

  (三)法向量的定義。

  OpenGL法向量定義函數(shù)為:

void glNormal3{bsifd}(TYPE nx,TYPE ny,TYPE nz);
void glNormal3{bsifd}v(const TYPE *v);

  非向量形式定義法向采用第一種方式,即在函數(shù)中分別給出法向三個(gè)分量值nx、ny和nz;向量形式定義采用第二種,即將v設(shè)置為一個(gè)指向擁有三個(gè)元素的指針,例如v[3]={nx,ny,nz}。

  五、顯示列表

  (一)定義顯示列表。

  前面所舉出的例子都是瞬時(shí)給出函數(shù)命令,OpenGL瞬時(shí)執(zhí)行相應(yīng)的命令,這種繪圖方式叫做立即或瞬時(shí)方式(immediate mode)。OpenGL顯示列表(Display List)是由一組預(yù)先存儲(chǔ)起來(lái)的留待以后調(diào)用的OpenGL函數(shù)語(yǔ)句組成的,當(dāng)調(diào)用顯示列表時(shí)就依次執(zhí)行表中所列出的函數(shù)語(yǔ)句。顯示列表可以用在以下場(chǎng)合:

  1)矩陣操作

  大部分矩陣操作需要OpenGL計(jì)算逆矩陣,矩陣及其逆矩陣都可以保存在顯示列表中。

  2)光柵位圖和圖像

  程序定義的光柵數(shù)據(jù)不一定是適合硬件處理的理想格式。當(dāng)編譯組織一個(gè)顯示列表時(shí),OpenGL可能把數(shù)據(jù)轉(zhuǎn)換成硬件能夠接受的數(shù)據(jù),這可以有效地提高畫(huà)位圖的速度。

  3)光、材質(zhì)和光照模型

  當(dāng)用一個(gè)比較復(fù)雜的光照環(huán)境繪制場(chǎng)景時(shí),因?yàn)椴馁|(zhì)計(jì)算可能比較慢。若把材質(zhì)定義放在顯示列表中,則每次改換材質(zhì)時(shí)就不必重新計(jì)算了,因此能更快地繪制光照?qǐng)鼍啊?br>
  4)紋理

  因?yàn)橛布募y理格式可能與OpenGL格式不一致,若把紋理定義放在顯示列表中,則在編譯顯示列表時(shí)就能對(duì)格式進(jìn)行轉(zhuǎn)換,而不是在執(zhí)行中進(jìn)行,這樣就能大大提高效率。

  5)多邊形的圖案填充模式,即可將定義的圖案放在顯示列表中。

  OpenGL提供類(lèi)似于繪制圖元的結(jié)構(gòu)即類(lèi)似于glBegin()與glEnd()的形式創(chuàng)建顯示列表,其相應(yīng)的函數(shù)為:

void glNewList(GLuint list,GLenum mode);
void glEndList(void);

  glNewList()函數(shù)說(shuō)明一個(gè)顯示列表的開(kāi)始,其后的OpenGL函數(shù)存入顯示列表中,直至調(diào)用結(jié)束表的函數(shù)glEndList(void)。glNewList()函數(shù)中的參數(shù)list是一個(gè)正整數(shù),它標(biāo)志唯一的顯示列表;參數(shù)mode的可能值有GL_COMPILE和GL_COMPILE_AND_EXECUTE;若要使列表中函數(shù)語(yǔ)句只存入而不執(zhí)行,則用GL_COMPILE;若要使列表中的函數(shù)語(yǔ)句存入表中且按瞬時(shí)方式執(zhí)行一次,則用GL_COMPILE_AND_EXECUTE。

  注意:并不是所有的OpenGL函數(shù)都可以在顯示列表中存儲(chǔ)且通過(guò)顯示列表執(zhí)行。一般來(lái)說(shuō),用于傳遞參數(shù)或返回?cái)?shù)值的函數(shù)語(yǔ)句不能存入顯示列表,因?yàn)檫@張表有可能在參數(shù)的作用域之外被調(diào)用;如果在定義顯示列表時(shí)調(diào)用了這樣的函數(shù),則它們將按瞬時(shí)方式執(zhí)行并且不保存在顯示列表中,有時(shí)在調(diào)用執(zhí)行顯示列表函數(shù)時(shí)會(huì)產(chǎn)生錯(cuò)誤。以下列出的是不能存入顯示列表的OpenGL函數(shù):

  glDeleteLists()    glIsEnable()
  glFeedbackBuffer()   glIsList()
  glFinish()       glPixelStore()
  glGenLists()      glRenderMode()
  glGet*()        glSelectBuffer()

  在建立顯示列表以后就可以調(diào)用執(zhí)行顯示列表的函數(shù)來(lái)執(zhí)行它,并且允許在程序中多次執(zhí)行同一顯示列表,同時(shí)也可以與其它函數(shù)的瞬時(shí)方式混合使用。顯示列表執(zhí)行的函數(shù)形式如下:

  void glCallList(GLuint list);

  參數(shù)list指定被執(zhí)行的顯示列表。顯示列表中的函數(shù)語(yǔ)句按它們被存放的順序依次執(zhí)行;若list沒(méi)有定義,則不會(huì)產(chǎn)生任何事情。

  (二)管理顯示列表

  在實(shí)際應(yīng)用中,一般調(diào)用函數(shù)glGenList()來(lái)創(chuàng)建多個(gè)顯示列表,這樣可以避免意外刪除,產(chǎn)生一個(gè)沒(méi)有用過(guò)的顯示列表。此外,在管理顯示列表的過(guò)程中,還可調(diào)用函數(shù)glDeleteLists()來(lái)刪除一個(gè)或一個(gè)范圍內(nèi)的顯示列表。

  1)GLuint glGenList(GLsizei range)

  該函數(shù)分配range個(gè)相鄰的未被占用的顯示列表索引。這個(gè)函數(shù)返回的是一個(gè)正整數(shù)索引值,它是一組連續(xù)空索引的第一個(gè)值。返回的索引都標(biāo)志為空且已被占用,以后再調(diào)用這個(gè)函數(shù)時(shí)不再返回這些索引。若申請(qǐng)索引的指定數(shù)目不能滿足或range為0則函數(shù)返回0。

  2)GLboolean glIsList(GLuint list)

  該函數(shù)詢問(wèn)顯示列表是否已被占用的情況,若索引list已被占用,則函數(shù)返回TURE;反之,返回FAULSE。

  3)void glDeleteLists(GLuint list,GLsizei range)

  該函數(shù)刪除一組連續(xù)的顯示列表,即從參數(shù)list所指示的顯示列表開(kāi)始,刪除range個(gè)顯示列表,并且刪除后的這些索引重新有效。

  (三)多級(jí)顯示列表

  多級(jí)顯示列表的建立就是在一個(gè)顯示列表中調(diào)用另一個(gè)顯示列表,也就是說(shuō),在函數(shù)glNewList()與glEndList()之間調(diào)用glCallList()。多級(jí)顯示列表對(duì)于構(gòu)造由多個(gè)元件組成的物體十分有用,尤其是某些元件需要重復(fù)使用的情況。但為了避免無(wú)窮遞歸,顯示列表的嵌套深度最大為64(也許更高些,這依賴于不同的OpenGL實(shí)現(xiàn)),當(dāng)然也可調(diào)用函數(shù)glGetIntegerv()來(lái)獲得這個(gè)最大嵌套深度值。OpenGL也允許用一個(gè)顯示列表包含幾個(gè)低級(jí)的顯示列表來(lái)模擬建立一個(gè)可編輯的顯示列表。

  下面的一段代碼使用了列表嵌套來(lái)顯示一個(gè)三角形:

  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();