新书推介:《语义网技术体系》
作者:瞿裕忠,胡伟,程龚
   XML论坛     W3CHINA.ORG讨论区     计算机科学论坛     SOAChina论坛     Blog     开放翻译计划     新浪微博  
 
  • 首页
  • 登录
  • 注册
  • 软件下载
  • 资料下载
  • 核心成员
  • 帮助
  •   Add to Google

    >> 本版讨论高级C/C++编程、代码重构(Refactoring)、极限编程(XP)、泛型编程等话题
    [返回] 计算机科学论坛计算机技术与应用『 C/C++编程思想 』 → 基于VC++的OpenGL编程讲座之(四)------曲线和曲面[转帖] 查看新帖用户列表

      发表一个新主题  发表一个新投票  回复主题  (订阅本版) 您是本帖的第 5283 个阅读者浏览上一篇主题  刷新本主题   树形显示贴子 浏览下一篇主题
     * 贴子主题: 基于VC++的OpenGL编程讲座之(四)------曲线和曲面[转帖] 举报  打印  推荐  IE收藏夹 
       本主题类别:     
     一分之千 帅哥哟,离线,有人找我吗?射手座1984-11-30
      
      
      威望:1
      等级:研一(随老板参加了WWW大会还和Tim Berners-Lee合了影^_^)
      文章:632
      积分:4379
      门派:XML.ORG.CN
      注册:2006/12/31

    姓名:(无权查看)
    城市:(无权查看)
    院校:(无权查看)
    给一分之千发送一个短消息 把一分之千加入好友 查看一分之千的个人资料 搜索一分之千在『 C/C++编程思想 』的所有贴子 引用回复这个贴子 回复这个贴子 查看一分之千的博客楼主
    发贴心情 基于VC++的OpenGL编程讲座之(四)------曲线和曲面[转帖]

    作者: 刘涛 出处: 天极网

    计算机图形学中,所有的光滑曲线、曲面都采用线段或三角形逼近来模拟,但为了精确地表现曲线,通常需要成千上万个线段或三角形来逼近,这种方法对于计算机的硬件资源有相当高的要求。然而,许多有用的曲线、曲面在数学上只需要用少数几个参数(如控制点等)来描述。这种方法所需要的存储空间比线段、三角形逼近的方法来所需要的空间要小得多,并且控制点方法描述的曲线、曲面比线段、三角形逼近的曲线、曲面更精确。

      为了说明如何在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 <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();
      }
    二、曲面构造

      曲面的绘制方法基本上与曲线的绘制方法是相同的,所不同的是曲面使用二维求值器,并且控制点连接起来形成一个网格。

      对于曲面,求值器除了使用二个参数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 <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);
    }
    三、图元逼近法绘制三维物体

      在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个多边形等,具体实现方法这里就不再赘述了。


    [此贴子已经被作者于2007-7-12 10:55:30编辑过]

       收藏   分享  
    顶(0)
      




    ----------------------------------------------
    越学越无知

    点击查看用户来源及管理<br>发贴IP:*.*.*.* 2007/7/11 10:54:00
     
     GoogleAdSense射手座1984-11-30
      
      
      等级:大一新生
      文章:1
      积分:50
      门派:无门无派
      院校:未填写
      注册:2007-01-01
    给Google AdSense发送一个短消息 把Google AdSense加入好友 查看Google AdSense的个人资料 搜索Google AdSense在『 C/C++编程思想 』的所有贴子 访问Google AdSense的主页 引用回复这个贴子 回复这个贴子 查看Google AdSense的博客广告
    2024/11/23 1:17:39

    本主题贴数1,分页: [1]

    管理选项修改tag | 锁定 | 解锁 | 提升 | 删除 | 移动 | 固顶 | 总固顶 | 奖励 | 惩罚 | 发布公告
    W3C Contributing Supporter! W 3 C h i n a ( since 2003 ) 旗 下 站 点
    苏ICP备05006046号《全国人大常委会关于维护互联网安全的决定》《计算机信息网络国际联网安全保护管理办法》
    61.523ms