程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> 基於VC++的OpenGL編程講座之曲線和曲面

基於VC++的OpenGL編程講座之曲線和曲面

編輯:C++入門知識

  計算機圖形學中,所有的光滑曲線、曲面都采用線段或三角形逼近來模擬,但為了精確地表現曲線,通常需要成千上萬個線段或三角形來逼近,這種方法對於計算機的硬件資源有相當高的要求。然而,許多有用的曲線、曲面在數學上只需要用少數幾個參數(如控制點等)來描述。這種方法所需要的存儲空間比線段、三角形逼近的方法來所需要的空間要小得多,並且控制點方法描述的曲線、曲面比線段、三角形逼近的曲線、曲面更精確。
   為了說明如何在OpenGL中繪制復雜曲線和曲面,我們對上述兩類比方法都進行了介紹。下面我們先來介紹有關基礎知識,然後再看是如何實現的吧。
  
      一、曲線的繪制
  
     OpenGL通過一種求值器的機制來產生曲線和曲面,該機制非常靈活,可以生成任意角度的多項式曲線,並可以將其他類型的多邊形曲線和曲面轉換成貝塞爾曲線和曲面。這些求值器能在任何度的曲線及曲面上計算指定數目的點。隨後,OpenGL利用曲線和曲面上的點生成標准OpenGL圖元,例如與曲線或曲面近似的線段和多邊形。由於可讓OpenGL計算在曲線上所需的任意數量的點,因此可以達到應用所需的精度。
   對於曲線,OpenGL中使用glMap1*()函數來創建一維求值器,該函數原型為:
  
   void glMap1{fd}(GLenum target,TYPE u1,TYPE u2,
   GLint stride, GLint order,const TYPE *points);

     函數的第一個參數target指出控制頂點的意義以及在參數points中需要提供多少值,具體值見表一所示。參數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 紋理坐標   表一、參數target的取值表
  
     使用求值器定義曲線後,必須要啟動求值器,才能進行下一步的繪制工作。啟動函數仍是glEnable(),其中參數與glMap1*()的第一個參數一致。同樣,關閉函數為glDisable(),參數也一樣。
  
     一旦啟動一個或多個求值器,我們就可以構造近似曲線了。最簡單的方法是通過調用計算坐標函數glEvalcoord1*()替換所有對函數glVertex*()的調用。與glVertex*()使用二維、三維和四維坐標不同,glEvalcoord1*()將u值傳給所有已啟動的求值器,然後由這些已啟動的求值器生成坐標、法向量、顏色或紋理坐標。OpenGL曲線坐標計算的函數形式如下:
  
   void glEvalCoord1{fd}[v](TYPE u);
     該函數產生曲線坐標值並繪制。參數u是定義域內的值,這個函數調用一次只產生一個坐標。在使用glEvalCoord1*()計算坐標,因為u可取定義域內的任意值,所以由此計算出的坐標值也是任意的。
  
     使用glEvalCoord1*()函數的優點是,可以對U使用任意值,然而,假如想對u使用N個不同的值,就必須對glEvalCoord1*()函數執行N次調用,為此,OpenGL提供了等間隔值取值法,即先調用glMapGrid1*()定義一個間隔相等的一維網格,然後再用glEvalMesh1()通過一次函數執行,將求值器應用在網格上,計算相應的坐標值。下面具體解釋這兩個函數:
  
     1、void glMapGrid1{fd}(GLint n,TYPE u1,TYPE u2);
  

     定義一個網格,從u1到u2分為n步,它們是等間隔的。實際上,這個函數定義的是參數空間網格。
  
     2、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();

     為了進一步說明OpenGL中曲線的繪制方法。下面我們來看一個簡單的例子,這是用四個控制頂點來畫一條三次Bezier曲線。程序如下(注:這是本講座中提供的第一個完整的OpenGL實例代碼,假如讀者朋友對整個程序結構有些迷惑的話,也不要緊,慢慢地往下看,先有一個感官上的印象,主要是把握如何實現曲線繪制這一部分。關於OpenGL的程序整體結構實現,筆者將在第五講中專門闡述):
  
   #include "glos.h"
   #include
   #include
   #include
   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();
      }

   二、曲面構造
  
     曲面的繪制方法基本上與曲線的繪制方法是相同的,所不同的是曲面使用二維求值器,並且控制點連接起來形成一個網格。
  
     對於曲面,求值器除了使用二個參數U、V之外,其余與一維求值器基本相同。頂點坐標 、顏色、法線矢量和紋理坐標都對應於曲面而不是曲線。在OpenGL中定義二維求值器的函數是:
  
   void glMap2{fd}(GLenum target,TYPE u1,TYPE u2,GLint ustride,GLint uorder,TYPE v1,TYPE v2,
   GLint vstride,GLint vorder,TYPE points);

     參數target可以是表一中任意值,不過需將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曲面的例子:
  
   /* 控制點的坐標 */
      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();
      }

     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曲面的例程:
  
  
   #include "glos.h"
   #include
   #include
   #include
   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);
   }

   三、圖元逼近法繪制三維物體
     在OpenGL的輔助庫中,提供了繪制11種基本幾何圖形的函數,具體參考第一講的有關內容,在此不再贅述。這裡我們討論用另外一種方法來繪制三維物體 。
     需要注重的是,這裡我們用來近似曲面的多邊形最好選擇三角形,而不是四邊形或其他外形的多邊形,這是因為三角形的三個頂點在任何時候都位於同一平面內,它一定是非常簡單的非凹多邊形,而四邊形或其他多邊形的頂點可能不在同一平面內,也就有可能不是簡單多邊形,對於這樣的多邊形,OpenGL是不能正常處理的。假設我們繪制一個球體,球體表面用很多個小三角形拼接而成,顯然,用來近似球面的三角形越小、三角形越多,那麼球面就越光滑。為了簡要地說明如何用三角形逼近球體,這裡我們使用三角形來構造一個20面體,二十面體的頂點坐標定義在vdata[][]數組中,tindinces[][]數組定義了構成二十面體的二十個三角形頂點的繪制順序。下面是主要實現代碼:
   #define x 5.25731
   #define z 8.50651
   static GLfloat vdata[12][3]={
   {x,0.0,z},{x,0.0,z},{-x,0.0,-z},{x,0.0,-z},
   {0.0,z,x},{0.0,z,-x},{0.0,-z-x},{0.0,-z,-x},
   {z,x,0.0},{-z,x,0.0},{z,-x,0.0},{-z,-x,0.0}
   };
   static GLint tindices[20][3]={
   {0,4,1},{0,9,4},{9,5,4},{4,5,8},{4,8,1},
   {8,10,1},{8,3,10},{5,3,8},{5,2,3},{2,7,3},
   {7,10,3},{7,6,10},{7,11,6},{11,0,6},
   {6,1,10},{9,0,11},{9,11,2},{9,2,5},{7,2,11}
   };
   glColor3f(1.0, 0.0, 0.0);
   for(int i=0;i <20;i++){
   glBegin(GL_TRIANGLES);
   glVertex3fv(&vdata[tindices[i][0]][0]);
   glVertex3fv(&vdata[tindices[i][1]][0]);
   glVertex3fv(&vdata[tindices[i][2]][0]);
   glEnd();
   }

     顯然,用正二十面體來表示一個球體顯得過於粗糟,可以通過增加面數的方法使正多面體和求更為接近,一種簡單的方法是剖分法,即將前面定義的三角形面分成幾個面,例如,一分為四,形成4個多邊形等,具體實現方法這裡就不再贅述了。
 
  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved