传统的曲线或者曲面的生成方式是使用一些极短或者极小的直线和平面来逼近曲线和曲面,这种方式一方面需要消耗较多的资源且生成过程相对比较复杂,另一方面使用这种方式生成的曲线和曲面的最终效果好坏取决于用于逼近的线段和平面。贝赛尔曲线是这样的一种曲线,他使用一系列的点来控制一条曲线的各个部分,使之根据贝赛尔模型形成一个光滑完整的曲线。使用贝赛尔曲线不仅系统开销小而且由于整个曲线是基于一个数学模型,因此可以说曲线是完美的。最简单的一种贝赛尔曲线是由4个控制点组成,其中两个控制点形成了曲线的两个端点,而另外两个控制点用来控制曲线的弯曲程度和弯曲方向。在贝赛尔曲线上有这样的一个特征:端点和他邻近控制点的的连线是曲线在端点上的切线。
在OpenGL系统当中生成贝赛尔曲线非常的方便,我们首先要定义的是一组控制点,然后我们将这个控制点提供给OpenGL系统,提交的过程使用函数glMap1f,这个函数的原型如下:
void
glMap1f(
GLenum target, // 生成目标,最终生成绘制点用GL_MAP_VERTEX_3
GLfloat u1, // 贝赛尔曲线模型的参数,这个参数定义域为[0,1]如果生成完整的贝赛尔曲线必须在整个
GLfloat u2, // 定义域当中生成。U1代表下限,U2代表上限,一般分别设为0和1
GLint stride, // 每个控制点所使用的浮点数个数
GLint order, // 曲线的阶,等于曲线上的控制点数量
const GLfloat * points // 指向控制点数组的指针
);
GLenum target, // 生成目标,最终生成绘制点用GL_MAP_VERTEX_3
GLfloat u1, // 贝赛尔曲线模型的参数,这个参数定义域为[0,1]如果生成完整的贝赛尔曲线必须在整个
GLfloat u2, // 定义域当中生成。U1代表下限,U2代表上限,一般分别设为0和1
GLint stride, // 每个控制点所使用的浮点数个数
GLint order, // 曲线的阶,等于曲线上的控制点数量
const GLfloat * points // 指向控制点数组的指针
);
然后,就像我们上面做的那样,如果需要OpenGL绘制贝赛尔曲线上的点那么我们就用glEnable打开GL_MAP_VERTEX_3开关。好了到这里我们完成了所有的准备工作。接下来我们要开始真正的绘制我们的贝赛尔曲线。绘制贝赛尔曲线的方法事实上有两种,第一种方法我们用glEvalCoordf函数来生成贝赛尔曲线上的点,这个函数的参数就是我们上面讲的贝赛尔数学模型上的u,因此我们要生成一条完整贝赛尔曲线那么就需要连续地使用一串介于0和1之间的浮点数来调用函数,当然了在生成这些坐标的前后我们仍然要加上glBegin和glEnd函数的调用:);接下来是第二种方法,这种方法相对比较方便,他在OpenGL内部直接进行网格化,也就是将曲线等分,并且在绘制的时候由OpenGL自己生成坐标并且绘制不需要我们再调用glBegin...glEnd。进行网格化的函数是glGridMap,原型如下:
void
glMapGrid1f(
GLint un, // 将曲线分成己部分,换句话讲具体到模型其实就是将参数u分成几部分
GLfloat u1, //开始的网格点
GLfloat u2 //结束的网格点
);
GLint un, // 将曲线分成己部分,换句话讲具体到模型其实就是将参数u分成几部分
GLfloat u1, //开始的网格点
GLfloat u2 //结束的网格点
);
这个部分的调用应该和贝赛尔曲线的准备工作一起放在初始化过程中,因为这些东西在整个程序运行过程当中只需要执行一遍。然后就是根据网格化后的曲线进行绘制,绘制需要调用的函数是glEvalMesh,其原型是:
void
glEvalMesh1(
GLenum mode, //绘图模式,GL_POINT点,GL_LINE线,GL_FILL填充,当然GL_FILL应该用在曲面
GLint i1, //开始绘制的网格点
GLint i2 //结束绘制的网格点
);
GLenum mode, //绘图模式,GL_POINT点,GL_LINE线,GL_FILL填充,当然GL_FILL应该用在曲面
GLint i1, //开始绘制的网格点
GLint i2 //结束绘制的网格点
);
好了,整个贝赛尔曲线绘制的过程已经完整了,下面就是一个完整的例程:
GLfloat point[
4
][
3
]
=
{{-2,0,0},{-1,2,0},{1,-2,0},{2,0,0}}
;
int InitGL(GLvoid) // All Setup For OpenGL Goes Here
{
glShadeModel(GL_SMOOTH); // Enable Smooth Shading
glClearColor(0.0f, 0.0f, 0.0f, 0.5f); // Black Background
glClearDepth(1.0f); // Depth Buffer Setup
glEnable(GL_DEPTH_TEST); // Enables Depth Testing
glDepthFunc(GL_LEQUAL); // The Type Of Depth Testing To Do
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); // Really Nice Perspective Calculations
glMap1f(GL_MAP1_VERTEX_3,0,1,3,4,(GLfloat *)point);
glEnable(GL_MAP1_VERTEX_3);
return TRUE; // Initialization Went OK
}
int DrawGLScene(GLvoid) // Here's Where We Do All The Drawing
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear Screen And Depth Buffer
glLoadIdentity(); // Reset The Current Modelview Matrix
glColor3f(1,1,1);
glBegin(GL_LINE_STRIP);
for (int i = 0;i <= 30;i++)
glEvalCoord1f(i / 30.0);
glEnd();
return TRUE; // Everything Went OK
}
int InitGL(GLvoid) // All Setup For OpenGL Goes Here
{
glShadeModel(GL_SMOOTH); // Enable Smooth Shading
glClearColor(0.0f, 0.0f, 0.0f, 0.5f); // Black Background
glClearDepth(1.0f); // Depth Buffer Setup
glEnable(GL_DEPTH_TEST); // Enables Depth Testing
glDepthFunc(GL_LEQUAL); // The Type Of Depth Testing To Do
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); // Really Nice Perspective Calculations
glMap1f(GL_MAP1_VERTEX_3,0,1,3,4,(GLfloat *)point);
glEnable(GL_MAP1_VERTEX_3);
return TRUE; // Initialization Went OK
}
int DrawGLScene(GLvoid) // Here's Where We Do All The Drawing
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear Screen And Depth Buffer
glLoadIdentity(); // Reset The Current Modelview Matrix
glColor3f(1,1,1);
glBegin(GL_LINE_STRIP);
for (int i = 0;i <= 30;i++)
glEvalCoord1f(i / 30.0);
glEnd();
return TRUE; // Everything Went OK
}
讲完贝赛尔曲线,接着就来看看贝赛尔曲面,贝赛尔曲面和贝赛尔曲线一样,也是由一系列的控制点来控制曲面的,他们的基本属性都是相同的。在贝赛尔模型当中曲线的参数只有一个u,而曲面有两个u和v,我没有仔细研究这个数学模型,但是根据NeHe和Redbook的几个程序以及我自己捣腾出来的一些东西加上我的猜测,我认为所谓u和v两个参数应该类似于两个坐标轴一样表示平面上两个方向上曲面上的点,然后根据一组模型来计算这个点离开基础平面的高度。好,言归正传,我们来看看怎么生成一个贝赛尔曲面。首先我们需要一组控制点,依旧拿一个浮点数组来储存,想象我们的曲面一共用16个控制点,一般而言我们将他排列为4*4的一个矩阵,也就是说这个数组应该是point[4][4][3]这样的形式(一个点由XYZ三个分量表示:)),这里其实和曲线一样,使用glMap2f来提交这组控制点,函数原型如下:
void
glMap2f(
GLenum target,
GLfloat u1,
GLfloat u2,
GLint ustride, // u方向上一个控制点与下一个控制点的距离
GLint uorder, // 阶数,等于控制点数量
GLfloat v1,
GLfloat v2,
GLint vstride, // u方向上一个控制点与下一个控制点的距离
GLint vorder, // 阶数,等于控制点数量
const GLfloat * points
);
// 其他参数其实和glMap1f没什么差别。我在NeHe和Redbook的几个程序当中发现,其实v方向上用来控制曲面的曲线只
// 有两条,而这两条恰恰在曲面的两个边上,我想根据上面的曲线叫2阶贝赛尔曲线这个应该叫二阶贝赛尔曲面吧:),
// 要创建更复杂的曲线和曲面实际上只是把更多的二阶曲线曲面合起来而已
GLenum target,
GLfloat u1,
GLfloat u2,
GLint ustride, // u方向上一个控制点与下一个控制点的距离
GLint uorder, // 阶数,等于控制点数量
GLfloat v1,
GLfloat v2,
GLint vstride, // u方向上一个控制点与下一个控制点的距离
GLint vorder, // 阶数,等于控制点数量
const GLfloat * points
);
// 其他参数其实和glMap1f没什么差别。我在NeHe和Redbook的几个程序当中发现,其实v方向上用来控制曲面的曲线只
// 有两条,而这两条恰恰在曲面的两个边上,我想根据上面的曲线叫2阶贝赛尔曲线这个应该叫二阶贝赛尔曲面吧:),
// 要创建更复杂的曲线和曲面实际上只是把更多的二阶曲线曲面合起来而已
好,这样我们的准备工作完成了,接下来,就是正式画曲面的过程了。同样有两种方法,第一用glEvalCoord2f来取得曲面上的点,这个函数需要两个参数u和v,但是在我几次使用的经验来看用这种方式一方面写出来的程序很难看,另一方面确实将简单东西复杂化了,搞到最后自己也搞不清楚自己在干什么了。所以我推荐第二种方法:网格化。这种方法真的很简单,使用glMapGrid2f来生成网格,然后用glEvalMesh2f来命令OpenGL绘图,这两个函数的参数如果在你看了上面关于贝赛尔曲线的部分就算猜也能猜得出来它是什么意义。这样你看我们的代码其实只用了2行就完成了第一种方法的那些要用到嵌套循环来完成的东西。
好,什么都完成了,接下来是代码:
float
point[
4
][
4
][
3
]
=
{{{-2,0,2},{-1,2,2},{1,-2,2},{2,0,2}},
{{-2,0,1},{-1,2,1},{1,-2,1},{2,0,1}},
{{-2,0,0},{-1,2,0},{1,-2,0},{2,0,0}},
{{-2,0,-1},{-1,2,-1},{1,-2,-1},{2,0,-1}}} ;
int InitGL(GLvoid) // All Setup For OpenGL Goes Here
{
glShadeModel(GL_SMOOTH); // Enable Smooth Shading
glClearColor(0.0f, 0.0f, 0.0f, 0.5f); // Black Background
glClearDepth(1.0f); // Depth Buffer Setup
glEnable(GL_DEPTH_TEST); // Enables Depth Testing
glDepthFunc(GL_LEQUAL); // The Type Of Depth Testing To Do
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); // Really Nice Perspective Calculations
if (!LoadTexture())
return false;
glEnable(GL_TEXTURE_2D);
glMap2f(GL_MAP2_VERTEX_3,0,1,3,4,0,1,12,4,(GLfloat *)point);
glEnable(GL_MAP2_VERTEX_3);
glEnable(GL_AUTO_NORMAL);
glMap2f(GL_MAP2_TEXTURE_COORD_2,0,1,2,2,0,1,2,4,(GLfloat *)TexPt);
glEnable(GL_MAP2_TEXTURE_COORD_2);
glMapGrid2f(20,0,1,20,0,1);
return TRUE; // Initialization Went OK
}
int DrawGLScene(GLvoid) // Here's Where We Do All The Drawing
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear Screen And Depth Buffer
glLoadIdentity(); // Reset The Current Modelview Matrix
glTranslatef(0,0,zoom);
glRotatef(xRot,1,0,0);
if (bLine)
glEvalMesh2(GL_FILL,0,20,0,20);
else
glEvalMesh2(GL_LINE,0,20,0,20);
glPointSize(5);
glColor3f(1,1,1);
return TRUE; // Everything Went OK
}
{{-2,0,1},{-1,2,1},{1,-2,1},{2,0,1}},
{{-2,0,0},{-1,2,0},{1,-2,0},{2,0,0}},
{{-2,0,-1},{-1,2,-1},{1,-2,-1},{2,0,-1}}} ;
int InitGL(GLvoid) // All Setup For OpenGL Goes Here
{
glShadeModel(GL_SMOOTH); // Enable Smooth Shading
glClearColor(0.0f, 0.0f, 0.0f, 0.5f); // Black Background
glClearDepth(1.0f); // Depth Buffer Setup
glEnable(GL_DEPTH_TEST); // Enables Depth Testing
glDepthFunc(GL_LEQUAL); // The Type Of Depth Testing To Do
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); // Really Nice Perspective Calculations
if (!LoadTexture())
return false;
glEnable(GL_TEXTURE_2D);
glMap2f(GL_MAP2_VERTEX_3,0,1,3,4,0,1,12,4,(GLfloat *)point);
glEnable(GL_MAP2_VERTEX_3);
glEnable(GL_AUTO_NORMAL);
glMap2f(GL_MAP2_TEXTURE_COORD_2,0,1,2,2,0,1,2,4,(GLfloat *)TexPt);
glEnable(GL_MAP2_TEXTURE_COORD_2);
glMapGrid2f(20,0,1,20,0,1);
return TRUE; // Initialization Went OK
}
int DrawGLScene(GLvoid) // Here's Where We Do All The Drawing
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear Screen And Depth Buffer
glLoadIdentity(); // Reset The Current Modelview Matrix
glTranslatef(0,0,zoom);
glRotatef(xRot,1,0,0);
if (bLine)
glEvalMesh2(GL_FILL,0,20,0,20);
else
glEvalMesh2(GL_LINE,0,20,0,20);
glPointSize(5);
glColor3f(1,1,1);
return TRUE; // Everything Went OK
}
关于曲面的纹理映射NeHe的和Redbook的都不太好懂,虽然我自己凭感觉也捣腾出来一个,不过效果不怎么好,容我再研究研究明天再写。