1.实验目的:
掌握B样条、NURBS(非均匀有理B样条)曲线、曲面的概念。掌握B样条、NURBS曲面编程方法。
2.实验内容:
结合示范代码了解曲线B样条曲面生成原理与算法实现,尤其是NURBS曲面。调试、编译、修改示范程序。
3.实验原理:
求值器能够描述任何角度的多项式或有理多项式样条或表面,包括B-样条,NURBS(非均匀有理B-样条)表面,Bezier曲线和表面,以及Hermite样条。由于求值器只提供了对曲线或表面底层描述,需要使用更高层次的NURBS接口来生成B样条曲面。 OpenGL提供了NURBS接口,该接口封装了大量代码,不仅包含渲染功能,也提供了修剪曲面等额外功能,NURBS函数使用平面多边形进行渲染。B样条曲面包含非均匀有理B-样条,另外Bezier的缺点是增加很多控制点时曲线变得不可控,而B样条曲面调整4个控制点可以得到较好的效果。 NURBS接口生成B样条曲面的过程如下。 (1)生成控制点和创建NURBS对象:
GLUnurbsObj *theNurb;
init_surface();
theNurb = gluNewNurbsRenderer();
glEnable(GL_AUTO_NORMAL); // 开启自动生成法线向量:
glEnable(GL_NORMALIZE); //规范化法线向量
(2)设置NURBS渲染属性和回调函数,一般的属性设置包括以下3种:
gluNurbsProperty(theNurb, GLU_SAMPLING_METHOD, GLU_PATH_LENGTH);
gluNurbsProperty(theNurb, GLU_SAMPLING_TOLERANCE, 25.0);
gluNurbsProperty(theNurb, GLU_DISPLAY_MODE, GLU_FILL);
gluNurbsCallback(theNurb, GLU_ERROR, nurbsError);//这里可能需要强制转nurbsError类型。
(3)获取NURBS获取分格化后的基本直线和多边形图元,包括顶点,颜色,纹理坐标,法线。获取NURBS获取图元的前提条件,需要设置GLU_NURBS_TESSELLATOR属性。 这样NURBS分格化的直线和多边形图元不会直接渲染,而是返回到回调函数重新提交给渲染管线。
gluNurbsProperty(theNurb, GLU_NURBS_MODE, GLU_NURBS_TESSELLATOR);
gluNurbsProperty(theNurb, GLU_SAMPLING_TOLERANCE, 25.0);
gluNurbsProperty(theNurb, GLU_DISPLAY_MODE, GLU_FILL);
设置回调函数:
gluNurbsCallback(theNurb, GLU_ERROR, nurbsError);
gluNurbsCallback(theNurb, GLU_NURBS_BEGIN, beginCallback);
gluNurbsCallback(theNurb, GLU_NURBS_VERTEX, vertexCallback);
gluNurbsCallback(theNurb, GLU_NURBS_NORMAL, normalCallback);
gluNurbsCallback(theNurb, GLU_NURBS_END, endCallback);
(4)开始绘制:
gluBeginSurface(theNurb)。
(5)根据控制点绘制曲线或曲面:
gluNurbsSurface(theNurb,8, knots, 8, knots,4 * 3, 3, &ctlpoints[0][0][0], 4, 4, L_MAP2_VERTEX_3);
(6) 修剪NURBS表面,在这里可以定义修剪曲线,来修剪NURBS表面,按照规定根据曲线绕向行走左边的区域会被保留,右边的区域会被踢除,嵌套的曲线中的外部和内部曲线绕向不能相同否则剔除区域就会产生二义性而出现错误。 定义修剪曲线可以通过:gluPwlCurve函数来创建一条分段的线性曲线或用gluNurbsCurve函数创建一条NURBS曲线。gluPwlCurve不能定义很弯曲的曲线,更多是定义线段集合,gluNurbsCurve可以定义比较弯曲的曲线。gluBeginTrim (theNurb);void APIENTRY gluPwlCurve (GLUnurbs *nobj, GLint count(//曲线上点数), GLfloat *array (由array数组提供曲线上的点,array两个相邻顶点之间浮点值的个数,可以是2,3), GLint stride,GLenum type(//GLU_MAP1_TRIM2 或GLU_MAP1_TRIM3)); gluEndTrim (theNurb); (7)通过gluEndSurface(theNurb)来完成曲线或曲面的绘制。 (8)通过gluDeleteNurbsRenderer(theNurb)来清理NURBS对象,释放所占的内存。
4.实验代码:
#include <GL/glut.h>
#include <stdlib.h>
#include <stdio.h>
#ifndef CALLBACK
#define CALLBACK
#endif
GLfloat ctlpoints[4][4][3];
int showPoints = 0;
GLUnurbsObj *theNurb;
//初始化曲面控制点,控制点阈值[-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 CALLBACK nurbsError(GLenum errorCode)
{
const GLubyte *estring;
estring = gluErrorString(errorCode);
fprintf(stderr, "Nurbs Error: %s\n", estring);
exit(0);
}
/* Initialize material property and depth buffer.
*/
void init(void)
{
GLfloat mat_diffuse[] = { 0.7, 0.7, 0.7, 1.0 };
GLfloat mat_specular[] = { 1.0, 1.0, 1.0, 1.0 };
GLfloat mat_shininess[] = { 100.0 };
glClearColor(0.0, 0.0, 0.0, 0.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);
glEnable(GL_DEPTH_TEST);
// 开启自动生成法线向量
glEnable(GL_AUTO_NORMAL);
// 规范化法线向量,不规范会有问题的
glEnable(GL_NORMALIZE);
// 1.生成控制点和创建NURBS对象
init_surface();
theNurb = gluNewNurbsRenderer();
// 2.设置NURBS渲染属性和回调函数
// 参数可以是GLU_DOMAIN_DISTANCE,那么需要GLU_U_STEP或GLU_V_STEP来指定u,v方向的采样点数量默认都是100.
gluNurbsProperty(theNurb, GLU_SAMPLING_METHOD, GLU_PATH_LENGTH);
// GLU_PATH_LENGTH时最大边分格化距离,边长度超过该距离就会分割出更多顶点和轮廓。
// GLU_PARAMETRIC_ERROR被分格化的多边形和他们近似模拟的表面之间的最大距离,超过则分格化的多边形会被分割。
gluNurbsProperty(theNurb, GLU_SAMPLING_TOLERANCE, 25.0);
gluNurbsProperty(theNurb, GLU_DISPLAY_MODE, GLU_FILL);
// 如果在视景体外部那么不启用分格化,提高性能
gluNurbsProperty(theNurb, GLU_CULLING, GLU_TRUE);
// 从OGL服务器获取投影矩阵,模型视图矩阵和视口,如果是GLU_FALSE那么需要gluLoadSampliingMatrices来提供这些矩阵。
gluNurbsProperty(theNurb, GLU_AUTO_LOAD_MATRIX, GLU_TRUE);
// 获取属性值用gluGetNurbsProperty
GLfloat cullMethod = 0.0f;
gluGetNurbsProperty(theNurb, GLU_CULLING, &cullMethod);
// 设置错误回调
gluNurbsCallback(theNurb, GLU_ERROR, (void(__stdcall*) (void))nurbsError);
}
void myDisplay(void)
{
// 每个控制点(节点)uv的上下界,从[0,1]类似求值器的插值指定
GLfloat knots[8] = { 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0 };
int i, j;
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glPushMatrix();
glRotatef(330.0, 1., 0., 0.);
glScalef(0.5, 0.5, 0.5);
// 3.开始绘制
gluBeginSurface(theNurb);
gluNurbsSurface(theNurb,
8, knots, 8, knots,
4 * 3, 3, &ctlpoints[0][0][0],
4, 4, GL_MAP2_VERTEX_3);
gluNurbsSurface(theNurb,
8, knots, 8, knots,
4 * 3, 3, &ctlpoints[0][0][0],
4, 4, GL_MAP2_NORMAL);
// 完成曲线或曲面的绘制
gluEndSurface(theNurb);
// 曲线的绘制用glBeginCurve, glNurbsCurve glEndCurve来指定,参数含义同曲面。
if (showPoints) {
glPointSize(5.0);
glDisable(GL_LIGHTING);
glColor3f(1.0, 1.0, 0.0);
glBegin(GL_POINTS);
for (i = 0; i < 4; i++) {
for (j = 0; j < 4; j++) {
glVertex3f(ctlpoints[i][j][0],
ctlpoints[i][j][1], ctlpoints[i][j][2]);
}
}
glEnd();
glEnable(GL_LIGHTING);
}
glPopMatrix();
glFlush();
}
void myReshape(int w, int h)
{
glViewport(0, 0, (GLsizei)w, (GLsizei)h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(45.0, (GLdouble)w / (GLdouble)h, 3.0, 8.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glTranslatef(0.0, 0.0,-5.0);
}
void myKeyboard(unsigned char key, int x, int y)
{
switch (key) {
case 'c':
case 'C':
showPoints = !showPoints;
glutPostRedisplay();
break;
case 27:
gluDeleteNurbsRenderer(theNurb);
exit(0);
break;
default:
break;
}
}
int main(int argc, char** argv)
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB | GLUT_DEPTH);
glutInitWindowSize(500, 500);
glutInitWindowPosition(100, 100);
glutCreateWindow(argv[0]);
init();
glutReshapeFunc(myReshape);
glutDisplayFunc(myDisplay);
glutKeyboardFunc(myKeyboard);
glutMainLoop();
return 0;
}
运行结果如图A.11(a)所示。
图A.11(a)生成B样条曲面
5.实验提高
根据控制点(-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)重新生成并显示B样条曲面,见图A.11(b)。
图A.11(b)重新生成B样条曲面