在VC++中使用OpenGL绘制典型曲面

类别:VC语言 点击:0 评论:0 推荐:
在VC++中使用OpenGL绘制典型曲面

[正文]

  摘要: 本文主要讨论了在VC++中使用OpenGL绘制Bezier、NURBS等典型曲面的一般性方法。

  关键词: OpenGL;Bezier;NURBS;曲面绘制

  OpenGL中对复杂物体的建摸

  基本几何图元是OpenGL进行建模的最基本的方法,但其对较复杂真实物体的建模则比较困难。对于这些复杂物体的建模,需要用到OpenGL基本库和功能库函数(gl库和glu库)以对图元进行扩展并完成法向计算、曲线生成和曲面构造等内容。这种对基本图元的扩展实际也就是对点、线及多边形的扩展。OpenGL中定义的点可具有不同大小的尺寸,其扩展的函数形式为:

void glPointSize(GLfloat size);

  其参数size以象素为单位设置了点的宽度,其值必须为正,缺省值1.0。对于线的扩展,可通过下面的函数来分别指定其宽度和绘制类型:

void glLineWidth(GLfloat width);
void glLineStipple(GLint factor,GLushort pattern);

  glLineWidth()的参数width以象素为单位指定线宽,其值必须为正,缺省值为1.0。glLineStipple()的参数factor为对模式进行拉伸的比例因子,参数pattern指定了线的模式(例如11001100将绘制一条虚线,为1时绘制,为0时不绘制)。该函数只有在启用了函数glEnable(GL_LINE_STIPPLE)后才可以使用,当不再使用时调用glDisable(GL_LINE_STIPPLE)将其关闭。扩展多边形的绘制模式包括全填充式、轮廓点式、轮廓线式及图案填充式等几种。使用时,首先调用glPolygonMode()设置多边形的模式设置:

void glPolygonMode(GLenum face,GLenum mode);
 
  参数face为GL_FRONT、GL_BACK或GL_FRONT_AND BACK;mode取值可以是GL_POINT、GL_LINE或GL_FILL,分别表示多边型的轮廓点、轮廓线和填充模式的绘制方式。缺省设置为填充模式。设置完成后可进行图案填充的设置:

void glPolygonStipple(const GLubyte *mask);

  其参数mask必须为一指向32×32大小的位图的指针,值为1时绘制、为0不绘制。该函数的使用同样也需要进行如下启动、关闭设置:

glEnable(GL_POLYGON-STIPPLE);
glDisable(GL_POLYGON_STIPPLE);

  复杂模型的建模不同与简单模型的建模,在简单模型中一个平面上各点的法向(mormal vector)是一样的,均等于此平面的法向。对于复杂模型中由众多小的平面多边形逼近而成的曲面,其每个顶点的法向量都不一样,因此曲面上每个点的法向计算结果根据采取的不同算法而有不同的结果。OpenGL只提供赋予当前顶点法向量的函数,而不提供对法向量计算的方法,法向量的计算需要由开发者来完成。下面给出一种简单的计算方法:

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;
  }
  
  其参数gx[3],gy[3]和gz[3]为逼近曲面的一个三角形的三个顶点P0,P1和P2。通过计算矢量P0-P1与矢量P2-P1的叉乘而得到其平面法向量,并在归一化后保存到由参数ddnv所指向的数组中。至于顶点法向的计算则多是取邻近平面法向量的均值。OpenGL提供的法向定义函数为:

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

  通过这两个函数可以设置当前法向值。对于非向量形式的定义采用前一种方式,通过参数nx、ny和nz分别给出法向三个分量值;对于向量形式的定义采取后一种方式,将v设置为指向法向三分量的指针。在应用时,通常要对法向进行归一化处理。

  构造曲线、曲面

  在进行复杂物体建模时,使用的光滑曲线、曲面都是由一些线段和多边形逼近而成,并通过少数几个控制点对其进行描述。曲线的定义由glMap1*()函数完成:

void glMap1{fd}(GLenum target,TYPE u1,TYPE u2,GLint stride, GLint order,const TYPE *points);

  参数target指出了控制顶点的意义以及在points参数中需要提供多少值;points指针可以指向控制点集、RGBA颜色值或是纹理坐标串等。参数u1和u2限定了变量U的取值范围,通常是从0变化到1;stride表示跨度(在每块存储区内浮点数或双精度数的个数,即两个控制点间的偏移量);最后的参数order为阶数,是次数加1,与控制点数一致。曲线定义后必须再glEnable()函数显式启动后才能起作用,其参数与target保持一致。在使用完毕后通过glDisable()函数将其关闭。曲线坐标可通过glEvalCoord1*()函数进行计算:

