OpenGL超级宝典笔记——贝塞尔曲线和曲面(转)

http://my.oschina.net/sweetdark/blog/183721

参数方程表现形式

在中学的时候,我们都学习过直线的参数方程:y = kx + b;其中k表示斜率,b表示截距(即与y轴的交点坐标)。类似地,我们也可以用一个参数方程来表示一条曲线。1962年,法国工程师贝塞尔发明了贝塞尔曲线方程。关于贝塞尔曲线的详细介绍可以参考(维基贝塞尔)。这里只介绍OpenGL实现贝塞尔的函数。

OpenGl定义一条曲线时,也把它定义为一个曲线方程。我们把这条曲线的参数成为u,它的值域就是曲线的定义域。曲面则需要u和v两个参数来描述。注意,u和v参数只表示了描述曲线的参数方程的范围,它们并没有反映实际的坐标值。其坐标可以表示为:

x = f(u); y = g(u); z = h(u);

如下图:

控制点

贝塞尔曲线的形状由控制点来控制。贝塞尔曲线的控制点个数为曲线的阶。根据控制点的个数,贝塞尔曲线又分为二次贝塞尔曲线,三次贝塞尔曲线,高阶贝塞尔曲线。

线性曲线

线性贝塞尔曲线演示动画,t in [0,1]

二次方曲线

为建构二次贝塞尔曲线,可以中介点Q0Q1作为由0至1的t

  • P0P1的连续点Q0,描述一条线性贝塞尔曲线。
  • P1P2的连续点Q1,描述一条线性贝塞尔曲线。
  • Q0Q1的连续点Bt),描述一条二次贝塞尔曲线。


二次贝塞尔曲线的结构
二次贝塞尔曲线演示动画,t in [0,1]

三次方曲线

为建构高阶曲线,便需要相应更多的中介点。对于三次曲线,可由线性贝塞尔曲线描述的中介点Q0Q1Q2,和由二次曲线描述的点R0R1所建构:


三次贝塞尔曲线的结构
三次贝塞尔曲线演示动画,t in [0,1]

连续性

两段曲线是否相连接,代表这两段曲线是否连续的。曲线的连续性分为4种,无连续,点连续,正切连续,曲率连续。下图分别表示了这几种情况:

其中曲率连续的曲线过渡的更平滑。我们可以通过参数来设置曲线的连续性。

求值器

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);就可以得到一个收到光照的曲面了。

时间: 2024-10-01 11:58:11

OpenGL超级宝典笔记——贝塞尔曲线和曲面(转)的相关文章

OpenGL超级宝典笔记----渲染管线

在OpenGL中任何事物都在3D空间中,但是屏幕和窗口是一个2D像素阵列,所以OpenGL的大部分工作都是关于如何把3D坐标转变为适应你屏幕的2D像素.3D坐标转为2D坐标的处理过程是由OpenGL的图形渲染管线完成的.图像渲染管线可以被划分为两个主要部分:第一个部分把你的3D坐标转换为2D坐标,第二部分是把2D坐标转变为实际的有颜色的像素. 渲染管线接收一组3D坐标,然后把它们转变为你屏幕上的有色2D像素.渲染管线可以被划分为几个阶段,每个阶段需要把前一阶段的输出作为输入.所有这些阶段都是高度

OpenGL超级宝典笔记——深度纹理和阴影 【转】

