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分量的公式如下:
其中 r 是反射光線的方向向量,v是往視口方向的向量,其中 r 的計算:
這個計算過程會非常耗時,我們可以用一個trick來改善一下。
定義一個 h (halfway vector)向量:
下圖是 h 和其他向量的位置關係。
specular分量的計算就可以轉化成:
相比於計算 r ,h 的計算相對比較簡單,而 h 和 n 之間的夾角與 v 和 r 之間的夾角大小几乎相同!那就意味著我們可以用 h.n 來代替 r.v
這個優化就是傳說中的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