OpenGL基礎圖形編程 - 複雜物體建模

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.二、法向計算
  法向,又稱法向量(Mormal Vector)。對於一個平面,其上各點的法向的同樣,統一爲這個平面的法向,因此稱爲平面法向。對於一個曲面,雖然它在計算機圖形中是由許多片小的平面多邊形逼近,可是每一個頂點的法向都不同,所以曲面上每一個點的法向計算就能夠根據不一樣的應用有不一樣的算法,則最後效果也不相同。OpenGL有很大的靈活性,它只提供賦予當前頂點法向的函數,並不在內部具體計算其法向量,這個值由編程者本身根據須要計算。下面介紹一下法向基本計算方法和OpenGL法向定義。 

  13.2.1 法向基本計算方法
  首先,講述平面法向的計算方法。在一個平面內,有兩條相交的線段,假設其中一條爲矢量W,另外一條爲矢量V,且平面法向爲N,如圖13-3所示,則平面法向就等於兩個矢量的叉積(遵循右手定則),即N=WxV。

數組

圖13-3 平面法向計算


  好比計算一個三角形平面的法向,就能夠用它的三個頂點來計算,如圖13-4所示。

ide

圖13-4 三角形平面法向計算


  設三個頂點分別爲P0、P一、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)/4oop


圖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.三、曲線生成
  計算機圖形學中,全部的光滑曲線都採用線段逼近來模擬,並且許多有用的曲線在數學上只用少數幾個參數(如控制點等)來描述。本節簡要地介紹一下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);
  }


spa

圖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*()控制點的數據類型orm


  曲線定義後,必需要啓動,才能進行下一步的繪製工做。啓動函數還是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.四、曲面構造
  一樣,計算機圖形學中的全部光滑曲面也都採用多邊形逼近來繪製,並且許多有用的曲面在數學上也只用少數幾個參數(如控制點或網等)來描述。一般,若用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()。u一、u2爲u的最大值和最小值;v一、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曲面
相關文章
相關標籤/搜索