NURBS基本知识
可以使用求值器描述任何角度的多项式或有理多项式样条或表面。他们几乎包含了如今所有常见的样条或样条表面,包括B-样条,NURBS(非均匀有理B-样条)表面,Bezier曲线和表面,以及Hermite样条,由于求值器只是提供了对曲线或表面的底层描述。程序员一般使用更高层次的接口,GLU提供了NURBS接口,该接口封装了大量代码,最终渲染大部分是由求值器完成的。但也提供了额外的功能,例如修剪曲线,NURBS函数使用平面多边形进行渲染。狭义的NURBS是指Non-Uniform Rational B-Spline是指非均匀有理B-样条,Bezier的缺点是增加很多控制点时曲线变得不可控,需要B-Spline曲线,类似多个4阶Bezier拼接,调整4个控制点可以得到较好的效果。这里是指广义的NURBS接口。
NURBS渲染曲线或曲面的过程:
1.生成控制点和创建NURBS对象
GLUnurbsObj *theNurb;
init_surface();
theNurb = gluNewNurbsRenderer();
// 开启自动生成法线向量
glEnable(GL_AUTO_NORMAL);
// 规范化法线向量,不规范会有问题的
glEnable(GL_NORMALIZE);
2.设置NURBS渲染属性和回调函数
1)一般的属性设置
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);
获取NURBS获取分格化后的基本直线和多边形图元
2)
获取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);
3.开始绘制
gluBeginSurface(theNurb);
4.根据控制点绘制曲线或曲面
gluNurbsSurface(theNurb,
8, knots, 8, knots,
4 * 3, 3, &ctlpoints[0][0][0],
4, 4, GL_MAP2_VERTEX_3);
修剪NURBD表面
在这里可以定义修剪曲线,来修剪NURBS表面,按照规定根据曲线绕向行走左边的区域会被保留,右边的区域会被踢除,嵌套的曲线中的外部和内部曲线绕向不能相同否则剔除区域就会产生二义性而出现错误。定义修剪曲线可以通过:
gluPwlCurve函数来创建一条分段的线性曲线或用gluNurbsCurve函数创建一条NURBS曲线。
gluPwlCurve不能定义很弯曲的曲线,更多是定义线段集合,gluNurbsCurve可以定义比较弯曲的曲线(更细分)。
gluBeginTrim (theNurb);
void APIENTRY gluPwlCurve (
GLUnurbs *nobj,
GLint count, // 曲线上count个点
GLfloat *array, // array数组提供曲线上的点
// array两个相邻顶点之间浮点值的个数,可以是2,3若3时候为(u',v', w'),u = u'/w', v'=v'/w'
GLint stride,
GLenum type); // GLU_MAP1_TRIM2 或GLU_MAP1_TRIM3
gluEndTrim (theNurb);
5.完成曲线或曲面的绘制
gluEndSurface(theNurb);
6.清理NURBS对象,释放所占的内存
gluDeleteNurbsRenderer(theNurb);
实例代码:
基本的NURBS绘制曲面
#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;
/*
* Initializes the control points of the surface to a small hill.
* The control points range from -3 to +3 in x, y, and z
*/
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渲染属性和回调函数
// Specifies how a NURBS surface should be tessellated.设置分格化参数。
// When set to GLU_PATH_LENGTH, the surface is rendered so that the maximum length,
// in pixels, of the edges of the tessellation polygons is no greater than what is specified by GLU_SAMPLING_TOLERANCE.
// 参数可以是GLU_DOMAIN_DISTANCE,那么需要GLU_U_STEP或GLU_V_STEP来指定u,v方向的采样点数量默认都是100.
// The initial value of GLU_SAMPLING_METHOD is GLU_PATH_LENGTH.
gluNurbsProperty(theNurb, GLU_SAMPLING_METHOD, GLU_PATH_LENGTH);
// GLU_PATH_LENGTH时最大边分格化距离,边长度超过该距离就会分割出更多的顶点和轮廓
// GLU_PARAMETRIC_ERROR时是被分格化的多边形和他们近似模拟的表面之间的最大距离,超过则分格化的多边形会被分割。
gluNurbsProperty(theNurb, GLU_SAMPLING_TOLERANCE, 25.0);
// value can be set to GLU_OUTLINE_POLYGON, GLU_FILL, or GLU_OUTLINE_PATCH.
// The initial value is GLU_FILL.
gluNurbsProperty(theNurb, GLU_DISPLAY_MODE, GLU_FILL);//GLU_OUTLINE_PATCH GLU_FILL
//gluNurbsProperty(theNurb, GLU_NURBS_MODE, GLU_NURBS_RENDERER);// GLU_NURBS_TESSLLATOR
// 如果在视景体外部那么不启用分格化,提高性能
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,
nurbsError);
}
void display(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(
GLUnurbs *nobj,
//The number of knots in the parametric u direction.
GLint sknot_count,
//An array of sknot_count nondecreasing knot values in the parametric u direction.类似求值器u,v上下界指定。
float *sknot,
// The number of knots in the parametric v direction.
GLint tknot_count,
// An array of tknot_count nondecreasing knot values in the parametric v direction.
GLfloat *tknot,
// The offset (as a number of single precisionfloating-point values) between successive control points in the parametric u direction in ctlarray.
GLint s_stride,
// The offset (in single precisionfloating-point values) between successive control points in the parametric v direction in ctlarray.
GLint t_stride,
// An array containing control points for the NURBS surface.
// The offsets between successive control points in the parametric u and v directions are given by s_stride and t_stride.
GLfloat *ctlarray,
// The order of the NURBS surface in the parametric u direction. The order is one more than the degree, hence a surface that is cubic in u has a u order of 4.
// s方向(u方向)控制点的个数,也就是曲线度数+1
GLint sorder,
// The order of the NURBS surface in the parametric v direction. The order is one more than the degree, hence a surface that is cubic in v has a v order of 4.
GLint torder,
// The type of the surface. The type parameter can be any of the valid two-dimensional evaluator types (such as GL_MAP2_VERTEX_3 or GL_MAP2_COLOR_4).
GLenum type);*/
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 reshape(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 keyboard(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(reshape);
glutDisplayFunc(display);
glutKeyboardFunc (keyboard);
glutMainLoop();
return 0;
}
修剪NURBD表面代码
#include <stdlib.h>
#include <GL/glut.h>
#include <stdio.h>
#ifndef CALLBACK
#define CALLBACK
#endif
GLfloat ctlpoints[4][4][3];
GLUnurbsObj *theNurb;
/*
* Initializes the control points of the surface to a small hill.
* The control points range from -3 to +3 in x, y, and z
*/
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);
init_surface();
theNurb = gluNewNurbsRenderer();
gluNurbsProperty(theNurb, GLU_SAMPLING_TOLERANCE, 25.0);
gluNurbsProperty(theNurb, GLU_DISPLAY_MODE, GLU_FILL);
gluNurbsCallback(theNurb, GLU_ERROR,
nurbsError);
}
void display(void)
{
GLfloat knots[8] = {0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0};
// 逆时针
GLfloat edgePt[5][2] = /* counter clockwise */
{{0.0, 0.0}, {1.0, 0.0}, {1.0, 1.0}, {0.0, 1.0}, {0.0, 0.0}};
GLfloat curvePt[4][2] = /* clockwise */ // 顺时针
{{0.25, 0.5}, {0.25, 0.75}, {0.75, 0.75}, {0.75, 0.5}};
GLfloat curveKnots[8] =
{0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0};
GLfloat pwlPt[4][2] = /* clockwise */
{{0.75, 0.5}, {0.5, 0.25}, {0.25, 0.5}};
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glPushMatrix();
glRotatef(330.0, 1.,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);
gluBeginTrim (theNurb);
// 修剪表面由一条pwl描述的分段曲线来定义,整个轮廓的外部曲线,根据顶点出现顺序是逆时针的内部渲染,外部裁剪。
// gluPwlCurve不能定义很弯曲的曲线,更多是定义线段集合,gluNurbsCurve可以定义比较弯曲的曲线(更细分)。
gluPwlCurve (theNurb, 5, &edgePt[0][0], 2, GLU_MAP1_TRIM_2);
gluEndTrim (theNurb);
gluBeginTrim (theNurb);
// 修剪曲线有一条pwl描述的分段曲线和一条NURBS曲线首尾链接形成一条闭合的裁剪曲线
// gluNurbsCurve可以定义比较弯曲的曲线(更细分),gluPwlCurve更多是定义线段集合,
// 根据曲线控制点的顺序是顺时针的裁剪曲线,因此曲线内部被裁剪,外部渲染出来。
gluNurbsCurve (theNurb, 8, curveKnots, 2,
&curvePt[0][0], 4, GLU_MAP1_TRIM_2);
gluPwlCurve (theNurb, 3, &pwlPt[0][0], 2, GLU_MAP1_TRIM_2);
gluEndTrim (theNurb);
gluEndSurface(theNurb);
glPopMatrix();
glFlush();
}
void reshape(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 keyboard(unsigned char key, int x, int y)
{
switch (key) {
case 27:
exit(0);
break;
}
}
/* Main Loop
*/
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(reshape);
glutDisplayFunc(display);
glutKeyboardFunc (keyboard);
glutMainLoop();
return 0;
}