OpenGL超級寶典筆記——貝塞爾曲線和曲面

參數方程表現形式

在中學的時候,咱們都學習過直線的參數方程:y = kx + b;其中k表示斜率,b表示截距(即與y軸的交點座標)。相似地,咱們也能夠用一個參數方程來表示一條曲線。1962年,法國工程師貝塞爾發明了貝塞爾曲線方程。關於貝塞爾曲線的詳細介紹能夠參考(維基貝塞爾)。這裏只介紹OpenGL實現貝塞爾的函數。html

OpenGl定義一條曲線時,也把它定義爲一個曲線方程。咱們把這條曲線的參數成爲u,它的值域就是曲線的定義域。曲面則須要u和v兩個參數來描述。注意,u和v參數只表示了描述曲線的參數方程的範圍,它們並無反映實際的座標值。其座標能夠表示爲:數組

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

以下圖:學習

image

控制點

貝塞爾曲線的形狀由控制點來控制。貝塞爾曲線的控制點個數爲曲線的階。根據控制點的個數,貝塞爾曲線又分爲二次貝塞爾曲線,三次貝塞爾曲線,高階貝塞爾曲線。動畫

image

線性曲線

Image(1)

線性貝塞爾曲線演示動畫,t in [0,1]spa

二次方曲線

爲建構二次貝塞爾曲線,能夠中介點Q0Q1做爲由0至1的t.net

  • P0P1的連續點Q0,描述一條線性貝塞爾曲線。指針

  • P1P2的連續點Q1,描述一條線性貝塞爾曲線。code

  • Q0Q1的連續點Bt),描述一條二次貝塞爾曲線。xml

Image(1)
Image(2)

二次貝塞爾曲線的結構
二次貝塞爾曲線演示動畫,t in [0,1]

三次方曲線

爲建構高階曲線,便須要相應更多的中介點。對於三次曲線,可由線性貝塞爾曲線描述的中介點Q0Q1Q2,和由二次曲線描述的點R0R1所建構:

Image(2)
Image(3)

三次貝塞爾曲線的結構
三次貝塞爾曲線演示動畫,t in [0,1]

連續性

兩段曲線是否相鏈接,表明這兩段曲線是否連續的。曲線的連續性分爲4種,無連續,點連續,正切連續,曲率連續。下圖分別表示了這幾種狀況:

image

其中曲率連續的曲線過渡的更平滑。咱們能夠經過參數來設置曲線的連續性。

求值器

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();

}

image

在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);

image

光照和法線

求值器還能夠幫咱們生成表面的法線,只需簡單的修改一些代碼:

把glEvalMesh2(GL_LINE, 0, 10, 0, 10);替換爲glEvalMesh2(GL_FILL, 0, 10, 0, 10);而後在初始化時 SetupRC中調用glEnable(GL_AUTO_NORMAL);就能夠獲得一個收到光照的曲面了。

image

相關文章
相關標籤/搜索