OpenGL快速入門
概述
OpenGL
OpenGL是渲染2D、3D向量圖形硬體的一種軟體介面。本質上說,它是一個3D圖形和模型庫,具有高度的可移植性,並且具有非常快的渲染速度。OpenGL並不是一種語言,而是更像一個C執行時函式庫。它提供了一些預包裝的功能,幫助開發人員編寫功能強大的三維應用程式。 OpenGL可以再多種作業系統平臺上執行,例如各種版本的Windows、UNIX/Linux、Mac OS 和 OS/2等。如今,OpenGL廣泛流行於遊戲、醫學影像、地理資訊、氣象模擬等領域,是高效能影象和互動性場景處理的工業標準。 OpenGL的高效實現(利用了圖形加速硬體)存在於Windows,部分UNIX平臺和Mac OS。這些實現一般由顯示裝置廠商提供,而且非常依賴於該廠商提供的硬體。
OpenGL ES與WebGL
OpenGL ES (OpenGL for Embedded Systems) 是 OpenGL 三維圖形 API 的子集,針對手機、PDA和遊戲主機等嵌入式裝置而設計
WebGL(全寫Web Graphics Library)是一種3D繪圖協議,這種繪圖技術標準允許把JavaScript和OpenGL ES 結合在一起,通過增加OpenGL ES 的一個JavaScript繫結,WebGL可以為HTML5 Canvas提供硬體3D加速渲染,這樣Web開發人員就可以藉助系統顯示卡來在瀏覽器裡更流暢地展示3D場景和模型了,還能建立複雜的導航和資料視覺化。
OpenGL發展史
OpenGL是個開放的標準,雖然它由SGI(美國矽圖公司)首創,但它的標準並不是控制在SGI的手中,而是由OpenGL體系結構稽核委員會(ARB)所掌管。 ARB由SGC、DEC、IBM、Intel和Microsoft等著名公司1992年創立,後來又陸續添加了nVidia、ATI等圖形晶片領域的巨擎。 ARB每隔4年開一次會,對OpenGL規範進行維護和改善,並出臺計劃對OpenGL標準進行升級,使OpenGL一直保持與時代的同步。
2006年,SGIG公司把OpenGL標準的控制從ARB移交給一個新的工作組:Khronos小組(www.khronos.org)。 Khronos是一個由成員提供資金的行業協會,專注於開放媒體標準的建立和維護。
軟體安裝
在正式開始學習OpenGL之前,我們需要先配置好OpenGL的軟體環境。
IDE
支援OpenGL的IDE有很多,OpenGL的開發環境我們選擇的是Visual Studio,可以從Visual Studio官網下載最新的版本。
GLFW
OpenGL是一個圖形庫,而要畫圖,就需要先建立一個視窗。不幸的是,OpenGL並沒有提供建立視窗的功能,必須自己建立視窗。而建立視窗在每一個作業系統上都不同的(在Windows上程式碼量也不少),為了方便,我們會使用一個視窗庫來簡化這一過程。常用的OpenGL視窗庫有GLUT、GLFW和SDL,此處為我們選擇使用得比較多的GLFW。
Visual Studio對於OpenGL(gl.h)只支援到1.1,而我們使用的是OpenGL 3.3。但是,OpenGL是由顯示卡支援的,顯示卡已經提供了我們需要的OpenGL函式。因此就需要在執行程式時動態地獲取函式地址。在Windows下,以glGenBuffers為例,大概是這樣的:
#include <windows.h>
#include <GL/gl.h>
...
// define the functions' prototypes
typedef void * (*WGLGETPROCADDRESS)(const char *);
typedef void (*GLGENBUFFERS)(GLsizei, GLsizei *);
// load opengl32.dll and query wglGetProcAddress' address
HMODULE hDll = LoadLibrary("opengl32.dll");
WGLGETPROCADDRESS wglGetProcAddress = (WGLGETPROCADDRESS)GetProcAddress(hDll, "wglGetProcAddress");
// query OpenGL functions' addresses
GLGENBUFFERS glGenBuffers = (GLGENBUFFERS)wglGetProcAddress("glGenBuffers");
// now the function can be used as normal
GLuint vbo;
glGenBuffers(1, &vbo);
複製程式碼
當然,GLFW可以從它的官方網站上下載。然後,你可以直接下載它的binaries,或者自己使用CMake編譯。如果自己使用CMake編譯,可以參考下面的文章: GLFW 環境配置,建立視窗
如果下載已經編譯好的binaries,解壓並開啟,可以找到一個include資料夾和若干lib-xxxx資料夾(xxxx是編譯器名)。include資料夾裡含有一個GLFW資料夾,裡面有glfw3.h(還有一個glfw3native.h不用管)
詳細文件可以參考官方的介紹,或者直接從GLFW官方網站的下載頁上獲取原始碼包。
OpenGL基礎知識
資料型別和函式名
OpenGL的資料型別定義可以與其它語言一致,但建議在ANSI C下最好使用以下定義的資料型別,例如GLint、GLfloat等。
字首 | 資料型別 | 相應C語言型別 | OpenGL型別 |
---|---|---|---|
b | 8-bit integer | signed char | GLbyte |
s | 16-bit integer | short | GLshort |
i | 32-bit integer | long | GLint,GLsizei |
f | 32-bit floating-point | float | GLfloat,GLclampf |
d | 64-bit floating-point | double | GLdouble,GLclampd |
ub | 8-bit unsigned integer | unsigned char | GLubyte,GLboolean |
us | 16-bit unsigned integer | unsigned short | GLushort |
ui | 32-bit unsigned integer | unsigned long | GLuint,GLenum,GLbitfield |
從上表可以看出,OpenGL的庫函式命名方式很有規律,瞭解這種規律後閱讀和編寫程式都比較容易方便。 首先,每個庫函式有字首gl、glu、glx或aux,表示此函式分屬於基本庫、實用庫、X視窗擴充庫或輔助庫,其後的函式名頭字母大寫,字尾是引數型別的簡寫,取i、f。例如:
glVertex2i(2,4);
glVertex3f(2.0,4.0,5.0);
複製程式碼
如上,有的函式引數型別字尾前帶有數字2、3、4。其中,2代表二維,3代表三維,4代表alpha值。
除此之外,有些OpenGL函式最後帶一個字母v,表示函式引數可用一個指標指向一個向量(或陣列)來替代一系列單個引數值。下面兩種格式都表示設定當前顏色為紅色,二者等價。
glColor3f(1.0,0.0,0.0);
float color_array[]={1.0,0.0,0.0};
glColor3fv(color_array);
複製程式碼
除了以上基本命名方式外,還有一種帶“”星號的表示方法,例如glColor(),它表示可以用函式的各種方式來設定當前顏色。同理,glVertex*v()表示用一個指標指向所有型別的向量來定義一系列頂點座標值。
示例
例如有下面一個示例程式,也是一個初學者學習的第一個示例程式。原始碼如下:
// main.cpp
// opengl_progress_struct
#include <GLUT/GLUT.h>
#include <OpenGL/OpenGL.h>
// 初始化引數
void init() {
glClearColor(0.1, 0.1, 0.4, 0.0);
glShadeModel(GL_SMOOTH);
}
// 繪圖回撥函式
void display() {
// 清除之前幀資料
glClear(GL_COLOR_BUFFER_BIT);
// 繪製三角形
glBegin(GL_TRIANGLES);
glColor3f(1, 0, 0);
glVertex3f(-1, -1, -5);
glColor3f(0, 1, 0);
glVertex3f(1, -1, -5);
glColor3f(0, 0, 1);
glVertex3f(0, 1, -5);
glEnd();
// 執行繪圖命令
glFlush();
}
// 視窗大小變化回撥函式
void reshape(int w, int h) {
glViewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(60.0, (GLfloat)w/(GLfloat)h, 0.1, 100000.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
int main(int argc, const char * argv[]) {
// 初始化顯示模式
glutInit(&argc, const_cast<char **>(argv));
glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);
// 初始化視窗
glutInitWindowSize(500, 500);
glutInitWindowPosition(100, 100);
glutCreateWindow(argv[0]);
init();
glutReshapeFunc(reshape);
glutDisplayFunc(display);
// 開始主迴圈繪製
glutMainLoop();
return 0;
}
複製程式碼
執行效果如下:
幾何圖形繪製
在空間直角座標系中,任意一點可用一個三維座標矩陣[x y z]表示。如果將該點用一個四維座標的矩陣[Hx Hy Hz H]表示時,則稱為齊次座標表示方法。在齊次座標中,最後一維座標H稱為比例因子。 在OpenGL中,二維座標點全看作三維座標點,所有點都用齊次座標來描述,統一作為三維齊次點來處理。每個齊次點用一個向量(x, y, z, w)表示,其中四個元素全不為零。齊次點具有下列幾個性質: 1)如果實數a非零,則(x, y, x, w)和(ax, ay, az, aw)表示同一個點,類似於x/y = (ax)/( ay)。 2)三維空間點(x, y, z)的齊次點座標為(x, y, z, 1.0),二維平面點(x,y)的齊次座標為(x, y, 0.0, 1.0)。 3)當w不為零時,齊次點座標(x, y, z, w)即三維空間點座標(x/w, y/w, z/w);當w為零時,齊次點(x, y, z, 0.0)表示此點位於某方向的無窮遠處。 注意:OpenGL中指定w大於或等於0.0。
幾何圖形
在集合圖形中,會涉及到幾個概念:
點
用浮點值表示的點稱為頂點(Vertex)。所有頂點在OpenGL內部計算時都作為三維點處理,用二維座標(x, y)定義的點在OpenGL中預設z值為0。所有頂點座標用齊次座標(x, y, z, w) 表示,如果w不為0.0,這些齊次座標表示的頂點即為三維空間點(x/w, y/w, z/w)。程式設計者可以自己指定w值,但很少這樣做。一般來說,w預設為1.0。
線
在OpenGL中,線代表線段(Line Segment),不是數學意義上的那種沿軸兩個方向無限延伸的線。這裡的線由一系列頂點順次連結而成,有閉合和不閉合兩種。
多邊形
OpenGL中定義的多邊形是由一系列線段依次連結而成的封閉區域。這些線段不能交叉,區域內不能有空洞,多邊形必須在凸多邊形,否則不能被OpenGL函式接受。
繪製圖元
定義頂點
在OpenGL中,所有幾何物體最終都由有一定順序的頂點集來描述的。函式glVertex{234}{sifd}[v](TYPE coords)可以用二維、三維或齊次座標定義頂點。例如:
glVertex2s(2,3);
glVertex3d(0.0,1.0,3.1414926535);
glVertex4f(2.4,1.0,-2.2,2.0);
GLfloat pp[3]={5.0,2.0,10.2};
glVertex3fv(pp);
複製程式碼
第一例子表示一個空間頂點(2, 3, 0),第二個例子表示用雙精度浮點數定義一個頂點,第三個例子表示用齊次座標定義一個頂點,其真實座標為(1.2, 0.5, -1.1),最後一個例子表示用一個指標(或陣列)定義頂點。
幾何圖元
在實際應用中,通常用一組相關的頂點序列以一定的方式組織起來定義某個幾何圖元,而不採用單獨定義多個頂點來構造幾何圖元。在OpenGL中,所有被定義的頂點必須放在glBegain()和glEnd()兩個函式之間才能正確表達一個幾何圖元或物體,否則,glVertex*()不完成任何操作。例如:
glBegin(GL_POLYGON);
    glVertex2f(0.0,0.0);
    glVertex2f(0.0,3.0);
    glVertex2f(3.0,3.0);
    glVertex2f(4.0,1.5);
    glVertex2f(3.0,0.0);
