非均匀有理样条NURBS( Non-Uniform Rational B-Splines ) 是近年来发展迅速,应用广泛的一种表示曲线曲面造型技术。它能够精确地表示二次规则曲线曲面,从而能用统一的数学形式表示规则曲面与自由曲面,具有可影响曲线曲面形状的权因子,使形状更宜于控制和实现。1991 年国际标准化组织颁布了关于工业产品数据交换的STEP 国际标准,将NURBS 方法作为定义工业产品几何形状的唯一数学描述方法,从而使NURBS 方法成为曲面造型技术发展趋势中最重要的基础。
OpenGL 对NURBS 的支持是通过实用库实现的,相应的函数有
gluNewNurbsRenderer
gluNurbsProperty
gluNurbsSurface
gluBeginSurface
gluEndSurface
gluDeleteNurbsRenderer
创建NURBS 曲面时,首先使用gluNewNurbsRenderer 创建一个NURBS 对象,然后使用gluNurbsProperty 设置NURBS 对象的属性。gluBeginSurface~gluEndSurface 的作用类似glBegin~glEnd ,在两个函数间使用gluNurbsSurface 来定义NURBS 曲面的具体形状。gluEndSurface 完成了NURBS 曲面的定义后就可以显示了。需要注意的是,不再使用NURBS 对象时,要记得使用 gluDeleteNurbsRenderer 释放掉其对象所占用的内存。
gluNewNurbsRenderer 函数不带任何参数,调用成功后返回一个GLUnurbsObj 类型的指针,指向创建成功的NURBS 对象。其原型如下
GLUnurbsObj* gluNewNurbsRenderer( void );
创建NURBS 对象的代码类似如下
GLUnurbsObj *theNurb;
theNurb = gluNewNurbsRenderer();
接下来,要使用 gluNurbsProperty 来设置theNurb 的属性。gluNurbsProperty 的原型如下
void gluNurbsProperty(
GLUnurbsObj *nobj,
GLenum property,
GLfloat value
);
nobj 参数就是使用gluNewNurbsRenderer 创建的NURBS 对象。property 是需要设置的属性,可以取的值如下表10-2 所示。
表 10-2 gluNurbsProperty 参数含义
Property 取值 |
含义 |
GLU_SAMPLING_TOLERANCE |
当抽样方法设置为GLU_PATH_LENGTH 时指定抽样误差,单位是像素。缺省值为50.0 。 |
GLU_DISPLAY_MODE |
使用value 参数来定义NURBS 曲面的渲染方式,此时value可以取值GLU_FILL ,GLU_OUTLINE_POLYGON和GLU_OUTLINE_PATCH 。 GLU_FILL 表示曲面填充多边形方式渲染,曲面是一个整体的平滑曲面,这也是缺省的方式。 GLU_OUTLINE_POLYGON 表示曲面仅仅画出由小方格组成的外轮廓,表现出来的就是网格曲面。 GLU_OUTLINE_PATCH 则纯粹是一个外轮廓边沿。 |
GLU_CULLING |
value 参数是一个BOOL 型的数值,确定NURBS 曲线是否丢弃视点范围之外的控制点,缺省为GL_FALSE 。 |
GLU_AUTO_LOAD_MATRIX |
value 参数是一个BOOL 型的数值。当为GL_TRUE 时,表示NURBS 要从OpenGL 服务器下载投影矩阵,模型观察矩阵和视点来计算对每一个NURBS 曲线计算抽样和消隐矩阵。当value 为GL_FALSE 时,表示必须由本地应用提供投影矩阵,模型观察矩阵和视点。对于非网络应用来说,该项可以忽略。 |
GLU_PARAMETRIC_TOLERANCE |
当抽样方法为GLU_PARAMETRIC_ERROR 时指定最大距离,单位是像素,缺省值为0.5 。 |
GLU_SAMPLING_METHOD |
指定怎样利用方格拼成NURBS 曲面,即拟合NURBS 的方法。对应的value 可以取GLU_PATH_LENGTH,GLU_PARAMETRIC_ERROR 和GLU_DOMAIN_DISTANCE 。 当value 取值为GLU_PATH_LENGTH (缺省值)时,用于组成曲面的小方格的边长最大不超过以GLU_SAMPLING_TOLERANCE 设置的值。 当value 取值为GLU_PARAMETRIC_ERROR 时,以GLU_PARAMETRIC_TOLERANCE 指定的最大距离值在小方格和整个表面之间。 当value 取值为 GLU_DOMAIN_DISTANCE 时,指明在u 、v方向上每单位长度有多少个抽样点。 |
GLU_U_STEP |
指定沿u 方向每单位长度的采样点数,当LU_SAMPLING_METHOD 设置成GLU_DOMAIN_DISTANCE时GLU_U_STEP 有效。缺省为100 。 |
GLU_V_STEP |
指定沿v 方向每单位长度的采样点数,当LU_SAMPLING_METHOD 设置成GLU_DOMAIN_DISTANCE时GLU_U_STEP 有效。缺省为100 。 |
value 表示property 的值,它可以是一个数字,也可以是GLU_PATH_LENGTH 、 GLU_PARAMETRIC_ERROR和GLU_DOMAIN_DISTANCE 三者之一。
下面是一个NURBS 曲面的生成代码。
//NURBS 对象
GLUnurbsObj *theNurb;
// 三种显示模式
GLfloat DisplayMode[3] = {GLU_FILL, GLU_OUTLINE_POLYGON, GLU_OUTLINE_PATCH};
int Mode = 0; // 缺省的显示模式为GLU_FILL
BOOL bModePressed = FALSE; // 改变显示模式选择按键
// 改变显示模式的按键处理在WndProc 中
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_ACTIVATE:
……
case WM_SIZE:
……
case WM_KEYDOWN:
switch(wParam)
{
case VK_ESCAPE:
PostQuitMessage(0);
return 0;
case VK_SPACE: // 空格键进行显示模式切换
bModePressed = TRUE;
Mode ++;
if(Mode >2)
Mode = 0;
break;
}
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
在glInit() 中,我们来建立一个光源,并且创建一种材质,将其应用到NURBS 曲面上。
int glInit()
{
// 启用阴影平滑(Smooth Shading) 。
glShadeModel(GL_SMOOTH);
// 设置深度缓冲
glClearDepth(1.0f);
// 启动深度测试
glEnable(GL_DEPTH_TEST);
// 深度测试的类型
glDepthFunc(GL_LEQUAL);
// 进行透视修正
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
// 翡翠、祖母绿
GLfloat mat_ambient[] = { 0.021500, 0.174500, 0.021500, 0.550000 };
GLfloat mat_diffuse[] = { 0.075680, 0.614240, 0.075680, 0.550000 };
GLfloat mat_specular[] = {0.633000, 0.727811, 0.633000, 0.550000};
GLfloat mat_shininess[] = {76.800003 };
glColor3f(1.0f, 1.0f, 1.0f);
// 材质属性
glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, mat_ambient);
glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, mat_diffuse);
glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, mat_specular);
glMaterialfv(GL_FRONT_AND_BACK, GL_SHININESS, mat_shininess);
// 启动光照,使用缺省的光照参数
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
// 自动计算法向量
glEnable(GL_AUTO_NORMAL);
// 创建一个新的NURBS 对象
theNurb = gluNewNurbsRenderer();
// 设置NURBS 对象的参数
gluNurbsProperty(theNurb, GLU_SAMPLING_TOLERANCE, 25.0);
gluNurbsProperty(theNurb, GLU_DISPLAY_MODE, GLU_FILL);
return TRUE;
}
在glShutdown 中必须添加将NURBS 对象释放的代码,否则,即便应用程序关闭,对象占用的内存也可能会一直无法释放。
void glShutdown()
{
……
if(theNurb)
gluDeleteNurbsRenderer(theNurb);
}
// 定义NURBS 的控制点,共25 个
GLfloat CtlPoints[5][5][3]=
{
{{-3.0, -3.0, -3.0}, {-3.0, -1.0, -3.0}, {-3.0, 0.0, -3.0},
{-3.0, 1.0, -3.0}, {-3.0, 3.0, -3.0}},
{{-1.0, -3.0, -3.0}, {-1.0, -1.0, -9.0}, {-1.0 ,0.0, 9.0},
{-1.0, 1.0, -9.0}, {-1.0, 3.0, -3.0}},
{{1.0, -3.0, -3.0}, {1.0, -1.0, 3.0}, {1.0, 0.0, 3.0,},
{1.0, 1.0, 3.0}, {1.0, 3.0, -3.0}},
{{3.0, -3.0, -3.0}, {3.0, -1.0, -3.0},{3.0, 0.0, -3.0},
{3.0, 1.0, -3.0},{3.0, 3.0, -3.0}},
{{5.0, -3.0, -3.0}, {5.0, -1.0, -9.0},{5.0, 0.0, 9.0},
{5.0, 1.0, -9.0},{5.0, 3.0, -3.0}}
};
GLfloat knots[10] = {0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0};
void glMain()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glLoadIdentity(); // 加载单位矩阵
glTranslatef(0.0f, -1.0f, -10.0f);
glRotatef(90, 0.0f, 0.0f, 1.0f);
// 根据按键确定切换显示模式
if(bModePressed)
{
bModePressed = FALSE;
gluNurbsProperty(theNurb, GLU_DISPLAY_MODE, DisplayMode[Mode]);
}
// 开始定义NURBS 曲面
gluBeginSurface(theNurb);
gluNurbsSurface(theNurb,
10,
knots,
10,
knots,
15,
3,
&CtlPoints[0][0][0],
5, 5,
GL_MAP2_VERTEX_3);
gluEndSurface(theNurb); // 结束曲面定义并显示
SwapBuffers(g_hDC);// 交换前后缓冲区
}
程序运行后,可以看到一个脸部模型的大概轮廓已经显示出来。当然,真正的实现一个完整的面具模型还需要更多的控制点。按空格键后还可以看到网格状的曲面和仅有外边沿轮廓的曲面,仅有外边沿轮廓的曲面基本上已经看不出是一个曲面了,更象一个平面多边形。效果如图10-11 所示。