目录[-] 光源视角 新型的纹理 深度纹理的大小 首先绘制阴影 然后是光照 投影阴影贴图 阴影比较 之前我们介绍过简单的把物体压平到投影平面来制造阴影.但这种阴影方式有其局限性(如投影平面须是平面).在OpenGL1.4引入了一种新的方法阴影贴图来产生阴影. 阴影贴图背后的原理是简单的.我们先把光源的位置当作照相机的位置,我们从这个位置观察物体,我们就知道哪些物体的表面是被照射到(被光源看到) 的,哪些是没有被照射到(被遮挡住)的(在某个方向上离光源最近的表面是被照射的,后面的表面则没有被照射到

OpenGL超级宝典笔记——遮挡查询 [转]

目录[-] 遮挡查询之前 包围体 遮挡查询 在一个场景中,如果有有些物体被其他物体遮住了不可见.那么我们就不需要绘制它.在复杂的场景中,这可以减少大量的顶点和像素的处理,大幅度的提高帧率.遮挡查询就是允许我们判断一组图形在进行了深度测试之后是否可见. 遮挡查询之前 为了显示遮挡查询对性能的提升,我们需要一个对照组(不使用遮挡查询来渲染场景). 首先我们先绘制“主遮挡物”.这个主遮挡物不需要太多的细节,一般是墙,天花板,地板之类的物体.在下面的例子中我们,使用6面墙来组成这个主遮挡物. void 

【转载】OpenGL超级宝典笔记——GLSL语言基础

变量 GLSL的变量命名方式与C语言类似.变量的名称可以使用字母,数字以及下划线,但变量名不能以数字开头,还有变量名不能以gl_作为前缀,这个是GLSL保留的前缀,用于GLSL的内部变量.当然还有一些GLSL保留的名称是不能够作为变量的名称的. 基本类型 除了布尔型,整型,浮点型基本类型外,GLSL还引入了一些在着色器中经常用到的类型作为基本类型.这些基本类型都可以作为结构体内部的类型.如下表: 类型 描述 void 跟C语言的void类似,表示空类型.作为函数的返回类型,表示这个函数不返回值.

OpenGL超级宝典笔记二 - 基础变换

1.向量: 点乘:float m3dDotProduce3(u,v):返回两个单位向量的余弦值 叉乘:float m3dCrossProduct3(result,u,v):返回垂直于两个向量定义的平面的向量 2.矩阵: OpenGL使用的是列优先排序的矩阵 单位矩阵(对角线为1,其他为0):任何向量乘以一个单位矩阵都不会发生任何改变 3.变换:最终获得的变换矩阵会应用到每个顶点 视图矩阵x模型矩阵x投影矩阵->投影摄像机的位置变换,物体对象的位置变换,投影裁剪变换 若顶点向量为Vert,则变换公

OpenGL超级宝典笔记三 - 基础纹理1

纹理对象的使用:纹理图像数据的类型,图像数据的导入导出方式,纹理的使用过程:图像数据的加载,纹理参数的设置,纹理的绑定. 一.图像数据的分类:位图图像和像素图像 二.像素图像数据的包装: 在OpenGL中:使用4字节的行对齐方式,每一行补全到4的倍数,使用glPixelStorei(pname,param)可以改变或者恢复像素的存储方式 介绍常用的两组:内存<->缓冲区的压缩和解包 glPixcelStorei(GL_UNPACK_ALIGNMENT) ----图像数据从内存到缓冲区的解包设置

OpenGL超级宝典笔记——画三角形(转)

http://my.oschina.net/sweetdark/blog/161002 学习了画线的知识,我们可以使用GL_LINE_LOOP来画闭合的多边形.但是使用这种方式画出来的只有线框,多边形没有填充颜色.OpenGL支持绘制实心的多边形,并使用当前的颜色进行填充. 三角形 简单的三角形,需要指定三个顶点. 1: glBegin(GL_TRIANGLES); 2: glVertex2f(0.0f, 0.0f); // V0 3: glVertex2f(25.0f, 25.0f); //

OpenGL超级宝典笔记四 - 基础纹理2

一.使用MipMap贴图(主要是应用在纹理缩小过滤中) 优点: 防止当纹理缩小太大的时候,出现闪烁的问题 提高性能,需要缩小的时候就不加载大内存的纹理 缺点: 需要额外的内存作为代价 组成:由一系列缩小的纹理图像组成,子层是父层的1/4 过滤类型:NEAREST快速,LINEAR效果好 GL_NEAREST_MINMAP_NEAREST选择最邻近Mip层并执行邻近过滤 GL_NEAREST_MINMAP_LINEAR选择最Mip层线性插补并执行邻近过滤 GL_LINEAR_MINMAP_NEAR

OpenGL超级宝典笔记一 - 基础渲染

1.渲染中的双缓冲: 使用但缓冲的时候,在渲染每一帧的绘图时,会对画板进行擦除然后在慢慢填充绘制,如果绘制时间过长的时候,就会出现闪烁的现象.为解决这个问题,所以引入双缓冲 双缓冲相当于,在显示的画板中重新创建另外一个画板,绘制的过程在另外的画板进行,绘制完成之后,将显示的画板的内容直接替换成另外一个画板的内容.这样呈现的过程中就不会出现闪烁的问题,即使是绘制的过程比较慢,最多就是出现顿的现象 在例子中渲染的RenderScene()函数中,每次渲染结束之后都会调用glutSwapBuffers