OpenGL復雜物體建模
目 錄13.1 圖元擴展
13.2 法向計算
13.3 曲線生成
13.4 曲面構造
在前面的章節中已經講述了最基本的幾何圖元建模,但在實際應用中只有這些圖元是很不夠的,要生成一個稍微復雜點的曲線、曲面或一些不規則的物體(例如山脈等)相當費事。令人比較高興的是,OpenGL基本庫(gl庫)和功能庫(glu庫)為我們提供了很大的方便,這一章將詳細介紹圖元擴展、法向計算、曲線生成和曲面構造等內容。
13.1 圖元擴展
13.1.1 點和線
下面分別介紹點和線的擴展形式及用法。
1)點。OpenGL中定義的點可以有不同的尺寸,其函數形式為:
void glPointSize(GLfloat size);
設置點的寬度(以象素為單位)。參數size必須大于0.0,缺省時為1.0。
2)線。OpenGL能指定線的各種寬度和繪制不同的虛點線,如點線、虛線等。相應的函數形式如下:
void glLineWidth(GLfloat width);
設置線寬(以象素為單位)。參數width必須大于0.0,缺省時為1.0。
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)關閉。下面舉出一個點線擴展應用實例:
例13-1 點線擴展應用例程(expntlin.c)
#include "glos.h" #include <GL/gl.h> #include <GL/glu.h> #include <GL/glaux.h> void myinit(void); void line2i(GLint x1,GLint y1,GLint x2,GLint y2); void CALLBACK display(void); void myinit (void) { glClearColor (0 , 0.0, 0.0, 0.0); glShadeModel (GL_FLAT); } void line2i(GLint x1,GLint y1,GLint x2,GLint y2) { glBegin(GL_LINES); glVertex2f(x1,y1); glVertex2f(x2,y2); glEnd(); } void CALLBACK display(void) { int i; glClear (GL_COLOR_BUFFER_BIT); /* 第一行繪制的是一系列大小尺寸不同的點(以象素為基本擴展單元) */ glColor3f(0.8,0.6,0.4); for (i = 1; i <= 10; i++) { glPointSize(i*2); glBegin (GL_POINTS); glVertex2f (30.0 + ((GLfloat) i * 50.0), 330.0); glEnd (); } /* 第二行繪制的是三條不同線型的線段 */ glEnable (GL_LINE_STIPPLE); glLineStipple (1, 0x0101); /* 點線 */ glColor3f(1.0 ,0.0,0.0); line2i (50, 250, 200, 250); glLineStipple (1, 0x00FF); /* 虛線 */ glColor3f(1.0,1.0,0.0); line2i (250 , 250 , 400, 250 ); glLineStipple (1, 0x1C47); /* 虛點線 */ glColor3f(0.0,1.0,0.0); line2i (450 , 250 , 600 , 250 ); /* 第三行繪制的是三條不同寬度的線段 */ glLineWidth (5.0); glLineStipple (1, 0x0101); glColor3f(1.0 ,0.0,0.0); line2i (50 , 200 , 200 , 200 ); glLineWidth (3.0); glLineStipple (1, 0x00FF); glColor3f(1.0 ,1.0,0.0); line2i (250 , 200 , 400 , 200 ); glLineWidth (2.0); glLineStipple (1, 0x1C47); glColor3f(0.0 ,1.0,0.0); line2i (450 , 200 , 600 , 200 ); /* 設置以下線段的寬度為 1 */ glLineWidth(1); /* 第四行繪制的是一條虛點線 */ glLineStipple (1, 0xff0c); glBegin (GL_LINE_STRIP); glColor3f(0.0 ,1.0,1.0); for (i = 0; i < 12; i++) glVertex2f (50.0 + ((GLfloat) i * 50.0), 150.0); glEnd (); /* 第五行繪制的是十條獨立的虛點斜線 */ glColor3f(0.4 ,0.3,0.8); for (i = 0; i < 10; i++) { line2i (50 + ( i * 50), 70, 75 + ((i+1) * 50), 100); } /* 第六行繪制的是一條虛點線,其中線型模式每個元素被重復操作5次 */ glLineStipple (5, 0x1C47); glColor3f(1.0 ,0.0,1.0); line2i (50 , 25 , 600 , 25 ); glFlush (); } void main(void) { auxInitDisplayMode (AUX_SINGLE | AUX_RGBA); auxInitPosition (0, 0, 650, 450); auxInitWindow ("External Points and Lines"); myinit (); auxMainLoop(display); }以上程序運行結果是顯示不同尺寸的點及不同線型和寬度的線的繪制方式。
![]() |
圖13-1 擴展點線 |
13.1.2 多邊形
多邊形的繪制模式包含有好幾種:全填充式、輪廓點式、輪廓線式以及圖案填充式。下面分別介紹相應的OpenGL函數形式。
1)多邊形模式設置。其函數為:
void glPolygonMode(GLenum face,GLenum mode);
控制多邊形指定面的繪制模式。參數face為GL_FRONT、GL_BACK或GL_FRONT_AND_BACK;參數mode為GL_POINT、GL_LINE或GL_FILL,分別表示繪制輪廓點式多邊形、輪廓線式多邊形或全填充式多邊形。缺省時,繪制的是正反面全填充式多邊形。
2)設置圖案填充式多邊形。其函數為:
void glPolygonStipple(const GLubyte *mask);
為當前多邊形定義填充圖案模式。參數mask是一個指向32x32位圖的指針。與虛點線繪制的道理一樣,某位為1時繪制,為0時什么也不繪。注意,在調用這個函數前,必須先啟動一下,即用glEnable(GL_POLYGON_STIPPLE);不用時用glDisable(GL_POLYGON_STIPPLE)關閉。下面舉出一個多邊形擴展繪制實例:
例 13-2 多邊形圖案填充例程(polystpl.c)
#include "glos.h" #include <GL/gl.h> #include <GL/glu.h> #include <GL/glaux.h> void myinit(void); void CALLBACK display(void); void myinit (void) { glClearColor (0.0, 0.0, 0.0, 0.0); glShadeModel (GL_FLAT); } 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.1,0.8,0.7); glEnable (GL_POLYGON_STIPPLE); glPolygonStipple (pattern); glRectf (48.0, 80.0, 210.0, 305.0); /* 繪制一個指定圖案填充的三角形 */ 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 main(void) { auxInitDisplayMode (AUX_SINGLE | AUX_RGBA); auxInitPosition (0, 0, 500, 400); auxInitWindow ("Polygon Stippling"); myinit (); auxMainLoop(display); }
![]() |
圖13-2 圖案填充多邊形 |
13.2、法向計算
法向,又稱法向量(Mormal Vector)。對于一個平面,其上各點的法向的一樣,統一為這個平面的法向,所以稱為平面法向。對于一個曲面,雖然它在計算機圖形中是由許多片小的平面多邊形逼近,但是每個頂點的法向都不一樣,因此曲面上每個點的法向計算就可以根據不同的應用有不同的算法,則最后效果也不相同。OpenGL有很大的靈活性,它只提供賦予當前頂點法向的函數,并不在內部具體計算其法向量,這個值由編程者自己根據需要計算。下面介紹一下法向基本計算方法和OpenGL法向定義。
13.2.1 法向基本計算方法
首先,講述平面法向的計算方法。在一個平面內,有兩條相交的線段,假設其中一條為矢量W,另一條為矢量V,且平面法向為N,如圖13-3所示,則平面法向就等于兩個矢量的叉積(遵循右手定則),即N=WxV。
![]() |
圖13-3 平面法向計算 |
比如計算一個三角形平面的法向,就可以用它的三個頂點來計算,如圖13-4所示。
![]() |
圖13-4 三角形平面法向計算 |
設三個頂點分別為P0、P1、P2,相應兩個向量為W、V,則三角平面法向的計算方式見下列一段代碼:
/* ------ get value of N (normal vector) ---------- */ 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,它指向法向的三個分量,并且程序中已經將法向單位化(或歸一化)了。
此外,對于曲面各頂點的法向計算有很多種,最常用的是平均平面法向法,如圖15-5 所示。在圖中,曲面頂點P的法向就等于其相鄰的四個平面的法向平均值,即:
Np = (N1+N2+N3+N4)/4 |
![]() |
圖13-5 曲面頂點的平均法向計算 |
13.2.2 法向定義
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}。因為法向的各分量值只定義法向的方向,因此它的大小不固定,但建議最好將各值限制在[-1.0,1.0]之間,即法向歸一化;若法向不歸一化,則在定義法向之前必須啟動法向歸一,即調用函數glEnable(GL_NORMALIZE),這樣會降低整個程序運行性能。下面舉出一個自己定義法向的例子:
例13-3 自定義顏色立方體法向例程(nmlcolr.c)
#include "glos.h" #include <GL/gl.h> #include <GL/glu.h> #include <GL/glaux.h> static GLfloat p1[]={0.5,-0.5,-0.5}, p2[]={0.5,0.5,-0.5}, p3[]={0.5,0.5,0.5}, p4[]={0.5,-0.5,0.5}, p5[]={-0.5,-0.5,0.5}, p6[]={-0.5,0.5,0.5}, p7[]={-0.5,0.5,-0.5}, p8[]={-0.5,-0.5,-0.5}; static GLfloat m1[]={1.0,0.0,0.0}, m2[]={-1.0,0.0,0.0}, m3[]={0.0,1.0,0.0}, m4[]={0.0,-1.0,0.0}, m5[]={0.0,0.0,1.0}, m6[]={0.0,0.0,-1.0}; static GLfloat c1[]={0.0,0.0,1.0}, c2[]={0.0,1.0,1.0}, c3[]={1.0,1.0,1.0}, c4[]={1.0,0.0,1.0}, c5[]={1.0,0.0,0.0}, c6[]={1.0,1.0,0.0}, c7[]={0.0,1.0,0.0}, c8[]={1.0,1.0,1.0}; void myinit(void); void CALLBACK myReshape(GLsizei w, GLsizei h); void CALLBACK display(void); void DrawColorBox(void); void myinit(void) { GLfloat light_ambient[]={0.3,0.2,0.5}; GLfloat light_diffuse[]={1.0,1.0,1.0}; GLfloat light_position[] = { 2.0, 2.0, 2.0, 1.0 }; GLfloat light1_ambient[]={0.3,0.3,0.2}; GLfloat light1_diffuse[]={1.0,1.0,1.0}; GLfloat light1_position[] = { -2.0, -2.0, -2.0, 1.0 }; glLightfv(GL_LIGHT0, GL_AMBIENT, light_ambient); glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse); glLightfv(GL_LIGHT0, GL_POSITION, light_position); glLightfv(GL_LIGHT1, GL_AMBIENT, light1_ambient); glLightfv(GL_LIGHT1, GL_DIFFUSE, light1_diffuse); glLightfv(GL_LIGHT1, GL_POSITION, light1_position); glEnable(GL_LIGHTING); glEnable(GL_LIGHT0); glEnable(GL_LIGHT1); glDepthFunc(GL_LESS); glEnable(GL_DEPTH_TEST); glColorMaterial(GL_FRONT_AND_BACK,GL_DIFFUSE); glEnable(GL_COLOR_MATERIAL); } void CALLBACK display(void) { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glPushMatrix(); glRotatef(45,0.0,1.0,0.0); glRotatef(315,0.0,0.0,1.0); DrawColorBox(); glPopMatrix(); glFlush(); } void CALLBACK myReshape(GLsizei w, GLsizei h) { glViewport(0, 0, w, h); glMatrixMode(GL_PROJECTION); glLoadIdentity(); if (w <= h) glOrtho (-1.5, 1.5, -1.5*(GLfloat)h/(GLfloat)w,1.50*(GLfloat)h/(GLfloat)w, -10.0, 10.0); else glOrtho (-1.5*(GLfloat)w/(GLfloat)h,1.5*(GLfloat)w/(GLfloat)h, -1.5, 1.5, -10.0, 10.0); glMatrixMode(GL_MODELVIEW); glLoadIdentity (); } void DrawColorBox(void) { glBegin (GL_QUADS); glColor3fv(c1); glNormal3fv(m1); glVertex3fv(p1); glColor3fv(c2); glVertex3fv(p2); glColor3fv(c3); glVertex3fv(p3); glColor3fv(c4); glVertex3fv(p4); glColor3fv(c5); glNormal3fv(m5); glVertex3fv(p5); glColor3fv(c6); glVertex3fv(p6); glColor3fv(c7); glVertex3fv(p7); glColor3fv(c8); glVertex3fv(p8); glColor3fv(c5); glNormal3fv(m3); glVertex3fv(p5); glColor3fv(c6); glVertex3fv(p6); glColor3fv(c3); glVertex3fv(p3); glColor3fv(c4); glVertex3fv(p4); glColor3fv(c1); glNormal3fv(m4); glVertex3fv(p1); glColor3fv(c2); glVertex3fv(p2); glColor3fv(c7); glVertex3fv(p7); glColor3fv(c8); glVertex3fv(p8); glColor3fv(c2); glNormal3fv(m5); glVertex3fv(p2); glColor3fv(c3); glVertex3fv(p3); glColor3fv(c6); glVertex3fv(p6); glColor3fv(c7); glVertex3fv(p7); glColor3fv(c1); glNormal3fv(m6); glVertex3fv(p1); glColor3fv(c4); glVertex3fv(p4); glColor3fv(c5); glVertex3fv(p5); glColor3fv(c8); glEnd(); } void main(void) { auxInitDisplayMode (AUX_SINGLE | AUX_RGBA); auxInitPosition (0, 0, 500,400); auxInitWindow ("ColorBox"); myinit(); auxReshapeFunc (myReshape); auxMainLoop(display); }以上程序運行結果是一個自定義法向的彩色正方體,其中每個頂點的顏色值不一樣,且為光滑的明暗處理模式。
| |
圖13-6 自定義法向的彩色立方體 |
13.3、曲線生成
計算機圖形學中,所有的光滑曲線都采用線段逼近來模擬,而且許多有用的曲線在數學上只用少數幾個參數(如控制點等)來描述。本節簡要地介紹一下OpenGL中Bezier曲線的繪制方法。
13.3.1 曲線繪制舉例
下面我們來看一個簡單的例子,這是用四個控制頂點來畫一條三次Bezier曲線。程序如下:
例13-4 Bezier曲線繪制例程(bzcurve.c)
#include "glos.h" #include <GL/gl.h> #include <GL/glu.h> #include <GL/glaux.h> void myinit(void); void CALLBACK myReshape(GLsizei w, GLsizei h); void CALLBACK display(void); GLfloat ctrlpoints[4][3] = { { -4.0, -4.0, 0.0 }, { -2.0, 4.0, 0.0 }, { 2.0, -4.0, 0.0 }, { 4.0, 4.0, 0.0 } }; void myinit(void) { glClearColor(0.0, 0.0, 0.0, 1.0); glMap1f(GL_MAP1_VERTEX_3, 0.0, 1.0, 3, 4, &ctrlpoints[0][0]); glEnable(GL_MAP1_VERTEX_3); glShadeModel(GL_FLAT); } void CALLBACK display(void) { int i; glClear(GL_COLOR_BUFFER_BIT); glColor3f(1.0, 1.0, 1.0); glBegin(GL_LINE_STRIP); for (i = 0; i <= 30; i++) glEvalCoord1f((GLfloat) i/30.0); glEnd(); /* 顯示控制點 */ glPointSize(5.0); glColor3f(1.0, 1.0, 0.0); glBegin(GL_POINTS); for (i = 0; i < 4; i++) glVertex3fv(&ctrlpoints[i][0]); glEnd(); glFlush(); } void CALLBACK myReshape(GLsizei w, GLsizei h) { glViewport(0, 0, w, h); glMatrixMode(GL_PROJECTION); glLoadIdentity(); if (w <= h) glOrtho(-5.0, 5.0, -5.0*(GLfloat)h/(GLfloat)w, 5.0*(GLfloat)h/(GLfloat)w, -5.0, 5.0); else glOrtho(-5.0*(GLfloat)w/(GLfloat)h, 5.0*(GLfloat)w/(GLfloat)h, -5.0, 5.0, -5.0, 5.0); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); } void main(void ) { auxInitDisplayMode (AUX_SINGLE | AUX_RGBA); auxInitPosition (0, 0, 500, 500); auxInitWindow ("Bezier Curves"); myinit(); auxReshapeFunc (myReshape); auxMainLoop(display); }
![]() |
圖13-7 一條光滑的Bezier曲線 |
13.3.2 曲線定義和啟動
OpenGL中曲線定義的函數為:
void glMap1{fd}(GLenum target,TYPE u1,TYPE u2,GLint stride, GLint order,const TYPE *points);函數的第一個參數target指出控制頂點的意義以及在參數points中需要提供多少值,具體值見表13-1所示。參數points指針可以指向控制點集、RGBA顏色值或紋理坐標串等。例如若target是GL_MAP1_COLOR_4,則就能在RGBA四維空間中生成一條帶有顏色信息的曲線,這在數據場可視化中應用極廣。參數u1和u2,指明變量U的范圍,U一般從0變化到1。參數stride是跨度,表示在每塊存儲區內浮點數或雙精度數的個數,即兩個控制點間的偏移量,比如上例中的控制點集ctrpoint[4][3]的跨度就為3,即單個控制點的坐標元素個數。函數參數order是次數加1,叫階數,與控制點數一致。
參數 | 意義 |
GL_MAP1_VERTEX_3 | x,y,z 頂點坐標 |
GL_MAP1_VERTEX_4 | x,y,z,w 頂點坐標 |
GL_MAP1_INDEX | 顏色表 |
GL_MAP1_COLOR_4 | R,G,B,A |
GL_MAP1_NORMAL | 法向量 |
GL_MAP1_TEXTURE_COORD_1 | s 紋理坐標 |
GL_MAP1_TEXTURE_COORD_2 | s,t 紋理坐標 |
GL_MAP1_TEXTURE_COORD_3 | s,t,r 紋理坐標 |
GL_MAP1_TEXTURE_COORD_4 | s,t,r,q 紋理坐標 |
表13-1 用于glMap1*()控制點的數據類型 |
曲線定義后,必須要啟動,才能進行下一步的繪制工作。啟動函數仍是glEnable(),其中參數與glMap1*()的第一個參數一致。同樣,關閉函數為glDisable(),參數也一樣。
13.3.3 曲線坐標計算
這里提到的坐標概念是廣義的,與以前定義的有點不同,具體地說就是表13-1所對應的類型值。OpenGL曲線坐標計算的函數形式如下:
void glEvalCoord1{fd}[v](TYPE u);
產生曲線坐標值并繪制。參數u是定義域內的值,這個函數調用一次只產生一個坐標。
13.3.4 定義均勻間隔曲線坐標值
在使用glEvalCoord1*()計算坐標,因為u可取定義域內的任意值,所以由此計算出的坐標值也是任意的。但是,目前用得最普遍的仍是取等間隔值。要獲得等間隔值,OpenGL提供了兩個函數,即先調用glMapGrid1*()定義一個一維網格,然后用glEvalMesh1()計算響應的坐標值。下面詳細解釋這兩個函數:
void glMapGrid1{fd}(GLint n,TYPE u1,TYPE u2);
定義一個網格,從u1到u2分為n步,它們是等間隔的。實際上,這個函數定義的是參數空間網格。
void glEvalMesh1(GLenum mode,GLint p1,GLint p2);
計算并繪制坐標點。參數mode可以是GL_POINT或GL_LINE,即沿曲線繪制點或沿曲線繪制相連的線段。這個函數的調用效果同在p1和p2之間的每一步給出一個glEvalCoord1()的效果一樣。從編程角度來說,除了當i=0或i=n,它準確以u1或u2作為參數調用glEvalCoord1()之外,它等價于一下代碼:
glBegin(GL_POINT);/* glBegin(GL_LINE_STRIP); */ for(i=p1;i<=p2;i++) glEvalCoord1(u1+i*(u2-u1)/n); glEnd();
13.4、曲面構造
同樣,計算機圖形學中的所有光滑曲面也都采用多邊形逼近來繪制,而且許多有用的曲面在數學上也只用少數幾個參數(如控制點或網等)來描述。通常,若用16個控制點描述一個曲面,要比用1000多個三角形和每個頂點的法向信息要節省很多內存。而且,1000個三角形僅僅只逼近曲面,而控制點可以精確地描述實際曲面,且可自動計算法向。本節簡要地介紹一下OpenGL中Bezier曲面的繪制方法,所有相關的函數都與曲線的情況類似,只是二維空間而已。
13.4.1 曲面定義和坐標計算
曲面定義函數為:
void glMap2{fd}(GLenum target,TYPE u1,TYPE u2,GLint ustride,GLint uorder, TYPE v1,TYPE v2,GLint vstride,GLint vorder,TYPE points);
參數target可以是表13-1中任意值,不過需將MAP1改為MAP2。同樣,啟動曲面的函數仍是glEnable(),關閉是glDisable()。u1、u2為u的最大值和最小值;v1、v2為v的最大值和最小值。參數ustride和vstride指出在控制點數組中u和v向相鄰點的跨度,即可從一個非常大的數組中選擇一塊控制點長方形。例如,若數據定義成如下形式:
GLfloat ctlpoints[100][100][3];
并且,要用從ctlpoints[20][30]開始的4x4子集,選擇ustride為100*3,vstride為3,初始點設置為ctlpoints[20][30][0]。最后的參數都是階數,uorder和vorder,二者可以不同。曲面坐標計算函數為:
void glEvalCoord2{fd}[v](TYPE u,TYPE v);
產生曲面坐標并繪制。參數u和v是定義域內的值。下面看一個繪制Bezier曲面的例子:
例13-5 Bezier網狀曲面繪制例程(bzwiresf.c)
#include "glos.h" #include <GL/gl.h> #include <GL/glu.h> #include <GL/glaux.h> void myinit(void); void CALLBACK myReshape(GLsizei w, GLsizei h); void CALLBACK display(void); /* 控制點的坐標 */ GLfloat ctrlpoints[4][4][3] = { {{-1.5, -1.5, 2.0}, {-0.5, -1.5, 2.0}, {0.5, -1.5, -1.0}, {1.5, -1.5, 2.0}}, {{-1.5, -0.5, 1.0}, {-0.5, 1.5, 2.0}, {0.5, 0.5, 1.0}, {1.5, -0.5, -1.0}}, {{-1.5, 0.5, 2.0}, {-0.5, 0.5, 1.0}, {0.5, 0.5, 3.0}, {1.5, -1.5, 1.5}}, {{-1.5, 1.5, -2.0}, {-0.5, 1.5, -2.0}, {0.5, 0.5, 1.0}, {1.5, 1.5, -1.0}} }; void myinit(void) { glClearColor (0.0, 0.0, 0.0, 1.0); glMap2f(GL_MAP2_VERTEX_3, 0, 1, 3, 4, 0, 1, 12, 4, &ctrlpoints[0][0][0]); glEnable(GL_MAP2_VERTEX_3); glMapGrid2f(20, 0.0, 1.0, 20, 0.0, 1.0); glEnable(GL_DEPTH_TEST); } void CALLBACK display(void) { int i, j; glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glColor3f(0.3, 0.6, 0.9); glPushMatrix (); glRotatef(35.0, 1.0, 1.0, 1.0); for (j = 0; j <= 8; j++) { glBegin(GL_LINE_STRIP); for (i = 0; i <= 30; i++) glEvalCoord2f((GLfloat)i/30.0, (GLfloat)j/8.0); glEnd(); glBegin(GL_LINE_STRIP); for (i = 0; i <= 30; i++) glEvalCoord2f((GLfloat)j/8.0, (GLfloat)i/30.0); glEnd(); } glPopMatrix (); glFlush(); } void CALLBACK myReshape(GLsizei w, GLsizei h) { glViewport(0, 0, w, h); glMatrixMode(GL_PROJECTION); glLoadIdentity(); if (w <= h) glOrtho(-4.0, 4.0, -4.0*(GLfloat)h/(GLfloat)w, 4.0*(GLfloat)h/(GLfloat)w, -4.0, 4.0); else glOrtho(-4.0*(GLfloat)w/(GLfloat)h, 4.0*(GLfloat)w/(GLfloat)h, -4.0, 4.0, -4.0, 4.0); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); } void main(void) { auxInitDisplayMode (AUX_SINGLE | AUX_RGBA); auxInitPosition (0, 0, 500, 500); auxInitWindow ("Wireframe Bezier Surface"); myinit(); auxReshapeFunc (myReshape); auxMainLoop(display); }以上程序運行結果是一個網狀的曲面。
![]() |
圖13-8 Bezier網狀曲面 |
13.4.2 定義均勻間隔的曲面坐標值
OpenGL中定義均勻間隔的曲面坐標值的函數與曲線的類似,其函數形式為:
void glMapGrid2{fd}(GLenum nu,TYPE u1,TYPE u2, GLenum nv,TYPE v1,TYPE v2); void glEvalMesh2(GLenum mode,GLint p1,GLint p2,GLint q1,GLint q2);
第一個函數定義參數空間的均勻網格,從u1到u2分為等間隔的nu步,從v1到v2分為等間隔的nv步,然后glEvalMesh2()把這個網格應用到已經啟動的曲面計算上。第二個函數參數mode除了可以是GL_POINT和GL_LINE外,還可以是GL_FILL,即生成填充空間曲面。下面舉出一個用網格繪制一個經過光照和明暗處理的Bezier曲面的例程:
例13-6 加光照的均勻格網Bezier曲面繪制例程(bzmesh.c)
#include "glos.h" #include <GL/gl.h> #include <GL/glu.h> #include <GL/glaux.h> void myinit(void); void initlights(void); void CALLBACK myReshape(GLsizei w, GLsizei h); void CALLBACK display(void); /* 控制點坐標 */ GLfloat ctrlpoints[4][4][3] = { {{-1.5, -1.5, 2.0}, {-0.5, -1.5, 2.0}, {0.5, -1.5, -1.0}, {1.5, -1.5, 2.0}}, {{-1.5, -0.5, 1.0}, {-0.5, 1.5, 2.0}, {0.5, 0.5, 1.0}, {1.5, -0.5, -1.0}}, {{-1.5, 0.5, 2.0}, {-0.5, 0.5, 1.0}, {0.5, 0.5, 3.0}, {1.5, -1.5, 1.5}}, {{-1.5, 1.5, -2.0}, {-0.5, 1.5, -2.0}, {0.5, 0.5, 1.0}, {1.5, 1.5, -1.0}} }; void initlights(void) { GLfloat ambient[] = { 0.4, 0.6, 0.2, 1.0 }; GLfloat position[] = { 0.0, 1.0, 3.0, 1.0 }; GLfloat mat_diffuse[] = { 0.2, 0.4, 0.8, 1.0 }; GLfloat mat_specular[] = { 1.0, 1.0, 1.0, 1.0 }; GLfloat mat_shininess[] = { 80.0 }; glEnable(GL_LIGHTING); glEnable(GL_LIGHT0); glLightfv(GL_LIGHT0, GL_AMBIENT, ambient); glLightfv(GL_LIGHT0, GL_POSITION, position); glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse); glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular); glMaterialfv(GL_FRONT, GL_SHININESS, mat_shininess); } void CALLBACK display(void) { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glPushMatrix(); glRotatef(35.0, 1.0, 1.0, 1.0); glEvalMesh2(GL_FILL, 0, 20, 0, 20); glPopMatrix(); glFlush(); } void myinit(void) { glClearColor (0.0, 0.0, 0.0, 1.0); glEnable (GL_DEPTH_TEST); glMap2f(GL_MAP2_VERTEX_3, 0, 1, 3, 4, 0, 1, 12, 4, &ctrlpoints[0][0][0]); glEnable(GL_MAP2_VERTEX_3); glEnable(GL_AUTO_NORMAL); glEnable(GL_NORMALIZE); glMapGrid2f(20, 0.0, 1.0, 20, 0.0, 1.0); initlights(); } void CALLBACK myReshape(GLsizei w, GLsizei h) { glViewport(0, 0, w, h); glMatrixMode(GL_PROJECTION); glLoadIdentity(); if (w <= h) glOrtho(-4.0, 4.0, -4.0*(GLfloat)h/(GLfloat)w, 4.0*(GLfloat)h/(GLfloat)w, -4.0, 4.0); else glOrtho(-4.0*(GLfloat)w/(GLfloat)h, 4.0*(GLfloat)w/(GLfloat)h, -4.0, 4.0, -4.0, 4.0); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); } void main(void) { auxInitDisplayMode (AUX_SINGLE | AUX_RGBA); auxInitPosition (0, 0, 500, 500); auxInitWindow ("Lighted and Filled Bezier Surface"); myinit(); auxReshapeFunc (myReshape); auxMainLoop(display); }以上程序運行結果是一個加上光影的曲面。
![]() |
圖13-9 帶光影的曲面 |
13.4.3 紋理曲面
在前面我們已經講過紋理的用法,這一節將結合曲面的生成試試紋理的應用。下面我們先看一個例子:
例13-17 紋理曲面例程繪制(texsurf.c)
#include "glos.h" #include <GL/gl.h> #include <GL/glu.h> #include <GL/glaux.h> #include <math.h> void myinit(void); void makeImage(void); void CALLBACK display(void); void CALLBACK myReshape(GLsizei w, GLsizei h); GLfloat ctrlpoints[4][4][3] = { {{-1.5, -1.5, 2.0}, {-0.5, -1.5, 2.0}, {0.5, -1.5, -1.0}, {1.5, -1.5, 2.0}}, {{-1.5, -0.5, 1.0}, {-0.5, 1.5, 2.0}, {0.5, 0.5, 1.0}, {1.5, -0.5, -1.0}}, {{-1.5, 0.5, 2.0}, {-0.5, 0.5, 1.0}, {0.5, 0.5, 3.0}, {1.5, -1.5, 1.5}}, {{-1.5, 1.5, -2.0}, {-0.5, 1.5, -2.0}, {0.5, 0.5, 1.0}, {1.5, 1.5, -1.0}} }; GLfloat texpts[2][2][2] = {{{0.0, 0.0}, {0.0, 1.0}}, {{1.0, 0.0}, {1.0, 1.0}}}; void CALLBACK display(void) { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glColor3f(1.0, 1.0, 1.0); glEvalMesh2(GL_FILL, 0, 20, 0, 20); glFlush(); } #define imageWidth 64 #define imageHeight 64 GLubyte image[3*imageWidth*imageHeight]; void makeImage(void) { int i, j; float ti, tj; for (i = 0; i < imageWidth; i++) { ti = 2.0*3.14159265*i/imageWidth; for (j = 0; j < imageHeight; j++) { tj = 2.0*3.14159265*j/imageHeight; image[3*(imageHeight*i+j)] = (GLubyte) 127*(1.0+sin(ti)); image[3*(imageHeight*i+j)+1] = (GLubyte) 127*(1.0+cos(2*tj)); image[3*(imageHeight*i+j)+2] = (GLubyte) 127*(1.0+cos(ti+tj)); } } } void myinit(void) { glMap2f(GL_MAP2_VERTEX_3, 0, 1, 3, 4, 0, 1, 12, 4, &ctrlpoints[0][0][0]); glMap2f(GL_MAP2_TEXTURE_COORD_2, 0, 1, 2, 2, 0, 1, 4, 2, &texpts[0][0][0]); glEnable(GL_MAP2_TEXTURE_COORD_2); glEnable(GL_MAP2_VERTEX_3); glMapGrid2f(20, 0.0, 1.0, 20, 0.0, 1.0); makeImage(); glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexImage2D(GL_TEXTURE_2D, 0, 3, imageWidth, imageHeight, 0, GL_RGB, GL_UNSIGNED_BYTE, image); glEnable(GL_TEXTURE_2D); glEnable(GL_DEPTH_TEST); glEnable(GL_NORMALIZE); glShadeModel (GL_FLAT); } void CALLBACK myReshape(GLsizei w, GLsizei h) { glViewport(0, 0, w, h); glMatrixMode(GL_PROJECTION); glLoadIdentity(); if (w <= h) glOrtho(-4.0, 4.0, -4.0*(GLfloat)h/(GLfloat)w, 4.0*(GLfloat)h/(GLfloat)w, -4.0, 4.0); else glOrtho(-4.0*(GLfloat)w/(GLfloat)h, 4.0*(GLfloat)w/(GLfloat)h, -4.0, 4.0, -4.0, 4.0); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glRotatef(35.0, 1.0, 1.0, 1.0); } void main(void) { auxInitDisplayMode (AUX_SINGLE | AUX_RGBA); auxInitPosition (0, 0, 500, 400); auxInitWindow ("Texture Surface"); myinit(); auxReshapeFunc (myReshape); auxMainLoop(display); }以上程序運行結果是一個帶紋理的曲面。
![]() |
圖13-10 帶紋理的曲面 |
13.4.4 NURBS曲面
OpenGL的功能庫提供了一系列NURBS曲面(非均勻有理B樣條曲面)的函數。本節不具體講各函數的用法,僅舉出一個應用例子,其余的讀者可以參考有關手冊。例程如下:
例13-8 NURBS曲面繪制例程(nurbsurf.c)
#include "glos.h" #include <GL/gl.h> #include <GL/glu.h> #include <GL/glaux.h> void myinit(void); void init_surface(void); void CALLBACK display(void); void CALLBACK myReshape(GLsizei w, GLsizei h); GLfloat ctlpoints[4][4][3]; GLUnurbsObj *theNurb; /* 初始化控制點坐標,x,y,z范圍從-3到3 */ void init_surface(void) { int u, v; for (u = 0; u < 4; u++) { for (v = 0; v < 4; v++) { ctlpoints[u][v][0] = 2.0*((GLfloat)u - 1.5); ctlpoints[u][v][1] = 2.0*((GLfloat)v - 1.5); if ( (u == 1 || u == 2) && (v == 1 || v == 2)) ctlpoints[u][v][2] = 3.0; else ctlpoints[u][v][2] = -3.0; } } } /* 定義曲面材質 (金色) */ void myinit(void) { GLfloat mat_diffuse[] = { 0.88, 0.66, 0.22, 1.0 }; GLfloat mat_specular[] = { 0.92, 0.9, 0.0, 1.0 }; GLfloat mat_shininess[] = { 80.0 }; glClearColor (0.0, 0.0, 0.0, 1.0); glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse); glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular); glMaterialfv(GL_FRONT, GL_SHININESS, mat_shininess); glEnable(GL_LIGHTING); glEnable(GL_LIGHT0); glDepthFunc(GL_LESS); glEnable(GL_DEPTH_TEST); glEnable(GL_AUTO_NORMAL); glEnable(GL_NORMALIZE); init_surface(); theNurb = gluNewNurbsRenderer(); gluNurbsProperty(theNurb, GLU_SAMPLING_TOLERANCE, 25.0); gluNurbsProperty(theNurb, GLU_DISPLAY_MODE, GLU_FILL); } void CALLBACK display(void) { GLfloat knots[8] = {0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0}; glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glPushMatrix(); glRotatef(330.0, 1.,0.,0.); glScalef (0.5, 0.5, 0.5); gluBeginSurface(theNurb); gluNurbsSurface(theNurb, 8, knots, 8, knots, 4 * 3, 3, &ctlpoints[0][0][0], 4, 4, GL_MAP2_VERTEX_3); gluEndSurface(theNurb); glPopMatrix(); glFlush(); } void CALLBACK myReshape(GLsizei w, GLsizei h) { glViewport(0, 0, w, h); glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective (45.0, (GLdouble)w/(GLdouble)h, 3.0, 8.0); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glTranslatef (0.0, 1.0, -5.0); } void main(void) { auxInitDisplayMode (AUX_SINGLE | AUX_RGBA); auxInitPosition (0, 0, 500, 500); auxInitWindow ("NURBS Surface"); myinit(); auxReshapeFunc (myReshape); auxMainLoop(display); }以上程序運行結果是一個銀白色的NURBS曲面。
![]() |
圖13-11 NURBS曲面 |