关于贝塞尔曲线,网上很多博客都已经给出了解释。。。真的好多。
但是我看了几百遍都不明白!!不知道大家跟我有没有同样的感受。所以就来个重点解释,通俗易懂版给大家吧~~
下面的这个图,相信你也看到过很多。然而,我这里也是需要贴一下这个图的(不知道是哪个大神的图,不好意思,借用一下)。
参数讲解
P0是曲线的开始点
P3是曲线的结束点
P1和P2是控制曲线走势的控制点,所以这两个点事实上是辅助作用,并不会在画布中被绘制出来
t参数重点讲解
t是辅助参数,可以看到它的值范围是[0,1]。这个t值作用于图中的所有直线(P0P1、P1P2、P2P3、两条绿线、蓝线)。
注意:图中真实绘制出来的,就只有红线,其他的都只是辅助的,并不会被真实绘制出来。
在上图中,你可以看成这个三次贝塞尔曲线由两个二次贝塞尔曲线组成。1.P0P1P2组成的二次贝塞尔曲线 2.P1P2P3组成的二次贝塞尔曲线。
所以要把上图的三次贝塞尔曲线拆分成两个二次贝塞尔曲线讲解
例如:
对于第一个二次贝塞尔曲线P0P1P2,
当t=0.5的时候(其实就是[0,1]的中间值,这个比较好理解),情况应该是这样的:
1.找到P0P1线(方向P0->P1,这是有方向的线段)的50% (因为t=0.5,即0.5*100%) 的位置,标上一个绿色的点
2.同1步骤,在P1P2线上的50%位置上标上一个绿色的点
3.把步骤1和2的绿色点连成一条线
4.然后在这条绿色的50%位置处标上一个红点,这个红点就是实际绘制的曲线中的一个点。(当t值不断变化,就会出现不同位置的红点,组成一条曲线)
同理,
第二个二次贝塞尔曲线的理解跟第一个二次贝塞尔曲线一样!
------------------------ 我不是分割线 ------------------------------------
注意:
1、双三次贝塞尔曲线的4条边界都是三次贝塞尔曲线,其特征网络有16个顶点。
2、opengl中的赛贝尔曲线:
求值器
OpenGL提供了一些函数来绘制贝塞尔曲线和曲面。我们只需要提供控制点和u,v作为参数,然后调用求值函数来绘制曲线。
2D曲线的例子:
//控制点 GLint numOfPoints = 4; static GLfloat controlPoints[4][3] = {{-4.0f, 0.0f, 0.0f},
{-6.0f, 4.0f, 0.0f},
{6.0f, -4.0f, 0.0f},
{4.0f, 0.0f, 0.0f}}; void SetupRC()
{
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glColor3f(1.0f, 0.0f, 1.0f);
}
//画控制点
void DrawPoints()
{
glPointSize(2.5f);
glBegin(GL_POINTS); for (int i = 0; i < numOfPoints; ++i)
{
glVertex3fv(controlPoints[i]);
}
glEnd();
}
void ChangeSize(GLsizei w, GLsizei h)
{
if (h == 0)
{
h = 1;
}
glViewport(0, 0, w, h);
//使用正交投影
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluOrtho2D(-10.0f, 10.0f, -10.0f, 10.0f);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
void RenderScene()
{
glClear(GL_COLOR_BUFFER_BIT);
//设置贝塞尔曲线,这个函数其实只需要调用一次,可以放在SetupRC中设置
glMap1f(GL_MAP1_VERTEX_3, //生成的数据类型
0.0f, //u值的下界
100.0f, //u值的上界
3, //顶点在数据中的间隔,x,y,z所以间隔是3
numOfPoints, //u方向上的阶,即控制点的个数
&controlPoints[0][0] //指向控制点数据的指针 );
//必须在绘制顶点之前开启
glEnable(GL_MAP1_VERTEX_3);
//使用画线的方式来连接点
glBegin(GL_LINE_STRIP);
for (int i = 0; i <= 100; i++)
{
glEvalCoord1f((GLfloat)i);
}
glEnd();
DrawPoints();
glutSwapBuffers();
}
在RenderScene函数中调用glMap1f来为曲线创建映射。第一个参数为GL_MAP1_VERTEX3,设置求值器产生顶点为三元组(x,y,z).还可以设置为产生纹理坐标和颜色信息。参考glMap1.后面的两个参数设定了u的取值范围[0,100],第四个参数指定了顶点在数组中的间隔,由于顶点是由3个浮点数组成,所以间隔是3.第五个参数指定了控制点的个数,最后一个参数是控制点数组。然后我们需要启用求值器,调用如下:
glEnable(GL_MAP1_VERTEX3);
glEvalCoord1f函数,接受一个参数为曲线的参数值。调用这个函数会通过求值函数求出顶点坐标值,然后内部调用了glVertex。这里使用连线的方式来连接这些顶点:
glBegin(GL_LINE_STRIP);
for(i = 0; I <= 100; i++)
{
glEvalCoord1f((GLfloat)i);
}
glEnd();
计算曲线
OpenGl还提供了更简单的方式来完成上面的任务。我们可以通过glMapGrid函数来设置一个网格,来告诉OpenGL在u的值域的范围内创建一个包含各个点的空间对称的网格。然后,我们调用glEvalMesh,使用指定的图元(GL_LINE或GL_POINTS)来链接各个点。
我们用下面的两个函数调用
glMapGrid1f(100, 0.0f, 100.0f);
glEvalMesh1(GL_LINE, 0, 100);
可以替换下面的代码
glBegin(GL_LINE_STRIP);
for (int i = 0; i <= 100; i++)
{
glEvalCoord1f((GLfloat)i);
}
glEnd();
使用这种方式更为紧凑。
3D表面
创建一个贝塞尔曲面与创建一个贝塞尔曲线类似。除了给出u的定义域之外,还要给出v的定义域。下面的例子是创建一个贝塞尔曲面。与之前不同的是,我们沿着v的定义域定义了3组控制点。为了保持曲面的简单,这几组控制点只是z值不同。用这种方式画的曲面,看起来像是曲线沿z轴的扩展。
//控制点 GLint nNumPoints = 3;
GLfloat ctrlPoints[3][3][3]= {{{ -4.0f, 0.0f, 4.0f},
{ -2.0f, 4.0f, 4.0f},
{ 4.0f, 0.0f, 4.0f }},
{{ -4.0f, 0.0f, 0.0f},
{ -2.0f, 4.0f, 0.0f},
{ 4.0f, 0.0f, 0.0f }},
{{ -4.0f, 0.0f, -4.0f},
{ -2.0f, 4.0f, -4.0f},
{ 4.0f, 0.0f, -4.0f }}}; //画控制点 void DrawPoints(void)
{ int i,j;
glColor3f(1.0f, 0.0f, 0.0f); //把点放大一点,看得更清楚 glPointSize(5.0f);
glBegin(GL_POINTS);
for(i = 0; i < nNumPoints; i++)
for(j = 0; j < 3; j++)
glVertex3fv(ctrlPoints[i][j]);
glEnd();
}
void RenderScene(void)
{
// Clear the window with current clearing color
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// 保存模型视图矩阵
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
//旋转一定的角度方便观察
glRotatef(45.0f, 0.0f, 1.0f, 0.0f);
glRotatef(60.0f, 1.0f, 0.0f, 0.0f);
glColor3f(0.0f, 0.0f, 1.0f); //设置映射方式,只需要设置一次可以在SetupRC中调用。
glMap2f(GL_MAP2_VERTEX_3, //生成的数据类型
0.0f, // u的下界
10.0f, //u的上界
3, //数据中点的间隔
3, //u方向上的阶
0.0f, //v的下界
10.0f, //v的上界
9, // 控制点之间的间隔
3, // v方向上的阶
&ctrlPoints[0][0][0]); //控制点数组
//启用求值器
glEnable(GL_MAP2_VERTEX_3);
//从0到10映射一个包含10个点的网格
glMapGrid2f(10,0.0f,10.0f,10,0.0f,10.0f);
// 计算网格
glEvalMesh2(GL_LINE,0,10,0,10);
//画控制点
DrawPoints();
glPopMatrix();
glutSwapBuffers();
}
在这里我们用glMap2f替换了之前的glMap1f, 这个函数指定了u和v两个域上的点。除了指定u的上界和下界之外,还要指定v的上界和下界。v定义域内点的距离是9,因为这里使用了3维数组,包含了3个u值,每个u值又包含了3个点,3x3=9。然后指定v方向上的阶,即每个u分支上v方向有多少个点。最后一个参数是指向控制点的指针。
然后我们设置求值器.
//启用求值器
glEnable(GL_MAP2_VERTEX_3);
//从0到10映射一个包含10个点的网格
glMapGrid2f(10,0.0f,10.0f,10,0.0f,10.0f);
计算网格网格表面,用线的方式表示。
// 计算网格
glEvalMesh2(GL_LINE,0,10,0,10);
光照和法线
求值器还可以帮我们生成表面的法线,只需简单的修改一些代码:
把glEvalMesh2(GL_LINE, 0, 10, 0, 10);替换为glEvalMesh2(GL_FILL, 0, 10, 0, 10);然后在初始化时 SetupRC中调用glEnable(GL_AUTO_NORMAL);就可以得到一个收到光照的曲面了。