1. 程式人生 > >OpenGL(七) 光照模型及設定

OpenGL(七) 光照模型及設定

OpenGL把現實世界中的光照系統近似歸為三部分,分別是光源、材質和光照環境。

光源就是光的來源,是“光”這種物質的提供者; 材質是指被光源照射的物體的表面的反射、漫反射(OpenGL不考慮折射)特性;

材質反映的是光照射到物體上後物體表現出來的對光的吸收、漫反射、反射等效能; 

光照環境反應環境中所有光源發出的光經過無數次反射、漫反射之後整體環境所表現出來的光照效果。指定合適的光照環境引數可以使得最後形成的畫面更接近於真實場景。

一、光源

光照模型

OpenGL中的光照模型中的反射光分為三個分量,分別是環境反射光(Ambient Light)、漫反射光(Diffuse Light)和鏡面反射光(Specular Light)。

  • 環境光Ambient:是由光源發出經環境多次散射而無法確定其入射方向的光,即似乎來自所有方向。其特徵是入射方向和出射方向均為任意方向。 
  • 漫射光Diffuse:來自特定方向,它垂直於物體時比傾斜時更明亮。一旦它照射到物體上,則在各個方向上均勻地發散出去,效果為無論視點在哪裡它都一樣亮,其特徵是入射方向唯一、出射方向為任意方向。
  • 鏡面光Specular:來自特定方向並沿另一方向反射出去,一個平行鐳射束在高質量的鏡面上產生100%的鏡面反射,其特徵是入射方向和出射方向均唯一。

建立光源

OpenGL中用函式glLightfv來建立光源,函式原型是:

 void glLightfv (GLenum light, GLenum pname, const GLfloat *params)
第一個引數light指定所建立的光源號,如GL_LIGHT0、GL_LIGHT1、...、GL_LIGHT7。

第二個引數pname指定光源特性,這個引數的具體資訊見下表所示。



第三個引數設定相應的光源特性值。

例如下邊定義了一個位置在(0,0,0),沒有環境光,鏡面反射光和漫反射光都為白光的光源:

GLfloat light_position[] = { 0.0, 0.0, 0.0, 0.0 }; 
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_POSITION, light_position); 
glLightfv(GL_LIGHT0, GL_AMBIENT , light_ambient );
glLightfv(GL_LIGHT0, GL_DIFFUSE , light_diffuse ); 
glLightfv(GL_LIGHT0, GL_SPECULAR, light_specular); 


光源的位置座標採用齊次座標(x, y, z, w)設定。第四個引數w為0.0時,定義相應的光源是定向光源,其所有的光線幾乎是互相平行的,典型的如太陽光。光源方向由定義的座標(x, y, z)指向(0,0,0);

w為1.0時,光源為定位光源。(x, y, z, w)指定光源在齊次座標系下的具體位置,該位置會根據模型視點矩陣進行變換。

光源的衰減

離光源越遠則光強越弱。由於定向光源是模擬的無窮遠得光源,所以不會根據距離改變而衰減,所以在定向光源中是禁用衰減的; 對於定位光源有衰減。OpenGL的光衰減是通過光源的發光量乘以衰減因子來實現衰減的。

衰減係數=1/(K0+K1*D+K2*D2)  

其中:  D=光源位置與頂點之間的距離 

K0= GL_CONSTANT_ATTENUATION       //常數衰減因子

K1= GL_LINER_ATTENUATION               // 線性衰減因子

K2= GL_QUADRATIC_ATTENUATION    // 二次衰減因子

OpenGL中預設的衰減因子為(1,0,0),即不進行衰減。

設定好以上光源的屬性之後,要使用glEnable(GL_LIGHTING)和glEnable(GL_LIGHTX)來啟用光照和第X號光源。

二、 材質

OpenGL用材質對光的紅、綠、藍三原色的反射率來近似定義材料的顏色。

物體的材質跟光源一樣,也分為環境、漫反射和鏡面反射成分,它們決定了材料對環境光、漫反射光和鏡面反射光的反射程度。其中對環境光和漫反射光的反射程度決定了物體的顏色。

比如對於一朵紅色的花,它對綠色和藍色的反射能力比較弱,對紅色分量的反射能力比較強,所以呈現出來是紅色。材質的鏡面反射率在RGB三個分量上通常是一致的,即反射的光跟光源的顏色基本是一致的,只不過強度會減弱。鏡面反射光的強度還取決於觀察點的位置,當觀察點正好處於入射光的反射光線上,亮斑的亮度達到最大值。

這3個材質的屬性都是反應的物體對外界光線的反射情況,有些物體本身可以發射弱光,OpenGL中是通過設定材料的輻射光來實現的,可以使物體看起來像是發射出設定的輻射光一樣,以達到特殊的效果。物體的輻射光使用glMaterialfv(GL_FRONT, GL_EMISSION,   earth_mat_emission)來設定。

