1.1 NURBS
非均匀有理样条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所示。