從生理學的角度上講,眼睛之因此看見各類物體,是由於光線直接或間接的從它們那裏到達了眼睛。人類對於光線強弱的變化的反應,比對於顏色變化的反應來得靈敏。所以對於人類而言,光線很大程度上表現了物體的立體感。
請看圖1,圖中繪製了兩個大小相同的白色球體。其中右邊的一個是沒有使用任何光照效果的,它看起來就像是一個二維的圓盤,沒有立體的感受。左邊的一個是使用了簡單的光照效果的,咱們經過光照的層次,很容易的認爲它是一個三維的物體。
OpenGL對於光照效果提供了直接的支持,只須要調用某些函數,即可以實現簡單的光照效果。可是在這以前,咱們有必要了解一些基礎知識。
1、創建光照模型
在現實生活中,某些物體自己就會發光,例如太陽、電燈等,而其它物體雖然不會發光,但能夠反射來自其它物體的光。這些光經過各類方式傳播,最後進入咱們的眼睛——因而一幅畫面就在咱們的眼中造成了。
就目前的計算機而言,要準確模擬各類光線的傳播,這是沒法作到的事情。好比一個四面都是粗糙牆壁的房間,一盞電燈所發出的光線在很短的時間內就會通過很是屢次的反射,最終幾乎佈滿了房間的每個角落,這一過程即便使用目前運算速度最快的計算機,也沒法精確模擬。不過,咱們並不須要精確的模擬各類光線,只須要找到一種近似的計算方式,使它的最終結果讓咱們的眼睛認爲它是真實的,這就能夠了。
OpenGL在處理光照時採用這樣一種近似:把光照系統分爲三部分,分別是光源、材質和光照環境。光源就是光的來源,能夠是前面所說的太陽或者電燈等。材質是指接受光照的各類物體的表面,因爲物體如何反射光線只由物體表面決定(OpenGL中沒有考慮光的折射),材質特色就決定了物體反射光線的特色。光照環境是指一些額外的參數,它們將影響最終的光照畫面,好比一些光線通過屢次反射後,已經沒法分清它到底是由哪一個光源發出,這時,指定一個「環境亮度」參數,可使最後造成的畫面更接近於真實狀況。
在物理學中,光線若是射入理想的光滑平面,則反射後的光線是很規則的(這樣的反射稱爲鏡面反射)。光線若是射入粗糙的、不光滑的平面,則反射後的光線是雜亂的(這樣的反射稱爲漫反射)。現實生活中的物體在反射光線時,並非絕對的鏡面反射或漫反射,但能夠當作是這兩種反射的疊加。對於光源發出的光線,能夠分別設置其通過鏡面反射和漫反射後的光線強度。對於被光線照射的材質,也能夠分別設置光線通過鏡面反射和漫反射後的光線強度。這些因素綜合起來,就造成了最終的光照效果。
2、法線向量
在OpenGL中,法線的方向是用一個向量來表示。不幸的是,OpenGL並不會根據你所指定的多邊形各個頂點來計算出這些多邊形所構成的物體的表面的每一個點的法線(這話聽着有些迷糊),一般,爲了實現光照效果,須要在代碼中爲每個頂點指定其法線向量。
指定法線向量的方式與指定顏色的方式有雷同之處。在指定顏色時,只須要指定每個頂點的顏色,OpenGL就能夠自行計算頂點之間的其它點的顏色。而且,顏色一旦被指定,除非再指定新的顏色,不然之後指定的全部頂點都將以這一貫量做爲本身的顏色。在指定法線向量時,只須要指定每個頂點的法線向量,OpenGL會自行計算頂點之間的其它點的法線向量。而且,法線向量一旦被指定,除非再指定新的法線向量,不然之後指定的全部頂點都將以這一貫量做爲本身的法線向量。使用glColor*函數能夠指定顏色,而使用glNormal*函數則能夠指定法線向量。
注意:使用glTranslate*函數或者glRotate*函數能夠改變物體的外觀,但法線向量並不會隨之改變。然而,使用glScale*函數,對每一座標軸進行不一樣程度的縮放,頗有可能致使法線向量的不正確,雖然OpenGL提供了一些措施來修正這一問題,但由此也帶來了各類開銷。所以,在使用了法線向量的場合,應儘可能避免使用glScale*函數。即便使用,也最好保證各座標軸進行等比例縮放。
3、控制光源
在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屬性也只有一個值,表示一個角度,它是光源發射光線所覆蓋角度的一半(見圖2),其取值範圍在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)這兩類屬性就不會用到了,問題就變得簡單明瞭。
4、控制材質
材質與光源類似,也須要設置衆多的屬性。不一樣的是,光源是經過glLight*函數來設置的,而材質則是經過glMaterial*函數來設置的。
glMaterial*函數有三個參數。第一個參數表示指定哪一面的屬性。能夠是GL_FRONT、GL_BACK或者GL_FRONT_AND_BACK。分別表示設置「正面」「背面」的材質,或者兩面同時設置。(關於「正面」「背面」的內容須要參看前些課程的內容)第2、第三個參數與glLight*函數的第2、三個參數做用相似。下面分別說明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模式要複雜,而且使用範圍較小,這裏不作討論。
5、選擇光照模型
這裏所說的「光照模型」是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。後者一般可使畫面效果更爲逼真(固然,若是自己就沒有執行任何紋理操做,這樣的分離就沒有任何意義)。
6、最後的準備
到如今能夠說是完事俱備了。不過,OpenGL默認是關閉光照處理的。要打開光照處理功能,使用下面的語句:
glEnable(GL_LIGHTING);
要關閉光照處理功能,使用glDisable(GL_LIGHTING);便可。
7、示例程序
到如今,咱們已經能夠編寫簡單的使用光照的OpenGL程序了。
咱們仍然以太陽、地球做爲例子(此次就不考慮月亮了^-^),把太陽做爲光源,模擬地球圍繞太陽轉動時光照的變化。因而,須要設置一個光源——太陽,設置兩種材質——太陽的材質和地球的材質。把太陽光線設置爲白色,位置在畫面正中。把太陽的材質設置爲微微散發出紅色的光芒,把地球的材質設置爲微微散發出暗淡的藍色光芒,而且反射藍色的光芒,鏡面指數設置成一個比較小的值。簡單起見,再也不考慮太陽和地球的大小關係,用一樣大小的球體來代替之。
關於法線向量。球體表面任何一點的法線向量,就是球心到該點的向量。若是使用glutSolidSphere函數來繪製球體,則該函數會自動的指定這些法線向量,沒必要再手工指出。若是是本身指定若干的頂點來繪製一個球體,則須要本身指定法線響亮。
因爲咱們使用的太陽是一個位置性光源,在設置它的位置時,須要利用到矩陣變換。所以,在設置光源的位置之前,須要先設置好各類矩陣。利用gluPerspective函數來建立具備透視效果的視圖。咱們也將利用前面課程所學習的動畫知識,讓整個畫面動起來。
下面給出具體的代碼:程序員
#include <gl/glut.h> #define WIDTH 400 #define HEIGHT 400 static 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); glLightfv(GL_LIGHT0, GL_AMBIENT, sun_light_ambient); glLightfv(GL_LIGHT0, GL_DIFFUSE, sun_light_diffuse); glLightfv(GL_LIGHT0, GL_SPECULAR, sun_light_specular); glEnable(GL_LIGHT0); 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(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; }
小結:
本課介紹了OpenGL光照的基本知識。編程
OpenGL把光照分解爲光源、材質、光照模式三個部分,根據這三個部分的各類信息,以及物體表面的法線向量,能夠計算獲得最終的光照效果。
光源、材質和光照模式都有各自的屬性,儘管屬性種類繁多,但這些屬性都只用不多的幾個函數來設置。使用glLight*函數可設置光源的屬性,使用glMaterial*函數可設置材質的屬性,使用glLightModel*函數可設置光照模式。
GL_AMBIENT、GL_DIFFUSE、GL_SPECULAR這三種屬性是光源和材質所共有的,若是某光源發出的光線照射到某材質的表面,則最終的漫反射強度由兩個GL_DIFFUSE屬性共同決定,最終的鏡面反射強度由兩個GL_SPECULAR屬性共同決定。
可使用多個光源來實現各類逼真的效果,然而,光源數量的增長將形成程序運行速度的明顯降低。
在使用OpenGL光照過程當中,屬性的種類和數量都很是繁多,一般,須要不少的經驗才能夠熟練的設置各類屬性,從而造成逼真的光照效果。(各位也看到了,其實這個課程的示例程序中,屬性設置也不怎麼好)。然而,設置這些屬性的藝術性遠遠超過了技術性,每每是一些美術製做人員設置好各類屬性(並保存爲文件),而後由程序員編寫的程序去執行繪製工做。所以,即便目前沒法熟練運用各類屬性,也沒必要過於擔憂。若是條件容許,能夠玩玩相似3DS MAX之類的軟件,對理解光照、熟悉各類屬性設置會有一些幫助。
在課程的最後,咱們給出了一個樣例程序,演示了太陽和地球模型中的光照效果。
函數