glEnd();
複製程式碼
以上這段程式定義了一個多邊形,如果將glBegin()中的引數GL_POLYGON改為GL_POINTS,則圖形變為一組頂點(5個)。
圖元標誌
點函式glBegin(GLenum mode)標誌描述一個幾何圖元的頂點列表的開始,其引數mode表示幾何圖元的描述型別。所有型別及說明見下表:
型別 | 說明 |
---|---|
GL_POINTS | 單個頂點集 |
GL_LINES | 多組雙頂點線段 |
GL_POLYGON | 單個簡單填充凸多邊形 |
GL_TRAINGLES | 多組獨立填充三角形 |
GL_QUADS | 多組獨立填充四邊形 |
GL_LINE_STRIP | 不閉合折線 |
GL_LINE_LOOP | 閉合折線 |
GL_TRAINGLE_STRIP | 線型連續填充三角形串 |
GL_TRAINGLE_FAN | 扇形連續填充三角形串 |
GL_QUAD_STRIP | 連續填充四邊形串 |
在glBegin()和glEnd()之間最重要的資訊就是由函式glVertex*()定義的頂點,必要時也可為每個頂點指定顏色、法向、紋理座標或其他,即呼叫相關的函式,如下表。
函式 | 說明 |
---|---|
glVertex*() | 設定頂點座標 |
glColor*() | 設定當前顏色 |
glIndex*() | 設定當前顏色表 |
glNormal*() | 設定法向座標 |
glCallList(),glCallLists() | 執行顯示列表 |
glTexCoord*() | 設定紋理座標 |
glEdgeFlag*() | 控制邊界繪製 |
glMaterial*() | 設定材質 |
glBegin(GL_POINTS);
    glColor3f(1.0,0.0,0.0); /* red color */
    glVertex(...);
    glColor3f(0.0,1.0,0.0); /* green color */
    glColor3f(0.0,0.0,1.0); /* blue color */
    glVertex(...);
    glVertex(...);
  glEnd();
複製程式碼
示例
為了更好的理解OpenGL幾何圖形的繪製,下面看一個綜合的示例。
#include <GLUT/GLUT.h>
#include <OpenGL/OpenGL.h>
// 初始化引數
void init() {
glClearColor(0.1, 0.1, 0.4, 0.0);
glShadeModel(GL_SMOOTH);
}
void DrawMyObjects(void){
/* draw some points */
glBegin(GL_POINTS);
glColor3f(1.0,0.0,0.0);
glVertex2f(-10.0,11.0);
glColor3f(1.0,1.0,0.0);
glVertex2f(-9.0,10.0);
glColor3f(0.0,1.0,1.0);
glVertex2f(-8.0,12.0);
glEnd();
/* draw some line_segments */
glBegin(GL_LINES);
glColor3f(1.0,1.0,0.0);
glVertex2f(-11.0,8.0);
glVertex2f(-7.0,7.0);
glColor3f(1.0,0.0,1.0);
glVertex2f(-11.0,9.0);
glVertex2f(-8.0,6.0);
glEnd();
/* draw one opened_line */
glBegin(GL_LINE_STRIP);
glColor3f(0.0,1.0,0.0);
glVertex2f(-3.0,9.0);
glVertex2f(2.0,6.0);
glVertex2f(3.0,8.0);
glVertex2f(-2.5,6.5);
glEnd();
/* draw one closed_line */
glBegin(GL_LINE_LOOP);
glColor3f(0.0,1.0,1.0);
glVertex2f(7.0,7.0);
glVertex2f(8.0,8.0);
glVertex2f(9.0,6.5);
glVertex2f(10.3,7.5);
glVertex2f(11.5,6.0);
glVertex2f(7.5,6.0);
glEnd();
/* draw one filled_polygon */
glBegin(GL_POLYGON);
glColor3f(0.5,0.3,0.7);
glVertex2f(-7.0,2.0);
glVertex2f(-8.0,3.0);
glVertex2f(-10.3,0.5);
glVertex2f(-7.5,-2.0);
glVertex2f(-6.0,-1.0);
glEnd();
/* draw some filled_quandrangles */
glBegin(GL_QUADS);
glColor3f(0.7,0.5,0.2);
glVertex2f(0.0,2.0);
glVertex2f(-1.0,3.0);
glVertex2f(-3.3,0.5);
glVertex2f(-0.5,-1.0);
glColor3f(0.5,0.7,0.2);
glVertex2f(3.0,2.0);
glVertex2f(2.0,3.0);
glVertex2f(0.0,0.5);
glVertex2f(2.5,-1.0);
glEnd();
/* draw some filled_strip_quandrangles */
glBegin(GL_QUAD_STRIP);
glVertex2f(6.0,-2.0);
glVertex2f(5.5,1.0);
glVertex2f(8.0,-1.0);
glColor3f(0.8,0.0,0.0);
glVertex2f(9.0,2.0);
glVertex2f(11.0,-2.0);
glColor3f(0.0,0.0,0.8);
glVertex2f(11.0,2.0);
glVertex2f(13.0,-1.0);
glColor3f(0.0,0.8,0.0);
glVertex2f(14.0,1.0);
glEnd();
/* draw some filled_triangles */
glBegin(GL_TRIANGLES);
glColor3f(0.2,0.5,0.7);
glVertex2f(-10.0,-5.0);
glVertex2f(-12.3,-7.5);
glVertex2f(-8.5,-6.0);
glColor3f(0.2,0.7,0.5);
glVertex2f(-8.0,-7.0);
glVertex2f(-7.0,-4.5);
glVertex2f(-5.5,-9.0);
glEnd();
/* draw some filled_strip_triangles */
glBegin(GL_TRIANGLE_STRIP);
glVertex2f(-1.0,-8.0);
glVertex2f(-2.5,-5.0);
glColor3f(0.8,0.8,0.0);
glVertex2f(1.0,-7.0);
glColor3f(0.0,0.8,0.8);
glVertex2f(2.0,-4.0);
glColor3f(0.8,0.0,0.8);
glVertex2f(4.0,-6.0);
glEnd();
/* draw some filled_fan_triangles */
glBegin(GL_TRIANGLE_FAN);
glVertex2f(8.0,-6.0);
glVertex2f(10.0,-3.0);
glColor3f(0.8,0.2,0.5);
glVertex2f(12.5,-4.5);
glColor3f(0.2,0.5,0.8);
glVertex2f(13.0,-7.5);
glColor3f(0.8,0.5,0.2);
glVertex2f(10.5,-9.0);
glEnd();
}
// 繪圖回撥函式
void display() {
// 清除之前幀資料
glClear(GL_COLOR_BUFFER_BIT);
DrawMyObjects();
// 執行繪圖命令
glFlush();
}
// 視窗大小變化回撥函式
void reshape(int w, int h) {
glViewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(60.0, (GLfloat)w/(GLfloat)h, 0.1, 100000.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
gluLookAt(0, 0, 25, 0, 0, -1, 0, 1, 0);
}
int main(int argc, const char * argv[]) {
// 初始化顯示模式
glutInit(&argc, const_cast<char **>(argv));
glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);
// 初始化視窗
glutInitWindowSize(500, 500);
glutInitWindowPosition(100, 100);
glutCreateWindow(argv[0]);
init();
glutReshapeFunc(reshape);
glutDisplayFunc(display);
// 開始主迴圈繪製
glutMainLoop();
return 0;
}
複製程式碼
執行效果如下圖:
座標系及座標變換
右手座標系
openGL採用右手座標系,關於左右手座標系區別可參考下圖。
座標空間
openGL 空間分為:
- 區域性空間(Local Space,或者稱為物體空間(Object Space))
- 世界空間(World Space)
- 觀察空間(View Space,或者稱為視覺空間(Eye Space))
- 裁剪空間(Clip Space)
- 螢幕空間(Screen Space)
區域性空間
區域性空間是指物體所在的座標空間,即物件最開始所在的地方。想象你在一個建模軟體中建立了一個立方體。你建立的立方體的原點有可能位於(0, 0, 0),即便它有可能最後在程式中處於完全不同的位置。甚至有可能你建立的所有模型都以(0, 0, 0)為初始位置。所以,你的模型的所有頂點都是在區域性空間中,它們相對於你的物體來說都是區域性的。
世界空間
如果我們將我們所有的物體匯入到程式當中,它們有可能會全擠在世界的原點(0, 0, 0)上,這並不是我們想要的結果。我們想為每一個物體定義一個位置,從而能在更大的世界當中放置它們。世界空間中的座標正如其名:是指頂點相對於世界的座標。如果你希望將物體分散在世界上擺放(特別是非常真實的那樣),這就是你希望物體變換到的空間。物體的座標將會從區域性變換到世界空間;該變換是由模型矩陣(Model Matrix)實現的。 模型矩陣是一種變換矩陣,它能通過對物體進行位移、縮放、旋轉來將它置於它本應該在的位置或朝向。你可以將它想像為變換一個房子,你需要先將它縮小(它在區域性空間中太大了),並將其位移至郊區的一個小鎮,然後在y軸上往左旋轉一點以搭配附近的房子。你也可以把上一節將箱子到處擺放在場景中用的那個矩陣大致看作一個模型矩陣;我們將箱子的區域性座標變換到場景/世界中的不同位置。
觀測空間
觀察空間經常被人們稱之OpenGL的攝像機(Camera)(所以有時也稱為攝像機空間(Camera Space)或視覺空間(Eye Space))。觀察空間是將世界空間座標轉化為使用者視野前方的座標而產生的結果。因此觀察空間就是從攝像機的視角所觀察到的空間。而這通常是由一系列的位移和旋轉的組合來完成,平移/旋轉場景從而使得特定的物件被變換到攝像機的前方。這些組合在一起的變換通常儲存在一個觀察矩陣(View Matrix)裡,它被用來將世界座標變換到觀察空間。
裁剪空間
在一個頂點著色器執行的最後,OpenGL期望所有的座標都能落在一個特定的範圍內,且任何在這個範圍之外的點都應該被裁剪掉(Clipped)。被裁剪掉的座標就會被忽略,所以剩下的座標就將變為螢幕上可見的片段。這也就是裁剪空間(Clip Space)名字的由來。 因為將所有可見的座標都指定在−1.0 −1.0到1.0 1.0的範圍內不是很直觀,所以我們會指定自己的座標集(Coordinate Set)並將它變換回標準化裝置座標系,就像OpenGL期望的那樣。 為了將頂點座標從觀察變換到裁剪空間,我們需要定義一個投影矩陣(Projection Matrix),它指定了一個範圍的座標,比如在每個維度上的−1000 −1000到1000 1000。投影矩陣接著會將在這個指定的範圍內的座標變換為標準化裝置座標的範圍(−1.0,1.0) (−1.0,1.0)。所有在範圍外的座標不會被對映到在−1.0 −1.0到1.0 1.0的範圍之間,所以會被裁剪掉。在上面這個投影矩陣所指定的範圍內,座標(1250,500,750) (1250,500,750)將是不可見的,這是由於它的x x座標超出了範圍,它被轉化為一個大於1.0 1.0的標準化裝置座標,所以被裁剪掉了。 如果只是圖元(Primitive),例如三角形,的一部分超出了裁剪體積(Clipping Volume),則OpenGL會重新構建這個三角形為一個或多個三角形讓其能夠適合這個裁剪範圍。 由投影矩陣建立的觀察箱(Viewing Box)被稱為平截頭體(Frustum),每個出現在平截頭體範圍內的座標都會最終出現在使用者的螢幕上。將特定範圍內的座標轉化到標準化裝置座標系的過程(而且它很容易被對映到2D觀察空間座標)被稱之為投影(Projection),因為使用投影矩陣能將3D座標投影(Project)到很容易對映到2D的標準化裝置座標系中。
螢幕空間
最終的座標將會被對映到螢幕空間中(使用glViewport中的設定),並被變換成片段。
空間變換
為了將座標從一個座標系變換到另一個座標系,我們需要用到幾個變換矩陣,最重要的幾個分別是模型(Model)、觀察(View)、投影(Projection)三個矩陣。物體頂點的起始座標再區域性空間(Local Space),這裡稱它為區域性座標(Local Coordinate),它在之後會變成世界座標(world Coordinate),觀測座標(View Coordinate),裁剪座標(Clip Coordinate),並最後以螢幕座標(Screen Corrdinate)的形式結束。
下面這張圖闡釋了 空間變換過程中的具體過程和結果。
相關API
空間變化相關的API有:
模型矩陣變換
void glTranslate{fd}(TYPE x,TYPE y,TYPE z)
void glRotate{fd}(TYPE angle,TYPE x,TYPE y,TYPE z)
void glScale{fd}(TYPE x,TYPE y,TYPE z)
複製程式碼
檢視矩陣變換
void gluLookAt(GLdouble eyex,GLdouble eyey,GLdouble eyez,GLdouble centerx,GLdouble centery,GLdouble centerz,GLdouble upx,GLdouble upy,GLdouble upz);
複製程式碼
投影變換
void glOrtho(GLdouble left,GLdouble right,GLdouble bottom,GLdouble top, GLdouble near,GLdouble far)
void gluOrtho2D(GLdouble left,GLdouble right,GLdouble bottom,GLdouble top)
void glFrustum(GLdouble left,GLdouble Right,GLdouble bottom,GLdouble top, GLdouble near,GLdouble far);
void gluPerspective(GLdouble fovy,GLdouble aspect,GLdouble zNear, GLdouble zFar);
複製程式碼
視口變換
glViewport(GLint x,GLint y,GLsizei width, GLsizei height);
複製程式碼
通用變換
void glLoadMatrix{fd}(const TYPE *m)
void glMultMatrix{fd}(const TYPE *m)
複製程式碼
OpenGL紋理
在三維圖形中,紋理對映(Texture Mapping)的方法運用得很廣,尤其描述具有真實感的物體。比如繪製一面磚牆,就可以用一幅真實的磚牆影象或照片作為紋理貼到一個矩形上,這樣,一面逼真的磚牆就畫好了。如果不用紋理對映的方法,則牆上的每一塊磚都必須作為一個獨立的多邊形來畫。另外,紋理對映能夠保證在變換多邊形時,多邊形上的紋理圖案也隨之變化。例如,以透視投影方式觀察牆面時,離視點遠的磚塊的尺寸就會縮小,而離視點 較近的就會大些。此外,紋理對映也常常運用在其他一些領域,如飛行模擬中常把一大片植被的影象對映到一些大多邊形上用以表示地面,或用大理石、木材、布匹等自然物質的影象作為紋理對映到多邊形上表示相應的物體。
紋理分類
按照紋理的使用場景和表現形式來分,紋理主要分為以下幾類:
- 一維紋理,例如,程式所繪製的帶紋理的鑲條的所有變化可能發生在同一個方向,一維紋理就像一個高度為1的二維紋理。
- 二維紋理,其實是最容易理解的,也是最常用的,具有橫向和縱向紋理座標的,通常一個圖片可以用作一個二維紋理。
- 三維紋理,最常見的應用是醫學和地球科學領域的渲染。在醫學應用程式中,三維紋理可以用於表示一系列的斷層計算成像系統(CT)或者核磁共振(MRI)影象。對於石油和天然氣研究人員,三維紋理可以用來對岩石底層進行建模。三維紋理可以看成一層層二維子影象矩形構成的。
- 球體紋理, 也就是環境紋理,目標是渲染具有完美反射能力的物體,它的表面顏色就是反射到人眼周圍環境的顏色。
- 立方體紋理,是一種特殊的紋理技術,它用6幅二維紋理影象構成一個以原點為中心的紋理立方體。立方體紋理非常適用於實現環境、反射和光照效果。
- 多重紋理,多重紋理允許應用幾個紋理,在紋理操作管線中把它們逐個應用到同一個多邊形上。
- 。。。
紋理定義
一維紋理
void glTexImage1D(GLenum target,GLint level,GLint components,GLsizei width,
 GLint border,GLenum format,GLenum type,const GLvoid *pixels);
複製程式碼
定義一個一維紋理對映,除了第一個引數target應設定為GL_TEXTURE_1D外,其餘所有的引數與函式TexImage2D()的一致,不過紋理影象是一維紋素陣列,其寬度值必須是2的冪,若有邊界則為2m+2。
二維紋理
void glTexImage2D(GLenum target,GLint level,GLint components,
           GLsizei width, glsizei height,GLint border,
           GLenum format,GLenum type, const GLvoid *pixels);
複製程式碼
定義一個二維紋理對映。其中引數target是常數GL_TEXTURE_2D。引數level表示多級解析度的紋理影象的級數,若只有一種解析度,則level設為0。 引數components是一個從1到4的整數,指出選擇了R、G、B、A中的哪些分量用於調整和混合,1表示選擇了R分量,2表示選擇了R和A兩個分量,3表示選擇了R、G、B三個分量,4表示選擇了R、G、B、A四個分量。 引數width和height給出了紋理影象的長度和寬度,引數border為紋理邊界寬度,它通常為0,width和height必須是2m+2b,這裡m是整數,長和寬可以有不同的值,b是border的值。紋理對映的最大尺寸依賴於OpenGL,但它至少必須是使用64x64(若帶邊界為66x66),若width和height設定為0,則紋理對映有效地關閉。 引數format和type描述了紋理對映的格式和資料型別,它們在這裡的意義與在函式glDrawPixels()中的意義相同,事實上,紋理資料與glDrawPixels()所用的資料有同樣的格式。引數format可以是GL_COLOR_INDEX、GL_RGB、GL_RGBA、GL_RED、GL_GREEN、GL_BLUE、GL_ALPHA、GL_LUMINANCE或GL_LUMINANCE_ALPHA(注意:不能用GL_STENCIL_INDEX和GL_DEPTH_COMPONENT)。類似地,引數type是GL_BYPE、GL_UNSIGNED_BYTE、GL_SHORT、 GL_UNSIGNED_SHORT、GL_INT、GL_UNSIGNED_INT、GL_FLOAT或GL_BITMAP。 引數pixels包含了紋理影象資料,這個資料描述了紋理影象本身和它的邊界。
紋理控制函式
OpenGL中的紋理控制函式如下:
void glTexParameter{if}[v](GLenum target,GLenum pname,TYPE param);
複製程式碼
第一個引數target可以是GL_TEXTURE_1D或GL_TEXTURE_2D,它指出是為一維或二維紋理說明引數;後兩個引數的可能值見下表。
引數 | 對應的值 |
---|---|
GL_TEXTURE_WRAP_S | GL_CLAMP ,GL_REPEAT |
GL_TEXTURE_WRAP_T | GL_CLAMP,GL_REPEAT |
GL_TEXTURE_MAG_FILTER | GL_NEAREST,GL_LINEAR |
GL_TEXTURE_MIN_FILTER | GL_NEAREST,GL_LINEAR,GL_NEAREST_MIPMAP_NEAREST ,GL_NEAREST_MIPMAP_LINEAR ,GL_LINEAR_MIPMAP_NEAREST ,GL_LINEAR_MIPMAP_LINEAR |
一般來說,紋理影象為正方形或長方形。但當它對映到一個多邊形或曲面上並變換到螢幕座標時,紋理的單個紋素很少對應於螢幕影象上的象素。根據所用變換和所用紋理對映,螢幕上單個象素可以對應於一個紋素的一小部分(即放大)或一大批紋素(即縮小)。下面用函式glTexParameter*()說明放大和縮小的方法:
glTexParameter*(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);
  glTexParameter*(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);
複製程式碼
實際上,第一個引數可以是GL_TEXTURE_1D或GL_TEXTURE_2D,即表明所用的紋理是一維的還是二維的;第二個引數指定濾波方法,其中引數值GL_TEXTURE_MAG_FILTER指定為放大濾波方法,GL_TEXTURE_MIN_FILTER指定為縮小濾波方法;第三個引數說明濾波方式,其值見表12-1所示。 若選擇GL_NEAREST則採用座標最靠近象素中心的紋素,這有可能使影象走樣;若選擇GL_LINEAR則採用最靠近象素中心的四個象素的加權平均值。GL_NEAREST所需計算比GL_LINEAR要少,因而執行得更快,但GL_LINEAR提供了比較光滑的效果。
同時,紋理座標可以超出(0, 1)範圍,並且在紋理對映過程中可以重複對映或約簡對映。在重複對映的情況下,紋理可以在s,t方向上重複。例如:
 glTexParameterfv(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT);
 glTexParameterfv(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT);
複製程式碼
紋理座標
在繪製紋理對映場景時,不僅要給每個頂點定義幾何座標,而且也要定義紋理座標。經過多種變換後,幾何座標決定頂點在螢幕上繪製的位置,而紋理座標決定紋理影象中的哪一個紋素賦予該頂點。並且頂點之間的紋理座標插值與基礎篇中所講的平滑著色插值方法相同。 紋理影象是方形陣列,紋理座標通常可定義成一、二、三或四維形式,稱為s,t,r和q座標,以區別於物體座標(x, y, z, w)和其他座標。一維紋理常用s座標表示,二維紋理常用(s, t)座標表示,目前忽略r座標,q座標象w一樣,一半值為1,主要用於建立齊次座標。OpenGL座標定義的函式是:
void gltexCoord{1234}{sifd}[v](TYPE coords);
複製程式碼
設定當前紋理座標,此後呼叫glVertex*()所產生的頂點都賦予當前的紋理座標。對於gltexCoord1*(),s座標被設定成給定值,t和r設定為0,q設定為1;用gltexCoord2*()可以設定s和t座標值,r設定為0,q設定為1;對於gltexCoord3*(),q設定為1,其它座標按給定值設定;用gltexCoord4*()可以給定所有的座標。使用適當的字尾(s,i,f或d)和TYPE的相應值(GLshort、GLint、glfloat或GLdouble)來說明座標的型別。注意:整型紋理座標可以直接應用,而不是象普通座標那樣被對映到[-1, 1]之間。
示例
#include <GLUT/GLUT.h>
#include <OpenGL/OpenGL.h>
#include "BMPLoader.h"
GLuint tex2D;
GLfloat angle;
// 初始化引數
void init() {
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LESS);
glClearColor(0.1, 0.1, 0.4, 0.0);
glShadeModel(GL_SMOOTH);
CBMPLoader bmpLoader;
bmpLoader.LoadBmp("/123-bmp.bmp");
// 建立紋理
glGenTextures(1, &tex2D);
glBindTexture(GL_TEXTURE_2D, tex2D);
// 紋理濾波引數設定
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
// 設定紋理資料
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, bmpLoader.imageWidth, bmpLoader.imageHeight, 0, GL_RGB, GL_UNSIGNED_BYTE, bmpLoader.image);
angle = 0;
}
/** 繪製木箱 */
void DrawBox(){
glEnable(GL_TEXTURE_2D);
/** 選擇紋理 */
glBindTexture(GL_TEXTURE_2D, tex2D);
/** 開始繪製四邊形 */
glBegin(GL_QUADS);
/// 前側面
glNormal3f(0.0f, 0.0f, 1.0f); /**指定法線指向觀察者 */
glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f);
glTexCoord2f(1.0f, 0.0f); glVertex3f(1.0f, -1.0f, 1.0f);
glTexCoord2f(1.0f, 1.0f); glVertex3f(1.0f, 1.0f, 1.0f);
glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 1.0f);
/// 後側面
glNormal3f(0.0f, 0.0f, -1.0f); /** 指定法線背向觀察者 */
glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, 1.0f, -1.0f);
glTexCoord2f(1.0f, 1.0f); glVertex3f(1.0f, 1.0f, -1.0f);
glTexCoord2f(0.0f, 1.0f); glVertex3f(1.0f, -1.0f, -1.0f);
/// 頂面
glNormal3f(0.0f, 1.0f, 0.0f); /**指定法線向上 */
glTexCoord2f(0.0f, 0.0f);glVertex3f(-1.0f, 1.0f, 1.0f);
glTexCoord2f(1.0f, 0.0f);glVertex3f(1.0f, 1.0f, 1.0f);
glTexCoord2f(1.0f, 1.0f);glVertex3f(1.0f, 1.0f, -1.0f);
glTexCoord2f(0.0f, 1.0f);glVertex3f(-1.0f, 1.0f, -1.0f);
/// 底面
glNormal3f(0.0f, -1.0f, 0.0f); /** 指定法線朝下 */
glTexCoord2f(0.0f, 0.0f);glVertex3f(-1.0f, -1.0f, 1.0f);
glTexCoord2f(1.0f, 0.0f);glVertex3f(1.0f, -1.0f, 1.0f);
glTexCoord2f(1.0f, 1.0f);glVertex3f(1.0f, -1.0f, -1.0f);
glTexCoord2f(0.0f, 1.0f);glVertex3f(-1.0f, -1.0f, -1.0f);
/// 右側面
glNormal3f(1.0f, 0.0f, 0.0f); /**指定法線朝右 */
glTexCoord2f(0.0f, 0.0f); glVertex3f(1.0f, -1.0f, -1.0f);
glTexCoord2f(1.0f, 0.0f); glVertex3f(1.0f, 1.0f, -1.0f);
glTexCoord2f(1.0f, 1.0f); glVertex3f(1.0f, 1.0f, 1.0f);
glTexCoord2f(0.0f, 1.0f); glVertex3f(1.0f, -1.0f, 1.0f);
/// 左側面
glNormal3f(-1.0f, 0.0f, 0.0f); /**指定法線朝左 */
glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, 1.0f, -1.0f);
glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 1.0f);
glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, -1.0f, 1.0f);
glEnd();
glDisable(GL_TEXTURE_2D);
}
// 繪圖回撥函式
void display() {
// 清除之前幀資料
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
glPushMatrix();
glTranslatef(0.0f, 0.0f, -5.0f);
glRotated(angle, 1, 1, 0);
DrawBox();
glPopMatrix();
// 執行繪圖命令
glFlush();
angle ++;
glutPostRedisplay();
}
// 視窗大小變化回撥函式
void reshape(int w, int h) {
glViewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(60.0, (GLfloat)w/(GLfloat)h, 0.1, 100000.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
int main(int argc, const char * argv[]) {
// 初始化顯示模式
glutInit(&argc, const_cast<char **>(argv));
glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB|GLUT_DEPTH);
// 初始化視窗
glutInitWindowSize(500, 500);
glutInitWindowPosition(100, 100);
glutCreateWindow(argv[0]);
init();
glutReshapeFunc(reshape);
glutDisplayFunc(display);
// 開始主迴圈繪製
glutMainLoop();
return 0;
}
複製程式碼
執行效果如下:
OpenGL光照和材質
當光照射到一個物體表面上時,會出現三種情形。首先,光可以通過物體表面向空間反射,產生反射光。其次,對於透明體,光可以穿透該物體並從另一端射出,產生透射光。最後,部分光將被物體表面吸收而轉換成熱。在上述三部分光中,僅僅是透射光和反射光能夠進入人眼產生視覺效果。這裡介紹的簡單光照模型只考慮被照明物體表面的反射光影響,假定物體表面光滑不透明且由理想材料構成,環境假設為由白光照明。 一般來說,反射光可以分成三個分量,即環境反射、漫反射和鏡面反射。環境反射分量假定入射光均勻地從周圍環境入射至景物表面並等量地向各個方向反射出去,通常物體表面還會受到從周圍環境來的反射光(如來自地面、天空、牆壁等的反射光)的照射,這些光常統稱為環境光(Ambient Light);漫反射分量表示特定光源在景物表面的反射光中那些向空間各方向均勻反射出去的光,這些光常稱為漫射光(Diffuse Light);鏡面反射光為朝一定方向的反射光,如一個點光源照射一個金屬球時會在球面上形成一塊特別亮的區域,呈現所謂“高光(Highlight)”,它是光源在金屬球面上產生的鏡面反射光(Specular Light)。對於較光滑物體,其鏡面反射光的高光區域小而亮;相反,粗糙表面的鏡面反射光呈發散狀態,其高光區域大而不亮。
光組成
在OpenGL簡單光照模型中的幾種光分為:輻射光(Emitted Light)、環境光(Ambient Light)、漫射光(Diffuse Light)、鏡面光(Specular Light)。
- 輻射光是最簡單的一種光,它直接從物體發出並且不受任何光源影響。
- 環境光是由光源發出經環境多次散射而無法確定其方向的光,即似乎來自所有方向。一般說來,房間裡的環境光成分要多些,戶外的相反要少得多,因為大部分光按相同方向照射,而且在戶外很少有其他物體反射的光。當環境光照到曲面上時,它在各個方向上均等地發散(類似於無影燈光)。
- 漫射光來自一個方向,它垂直於物體時比傾斜時更明亮。一旦它照射到物體上,則在各個方向上均勻地發散出去。於是,無論視點在哪裡它都一樣亮。來自特定位置和特定方向的任何光,都可能有散射成分。
- 鏡面光來自特定方向並沿另一方向反射出去,一個平行鐳射束在高質量的鏡面上產生100%的鏡面反射。光亮的金屬和塑料具有很高非反射成分,而象粉筆和地毯等幾乎沒有反射成分。因此,三某種意義上講,物體的反射程度等同於其上的光強(或光亮度)。
建立光源
光源有許多特性,如顏色、位置、方向等。選擇不同的特性值,則對應的光源作用在物體上的效果也不一樣,這在以後的章節中會逐步介紹的。下面詳細講述定義光源特性的函式glLight*():
void glLight{if}[v](GLenum light , GLenum pname, TYPE param)
複製程式碼
建立具有某種特性的光源。其中第一個引數light指定所建立的光源號,如GL_LIGHT0、GL_LIGHT1、...、GL_LIGHT7。第二個引數pname指定光源特性,這個引數的輔助資訊見表1-3所示。最後一個引數設定相應的光源特性值。
pname 引數名 | 預設值 | 說明 |
---|---|---|
GL_AMBIENT | (0.0, 0.0, 0.0, 1.0) | RGBA模式下環境光 |
GL_DIFFUSE | (1.0, 1.0, 1.0, 1.0) | RGBA模式下漫反射光 |
GL_SPECULAR | (1.0,1.0,1.0,1.0) | RGBA模式下鏡面光 |
GL_POSITION | (0.0,0.0,1.0,0.0) | 光源位置齊次座標(x,y,z,w) |
GL_SPOT_DIRECTION | (0.0,0.0,-1.0) | 點光源聚光方向向量(x,y,z) |
GL_SPOT_EXPONENT | 0.0 | 點光源聚光指數 |
GL_SPOT_CUTOFF | 180.0 | 點光源聚光截止角 |
GL_CONSTANT_ATTENUATION | 1.0 | 常數衰減因子 |
GL_LINER_ATTENUATION | 0.0 | 線性衰減因子 |
GL_QUADRATIC_ATTENUATION | 0.0 | 平方衰減因子 |
GLfloat light_position[] = { 1.0, 1.0, 1.0, 0.0 };
glLightfv(GL_LIGHT0, GL_POSITION, light_position);
複製程式碼
其中light_position是一個指標,指向定義的光源位置齊次座標陣列。其它幾個光源特性都為預設值。同樣,我們也可用類似的方式定義光源的其他幾個特性值。例如:
 GLfloat light_ambient [] = { 0.0, 0.0, 0.0, 1.0 };
 GLfloat light_diffuse [] = { 1.0, 1.0, 1.0, 1.0 };
GLfloat light_specular[] = { 1.0, 1.0, 1.0, 1.0 };
 glLightfv(GL_LIGHT0, GL_AMBIENT , light_ambient );
 glLightfv(GL_LIGHT0, GL_DIFFUSE , light_diffuse );
 glLightfv(GL_LIGHT0, GL_SPECULAR, light_specular);
複製程式碼
啟動光照
在OpenGL中,必須明確指出光照是否有效或無效。如果光照無效,則只是簡單地將當前顏色對映到當前頂點上去,不進行法向、光源、材質等複雜計算,那麼顯示的圖形就沒有真實感,如前幾章例程執行結果顯示。要使光照有效,首先得啟動光照,啟動光照需要用到如下函式。
glEnable(GL_LIGHTING);
複製程式碼
若使光照無效,則呼叫gDisable(GL_LIGHTING)可關閉當前光照。然後,必須使所定義的每個光源有效,如果只用了一個光源。
glEnable(GL_LIGHT0);
複製程式碼
其它光源類似,只是光源號不同而已。
材質顏色
OpenGL用材料對光的紅、綠、藍三原色的反射率來近似定義材料的顏色。像光源一樣,材料顏色也分成環境、漫反射和鏡面反射成分,它們決定了材料對環境光、漫反射光和鏡面反射光的反射程度。在進行光照計算時,材料對環境光的反射率與每個進入光源的環境光結合,對漫反射光的反射率與每個進入光源的漫反射光結合,對鏡面光的反射率與每個進入光源的鏡面反射光結合。對環境光與漫反射光的反射程度決定了材料的顏色,並且它們很相似。對鏡面反射光的反射率通常是白色或灰色(即對鏡面反射光中紅、綠、藍的反射率相同)。鏡面反射高光最亮的地方將變成具有光源鏡面光強度的顏色。例如一個光亮的紅色塑料球,球的大部分表現為紅色,光亮的高光將是白色的。材質的定義與光源的定義類似:
void glMaterial{if}[v](GLenum face,GLenum pname,TYPE param);
複製程式碼
定義光照計算中用到的當前材質。face可以是GL_FRONT、GL_BACK、GL_FRONT_AND_BACK,它表明當前材質應該應用到物體的哪一個面上;pname說明一個特定的材質;param是材質的具體數值,若函式為向量形式,則param是一組值的指標,反之為引數值本身。非向量形式僅用於設定GL_SHINESS。另外,引數GL_AMBIENT_AND_DIFFUSE表示可以用相同的RGB值設定環境光顏色和漫反射光顏色。
引數名 | 預設值 | 說明 |
---|---|---|
GL_AMBIENT | (0.2, 0.2, 0.2, 1.0) | 材料的環境光顏色 |
GL_DIFFUSE | (0.8, 0.8, 0.8, 1.0) | 材料的漫反射光顏色 |
GL_AMBIENT_AND_DIFFUSE | 材料的環境光和漫反射光顏色 | |
GL_SPECULAR | (0.0, 0.0, 0.0, 1.0) | 材料的鏡面反射光顏色 |
GL_SHINESS | 0.0 | 鏡面指數(光亮度) |
GL_EMISSION | (0.0, 0.0, 0.0, 1.0) | 材料的輻射光顏色 |
GL_COLOR_INDEXES | (0, 1, 1) | 材料的環境光、漫反射光和鏡面光顏色 |
材質RGB值 與 光源RGB
材質的顏色與光源的顏色有些不同。對於光源,R、G、B值等於R、G、B對其最大強度的百分比。若光源顏色的R、G、B值都是1.0,則是最強的白光;若值變為0.5,顏色仍為白色,但強度為原來的一半,於是表現為灰色;若R=G=1.0,B=0.0,則光源為黃色。對於材質,R、G、B值為材質對光的R、G、B成分的反射率。比如,一種材質的R=1.0、G=0.5、B=0.0,則材質反射全部的紅色成分,一半的綠色成分,不反射藍色成分。也就是說,若OpenGL的光源顏色為(LR、LG、LB),材質顏色為(MR、MG、MB),那麼,在忽略所有其他反射效果的情況下,最終到達眼睛的光的顏色為(LRMR、LGMG、LB*MB)。 同樣,如果有兩束光,相應的值分別為(R1、G1、B1)和(R2、G2、B2),則OpenGL將各個顏色成分相加,得到(R1+R2、G1+G2、B1+B2),若任一成分的和值大於1(超出了裝置所能顯示的亮度)則約簡到1.0。
示例
下面的示例將演示光照和材質在OpenGL上的應用。
#include <GLUT/GLUT.h>
#include <OpenGL/OpenGL.h>
// 初始化引數
void init() {
GLfloat ambient[] = { 0.0, 0.0, 0.0, 1.0 };
GLfloat diffuse[] = { 1.0, 1.0, 1.0, 1.0 };
// GLfloat specular[] = { 1.0, 1.0, 1.0, 1.0 };
GLfloat position[] = { 0.0, 0, -1.0, 0.0 };
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LESS);
glLightfv(GL_LIGHT0, GL_AMBIENT, ambient);
glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse);
// glLightfv(GL_LIGHT0, GL_SPECULAR, specular);
glLightfv(GL_LIGHT0, GL_POSITION, position);
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
glClearColor(0.0, 0.1, 0.1, 0.0) ;
}
// 繪圖回撥函式
void display() {
GLfloat no_mat[] = { 0.0, 0.0, 0.0, 1.0 };
GLfloat mat_ambient[] = { 0.7, 0.7, 0.7, 1.0 };
GLfloat mat_ambient_color[] = { 0.8, 0.8, 0.2, 1.0 };
GLfloat mat_diffuse[] = { 0.1, 0.5, 0.8, 1.0 };
GLfloat mat_specular[] = { 1.0, 1.0, 1.0, 1.0 };
GLfloat no_shininess[] = { 0.0 };
GLfloat low_shininess[] = { 5.0 };
GLfloat high_shininess[] = { 100.0 };
GLfloat mat_emission[] = {0.3, 0.2, 0.2, 0.0};
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
/* 第一行第一列繪製的球僅有漫反射光而無環境光和鏡面光。*/
glPushMatrix();
glTranslatef (-3.75, 3.0, 0.0);
glMaterialfv(GL_FRONT, GL_AMBIENT, no_mat);
glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);
glMaterialfv(GL_FRONT, GL_SPECULAR, no_mat);
glMaterialfv(GL_FRONT, GL_SHININESS, no_shininess);
glMaterialfv(GL_FRONT, GL_EMISSION, no_mat);
glutSolidSphere(1.0, 20, 20);
glPopMatrix();
/* 第一行第二列繪製的球有漫反射光和鏡面光,並有低高光,而無環境光 。*/
glPushMatrix();
glTranslatef (-1.25, 3.0, 0.0);
glMaterialfv(GL_FRONT, GL_AMBIENT, no_mat);
glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);
glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
glMaterialfv(GL_FRONT, GL_SHININESS, low_shininess);
glMaterialfv(GL_FRONT, GL_EMISSION, no_mat);
glutSolidSphere(1.0, 20, 20);
glPopMatrix();
/* 第一行第三列繪製的球有漫反射光和鏡面光,並有很亮的高光,而無環境光 。*/
glPushMatrix();
glTranslatef (1.25, 3.0, 0.0);
glMaterialfv(GL_FRONT, GL_AMBIENT, no_mat);
glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);
glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
glMaterialfv(GL_FRONT, GL_SHININESS, high_shininess);
glMaterialfv(GL_FRONT, GL_EMISSION, no_mat);
glutSolidSphere(1.0, 20, 20);
glPopMatrix();
/* 第一行第四列繪製的球有漫反射光和輻射光,而無環境和鏡面反射光。*/
glPushMatrix();
glTranslatef (3.75, 3.0, 0.0);
glMaterialfv(GL_FRONT, GL_AMBIENT, no_mat);
glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);
glMaterialfv(GL_FRONT, GL_SPECULAR, no_mat);
glMaterialfv(GL_FRONT, GL_SHININESS, no_shininess);
glMaterialfv(GL_FRONT, GL_EMISSION, mat_emission);
glutSolidSphere(1.0, 20, 20);
glPopMatrix();
/* 第二行第一列繪製的球有漫反射光和環境光,而鏡面反射光。*/
glPushMatrix();
glTranslatef (-3.75, 0.0, 0.0);
glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient);
glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);
glMaterialfv(GL_FRONT, GL_SPECULAR, no_mat);
glMaterialfv(GL_FRONT, GL_SHININESS, no_shininess);
glMaterialfv(GL_FRONT, GL_EMISSION, no_mat);
glutSolidSphere(1.0, 20, 20);
glPopMatrix();
/* 第二行第二列繪製的球有漫反射光、環境光和鏡面光,且有低高光。*/
glPushMatrix();
glTranslatef (-1.25, 0.0, 0.0);
glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient);
glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);
glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
glMaterialfv(GL_FRONT, GL_SHININESS, low_shininess);
glMaterialfv(GL_FRONT, GL_EMISSION, no_mat);
glutSolidSphere(1.0, 20, 20);
glPopMatrix();
/* 第二行第三列繪製的球有漫反射光、環境光和鏡面光,且有很亮的高光。*/
glPushMatrix();
glTranslatef (1.25, 0.0, 0.0);
glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient);
glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);
glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
glMaterialfv(GL_FRONT, GL_SHININESS, high_shininess);
glMaterialfv(GL_FRONT, GL_EMISSION, no_mat);
glutSolidSphere(1.0, 20, 20);
glPopMatrix();
/* 第二行第四列繪製的球有漫反射光、環境光和輻射光,而無鏡面光。*/
glPushMatrix();
glTranslatef (3.75, 0.0, 0.0);
glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient);
glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);
glMaterialfv(GL_FRONT, GL_SPECULAR, no_mat);
glMaterialfv(GL_FRONT, GL_SHININESS, no_shininess);
glMaterialfv(GL_FRONT, GL_EMISSION, mat_emission);
glutSolidSphere(1.0, 20, 20);
glPopMatrix();
/* 第三行第一列繪製的球有漫反射光和有顏色的環境光,而無鏡面光。*/
glPushMatrix();
glTranslatef (-3.75, -3.0, 0.0);
glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient_color);
glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);
glMaterialfv(GL_FRONT, GL_SPECULAR, no_mat);
glMaterialfv(GL_FRONT, GL_SHININESS, no_shininess);
glMaterialfv(GL_FRONT, GL_EMISSION, no_mat);
glutSolidSphere(1.0, 20, 20);
glPopMatrix();
/* 第三行第二列繪製的球有漫反射光和有顏色的環境光以及鏡面光,且有低高光。*/
glPushMatrix();
glTranslatef (-1.25, -3.0, 0.0);
glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient_color);
glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);
glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
glMaterialfv(GL_FRONT, GL_SHININESS, low_shininess);
glMaterialfv(GL_FRONT, GL_EMISSION, no_mat);
glutSolidSphere(1.0, 20, 20);
glPopMatrix();
/* 第三行第三列繪製的球有漫反射光和有顏色的環境光以及鏡面光,且有很亮的高光。*/
glPushMatrix();
glTranslatef (1.25, -3.0, 0.0);
glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient_color);
glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);
glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
glMaterialfv(GL_FRONT, GL_SHININESS, high_shininess);
glMaterialfv(GL_FRONT, GL_EMISSION, no_mat);
glutSolidSphere(1.0, 20, 20);
glPopMatrix();
/* 第三行第四列繪製的球有漫反射光和有顏色的環境光以及輻射光,而無鏡面光。*/
glPushMatrix();
glTranslatef (3.75, -3.0, 0.0);
glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient_color);
glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);
glMaterialfv(GL_FRONT, GL_SPECULAR, no_mat);
glMaterialfv(GL_FRONT, GL_SHININESS, no_shininess);
glMaterialfv(GL_FRONT, GL_EMISSION, mat_emission);
glutSolidSphere(1.0, 20, 20);
glPopMatrix();
// 執行繪圖命令
glFlush();
}
// 視窗大小變化回撥函式
void reshape(int w, int h) {
glViewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION);
g