以下內容從《openGL入門教程》整理而來~~~~
看一張圖片:
圖中繪製了兩個大小相同的白色球體。其中右邊的一個是沒有使用任何光照效果的,它看起來就像是一個二維的圓盤,沒有立體的感覺。左邊的一個是使用了簡單的光照效果的,我們通過光照的層次,很容易的認為它是一個三維的物體
眼睛之所以看見各種物體,是因為光線直接或間接的從它們那裡到達了眼睛。人類對於光線強弱的變化的反應,比對於顏色變化的反應來得靈敏。因此對於人類而言,光線很大程度上表現了物體的立體感。
建立光照模型
OpenGL在處理光照時採用這樣一種近似:把光照系統分為三部分,分別是光源、材質和光照環境。
光源就是光的來源,可以是前面所說的太陽或者電燈等。
材質是指接受光照的各種物體的表面,由於物體如何反射光線只由物體表面決定(OpenGL中沒有考慮光的折射),材質特點就決定了物體反射光線的特點。
光照環境是指一些額外的參數,它們將影響最終的光照畫面,比如一些光線經過多次反射後,已經無法分清它究竟是由哪個光源發出,這時,指定一個“環境亮度”參數,可以使最後形成的畫面更接近於真實情況。
在物理學中,光線如果射入理想的光滑平面,則反射後的光線是很規則的(這樣的反射稱為鏡面反射)。
光線如果射入粗糙的、不光滑的平面,則反射後的光線是雜亂的(這樣的反射稱為漫反射)。
現實生活中的物體在反射光線時,並不是絕對的鏡面反射或漫反射,但可以看成是這兩種反射的疊加。
對於光源發出的光線,可以分別設定其經過鏡面反射和漫反射後的光線強度。對於被光線照射的材質,也可以分別設定光線經過鏡面反射和漫反射後的光線強度。這些因素綜合起來,就形成了最終的光照效果。
法線向量
根據光的反射定律,由光的入射方向和入射點的法線就可以得到光的出射方向。
因此,對於指定的物體,在指定了光源後,即可計算出光的反射方向,進而計算出光照效果的畫面。
在OpenGL中,法線的方向是用一個向量來表示。
不幸的是,OpenGL並不會根據你所指定的多邊形各個頂點來計算出這些多邊形所構成的物體的表面的每個點的法線(這話聽著有些迷糊),通常,為了實現光照效果,需要在代碼中為每一個頂點指定其法線向量。
指定法線向量的方式與指定顏色的方式有雷同之處。在指定顏色時,只需要指定每一個頂點的顏色,OpenGL就可以自行計算頂點之間的其它點的顏色。
並且,顏色一旦被指定,除非再指定新的顏色,否則以後指定的所有頂點都將以這一向量作為自己的顏色。
在指定法線向量時,只需要指定每一個頂點的法線向量,OpenGL會自行計算頂點之間的其它點的法線向量。
並且,法線向量一旦被指定,除非再指定新的法線向量,否則以後指定的所有頂點都將以這一向量作為自己的法線向量。
使用glColor*函數可以指定顏色,而使用glNormal*函數則可以指定法線向量。
注意:使用glTranslate*函數或者glRotate*函數可以改變物體的外觀,但法線向量並不會隨之改變。
然而,使用glScale*函數,對每一座標軸進行不同程度的縮放,很有可能導致法線向量的不正確,雖然OpenGL提供了一些措施來修正這一問題,但由此也帶來了各種開銷。
因此,在使用了法線向量的場合,應盡量避免使用glScale*函數。即使使用,也最好保證各座標軸進行等比例縮放。
控制光源
在OpenGL中,僅僅支援有限數量的光源。
使用GL_LIGHT0表示第0號光源,GL_LIGHT1表示第1號光源,依次類推.
OpenGL至少會支援8個光源,即GL_LIGHT0到GL_LIGHT7。
使用glEnable函數可以開啟它們。例如,glEnable(GL_LIGHT0);可以開啟第0號光源。
使用glDisable函數則可以關閉光源。
一些OpenGL實現可能支援更多數量的光源,但總的來說,開啟過多的光源將會導致程式運行速度的嚴重下降,玩過3D Mark的朋友可能多少也有些體會。一些情境中可能有成百上千的電燈,這時可能需要採取一些近似的手段來進行編程,否則以目前的電腦而言,是無法運行這樣的程式的。
每一個光源都可以設定其屬性,這一動作是通過glLight*函數完成的。
glLight*函數具有三個參數,第一個參數指明是設定哪一個光源的屬性,第二個參數指明是設定該光源的哪一個屬性,第三個參數則是指明把該屬性值設定成多少。光源的屬性眾多,下面將分別介紹。
(1)GL_AMBIENT、GL_DIFFUSE、GL_SPECULAR屬性。
這三個屬性工作表示了光源所發出的光的反射特性(以及顏色)。
每個屬性由四個值表示,分別代表了顏色的R, G, B, A值。
GL_AMBIENT表示該光源所發出的光,經過非常多次的反射後,最終遺留在整個光照環境中的強度(顏色)。
GL_DIFFUSE表示該光源所發出的光,照射到粗糙表面時經過漫反射,所得到的光的強度(顏色)
GL_SPECULAR表示該光源所發出的光,照射到光滑表面時經過鏡面反射,所得到的光的強度(顏色)。
(2)GL_POSITION屬性。
表示光源所在的位置。
由四個值(X, Y, Z, W)表示。
如果第四個值W為零,則表示該光源位於無限遠處,前三個值表示了它所在的方向。
這種光源稱為方向性光源,通常,太陽可以近似的被認為是方向性光源。
如果第四個值W不為零,則X/W, Y/W, Z/W表示了光源的位置。
這種光源稱為位置性光源。對於位置性光源,設定其位置與設定多邊形頂點的方式相似,各種矩陣變換函數例如:glTranslate*、glRotate*等在這裡也同樣有效。
方向性光源在計算時比位置性光源快了不少,因此,在視覺效果允許的情況下,應該儘可能的使用方向性光源。
(3)GL_SPOT_DIRECTION、GL_SPOT_EXPONENT、GL_SPOT_CUTOFF屬性。
表示將光源作為聚光燈使用(這些屬性只對位置性光源有效)。很多光源都是向四面八方發射光線,但有時候一些光源則是只向某個方向發射,比如手電筒,只向一個較小的角度發射光線。
GL_SPOT_DIRECTION屬性有三個值,表示一個向量,即光源發射的方向。
GL_SPOT_EXPONENT屬性只有一個值,表示聚光的程度,為零時表示光照範圍內向各方向發射的光線強度相同,為正數時表示光照向中央集中,正對發射方向的位置受到更多光照,其它位置受到較少光照。數值越大,聚光效果就越明顯。
GL_SPOT_CUTOFF屬性也只有一個值,表示一個角度,它是光源發射光線所覆蓋角度的一半(見),其取值範圍在0到90之間,也可以取180這個特殊值。取值為180時表示光源發射光線覆蓋360度,即不使用聚光燈,向全周圍發射。
(4)GL_CONSTANT_ATTENUATION、GL_LINEAR_ATTENUATION、GL_QUADRATIC_ATTENUATION屬性。
這三個屬性工作表示了光源所發出的光線的直線傳播特性(這些屬性只對位置性光源有效)。
現實生活中,光線的強度隨著距離的增加而減弱,OpenGL把這個減弱的趨勢抽象成函數:
衰減因子 = 1 / (k1 + k2 * d + k3 * k3 * d)
其中d表示距離,光線的初始強度乘以衰減因子,就得到對應距離的光線強度。
k1, k2, k3分別就是GL_CONSTANT_ATTENUATION, GL_LINEAR_ATTENUATION, GL_QUADRATIC_ATTENUATION。
通過設定這三個常數,就可以控制光線在傳播過程中的減弱趨勢。
屬性還真是不少。當然了,如果是使用方向性光源,(3)(4)這兩類屬性就不會用到了,問題就變得簡單明了。
控制材質
材質與光源相似,也需要設定眾多的屬性。不同的是,光源是通過glLight*函數來設定的,而材質則是通過glMaterial*函數來設定的。
glMaterial*函數有三個參數。第一個參數表示指定哪一面的屬性。可以是GL_FRONT、GL_BACK或者GL_FRONT_AND_BACK。分別表示設定“正面”“背面”的材質,或者兩面同時設定。
第二、第三個參數與glLight*函數的第二、三個參數作用類似。
下面分別說明glMaterial*函數可以指定的材質屬性。
(1)GL_AMBIENT、GL_DIFFUSE、GL_SPECULAR屬性。
這三個屬性與光源的三個對應屬性類似,每一屬性都由四個值組成。
GL_AMBIENT表示各種光線照射到該材質上,經過很多次反射後最終遺留在環境中的光線強度(顏色)。
GL_DIFFUSE表示光線照射到該材質上,經過漫反射後形成的光線強度(顏色)。
GL_SPECULAR表示光線照射到該材質上,經過鏡面反射後形成的光線強度(顏色)。
通常,GL_AMBIENT和GL_DIFFUSE都取相同的值,可以達到比較真實的效果。
使用GL_AMBIENT_AND_DIFFUSE可以同時設定GL_AMBIENT和GL_DIFFUSE屬性。
(2)GL_SHININESS屬性。
該屬性只有一個值,稱為“鏡面指數”,取值範圍是0到128。
該值越小,表示材質越粗糙,點光源發射的光線照射到上面,也可以產生較大的亮點。該值越大,表示材質越類似於鏡面,光源照射到上面後,產生較小的亮點。
(3)GL_EMISSION屬性。該屬性由四個值組成,表示一種顏色。OpenGL認為該材質本身就微微的向外發射光線,以至於眼睛感覺到它有這樣的顏色,但這光線又比較微弱,以至於不會影響到其它物體的顏色。
(4)GL_COLOR_INDEXES屬性。
該屬性僅在色彩索引模式下使用,由於色彩索引模式下的光照比RGBA模式要複雜,並且使用範圍較小,這裡不做討論。
選擇光照模型
這裡所說的“光照模型”是OpenGL的術語,它相當於我們在前面提到的“光照環境”。
在OpenGL中,光照模型包括四個部分的內容:
全域環境光線(即那些充分散射,無法分清究竟來自哪個光源的光線)的強度、
觀察點位置是在較近位置還是在無限遠處、物體正面與背面是否分別計算光照、
鏡面顏色(即GL_SPECULAR屬性所指定的顏色)的計算是否從其它光照計算中分離出來,並在紋理操作以後在進行應用。
以上四方面的內容都通過同一個函數glLightModel*來進行設定。
該函數有兩個參數,第一個表示要設定的項目,第二個參數表示要設定成的值。
GL_LIGHT_MODEL_AMBIENT表示全域環境光線強度,由四個值組成。
GL_LIGHT_MODEL_LOCAL_VIEWER表示是否在近處觀看,若是則設定為GL_TRUE,否則(即在無限遠處觀看)設定為GL_FALSE。
GL_LIGHT_MODEL_TWO_SIDE表示是否執行雙面光照計算。如果設定為GL_TRUE,則OpenGL不僅將根據法線向量計算正面的光照,也會將法線向量反轉並計算背面的光照。
GL_LIGHT_MODEL_COLOR_CONTROL表示顏色計算方式。
如果設定為GL_SINGLE_COLOR,表示按通常順序操作,先計算光照,再計算紋理。
如果設定為GL_SEPARATE_SPECULAR_COLOR,表示將GL_SPECULAR屬性分離出來,先計算光照的其它部分,待紋理操作完成後再計算GL_SPECULAR。
後者通常可以使畫面效果更為逼真(當然,如果本身就沒有執行任何紋理操作,這樣的分離就沒有任何意義)。
OpenGL預設是關閉光照處理的。要開啟光照處理功能,使用下面的語句:
glEnable(GL_LIGHTING);
要關閉光照處理功能,使用glDisable(GL_LIGHTING);即可。
code:
#include <gl/glut.h>#define WIDTH 400#define HEIGHT 400static GLfloat angle = 0.0f;void myDisplay(void){glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //清空顏色和深度緩衝// 建立透視效果視圖glMatrixMode(GL_PROJECTION);//對投影矩陣操作glLoadIdentity();//將座標原點移到中心gluPerspective(90.0f, 1.0f, 1.0f, 20.0f);//設定透視投影矩陣glMatrixMode(GL_MODELVIEW);//對模型視景矩陣操作glLoadIdentity();gluLookAt(0.0, 5.0, -10.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);//視點轉換// 定義太陽光源,它是一種白色的光源{GLfloat sun_light_position[] = {0.0f, 0.0f, 0.0f, 1.0f};GLfloat sun_light_ambient[] = {0.0f, 0.0f, 0.0f, 1.0f};GLfloat sun_light_diffuse[] = {1.0f, 1.0f, 1.0f, 1.0f};GLfloat sun_light_specular[] = {1.0f, 1.0f, 1.0f, 1.0f};glLightfv(GL_LIGHT0, GL_POSITION, sun_light_position); //指定第0號光源的位置 glLightfv(GL_LIGHT0, GL_AMBIENT, sun_light_ambient); //GL_AMBIENT表示各種光線照射到該材質上, //經過很多次反射後最終遺留在環境中的光線強度(顏色)glLightfv(GL_LIGHT0, GL_DIFFUSE, sun_light_diffuse); //漫反射後~~glLightfv(GL_LIGHT0, GL_SPECULAR, sun_light_specular);//鏡面反射後~~~glEnable(GL_LIGHT0); //使用第0號光照glEnable(GL_LIGHTING); //在後面的渲染中使用光照glEnable(GL_DEPTH_TEST); //這句是啟用深度測試,這樣,在後面的物體會被擋著,例如房子後面有棵樹,如果不啟用深度測試, //你先畫了房子再畫樹,樹會覆蓋房子的;但啟用深度測試後無論你怎麼畫,樹一定在房子後面(被房子擋著) }// 定義太陽的材質並繪製太陽{GLfloat sun_mat_ambient[] = {0.0f, 0.0f, 0.0f, 1.0f};GLfloat sun_mat_diffuse[] = {0.0f, 0.0f, 0.0f, 1.0f};GLfloat sun_mat_specular[] = {0.0f, 0.0f, 0.0f, 1.0f};GLfloat sun_mat_emission[] = {0.5f, 0.0f, 0.0f, 1.0f};GLfloat sun_mat_shininess = 0.0f;glMaterialfv(GL_FRONT, GL_AMBIENT, sun_mat_ambient); //定義材料的前面採用 "多次反射"glMaterialfv(GL_FRONT, GL_DIFFUSE, sun_mat_diffuse); //材料的前面為 漫反射glMaterialfv(GL_FRONT, GL_SPECULAR, sun_mat_specular); //定義材料的前面為 鏡面反射glMaterialfv(GL_FRONT, GL_EMISSION, sun_mat_emission); //定義材料的前面為 鏡面指數glMaterialf (GL_FRONT, GL_SHININESS, sun_mat_shininess); //材料的前面 採用 的顏色glutSolidSphere(2.0, 40, 32);}// 定義地球的材質並繪製地球{GLfloat earth_mat_ambient[] = {0.0f, 0.0f, 0.5f, 1.0f};GLfloat earth_mat_diffuse[] = {0.0f, 0.0f, 0.5f, 1.0f};GLfloat earth_mat_specular[] = {0.0f, 0.0f, 1.0f, 1.0f};GLfloat earth_mat_emission[] = {0.0f, 0.0f, 0.0f, 1.0f};GLfloat earth_mat_shininess = 30.0f;glMaterialfv(GL_FRONT, GL_AMBIENT, earth_mat_ambient);glMaterialfv(GL_FRONT, GL_DIFFUSE, earth_mat_diffuse);glMaterialfv(GL_FRONT, GL_SPECULAR, earth_mat_specular);glMaterialfv(GL_FRONT, GL_EMISSION, earth_mat_emission);glMaterialf (GL_FRONT, GL_SHININESS, earth_mat_shininess);glRotatef(angle, 0.0f, -1.0f, 0.0f);glTranslatef(5.0f, 0.0f, 0.0f);//如果使用glutSolidSphere函數來繪製球體,則該函數會自動的指定這些法線向量,//不必再手工指出。如果是自己指定若干的頂點來繪製一個球體,則需要自己指定法線向量。glutSolidSphere(2.0, 40, 32);}glutSwapBuffers(); //交換緩衝區}void myIdle(void){angle += 1.0f;if( angle >= 360.0f )angle = 0.0f;myDisplay();}int main(int argc, char* argv[]){glutInit(&argc, argv);glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE);glutInitWindowPosition(200, 200);glutInitWindowSize(WIDTH, HEIGHT);glutCreateWindow("OpenGL光照示範");glutDisplayFunc(&myDisplay);glutIdleFunc(&myIdle);glutMainLoop();return 0;}
result: