溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

OpenGL進(jìn)階(十三) - GLSL光照(Lighting)

發(fā)布時(shí)間:2020-07-20 18:00:08 來源:網(wǎng)絡(luò) 閱讀:1349 作者:拳四郎 欄目:開發(fā)技術(shù)

提要

       在上一篇文章中,我們介紹了簡(jiǎn)單的Shading,同時(shí)提出了一個(gè)光照模型,模擬了一個(gè)點(diǎn)光源,但是,關(guān)于光的故事還沒有結(jié)束... 

       今天要學(xué)習(xí)的是方向光源(Directional Light),聚光燈,per pixel shading,halfway vector。

      關(guān)于光源的原理及數(shù)學(xué)描述,請(qǐng)參考:光線追蹤(RayTracing)算法理論與實(shí)踐(三)光照


方向光源

      方向光源就兩個(gè)參數(shù),方向和強(qiáng)度。

      還是簡(jiǎn)單的 ambient + diffuse + spec 光照模型。先看shader的代碼。

basic.vert

#version 400 layout (location = 0) in vec3 VertexPosition;   layout (location = 1) in vec3 VertexNormal;    out vec3 LightIntensity;  struct LightInfo{ 	vec4 Direction; 	vec3 Intensity; };  struct MaterialInfo{ 	vec3 Ka; 	vec3 Kd; 	vec3 Ks; 	float Shininess; };  uniform LightInfo Light; uniform	MaterialInfo Material;  uniform mat4 ModelViewMatrix; uniform mat3 NormalMatrix; uniform mat4 ProjectionMatrix; uniform mat4 MVP;   void getEyeSpace(out vec3 norm, out vec4 position) { 	norm =  normalize(NormalMatrix * VertexNormal); 	position = ModelViewMatrix * vec4(VertexPosition, 1.0); }    vec3 ads(vec4 position, vec3 norm) { 	vec3 s; 	if(Light.Direction.w == 0.0) 		s = normalize(vec3(Light.Direction)); 	else 		s = normalize(vec3(Light.Direction - position)); 	vec3 v = normalize(vec3(-position)); 	vec3 r = reflect(-s, norm);  	return Light.Intensity * (Material.Ka + Material.Kd*max(dot(s,norm), 0.0) +  	       Material.Ks * pow(max(dot(r,v),0.0), Material.Shininess)); }  void main() { 	vec3 eyeNorm; 	vec4 eyePosition; 	getEyeSpace(eyeNorm, eyePosition); 	LightIntensity = ads(eyePosition, eyeNorm); 	 	gl_Position = MVP * vec4( VertexPosition, 1.0); }

在ads函數(shù)中,首先通過nomal矩陣將頂點(diǎn)法向量變換到視口坐標(biāo)下,(nomal矩陣其實(shí)就是model-view矩陣的左上3x3的矩陣)然后通過model-view矩陣將頂點(diǎn)坐標(biāo)轉(zhuǎn)化為視口坐標(biāo)系(eye coordinates)下。

接下來的ads用來計(jì)算光照模型下頂點(diǎn)的顏色,分別計(jì)算三個(gè)分量,然后相加。


basic.frag

#version 400  in vec3 LightIntensity;  void main(void) { 	gl_FragColor = vec4(LightIntensity, 1.0); 	//gl_FragColor = vec4(1.0,1.0,0.1, 1.0); }
這個(gè)就是將根據(jù)頂點(diǎn)shader傳來的顏色對(duì)片段進(jìn)行賦值。

在cgl.cpp的setUniform函數(shù)中對(duì)Uniform變量進(jìn)行賦值。
void CGL::setUniform() {     mat4 projection = glm::perspective(45.0f, 4.0f / 3.0f, 0.1f, 100.0f);     mat4 mv = view * model;      prog.setUniform("Material.Kd", 0.9f, 0.5f, 0.3f);     prog.setUniform("Material.Ka", 0.9f, 0.5f, 0.3f);     prog.setUniform("Material.Ks", 0.8f, 0.8f, 0.8f);     prog.setUniform("Material.Shininess", 100.0f);     prog.setUniform("Light.Direction", vec4(1.0f, 0.0f, 0.0f, 0.0f));     prog.setUniform("Light.Intensity", 1.0f, 1.0f, 1.0f);      prog.setUniform("ModelViewMatrix", mv);     prog.setUniform("NormalMatrix",mat3( vec3(mv[0]), vec3(mv[1]), vec3(mv[2]) ));     prog.setUniform("MVP", projection * mv);  }

渲染的效果如下:

OpenGL進(jìn)階(十三) - GLSL光照(Lighting)

可以很明顯的感覺模型旋轉(zhuǎn)時(shí)到表面光照的變化。


halfway vector 性能優(yōu)化

        在前面的光照模型中,用于計(jì)算specular分量的公式如下:

OpenGL進(jìn)階(十三) - GLSL光照(Lighting)

其中 是反射光線的方向向量,v是往視口方向的向量,其中 r 的計(jì)算:

OpenGL進(jìn)階(十三) - GLSL光照(Lighting)

這個(gè)計(jì)算過程會(huì)非常耗時(shí),我們可以用一個(gè)trick來改善一下。

定義一個(gè) h (halfway vector)向量:

OpenGL進(jìn)階(十三) - GLSL光照(Lighting)

下圖是 和其他向量的位置關(guān)系。

OpenGL進(jìn)階(十三) - GLSL光照(Lighting)

specular分量的計(jì)算就可以轉(zhuǎn)化成:

OpenGL進(jìn)階(十三) - GLSL光照(Lighting)

         相比于計(jì)算 r ,的計(jì)算相對(duì)比較簡(jiǎn)單,而 h 和 n 之間的夾角與 和 r 之間的夾角大小幾乎相同!那就意味著我們可以用 h.n 來代替 r.v 從而可以帶利用 halfway vector 來獲得性能上的一些提升。雖然效果上會(huì)有那么小小的不同。

         后面的燈光的計(jì)算都會(huì)用到這個(gè)優(yōu)化。

聚光燈 Spotlight

        這里的采用一個(gè)最簡(jiǎn)單的聚光燈模型:

OpenGL進(jìn)階(十三) - GLSL光照(Lighting)


       燈光的屬性有:位置,強(qiáng)度,方向,衰減(exponent),裁剪角度。

       實(shí)現(xiàn)起來也比較簡(jiǎn)單,在投射角內(nèi)的物體,渲染方式和點(diǎn)光源的計(jì)算一樣,投射角之外的頂點(diǎn),著色的時(shí)候只有ambient。

        還是采用我們比較熟悉的per vertex shading 方式。在vert中定義聚光燈:

//baisc.vert #version 400 layout (location = 0) in vec3 VertexPosition;   layout (location = 1) in vec3 VertexNormal;    out vec3 LightIntensity;  struct SpotLightInfo{ 	vec4 position; 	vec3 direction; 	vec3 intensity; 	float exponent; 	float cutoff; };  struct MaterialInfo{ 	vec3 Ka; 	vec3 Kd; 	vec3 Ks; 	float Shininess; };  uniform SpotLightInfo Spot; uniform	MaterialInfo Material;  uniform mat4 ModelViewMatrix; uniform mat3 NormalMatrix; uniform mat4 ProjectionMatrix; uniform mat4 MVP;   void getEyeSpace(out vec3 norm, out vec4 position) { 	norm =  normalize(NormalMatrix * VertexNormal); 	position = ModelViewMatrix * vec4(VertexPosition, 1.0); }    vec3 adsSpotLight(vec4 position, vec3 norm) { 	vec3 s = normalize(vec3(Spot.position - position)); 	float angle = acos(dot(-s, normalize(Spot.direction))); 	float cutoff = radians(clamp(Spot.cutoff, 0.0, 90.0)); 	vec3 ambient = Spot.intensity * Material.Ka; 	 	if(angle < cutoff){ 		float spotFactor = pow(dot(-s, normalize(Spot.direction)), Spot.exponent); 		vec3 v = normalize(vec3(-position)); 		vec3 h = normalize(v + s); 		return ambient + spotFactor * Spot.intensity * (Material.Kd * max(dot(s, norm),0.0) 		      + Material.Ks * pow(max(dot(h,norm), 0.0),Material.Shininess));  	} 	else 	{ 		return ambient;  	} 	        }  void main() { 	vec3 eyeNorm; 	vec4 eyePosition; 	getEyeSpace(eyeNorm, eyePosition); 	 	LightIntensity = adsSpotLight(eyePosition, eyeNorm); 	 	gl_Position = MVP * vec4( VertexPosition, 1.0); }

幾個(gè)GLSL中的內(nèi)置函數(shù)在這里說明一下。

genType clamp( genType x, genType minVal, genType maxVal);

獲取三個(gè)數(shù)中第二大的數(shù)。

genType radians(genType degrees);

將角度轉(zhuǎn)換成弧度。

adsSpotLight是主要的函數(shù),先計(jì)算頂點(diǎn)和光源方向之間的夾角,判斷頂點(diǎn)是否在照射的區(qū)域,然后分別求得最終的顏色。


片段shader還是那樣:

#version 400  in vec3 LightIntensity;  void main(void) { 	gl_FragColor = vec4(LightIntensity, 1.0); }

uniform變量的賦值:

void CGL::setUniform() {     //model = glm::rotate(this->model, 10.0f, vec3(0.0f,1.0f,0.0f));     mat4 projection = glm::perspective(45.0f, 4.0f / 3.0f, 0.1f, 100.0f);     mat4 mv = view * model;     mat3 normalMatrix = mat3( vec3(view[0]), vec3(view[1]), vec3(view[2]) );      prog.setUniform("Material.Kd", 0.9f, 0.5f, 0.3f);     prog.setUniform("Material.Ka", 0.9f, 0.5f, 0.3f);     prog.setUniform("Material.Ks", 0.8f, 0.8f, 0.8f);     prog.setUniform("Material.Shininess", 100.0f);      vec4 lightPos = vec4(1.0f, 5.0f, 20.0f, 1.0f);    // prog.setUniform("Spot.position", lightPos);     prog.setUniform("Spot.position", view * lightPos);     prog.setUniform("Spot.direction", normalMatrix * vec3(-10.0,0.0,-40.0) );     //prog.setUniform("Spot.direction", vec3(10.9f,10.9f,10.9f)  );     prog.setUniform("Spot.intensity", vec3(0.9f,0.9f,0.9f) );     prog.setUniform("Spot.exponent", 30.0f );     prog.setUniform("Spot.cutoff", 15.0f );      prog.setUniform("ModelViewMatrix", mv);     prog.setUniform("NormalMatrix",normalMatrix);     prog.setUniform("MVP", projection * mv);  } 

       在這里給Spot.position賦值的時(shí)候不是 prog.setUniform("Spot.position", lightPos); 而是prog.setUniform("Spot.position", view * lightPos),因?yàn)樵趕hader中的計(jì)算都是在視口坐標(biāo)下進(jìn)行的,這樣做是為了統(tǒng)一坐標(biāo)。Spot.direction的賦值也是一樣。也可以把坐標(biāo)轉(zhuǎn)換這一步放到shader中去做。

最終效果如下:

OpenGL進(jìn)階(十三) - GLSL光照(Lighting)


逐像素著色 per pixel shading

        相對(duì)與前面將主要計(jì)算工作放在頂點(diǎn)shader中的 per vertex shading ,per pixel shading 指的是將計(jì)算放到片段shader中,這樣可以帶來更加真實(shí)可感的渲染效果。

basic2.vert

#version 400 layout (location = 0) in vec3 VertexPosition;   layout (location = 1) in vec3 VertexNormal;    out vec4 Position; out vec3 Normal;   uniform mat4 ModelViewMatrix; uniform mat3 NormalMatrix; uniform mat4 ProjectionMatrix; uniform mat4 MVP;   void getEyeSpace(out vec3 norm, out vec4 position) { 	norm =  normalize(NormalMatrix * VertexNormal); 	position = ModelViewMatrix * vec4(VertexPosition, 1.0); }   void main() { 	getEyeSpace(Normal, Position); 	gl_Position = MVP * vec4( VertexPosition, 1.0); }

basic2.frag 

#version 400  in vec4 Position; in vec3 Normal;  struct SpotLightInfo{ 	vec4 position; 	vec3 direction; 	vec3 intensity; 	float exponent; 	float cutoff; };  struct MaterialInfo{ 	vec3 Ka; 	vec3 Kd; 	vec3 Ks; 	float Shininess; };  uniform SpotLightInfo Spot; uniform	MaterialInfo Material;  vec3 adsSpotLight(vec4 position, vec3 norm) { 	vec3 s = normalize(vec3(Spot.position - position)); 	float angle = acos(dot(-s, normalize(Spot.direction))); 	float cutoff = radians(clamp(Spot.cutoff, 0.0, 90.0)); 	vec3 ambient = Spot.intensity * Material.Ka; 	 	if(angle < cutoff){ 		float spotFactor = pow(dot(-s, normalize(Spot.direction)), Spot.exponent); 		vec3 v = normalize(vec3(-position)); 		vec3 h = normalize(v + s); 		return ambient + spotFactor * Spot.intensity * (Material.Kd * max(dot(s, norm),0.0) 		      + Material.Ks * pow(max(dot(h,norm), 0.0),Material.Shininess));  	} 	else 	{ 		return ambient;  	} 	        }  void main(void) { 	gl_FragColor = vec4(adsSpotLight(Position, Normal), 1.0); 	//gl_FragColor = vec4(1.0,1.0,0.5, 1.0); }

看一下渲染效果:

OpenGL進(jìn)階(十三) - GLSL光照(Lighting)

最終的效果還是有一些差別,特別是光線交界的地方。


代碼下載

OpenGLPro13


參考

OpenGL 4.0 Shading Language Cookbook





向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI