在中學的時候,我們都學習過直線的參數方程: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]
為建構二次貝塞爾曲線,可以中介點Q0和Q1作為由0至1的t:
由P0至P1的連續點Q0,描述一條線性貝塞爾曲線。
由P1至P2的連續點Q1,描述一條線性貝塞爾曲線。
由Q0至Q1的連續點B(t),描述一條二次貝塞爾曲線。
二次貝塞爾曲線的結構
二次貝塞爾曲線演示動畫,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);就可以得到一個收到光照的曲面了。
OpenGL超級寶典 第4版 中文版PDF+英文版+源代碼 見 http://www.linuxidc.com/Linux/2013-10/91413.htm
OpenGL編程指南(原書第7版)中文掃描版PDF 下載 http://www.linuxidc.com/Linux/2012-08/67925.htm
OpenGL 渲染篇 http://www.linuxidc.com/Linux/2011-10/45756.htm
Ubuntu 13.04 安裝 OpenGL http://www.linuxidc.com/Linux/2013-05/84815.htm
OpenGL三維球體數據生成與繪制【附源碼】 http://www.linuxidc.com/Linux/2013-04/83235.htm
Ubuntu下OpenGL編程基礎解析 http://www.linuxidc.com/Linux/2013-03/81675.htm
如何在Ubuntu使用eclipse for c++配置OpenGL http://www.linuxidc.com/Linux/2012-11/74191.htm
更多《OpenGL超級寶典學習筆記》相關知識 見 http://www.linuxidc.com/search.aspx?where=nkey&keyword=34581