void glEvalCoord1{fd}[v](TYPE u);

  该函数将产生曲线坐标值并将其绘制。参数u为定义域内的任意值,每调用一次将只产生一个坐标,此坐标值也是任意的。但目前较多采用的是定义均匀间隔曲线坐标值,依次调用glMapGrid1*()和glEvalMesh1()可以获得等间隔值。这两个函数分别用来定义一个一维网格和计算相应的坐标值。

  曲面的构造可以是网格线和填充曲面形式,与曲线的构造很类似只是将其扩展为二维而已。下面给出曲面的定义函数:

void glMap2{fd}(GLenum target,TYPE u1,TYPE u2,GLint ustride,GLint uorder,TYPE v1,TYPE v2,GLint vstride,GLint vorder,TYPE points);
 
  这里target的意义与在glMap1*()中的意义相同;(u1,u2),(v1,v2)是二维曲面坐标;其他参数如uorder,vorder,ustride和vstride等的定义都类似于在曲线中的定义;points为控制点坐标。对曲面任意一点的计算可通过函数

void glEvalCoord2{fd}[v](TYPE u,TYPE v);

  来完成,通过在定义域内的曲线坐标值u,v来计算曲面内任意一点的世界坐标位置。对于曲面,也可以象曲线一样通过函数来定义均匀间隔的曲面坐标值:

  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()将此网格应用到已经启动的曲面计算上。glEvalMesh2()的mode参数除了可以是GL_POINT和GL_LINE外,也可以是GL_FILL(生成填充空间曲面)。

  Bezier曲面的绘制

  下面给出一个通过定义曲面和均匀网格绘制一个具有光照和明暗处理效果的Bezier曲面(图1)的部分主要代码:

GLfloat ctrlpoints[4][4][3] = {// 控制点坐标
{{-2.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}, {-1.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 Init()
{
 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); // 定义参数空间的均匀网格
 GLfloat ambient[4] = {0.4, 0.6, 0.2, 1.0}; // 初始化光照、材质的过程
 GLfloat position[4] = {0.0, 1.0, 3.0, 1.0};
 GLfloat mat_diffuse[4] = {0.8, 0.6, 0.3, 1.0};
 GLfloat mat_specular[4] = {0.8, 0.6, 0.3, 1.0};
 GLfloat mat_shininess[1] = {45.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);
}


图1 绘制的Bezier曲面

  NURBS曲面的绘制

  上面的例程通过给定少量控制点就可以很好的控制曲面的形状。在实际应用时也可以通过程序来动态生成或调整控制点。在本例中,对曲面的定义等操作均是显示调用本节前面介绍的glMap2f()、glMapGrid2f()等函数来实现的。除了这种方式,还可以利用OpenGL的功能库提供的绘制非均匀有理B样条曲面(NURBS曲面)的函数进行曲面绘制,下面给出实现此功能的部分示例代码:

GLfloat ctlpoints[4][4][3]; // 控制点的存储空间
GLUnurbsObj *theNurb; // 指向NURBS曲面对象的指针
void InitSurface()
{
 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] = 6;
   else ctlpoints[u][v][2] = -6;
  }
 }
}
void Init(void)
{
 GLfloat mat_diffuse[] = {0.8, 0.6, 0.3, 1.0}; // 定义曲面材质
 GLfloat mat_specular[] = {0.8, 0.6, 0.3, 1.0};
 GLfloat mat_shininess[] = {45.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);
 InitSurface(); // 初始化控制点
 theNurb = gluNewNurbsRenderer(); // 创建一个NURBS曲面对象
 // 修改NURBS曲面对象的属性
 gluNurbsProperty(theNurb, GLU_SAMPLING_TOLERANCE, 5.0);
 gluNurbsProperty(theNurb, GLU_DISPLAY_MODE, GLU_FILL);
}
void CALLBACK Display()
{
 GLfloat knots[8] = {0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0}; // NURBS曲面的控制向量
 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // 清屏
 glPushMatrix(); // 入栈
 glRotatef(30.0, -1.0, 0.0, 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(); // 强制刷新
}


图2 绘制的NURBS曲面

  该示例中对控制点的设定即是通过InitSurface()函数动态计算的,在初始化控制点后通过glu库的gluNewNurbsRenderer()函数创建一个NURBS曲面对象theNurb,并可通过gluNurbsProperty()函数修改其属性。在绘制NURBUS曲面时,通过gluBeginSurface()和gluEndSurface()进行界定,在其中通过gluNurbsSurface()函数完成对曲面数学模型的定义并确定曲面的形状,图2给出了上述代码的绘制结果。

本文地址:http://com.8s8s.com/it/it317.htm