總體來說材質定義了物體對環境光、漫反射光、鏡面光的反射(吸收)能力。我們看到的物體的顏色(或亮度)是光源的顏色(或亮度)經過物體的材質反射(吸收)之後發散出來的顏色(或亮度)。

定義材質的函式使用glMaterialfv,函式原型是:

void  glMaterialfv (GLenum face, GLenum pname, const GLfloat *params);

第一個引數face可以是GL_FRONT、GL_BACK、GL_FRONT_AND_BACK,它表明當前材質應該應用到物體的哪一個面上;

第二個引數pname指定正在設定的材質特性,這個引數的輔助資訊下表所示:


第三個params引數設定相應的材質的特性值。

三、 光照環境

光照環境是指該環境中的光線不是來自於特定的光源,它是一個全域性環境光,即那些在環境中經過了充分散射的光,這個光在環境中物體的各個表面上均勻泛射,環境中所有的物體都會受到該環境光的影響。

使用函式flLightModelfv來設定環境光,如下定義了一個微微發出藍光的環境光,在這種情況下,即使環境中沒有光源存在,也可以看到場景中的物體:

GLfloat mat_ambient[]   = {0.0f, 0.0f, 0.2f, 1.0f}; 
glMaterialfv(GL_FRONT, GL_AMBIENT,    mat_ambient); 

總結一下在OpenGL中加入光照的步驟為:
  • 1. 設定光源引數: 需要多次呼叫帶有不同引數的glLightfv函式,設定環境光(Ambient Light)、漫射光(Diffuse Light)、鏡面光(Specular Light)和光照位置(Position)等。
  • 2. 開啟光照,使用glEnable(GL_LIGHTING)和glEnable(GL_LIGHTXX)來開啟光照和XX號光源,前者相當於光       源的總開關,後者是對應的開啟相應號數的光源。 第3步是法線的設定,本文不涉及。 

下圖是對光照應用的例子,紅色球代表太陽,材質的輻射光為紅光,綠色球代表地球,繞太陽公轉,材質的輻射光為藍光,鏡面反射光為紅色(藍色地球上的紅色區域):

另一種演示效果,設定太陽的輻射演示為白色,地球的鏡面反射為白色,漫反射為紅色:


完整程式碼如下,可以設定不同的材質屬性,檢視不同的效果:

#include <gl/glut.h> 
#include <Windows.h>

static GLfloat angle = 0.0f;   
void myDisplay(void) 
{     
	glClearColor(0.3,0.7,0.5,0);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);  //清理顏色和深度快取     

	// 建立透視效果檢視      
	glMatrixMode(GL_PROJECTION);   
	glLoadIdentity();    
	gluPerspective(80.0f, 1.0f, 1.0f, 20.0f);   

	glMatrixMode(GL_MODELVIEW);   
	glLoadIdentity();   
	gluLookAt(0.0, 12.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}; //RGBA模式的環境光,為0
		GLfloat sun_light_diffuse[]   = {1.0f, 1.0f, 1.0f, 1.0f}; //RGBA模式的漫反射光,全白光
		GLfloat sun_light_specular[] = {1.0f, 1.0f, 1.0f, 1.0f};  //RGBA模式下的鏡面光 ,全白光
		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};  //定義材質的環境光顏色,為0
		GLfloat sun_mat_diffuse[]   = {0.0f, 0.0f, 0.0f, 1.0f};  //定義材質的漫反射光顏色,為0
		GLfloat sun_mat_specular[] = {0.0f, 0.0f, 0.0f, 1.0f};   //定義材質的鏡面反射光顏色,為0
		GLfloat sun_mat_emission[] = {0.8f, 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(3.0, 40, 32);    
	}

	// 定義地球的材質並繪製地球    
	{        
		GLfloat earth_mat_ambient[]   = {0.0f, 0.0f, 1.0f, 1.0f};  //定義材質的環境光顏色,騙藍色
		GLfloat earth_mat_diffuse[]   = {0.0f, 0.0f, 0.5f, 1.0f};  //定義材質的漫反射光顏色,偏藍色
		GLfloat earth_mat_specular[] = {1.0f, 0.0f, 0.0f, 1.0f};   //定義材質的鏡面反射光顏色,紅色
		GLfloat earth_mat_emission[] = {0.0f, 0.0f, 0.0f, 1.0f};   //定義材質的輻射光顏色,為0
		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(7.0f, 0.0f, 0.0f);        
		glutSolidSphere(3.0, 40, 32);     
	}    
	Sleep(10);
	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(400, 400);   
	glutCreateWindow("OpenGL光照演示");   
	glutDisplayFunc(&myDisplay);    
	glutIdleFunc(&myIdle);   
	glutMainLoop();   
	return 0;
}