在中學的時候,咱們都學習過直線的參數方程:y = kx + b;其中k表示斜率,b表示截距(即與y軸的交點座標)。相似地,咱們也能夠用一個參數方程來表示一條曲線。1962年,法國工程師貝塞爾發明了貝塞爾曲線方程。關於貝塞爾曲線的詳細介紹能夠參考(維基貝塞爾)。這裏只介紹OpenGL實現貝塞爾的函數。html
OpenGl定義一條曲線時,也把它定義爲一個曲線方程。咱們把這條曲線的參數成爲u,它的值域就是曲線的定義域。曲面則須要u和v兩個參數來描述。注意,u和v參數只表示了描述曲線的參數方程的範圍,它們並無反映實際的座標值。其座標能夠表示爲:數組
x = f(u); y = g(u); z = h(u);函數
以下圖:學習
貝塞爾曲線的形狀由控制點來控制。貝塞爾曲線的控制點個數爲曲線的階。根據控制點的個數,貝塞爾曲線又分爲二次貝塞爾曲線,三次貝塞爾曲線,高階貝塞爾曲線。動畫
線性貝塞爾曲線演示動畫,t in [0,1]spa
爲建構二次貝塞爾曲線,能夠中介點Q0和Q1做爲由0至1的t:.net
由P0至P1的連續點Q0,描述一條線性貝塞爾曲線。指針
由P1至P2的連續點Q1,描述一條線性貝塞爾曲線。code
由Q0至Q1的連續點B(t),描述一條二次貝塞爾曲線。xml
二次貝塞爾曲線的結構
二次貝塞爾曲線演示動畫,t in [0,1]
爲建構高階曲線,便須要相應更多的中介點。對於三次曲線,可由線性貝塞爾曲線描述的中介點Q0、Q1、Q2,和由二次曲線描述的點R0、R1所建構:
三次貝塞爾曲線的結構
三次貝塞爾曲線演示動畫,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();
使用這種方式更爲緊湊。
建立一個貝塞爾曲面與建立一個貝塞爾曲線相似。除了給出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);就能夠獲得一個收到光照的曲面了。