OpenGL遊戲程式設計基礎
OpenGL遊戲程式設計》 K.霍金/D.阿斯特 著 田昱川 譯
1. 定義顏色
glColor*()
2. 明暗處理
明暗處理可以是單調的,也可以是平滑的。
單調的明暗處理用單一的顏色進行繪製,通常使用最後一個頂點的顏色(OpenGL的GL_PLOYGON,它是用其指定的第一個頂點的顏色)。
平滑的明暗處理用插值法確定圖元之間的顏色
void glShadeMode(GLenum mode);函式指定明暗處理模型
引數:
GL_SMOOTH使用平滑明暗處理
GL_FLAT使用單調明暗處理
3. 光照
OpenGL通過將光近似地分解成紅、綠、藍分量來計算光和光照。即一個光的顏色由此光中的紅、綠、藍分量的數量決定。當光照照射到一個表面時,OpenGL根據這個表面的材質來確定此表面應該反射的光的紅、綠、藍分量的百分比數量。
OpenGL通過4種光的組合來模擬真實世界的光照:
1. 環境光。環境光看上去不是來自任何特定的方向。即使存在一個光源。具有強烈的散射性而不確定方向。被環境光照射的表面會將其向各個方向均勻反射。
2. 散射光。來自於一個確定的方向,但是它一旦遇到一個表面,就會被向各個方向均勻地反射,無聊眼睛處於什麼位置,此表面都具有同樣的亮度。
3. 鏡面反射光。具有方向性,表面的反射也有特定的方向,經常被稱為亮光
4. 發射光。帶有發射光的物體看起來就好像其自身會發光,只不過這樣的光不會對場景中的其他物體產生影響。在OpenGL中,發射光增加了物體的亮度,但任何光源都不會影響發射光。
4. 材質
OpenGl根據材質對紅、綠、藍色光的反射來近似模擬材質顏色。
例如一個純綠色的表面將反射所有照射過來的綠光而吸收掉所有照射過來的紅光和藍光。如果將綠色表面放在純紅光中,表面看起來會是黑色的。因為表面只能反射綠光。綠色表面放置在白光中,就能看到綠色的表面,放在綠光中也看到綠色的表面。
材質有3個與光一樣的顏色屬性:環境光、散射光、鏡面反射光。這3個屬性決定了材質反射相應的光分量的程度。例如,一個屬性為高環境光反射率、低散射光反射率、低鏡面光反射率的材質將會注意反射環境光而吸收散射光和鏡面反射光。
通常所指定的環境光和散射光的反射率決定了材質的顏色,一般這兩個值相等。
為了確保材質表面的鏡面反射高光區以光源的鏡面反射光分量的顏色來終結,通常將鏡面反射光的反射設為灰色或白色。如一個藍色表面大部分是藍色的,但是高光區是白色的。
5. 法線的使用
在OpenGl中為一個表面指定法線,呼叫glNormal3f()函式,此函式為其下要繪製的一個或者一組頂點定義了法線。
void glNormal3f(GLfloat nx, GLfloat ny, Glfloat nz);
傳給此函式的3個引數是此表面法向量的x,y,z座標分量。呼叫完此函式後緊接著就呼叫相應的頂點函式。在對場景進行光照時,OpenGL對平面的所有光照計算都使用這個法向量。
為了進行光照計算,OpenGL必須將傳遞給glNormal3f()函式的法線轉換成所謂的單位法線(unit normals)。確定OpenGL使用單位法線的方法是呼叫帶有GL_NORMALIZE或者GL_RESCALE_NORMALIZE引數的glEnable()函式。
GL_RESCALE_NORMALIZE:法線各向均勻地縮放並使長度為1.
GL_NORMALIZE更常用,通知OpenGL計算單位向量,不必自己計演算法線,但會帶來效能方面的問題。設定後OpenGL會檢查所傳遞的法線是否是單位法線,不是的話OpenGL會自己計算。
最好自己提前計算好單位法線。
6. OpenGL光照的使用
OpenGL允許在場景中至多同時使用8個光。
向場景中新增光照需要4個步驟:
1) 為每個物體的每個頂點計算法向量。法線確定了物體相對於光源的指向。
2) 建立、選擇並定位所有的光源
3) 建立並選擇一種光照模型。光照模型定義了環境光,並設定用於光照計算的視點位置
4) 位場景中的物體定義材質屬性
2) 建立、選擇並定位所有的光源 例
float ambientLight[] = {0.3f, 0.5f ,0.8 f ,1.0f}; //環境光
float diffuseLight[] = {0.25f ,0.25f, 0.25f ,1.0f};//散射光
float lightPosition[] ={0.0f ,0.0f, 0.0 f, 1.0f }; //光源位置
環境光具有偏向藍色的色調
散射光的效果是物體被散射光直接照射的面比其他沒被照射的面亮的多。
光源被設定在3D世界的原點(0,0,0),光源位置的第四個值通知OpenGL前3個值是表示一個點還是一個向量。第四個值是1,表示前3個座標表示一個點光源的位置。如果第四個值是0.0,OpenGL認為此光來自於一個特定的方向,而光源位置的前3個數值所定義的向量就是用於指定這個特定方向。如果向量是(0,0,0),這個光不會進行照射。
float lightPosition[]={0.0f , 0.0f, 1.0f, 0.0f};//來自z軸正方向的光
4) 定義材質屬性
float matAmbient[]={1.0f ,1.0f, 1.0f,1.0f};
float matDiff[]={1.0f,1.0f,1.0f,1.0f};
第一個變數matAmbient定義了材質表面對應光線中的環境光分量是如何反應的,每一個值都是1.0意味著環境光的紅、綠、藍分量全部會被此表面反射。matDiff變數定義了材質表面對光線中的散射光分量的反應。
5) 例子
glShadeMode(GL_SMOOTH); 使用平滑明暗處理
glEnable(GL_DEPTH_TEST); 剔除隱藏面
glEnable(GL_CULL_FACE); 不計算多邊形背面
glFrontFace(GL_CCW);多邊形逆時針方向是正面
glEnabel(GL_LIGHTING); 啟用光照
//為light0設定材質
glMaterialfv(GL_FRONT, GL_AMBIENT, matAmbient);
glMaterialfv(GL_FRONT, GL_DIFFUSE, matDiff);
//設定light0
glLightfv(GL_LIGHT0,GL_AMBIENT ,ambientLight);設定環境光分量
glLightfv(GL_LIGHT0,GL_DIFFUSE,diffuseLight);散射光分量
glLightfv(GL_LIGHT0,GL_POSITION,ligthPosition);設定光源在場景中的位置
glEnable(GL_LIGHT0);//啟用光0
GL_DEPTH_TEST標記將被遮擋的表面隱藏掉
GL_CULL_FACE不對多邊形的背面進行任何計算。
為了確定多邊形的正面和背面,呼叫帶有GL_CCW的glFrontFace函式。
glMaterialfv()設定用於所有光的光照計算的材質屬性,為多邊形的正面設定了環境光和散射光的材質屬性。
glLightfv()函式用來設定所用的每一個光的屬性。
7. OpenGL光照的使用(二)
1. 光源的建立
可以定義光的顏色、位置、方向。
最常見的函式是
void glLightfv(GLenum light ,GLenum pname TYPE *param)
引數light的值是GL_LIGHT0~~GL_LIGHT7 。
引數pname:
GL_AMBIENT 光的環境光分量的強度
GL_DIFFUSE 光的散射光分量的強度
GL_SPECULAR 光的鏡面反射光分量的強度
GL_POSITION 光的位置,點座標(x,y,z,w)形式
GL_SPOT_DIRECTION 聚光燈的方向,向量(x,y,z)的形式
GL_SPOT_EXPONENT 聚光燈指數(聚光燈焦點)
GL_SPOT_CUTOFF 聚光燈邊界(圓錐面與軸線之間的角度)
GL_CONSTANT_ATTENUATION 常衰減係數值
GL_LINEAR_ATTENUATION 線性衰減係數
GL_QUADRATIC_ATTENUATION 二次衰減係數值
在指定一個光的環境光分量時,也是在指定此光向場景新增的環境光的RGBA強度。預設情況下沒有環境光。
散射光可以認為是光源所發出的光的顏色,LIGHT0的預設值是(1.0,1.0,1.0,1.0),其餘的預設值是(0.0,0.0,0.0,0.0)。
鏡面反射光決定著高亮區的顏色,通常與GL_DIFFUSE相同,可以得到更加真實的視覺效果,GL_SPECULAR在LIGHT0的預設值是(1.0,1.0,1.0,1.0),其餘的預設值是(0.0,0.0,0.0,0.0)。
2. 光源的定位
光源的位置通過GL_POSITION引數和一個四值的向量(x,y,z,w)來定義。
如果w=0.0,(x,y,z)就定義了一個向量,指定了光線照過來的方向,這樣的光源稱為定向光源(directional light source),其所有光線是平行的。好像光源的位置處於無窮遠。
例
float liaghtPosition[]={0.0f,0.0f,1.0f,0.0f};
glLightfv(GL_LIGHT0,GL_POSITION,lightPosition);
定義一個來自於z軸正方向的定向光源,在預設的視體中指向螢幕外面
(指向螢幕外面 :不是來自z軸負方向?)
當w值非零時,所定義的是一個頂點光源,對於一個頂點光源,(x,y,z)定義了此光源在物體其次座標系中的座標位置,油燈和燈泡都是定點光源。
例
float lightPosition []= {0.0f,0.0f,0.0f,1.0f};
glLightfv(GL_LIGHT0,GL_POSITION,lightPositon);
3. 光的衰減
衰減是光隨著距離光源的距離的增大其強度的減小。只適應於定點光源。定向光源的衰減因子毫無意義,因為定向光源在無窮遠處。
GL_CONSTANT_ATTENUATION 預設是1.0
GL_LINEAR_ATTENUATION 預設是0.0
GL_QUADRATIC_ATTENUATION 預設是0.0
發散光(發射光?)和全域性環境光不受衰減影響
其他環境光、散射光、鏡面反射光都會受衰減影響。
使用衰減會減慢遊戲執行速度。
4. 聚光燈
將定點光源的輻射面從全方位減小到某一特定的方向,就會得掉一個聚光燈。建立聚光燈,除了需要做建立定點光源時做的所有事情之外,還要設定一些聚光燈特有的引數:聚光燈的邊界、聚光燈的方向、聚光燈的焦點。
聚光燈:沿其方向產生一個圓錐形的光柱。
GL_SPOT_CUTOFF引數設定圓錐面與軸線之間的角度。180度的GL_SPOT_CUTOFF意味著此光源將向所有方向輻射光線(180*2=360)。OpenGL允許的範圍是0°到90°。GL_SPOT_DIRECTION引數設定聚光燈的指向,值是一個(x,y,z)形式的向量,預設是(0.0,0.0,-1.0),指向z軸負方向。
GL_SPOT_EXPONENT引數設定聚光燈焦點,也就是聚光燈在其光柱中的聚光點,從此點到光柱邊界,光的強度隨之衰弱,直至在光柱的邊界消失。聚光燈指數越高,光源的聚光性越好。
例
float ambientLight[]={0.5f,0.5f,0.5f,1.0f}; //環境光
float diffuseLight[]={0.5f,0.5f,0.5f,1.0f};//散射光
float spotlightPosition[]={6.0f,0.5f,0.0f,1.0f};聚光燈位置
float spotlightDirection[]={-1.0f,0.0f,-1.0f};聚光燈方向
glEnable(GL_LIGHTING);啟用光照
glLightfv(GL_LIGHT0,GL_AMBIENT,ambientLight);設定環境光分量
glLightfv(GL_LIGHT0,GL_DIFFUSE,diffuseLight)設定散射光分量
glLightfv(GL_LIGHT0,GL_POSITION,spotlightPosition)光源位置
聚光燈屬性
glLightf(GL_LIGHT0,GL_SPOT_CUTOFF,40.0f);80度張角
glLightf(GL_LIGHT0,GL_SPOT_EXPONENT,30.0f);焦點
glLightfv(GL_LIGHT0,GL_SPOT_EIRECTION,spotlightDirection);
glEnable(GL_LIGHT0);啟動光源0
5. 材質的定義
設定一個材質與建立一個光源類似,只是函式不同
void glMaterialf(GLenum face ,GLenum pname,TYPE param);
void glMaterialfv(GLenum face ,GLenum panme ,TYPE*param);
引數face指定了材質如何應用於物體的多邊形,可以為GL_FRONT,GL_BACK,GL_FRONT_AND_BACK這三個值之一。
GL_FRONT材質用於多邊形的正面
GL_BACK材質用於多邊形的背面
GL_FRONT_AND_BACK材質用於多邊形的兩個面
引數pname:
GL_AMBIENT 材質的環境光顏色
GL_DIFFUSE 材質的散射光顏色
GL_AMBIENT_AND_DIFFUSE 材質的環境光和散射光顏色
GL_SPECULAR 材質的鏡面反射光顏色
GL_SHININESS 鏡面反射光指數
GL_EMISSION 材質的發射光顏色
呼叫glMaterial*()函式之後,所繪製的多邊形都會受其設定的材質影響,直到再次呼叫glMaterail*()函式。
設定材質屬性的另一個方法叫做顏色跟蹤。顏色跟蹤可以呼叫glColor*()函式來設定材質屬性。應用顏色跟蹤,首先要呼叫:
glEnable(GL_COLOR_MATERIAL); //啟用顏色跟蹤
然後呼叫glColorMaterial()函式來指定將會受glColor*()函式呼叫影響的材質屬性引數,
glEnable(GL_COLOR_MATERIAL);啟用顏色跟蹤
glColorMaterial(GL_FRONT,GL_DIFFUSE);多邊形正面,材質的散射光顏色屬性
glColor3f(1.0f,0.0f,0.0f);
6. 光照模型
OpenGL的光照模型允許設定4個影響場景的因素:
u 場景中的環境光強度
u 視點是否處於無窮遠(影響鏡面反射光反射角度的計算)
u 單面光照還是雙面光照
u 鏡面反射光顏色是否與環境光顏色和散射光顏色分離
使用glLightMode*()函式來定義光照模型
void glLightModel[if] (GLenum pname ,TYPE param);
void glLightMdoel[if]v (GLenum pname ,TYPE param);
引數pname指定了將要定義的光照模型的屬性,引數param就是相應的光照模型屬性的設定值,根據使用的函式版本形式的不同,可能是一個浮點型數值或者是一個數組。
pname:
GL_LIGHT_MODEL_AMBIENT場景的環境光強度(RGBA),預設是(0.2,0.2,0.2,1.0);一個光源的環境光分量對整個場景都是起作用的,一旦在光照模型中設定了GL_LIGHT_MODEL_AMBIENT引數,就會通知OpenGL在場景中設定一個環境光,它並不是來自於任何一個特定的光源,稱為全域性環境光。
GL_LIGHT_MODEL_LOCAL_VIEWER視點是否處於無窮遠,預設是GL_FALSE處於無窮遠,當建立一個鏡面反射光後,OpenGL會為場景中的物體計算其鏡面反射光的高亮區,相關物體頂點的方向和視點都將影響鏡面反射光高亮區的強度,在場景中使用非無窮遠的視點可以增加真實感,但速度效能會降低,因為要為每個頂點增加方向計算,預設情況是無窮遠
GL_LIGHT_MODEL_TWO_SIDE單面光照還是雙面光照,預設是GL_FALSE單面光照,此引數用來決定是否對多邊形的背面進行正確的光照計算,假如有一立方體,將其剖開,就能看到多邊形的背面(內部的光照不正確),要想使多邊形背面的光照被正確計算,需設定glLightModeli(GL_LIGHT_MODEL_TWO_SIDE,GL_TRUE);,告訴OpenGL將多邊形正面的法線反轉過來作為背面的法線。執行速度會減慢。
GL_LIGHT_MODEL_COLOR_CONTROL鏡面反射光顏色計算時是否與環境光和散射光分量,預設是GL_SINGLE_COLOR(不分離)。當對紋理對映進行光照,並且紋理與鏡面反射光的高光之間的配合不是很好的時候,使用此屬性。設定了引數後,OpenGL不會像通常將環境光分量、散射光分量、鏡面反射光分量和發射光分量的材質屬性都加在一起,而是會為光照下物體的每個頂點生成兩個顏色:主顏色和副顏色。主顏色包括了所有的非鏡面反射光分量,副顏色包括了鏡面反射光分量。進行紋理對映的時候,只有住顏色被應用,對映完成後副顏色被新增到結果上。這樣可以使鏡面反射光的高光效果更加明顯。
要想將鏡面反射光與其他分量分離:
glLightModeli(GL_LIGHT_MODEL_COLOR_CONTROL,GL_SEPARATE_SPECULAR_COLOR);
要將鏡面反射光與其他分量結合使用:
用GL_SINGLE_COLOR引數代替GL_SEPARATE_SPECULAR_COLOR引數。
只有在紋理時才應該設定此引數,不進行紋理對映就沒必要將鏡面反射光分量與其他分量分離。
7. 鏡面反射光的光照效果
float specularLight[]={1.0f,1.0f,1.0f,1.0f};鏡面反射光的值
float lightPosition[]={0.0f,0.0f,0.0f,1.0f};光源的位置
glEnable(GL_LIGHTING);啟用光照
//設定光照
glLightfv(GL_LIGHT0,GL_SPECULAR,specularLight);
glLightfv(GL_LIGHT0,GL_POSITION,lightPosition);
glEnable(GL_LIGHT0);
上述程式碼只設置了LIGHT0的鏡面反射光分量和位置,也可以同樣向LIGHT0新增環境光分量和散射光分量。這時鏡面反射光的分量被設定為(1.0,1.0,1.0,1.0)是一種非常明亮的白光,類似與陽光。
為了得到鏡面反射光的光照效果,必須定義響應的材質的鏡面反射光屬性:
float matSpecular[]={1.0f,1.0f,1.0f,1.0f};鏡面反射光材料屬性
//材質具有最小的光澤
glMaterialfv(GL_FRONT,GL_SPECULAR,matSpecular);
glMaterialf(GL_FRONT,GL_SHININESS,10.0f);
matSpecular變數被稱為鏡面反射光的反射率,此例中為(1.0,1.0,1.0,1.0),意思是此材質之後建立的任何表面會將幾乎所有照射在其表面上的鏡面反射光反射出去。
GL_SHININESS屬性定義鏡面發射光高光的聚焦程度,屬性的取值範圍是1.0~128.0,0.0代表鏡面反射光不聚焦,128.0表面會十分顯眼和明亮。
8. 光源的移動和旋轉
光源可以移動和旋轉,呼叫glLight*()函式定義一個光源的位置和方向時,所指定的資訊會被應用與當前的模型檢視矩陣。
如果移動的光源具有聚光燈的特效,需要為其設定GL_SPOT_DIRECTION引數,如果是向所有方向發射光線的點光源,就只要定義GL_POSITION引數。
探照燈(前燈)是目前的3D遊戲中常用的一種光源,光源的位置相對於眼睛或者視點的位置保持相對固定不變。為了達到這個效果,需要在眼睛座標控制元件中定義光源的位置,先將模型檢視矩陣設為單位矩陣,然後在其原點定義光源位置,如果沒有設定光源的方向,就只能得到一個位於視點的點光源。而如果希望得到探照燈或者前燈的效果,就必須將光源的方向設定為指向z軸的負方向。因為此光源的位置是 相對固定的,可以在初始化程式的時候指定其位置。避免每繪製一幀場景都要重新指定光源的位置。
除了投影矩陣堆疊、模型檢視矩陣堆疊、紋理矩陣堆疊,還有另外一種堆疊可以用來儲存當前的繪製狀態,通過傳遞給函式glPushAttrib()引數GL_LIGHTING_BIT可以儲存此場景中的所有光照資訊,這樣就可以先關掉光照,使我們能在沒有任何光照影響的情況下繪製物體,然後再呼叫glPopAttrib()函式開啟光照,恢復儲存在屬性堆疊中的光照屬性。
9.
8. 顏色混合
OpenGL中的顏色混合可以為場景帶來像透明這樣的視覺效果。利用透明,可以模
擬水、窗戶、玻璃以及其他可以看穿的物體。
啟用混合,就是通知OpenGL要將傳入圖元的顏色和幀緩衝中已有的顏色合併,然
後再將結果存回幀緩衝中。混合操作通常被視為表示顏色的RGB值與表示不透明的
alpha值之間的運算。較低的不透明度或者alpha將導致較高的透明性和半透明性。
將傳入的圖元稱為源,將當前幀快取中的畫素稱為目標。
為了使用混合,需要使用:
glEnable(GL_BLEND);
然後呼叫glBendFunc()函式來定義源和目標的混合因子。glBendFunc()函式中,預設的源混合因子是GL_ONE,目標的混合因子是GL_ZERO
源混合因子:
GL_ZERO 源的顏色設為(0,0,0,0)
GL_ONE 使用源的當前顏色
GL_DST_COLOR 源的顏色與目標的顏色相乘
GL_ONE_MINUS_DST_COLOR 源的顏色與[(1,1,1,1)-目標的顏色]相乘
GL_SRC_ALPHA 源的顏色與源的alpha相乘
GL_ONE_MINUS_SRC_ALPHA 源的顏色與[1-源alpha]相乘
GL_DST_ALPHA 源的顏色與目標alpha相乘
GL_ONE_MINUS_DST_ALPHA 源的顏色與[1-目標的alpha]相乘
GL_SRC_ALPHA_SATURATE 源的顏色與源的alpha值和[1-目標的alpha值]中最小的相乘
目標的混合因子
GL_ZERO 目標顏色為(0,0,0,0)
GL_ONE 使用目標的當前顏色
GL_SRC_COLOR 將目標顏色與源的顏色相乘
GL_ONE_MINUS_SRC_COLOR 將目標的顏色與[(1,1,1,1)-源的顏色]相乘
GL_SRC_ALPHA 目標的顏色與源的alpha相乘
GL_ONE_MINUS_SRC_ALPHA 目標的顏色與[1-源的alpha]相乘
GL_DST_ALPHA 目標的顏色與目標的alpha相乘
GL_ONE_MINUS_DST_ALPHA 目標的顏色與[1-目標的alpha]相乘
GL_SRC_ALPHA_SATURATE 目標的顏色與源的alpha和[1-目標alpha]中小的相乘
透明效果:
源:GL_SRC_ALPHA
目標:GL_ONE_MINUS_SRC_ALPHA
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
在3D中,為了保證場景被正確地繪製和混合,需要注意:
最重要的是用下面的命令開啟深度檢測:
glEnable(GL_DEPTH_TEST);
啟用深度檢測的目的是為了將位於其他物體後面的物體隱藏起來,深度緩衝是被用來跟蹤視點與視窗中的物體的每個畫素之間的距離的。當一個顏色被應用與一個畫素時,只有當此顏色所屬的物體距離視點的距離較原來畫素顏色所屬的物體距視點的距離近時,新的顏色才會取代原來畫素的顏色。一旦有取代發生,新的畫素的深度值就會相應的被存入深度快取中。這樣使OpenGL能夠隱藏位於不透明物體後面的物體。
在繪製場景時,要想在OpenGL中正確的進行混合就要涉及深度快取的開啟和關閉。先在深度快取的正常狀態下繪製所有的不透明物體,然後呼叫glDepthMask()函式將深度緩衝設定為只讀狀態,這樣就將繪製不透明物體時的深度值保護起來了,在深度快取為只讀狀態時,對透明物體的繪製不會影響到已經繪製好的不透明物體,因為深度快取不能被改變。但是透明物體的仍然進行深度檢測,與深度快取中的值比較,如果位於不透明物體後面,就不會被繪製,如果位於不透明物體的前面,透明物體將與不透明物體混合。
使用glDepthMask()函式,如果設定為只讀狀態,引數為GL_FALSE;
如果設定為正常狀態,引數為GL_TRUE。