1. 程式人生 > >OpenGL進階(十三)

OpenGL進階(十三)

提要

       在上一篇文章中,我們介紹了簡單的Shading,同時提出了一個光照模型,模擬了一個點光源,但是,關於光的故事還沒有結束... 

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

方向光源

      方向光源就兩個引數,方向和強度。

      用的Phong光照模型 Phong Reflection = ambient + diffuse + specular 光照模型。先看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函式中,首先通過nomal矩陣將頂點法向量變換到視口座標下,(nomal矩陣其實就是model-view矩陣的左上3x3的矩陣)然後通過model-view矩陣將頂點座標轉化為視口座標系(eye coordinates)下。

接下來的ads用來計算光照模型下頂點的顏色,分別計算三個分量,然後相加。

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);
}
這個就是將根據頂點shader傳來的顏色對片段進行賦值。在cgl.cpp的setUniform函式中對Uniform變數進行賦值。
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);

}

渲染的效果如下:

可以很明顯的感覺模型旋轉時到表面光照的變化。

halfway vector 效能優化

        在前面的光照模型中,用於計算specular分量的公式如下:


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


這個計算過程會非常耗時,我們可以用一個trick來改善一下。

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


下圖是 和其他向量的位置關係。


specular分量的計算就可以轉化成:


         相比於計算 r ,的計算相對比較簡單,而 h 和 n 之間的夾角與 和 r 之間的夾角大小几乎相同!那就意味著我們可以用 h.n 來代替 r.v

 從而可以帶利用 halfway vector 來獲得性能上的一些提升。雖然效果上會有那麼小小的不同。

這個優化就是傳說中的Blinn- Phong光照模型。

         後面的計算都用這個新的模型進行計算。

聚光燈 Spotlight

        這裡的採用一個最簡單的聚光燈模型:


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

       實現起來也比較簡單,在投射角內的物體,渲染方式和點光源的計算一樣,投射角之外的頂點,著色的時候只有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);
}

幾個GLSL中的內建函式在這裡說明一下。

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

獲取三個數中第二大的數。

genType radians(genType degrees);

將角度轉換成弧度。

adsSpotLight是主要的函式,先計算頂點和光源方向之間的夾角,判斷頂點是否在照射的區域,然後分別求得最終的顏色。

片段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賦值的時候不是 prog.setUniform("Spot.position", lightPos); 而是prog.setUniform("Spot.position", view * lightPos),因為在shader中的計算都是在視口座標下進行的,這樣做是為了統一座標。Spot.direction的賦值也是一樣。也可以把座標轉換這一步放到shader中去做。

最終效果如下:


逐畫素著色 per pixel shading

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

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 4.0 Shading Language Cookbook