從零開始學習OpenGL ES之四 – 光效
繼續我們的iPhone OpenGL ES之旅,我們將討論光效。目前,我們沒有加入任何光效。幸運的是,OpenGL在沒有設置光效的情況下仍然可以看見東西。 它只是提供一種十分單調的整體光讓我們看到物體。但是如果不定義光效,物體看上去都很單調,就像你在第二部分程序中看到的那樣。
陰影模型(Shade Model)
在深入討論OpenGL ES是怎樣處理光線之前,重要的是要了解OpenGL ES實際上定義了兩種shade model, GL_FLAT 和 GL_SMOOTH。我們將不會討論GL_FLAT,因爲這隻會讓你的程序看上去來自九十年代:
GL_FLAT 方式渲染的一個二十面體。15年前的實時渲染技術
從發光的角度來看,GL_FLAT將指定三角形上的每個像素都同等對待。多邊形上的每個像素都具有相同的顏色,陰影等。它提供了足夠的視覺暗示使其看上去有立體感而且它的計算比每個像素按不同方法計算更爲廉價,但是在這種方式下,物體看上去極爲不真實。現在有人使用它可能是爲了產生特殊的復古效果,但要使你的3D物體儘量真實,你應該使用 GL_SMOOTH 繪圖模式,它使用了一種平滑但較快速的陰影算法,稱爲Gouraud 算法。 GL_SMOOTH是默認值。
啓動光效
我假定你繼續使用第二部分的最終項目,即那個看上去不是很立體的旋轉二十面體的項目。如果你手頭上還沒有那個項目,在這裏下載。
第一件事就是要啓動光效。默認情況下,手工指定光效是被禁止的。現在我們打開這項功能。在GLViewController.m的setupView:方法中加入黑體部分:
-(void)setupView:(GLView*)view { const GLfloat zNear = 0.01, zFar = 1000.0, fieldOfView = 45.0; GLfloat size; glEnable(GL_DEPTH_TEST); glMatrixMode(GL_PROJECTION); size = zNear * tanf(DEGREES_TO_RADIANS(fieldOfView) / 2.0); CGRect rect = view.bounds; glFrustumf(-size, size, -size / (rect.size.width / rect.size.height), size / (rect.size.width / rect.size.height), zNear, zFar); glViewport(0, 0, rect.size.width, rect.size.height); glMatrixMode(GL_MODELVIEW); glEnable(GL_LIGHTING); glLoadIdentity(); glClearColor(0.0f, 0.0f, 0.0f, 1.0f); }
通常情況下,光效只需在設定時啓動一次。不需要在繪圖開始前後打開和關閉。可能有些特效的情況需要在程序執行時打開或關閉,但是大部分情況下,你只需在程序啓動時打開它。此單行代碼就是在OpenGL ES中啓動光效。運行時會怎樣?
啓動光效
我們啓動了光效,但是沒有創建任何光源。除清除緩存用的灰色外任何繪製的物體都被渲染成絕對的黑色。沒有太多的改進,對嗎?讓我們在場景中加入光源。
啓動光效的方式有些奇怪。OpenGL ES允許你創建8個光源。有一個常量對應於這些光源中的一個,常量爲GL_LIGHT0 到 GL_LIGHT7。可以任意組合這些光源中的五個,儘管習慣上從 GL_LIGHT0 作爲第一個光源,然後是 GL_LIGHT1 等等。下面是“打開”第一個光源GL_LIGHT0的方法:
glEnable(GL_LIGHT0);
一旦你啓動了光源,你必須設置光源的一些屬性。作爲初學者,有三個不同的要素用來定義光源。
光效三要素
在 OpenGL ES中,光由三個元素組成,分別是環境元素(ambient component), 散射元素(diffuse component)和 高光元素(specular component)。我們使用顏色來設定光線元素,這看上去有些奇怪,但是由於它允許你同時指定各光線元素的顏色和相對強度,這個方法工作得很好。明亮的白色光定義爲白色 ({1.0, 1.0, 1.0, 1.0}),而暗白色可能定義爲灰色 ({0.3, 0.3, 0.3 1.0})。 你還可以通過改變紅,綠,藍元素的百分比來調整色偏。
下圖說明了各要素產生的效果。
高光元素定義了光線直接照射並反射到觀察者從而形成了物體上的“熱點”或光澤。光點的大小取決於一些因素,但是如果你看到如上圖黃球所示一個區域明顯的光斑,那通常就是來自於一個或多個光源的高光部分。
散射元素定義了比較平均的定向光源,在物體面向光線的一面具有光澤。
環境光則沒有明顯的光源。其光線折射與許多物體,因此無法確定其來源。環境元素平均作用於場景中的所有物體的所有面。
環境光
你的光效中有越多的環境元素,那麼就越不會產生引入注目的效果。所有光線的環境元素會融合在一起產生效果,意思是場景中的總環境光效是由所有啓動光源的環境光組合在一起所決定的。如果你使用了不止一個光源,那麼最好是隻指定一個光源的環境元素,而設定其他所有光源的環境因素爲黑 ({0.0, 0.0, 0.0, 1.0}),從而很容易地調整場景的環境光效。
下面演示了怎樣指定一個很暗的白色光源:
const GLfloat light0Ambient[] = {0.05, 0.05, 0.05, 1.0}; glLightfv(GL_LIGHT0, GL_AMBIENT, light0Ambient);
使用像這樣的很低的環境元素值使場景看上去更引入注目,但同時也意味着物體沒有面向光線的面或者有其他物體擋住的物體將在場景中看得不是很清楚。
散射光
在OpenGL ES中可以設定的第二個光線元素是 散射元素(diffuse component)。在現實世界裏,散射光線是諸如穿透光纖或從一堵白牆反射的光線。散射光線是發散的,因而參數較柔和的光,一般不會像直射光一樣產生光斑。如果你曾經觀察過職業攝影家使用攝影室燈光,你可能會看到他們使用柔光箱 或者反光傘。兩者都會穿透像白布之類的輕型材料並反射與輕型有色材料從而使光線發散以產生令人愉悅的照片。在OpenGL ES中,散射元素作用類似,它使光線均勻地散佈到物體之上。然而,不像環境光,由於它是定向光,只有面向光線的物體面纔會反射散射光,而場景中的所有多面體都會被環境光照射。
下面的例子演示了設定場景中的第一個散射元素:
const GLfloat light0Diffuse[] = {0.5, 0.5, 0.5, 1.0}; glLightfv(GL_LIGHT0, GL_DIFFUSE, light0Diffuse);
高光
最後,我們討論高光。這種類型的光是十分直接的,它們會以熱點和光暈的形式反射到觀察者的眼中。如果你想產生聚光燈的效果,那麼應該設置一個很大的高光元素值及很小的散射和環境元素值(還需要定義其他一些參數,等下會有介紹)。
注意: 在下一篇文章中你將看到,光線的高光值是確定高光尺寸的唯一因素。
下面是設定高光元素的例子:
const GLfloat light0Specular[] = {0.7, 0.7, 0.7, 1.0};
位置
還需要設定光效的另一個重要屬性,即光源3D空間中的位置。這不會影響環境元素,但其他兩個元素由於其本性,只有在OpenGL在知道了場景中物體與光的相對位置後才能計算。例如:
const GLfloat light0Position[] = {10.0, 10.0, 10.0, 0.0}; glLightfv(GL_LIGHT0, GL_POSITION, light0Position);
此位置將第一個光源放置在觀察者後方的右上角。
這些是用於設定幾乎所有光線的屬性。如果你沒有設定其中一個元素,那麼它就採用默認值黑色 {0.0, 0.0, 0.0, 1.0}。如果你沒有定義位置,那麼它就處於原點,通常這不是你想要的結果。
你可能想知道alpha值對光線的作用。對環境光和高光而言這是個愚蠢的問題。然而在計算散射光確定光線是怎樣反射時,需要用到它。我們將在討論材質時再解釋它是怎樣工作的,因爲材質和光線值都將出現在方程式中。我們下次再討論材質,現在將 alpha 設爲1.0。改變其值對本文的程序不會產生任何影響,但有可能對以後的程序至少是有關散射元素的部分產生影響。
還有一些光線元素你可選擇使用。
創建點光源(聚光燈)
如果你希望創建一個定向點光源 (一種指向特定方向並照亮特定角度範圍的光源,本質上,它與燈泡照亮各個方向相反,它只照亮一個錐臺的範圍),那麼你需要設定兩個額外參數。設定 GL_SPOT_DIRECTION 允許你指定光照的方向,它類似於上一篇文章中介紹的視野角度的計算。窄角度將產生很小範圍的點光源,而寬角度則產生像泛光燈一樣的效果。
指定光的方向
通過指定定義了光線指向的x,y和z值來使 GL_SPOT_DIRECTION的工作。然而,光線並不指向你在空間中定義的那一點。你提供的三個座標值是向量(vector), 而非頂點。這是一個很細微但十分重要的區別。一個代表向量的數據結構與一個頂點的數據結構完全一樣(都有三個 GLfloats,其中每一個分別是笛卡爾的一個軸)。然而,向量的數據是用來表示方向而不是空間中的一點。
每個人都知道兩點可以定義一條線段,那麼空間中的一點怎麼可能指定方向?這是因爲存在一個隱性的第二點作爲起點,即原點。如果你從原點畫一條線到向量定義的點,那就是向量代表的方向。向量還可用於表示速度和距離,一個遠離原點的點表示速度越快或距離更遠。在大部分的OpenGL應用中,並未使用距離原點的距離。實際上,在大部分使用向量的情況下,我們需要將向量標準化(normalize) 爲長度 1.0。關於向量的標準化,隨着我們繼續深入將會討論到。現在你只需知道,如果你希望定義一個定向光,那麼你 必須創建一個定義了光的方向的向量。下例演示了定義一個沿z軸而下的光源:
const GLfloat light0Direction = {0.0, 0.0, -1.0}; glLightfv(GL_LIGHT0, GL_SPOT_DIRECTION, light0Direction);
現在,如果你希望光線指向一個特定物體,應該怎麼辦?實際上很簡單,將光的位置和物體的位置傳入OpenGLCommon.h的函數 Vector3DMakeWithStartAndEndPoints()中,它將返回一個被光照射到的指定點的標準化向量。然後,再將其作爲 GL_SPOT_DIRECTION 的值。
指定光的角度
除非你限制光照的角度,否則指定光的方向並不會產生顯著的效果。因爲當你指定 GL_SPOT_CUTOFF 值時,它定義了中心線兩邊的角度,所以如果你指定截止角時,它必須小於180°。如果你的定義爲45°,那麼實際上你創建了一個總角度爲90°的點光源。這意味着你可設定的 GL_SPOT_CUTOFF 的最大值爲 180°。下圖說明了這個概念:
下例演示了怎樣限制角度爲 90°(使用 45° 截止角):
glLightf(GL_LIGHT0, GL_SPOT_CUTOFF, 45.0);
還有三個光的屬性可以設置。它們是配合使用的,這些超出了本文的範圍。以後,我可能在有關光衰減 (光線隨着遠離光源而減弱)的文章中討論到。通過調整衰減值可以產生很漂亮的效果。
綜合
讓我們綜合所學內容在setupView: 方法中設定一個光源。使用下列代碼替換 setupView: 方法中原有的代碼:
-(void)setupView:(GLView*)view { const GLfloat zNear = 0.01, zFar = 1000.0, fieldOfView = 45.0; GLfloat size; glEnable(GL_DEPTH_TEST); glMatrixMode(GL_PROJECTION); size = zNear * tanf(DEGREES_TO_RADIANS(fieldOfView) / 2.0); CGRect rect = view.bounds; glFrustumf(-size, size, -size / (rect.size.width / rect.size.height), size / (rect.size.width / rect.size.height), zNear, zFar); glViewport(0, 0, rect.size.width, rect.size.height); glMatrixMode(GL_MODELVIEW); // Enable lighting glEnable(GL_LIGHTING); // Turn the first light on glEnable(GL_LIGHT0); // Define the ambient component of the first light const GLfloat light0Ambient[] = {0.1, 0.1, 0.1, 1.0}; glLightfv(GL_LIGHT0, GL_AMBIENT, light0Ambient); // Define the diffuse component of the first light const GLfloat light0Diffuse[] = {0.7, 0.7, 0.7, 1.0}; glLightfv(GL_LIGHT0, GL_DIFFUSE, light0Diffuse); // Define the specular component and shininess of the first light const GLfloat light0Specular[] = {0.7, 0.7, 0.7, 1.0}; const GLfloat light0Shininess = 0.4; glLightfv(GL_LIGHT0, GL_SPECULAR, light0Specular); // Define the position of the first light const GLfloat light0Position[] = {0.0, 10.0, 10.0, 0.0}; glLightfv(GL_LIGHT0, GL_POSITION, light0Position); // Define a direction vector for the light, this one points right down the Z axis const GLfloat light0Direction[] = {0.0, 0.0, -1.0}; glLightfv(GL_LIGHT0, GL_SPOT_DIRECTION, light0Direction); // Define a cutoff angle. This defines a 90° field of vision, since the cutoff // is number of degrees to each side of an imaginary line drawn from the light's // position along the vector supplied in GL_SPOT_DIRECTION above glLightf(GL_LIGHT0, GL_SPOT_CUTOFF, 45.0); glLoadIdentity(); glClearColor(0.0f, 0.0f, 0.0f, 1.0f); }
很簡單吧?應該一切都準備好了吧?好,我們試着運行一下看看。
這是什麼?太糟糕了吧!我們設定了光源,可是看不到任何東西啊?屏幕上只是顯示一個黑和灰的形狀,它甚至看上去不像個3D形狀,比以前還糟糕。
不要緊張,這很正常(注: It’s Normal在這裏有兩層意思,一是正常,二是法線)
Normal數學上的意思是“垂直於”。這是我們目前缺少的。法線。一個背面的法線(或多邊形的法線)是一個垂直於指定多邊形表面的向量(或直線)。參考下圖:
OpenGL 渲染一個形狀時並不需要知道法線,但在你使用定向光線時需要用到。OpenGL需要表面法線來確定光線是怎樣與各多邊形交互作用的。
OpenGL 要求我們爲各使用的頂點提供法線。計算一個三角形的表面法線是很簡單的,它是三角形兩邊的叉積。代碼如下:
static inline Vector3D Triangle3DCalculateSurfaceNormal(Triangle3D triangle) { Vector3D u = Vector3DMakeWithStartAndEndPoints(triangle.v2, triangle.v1); Vector3D v = Vector3DMakeWithStartAndEndPoints(triangle.v3, triangle.v1); Vector3D ret; ret.x = (u.y * v.z) - (u.z * v.y); ret.y = (u.z * v.x) - (u.x * v.z); ret.z = (u.x * v.y) - (u.y * v.x); return ret; }
Vector3DMakeWithStartAndEndPoints()取兩頂點值計算其標準化向量。那麼既然計算表面法線如此簡單,爲什麼OpenGL ES不爲我們完成?有兩個原因,第一個和最重要的原因是,開銷太大。對每個多邊形而言,有許多浮點乘除計算以及調用sqrtf()的開銷。
第二,因爲我們使用 GL_SMOOTH 渲染,所以OpenGL ES需要知道頂點法線(vertex normal) 而不是表面法線(上述計算)。因爲頂點法線要求你計算使用了該頂點的所有表面法線的平均向量,所以開銷更大。
讓我們看一個例子。
請注意這不是一個正方體。簡單起見,讓我們看看一個平面的六個三角形構成的兩維形狀。它總共由七個頂點構成。頂點A由所有六個三角形共享,所以此頂點的頂點法線是所有七個三角形(注:六個吧?)的表面法線的平均值。平均值的計算是基於各向量元素的,即x值被平均,y值被平均,然後z值被平均,結果組合在一起構成了平均向量。
所以,我們怎樣計算二十面體的向量?這其實是一個很簡單的形狀,在計算頂點法線時並不會造成顯著的延時。通常,你不會工作於這麼少頂點的物體,而將處理複雜得多而且數量更多的物體。結果是,除非沒有替代方法,否則你希望避免使用頂點法線的實時計算。這種情況下,我編寫了一個小命令行程序,它循環處理每個頂點及三角形索引,來計算二十面體的各頂點法線。該程序將結果以C struct的形式輸出到控制檯,然後我複製到我的OpenGL程序中。
注意:大部分3D程序都會爲你提供法線的計算,但你需要小心使用 – 大部分3D文件格式存儲的是表面法線而不是頂點法線,所以你至少需要計算表面法線的平均值來生成頂點法線。在以後的文章中我將介紹加載和創建3D物體,或參閱有關加載Wavefront OBJ 文件格式的文章。
下面是我寫的計算二十面體頂點法線的命令行程序:
#import <Foundation/Foundation.h> #import "OpenGLCommon.h" int main (int argc, const char * argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; NSMutableString *result = [NSMutableString string]; static const Vertex3D vertices[]= { {0, -0.525731, 0.850651}, // vertices[0] {0.850651, 0, 0.525731}, // vertices[1] {0.850651, 0, -0.525731}, // vertices[2] {-0.850651, 0, -0.525731}, // vertices[3] {-0.850651, 0, 0.525731}, // vertices[4] {-0.525731, 0.850651, 0}, // vertices[5] {0.525731, 0.850651, 0}, // vertices[6] {0.525731, -0.850651, 0}, // vertices[7] {-0.525731, -0.850651, 0}, // vertices[8] {0, -0.525731, -0.850651}, // vertices[9] {0, 0.525731, -0.850651}, // vertices[10] {0, 0.525731, 0.850651} // vertices[11] }; static const GLubyte icosahedronFaces[] = { 1, 2, 6, 1, 7, 2, 3, 4, 5, 4, 3, 8, 6, 5, 11, 5, 6, 10, 9, 10, 2, 10, 9, 3, 7, 8, 9, 8, 7, 0, 11, 0, 1, 0, 11, 4, 6, 2, 10, 1, 6, 11, 3, 5, 10, 5, 4, 11, 2, 7, 9, 7, 1, 0, 3, 9, 8, 4, 8, 0, }; Vector3D *surfaceNormals = calloc(20, sizeof(Vector3D)); // Calculate the surface normal for each triangle for (int i = 0; i < 20; i++) { Vertex3D vertex1 = vertices[icosahedronFaces[(i*3)]]; Vertex3D vertex2 = vertices[icosahedronFaces[(i*3)+1]]; Vertex3D vertex3 = vertices[icosahedronFaces[(i*3)+2]]; Triangle3D triangle = Triangle3DMake(vertex1, vertex2, vertex3); Vector3D surfaceNormal = Triangle3DCalculateSurfaceNormal(triangle); Vector3DNormalize(&surfaceNormal); surfaceNormals[i] = surfaceNormal; } Vertex3D *normals = calloc(12, sizeof(Vertex3D)); [result appendString:@"static const Vector3D normals[] = {\n"]; for (int i = 0; i < 12; i++) { int faceCount = 0; for (int j = 0; j < 20; j++) { BOOL contains = NO; for (int k = 0; k < 3; k++) { if (icosahedronFaces[(j * 3) + k] == i) contains = YES; } if (contains) { faceCount++; normals[i] = Vector3DAdd(normals[i], surfaceNormals[j]); } } normals[i].x /= (GLfloat)faceCount; normals[i].y /= (GLfloat)faceCount; normals[i].z /= (GLfloat)faceCount; [result appendFormat:@"\t{%f, %f, %f},\n", normals[i].x, normals[i].y, normals[i].z]; } [result appendString:@"};\n"]; NSLog(result); [pool drain]; return 0; }
可能有點粗糙,但它很好的完成了工作,允許我們預先計算頂點法線,所以在運行時不需要進行計算。程序輸出如下:
static const Vector3D normals[] = { {0.000000, -0.417775, 0.675974}, {0.675973, 0.000000, 0.417775}, {0.675973, -0.000000, -0.417775}, {-0.675973, 0.000000, -0.417775}, {-0.675973, -0.000000, 0.417775}, {-0.417775, 0.675974, 0.000000}, {0.417775, 0.675973, -0.000000}, {0.417775, -0.675974, 0.000000}, {-0.417775, -0.675974, 0.000000}, {0.000000, -0.417775, -0.675973}, {0.000000, 0.417775, -0.675974}, {0.000000, 0.417775, 0.675973}, };
指定頂點法線
首先我們要啓動法線數組:
glEnableClientState(GL_NORMAL_ARRAY);
使用下列調用傳遞法線數組:
glNormalPointer(GL_FLOAT, 0, normals);
將所有這些加到 drawSelf: 方法中:
- (void)drawView:(GLView*)view; { static GLfloat rot = 0.0; // This is the same result as using Vertex3D, just faster to type and // can be made const this way static const Vertex3D vertices[]= { {0, -0.525731, 0.850651}, // vertices[0] {0.850651, 0, 0.525731}, // vertices[1] {0.850651, 0, -0.525731}, // vertices[2] {-0.850651, 0, -0.525731}, // vertices[3] {-0.850651, 0, 0.525731}, // vertices[4] {-0.525731, 0.850651, 0}, // vertices[5] {0.525731, 0.850651, 0}, // vertices[6] {0.525731, -0.850651, 0}, // vertices[7] {-0.525731, -0.850651, 0}, // vertices[8] {0, -0.525731, -0.850651}, // vertices[9] {0, 0.525731, -0.850651}, // vertices[10] {0, 0.525731, 0.850651} // vertices[11] }; static const Color3D colors[] = { {1.0, 0.0, 0.0, 1.0}, {1.0, 0.5, 0.0, 1.0}, {1.0, 1.0, 0.0, 1.0}, {0.5, 1.0, 0.0, 1.0}, {0.0, 1.0, 0.0, 1.0}, {0.0, 1.0, 0.5, 1.0}, {0.0, 1.0, 1.0, 1.0}, {0.0, 0.5, 1.0, 1.0}, {0.0, 0.0, 1.0, 1.0}, {0.5, 0.0, 1.0, 1.0}, {1.0, 0.0, 1.0, 1.0}, {1.0, 0.0, 0.5, 1.0} }; static const GLubyte icosahedronFaces[] = { 1, 2, 6, 1, 7, 2, 3, 4, 5, 4, 3, 8, 6, 5, 11, 5, 6, 10, 9, 10, 2, 10, 9, 3, 7, 8, 9, 8, 7, 0, 11, 0, 1, 0, 11, 4, 6, 2, 10, 1, 6, 11, 3, 5, 10, 5, 4, 11, 2, 7, 9, 7, 1, 0, 3, 9, 8, 4, 8, 0, }; static const Vector3D normals[] = { {0.000000, -0.417775, 0.675974}, {0.675973, 0.000000, 0.417775}, {0.675973, -0.000000, -0.417775}, {-0.675973, 0.000000, -0.417775}, {-0.675973, -0.000000, 0.417775}, {-0.417775, 0.675974, 0.000000}, {0.417775, 0.675973, -0.000000}, {0.417775, -0.675974, 0.000000}, {-0.417775, -0.675974, 0.000000}, {0.000000, -0.417775, -0.675973}, {0.000000, 0.417775, -0.675974}, {0.000000, 0.417775, 0.675973}, }; glLoadIdentity(); glTranslatef(0.0f,0.0f,-3.0f); glRotatef(rot,1.0f,1.0f,1.0f); glClearColor(0.7, 0.7, 0.7, 1.0); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_COLOR_ARRAY); glEnableClientState(GL_NORMAL_ARRAY); glVertexPointer(3, GL_FLOAT, 0, vertices); glColorPointer(4, GL_FLOAT, 0, colors); glNormalPointer(GL_FLOAT, 0, normals); glDrawElements(GL_TRIANGLES, 60, GL_UNSIGNED_BYTE, icosahedronFaces); glDisableClientState(GL_VERTEX_ARRAY); glDisableClientState(GL_COLOR_ARRAY); glDisableClientState(GL_NORMAL_ARRAY); static NSTimeInterval lastDrawTime; if (lastDrawTime) { NSTimeInterval timeSinceLastDraw = [NSDate timeIntervalSinceReferenceDate] - lastDrawTime; rot+=50 * timeSinceLastDraw; } lastDrawTime = [NSDate timeIntervalSinceReferenceDate]; }
基本完工
運行,你將看到一個真實的三維旋轉物體。
但是顏色呢?
請聽下回分解:OpenGL ES 材質。 當你使用光效和平滑陰影時,OpenGL期待你爲多邊形提供材質(material) (或紋理 (texture))。材質比在顏色數組中提供簡單顏色要複雜得多。材質像光一樣由許多元素構成,可以產生不同的表面效果。物體的表面效果實際上是由場景中的光和多邊形的材質決定的。
但是,我們不希望顯示一個灰暗的二十面體。所以我介紹另一個OpenGL ES的配置參數: GL_COLOR_MATERIAL。啓動它:
glEnable(GL_COLOR_MATERIAL);
OpenGL 將使用我們提供的顏色數組來創建簡單的材質,結果如下: