update: 2012/05/26
reference:
1. Tutorial - Getting Started with the OpenGL Shading Language (GLSL)
2. OpenGL: Starting with the Shading Language-0
3. OpenGL: Starting with the Shading Language-1
4. OpenGL: Starting with the Shading Language-2
Fragment Shader
A. 說明
1. 光源種類:
a. Ambient Light(環境光):
(1). 光線來自四處, 非特定位置所發出的光線.
(2). 特性: 被照射的物體, 在每個方向, 角度上受到等量的光線.
b. Diffuse Light(漫射/散射光):
(1). 有方向性, 均勻照射到表面.
(2). 例如: 午後太陽光從窗戶射入.
c. Specular Light(反射/鏡射光):
(1). 有方向性, 使物體反射到某個角度.
(2). 與入射角度有關係, 如: 聚光燈.
B. 開啓 shader.fp 檔案:
在此處, 這個 fragment shader 的工作是: 當啟用 program object 時, 對每個要被
畫出來的 fragment (或 pixel) 計算其散射光(diffuse)與鏡射光(specular)的值.
// 常數: 定義光源的數量
const int NUM_LIGHTS = 3;
// 定義環境光源: AMBIENT
//
// 代表該像素點在沒有受到任何光源(在此有三個光源)散射(diffuse)
// 或鏡射(specular)的作用下, 所呈現出來的顏色.
const vec3 AMBIENT = vec3(0.1, 0.1, 0.1);
// 用來計算隨著像素點離光源距離越遠所造成的光線減弱
const float MAX_DIST = 2.5;
const float MAX_DIST_SQUARED = MAX_DIST * MAX_DIST;
// lightColor 包含了從應用程式傳送來的光源顏色(在此有三個光源)
//
// vec3 的 x, y ,z 分量, 分別代表顏色的紅, 綠, 藍之分量.
uniform vec3 lightColor[NUM_LIGHTS];
// varyings 變數的值, 會先在 vertex shader 設定好, 然後被傳送到 fragment shader
// 裡(此處), 在這個過程中, 每個從 vertex shader 傳送到 fragment shader 的資料,
// 在繪製表面的過程中都會被進行內插處理(interpolated).
//
// 目前 fragment 的法線
varying vec3 fragmentNormal;
// 從目前的 fragment 指向攝影機的向量
varying vec3 cameraVector;
// 從目前的 fragment 指向每個光源(在此有三個)位置的向量之陣列
varying vec3 lightVector[NUM_LIGHTS];
// fragment shader 的 main 函式, 它會對每個 fragment 都進行處理.
void
main()
{
// initialize diffuse/specular lighting
//
// 對目前的 fragment 受到光源的總散射(diffuse)與總鏡射(specular)進行初始化,
// 個別光源所產生的散射(diffuse)與鏡射(specular)之值會分別被加入(到向量裡).
vec3 diffuse = vec3(0.0, 0.0, 0.0);
vec3 specular = vec3(0.0, 0.0, 0.0);
// normalize the fragment normal and camera direction
//
// 將從 vertex shader 傳送來的法線, 使用內建的函式: normalize 進行正規化,
// 因為經過內插處理後的 fragmentNormal, 已不再是單位向量了.
vec3 normal = normalize(fragmentNormal);
// 對從 vertex shader 傳送來的 cameraVector, 進行正規化處理.
vec3 cameraDir = normalize(cameraVector);
// loop through each light
//
// 開始個別計算光源對目前 fragment 所產生的 Diffuse/Specular
for(int i = 0; i < NUM_LIGHTS; ++i) {
// calculate distance between 0.0 and 1.0
//
// MAX_DIST_SQUARED: 代表 fragment 可接收到光源影響的最遠距離之平方
//
// 內建函式 dot: 回傳向量內積的結果.
// 內建函式 min: 回傳較小的值.
//
// dist 的值將介於 0.0 ~ 1.0 之間
float dist = min(dot(lightVector[i], lightVector[i]), MAX_DIST_SQUARED) / MAX_DIST_SQUARED;
// 距離係數: distFactor
//
// 1. 如果目前的 fragment 距離光源很近, 距離係數會接近 1.0 (完全光照)
//
// 2. 如果目前的 fragment 與光源的距離是大於或等於 MAX_DIST_SQUARED,
// 距離係數會等於 0.0 (完全不受光源影響)
float distFactor = 1.0 - dist;
// diffuse
//
// 計算光源的散射效果
//
// 從目前的 fragment 指向光源的向量被正規化, 並儲存在 lightDir 裡
vec3 lightDir = normalize(lightVector[i]);
// fragment 的法線與 lightDir 的內積值儲存在 diffuseDot 裡
//
// 如果二個向量指向同一個方向, 內積的結果為 1.0, 會受到完全的散射光影響;
//
// 隨著向量夾角的增加, 內積的值逐漸變少, 當二個向量指向完全相反的方向時,
// 內積為負值(此時, 目前的 fragment 沒有被光源照射到).
float diffuseDot = dot(normal, lightDir);
// 接著, 將內積的結果加總到 diffuse 向量上.
//
// 內建函式 clamp: 當內積的結果為負時, diffuse 向量的大小也不會減少.
diffuse += lightColor[i] * clamp(diffuseDot, 0.0, 1.0) * distFactor;
// specular
//
// 計算光源的鏡射光效果, 鏡射光會在物體表面產生高光點效果.
//
// 向量 halfAngle, 是 "從目前的 fragment 到攝影機方向" 和 "從目前的 fragment
// 到光源位置方向" 的半途(halfway)向量之正規化向量.
//
// 鏡射光效果與散射光效果類似, 只不過在此處, 會將 fragment 法線比喻為
// halfAngle 向量, 以取代光源方向的向量, 如下圖所示:
// 紅球代表被單一光源照射的物體.
// 紅, 綠, 藍三線相交的點代表目前的單一 fragment.
// 紅線代表正規化的光源方向之向量. // 紅, 綠, 藍三線相交的點代表目前的單一 fragment.
// 藍線代表正規化的攝影機方向之向量.
//
// 如果把紅, 藍二個向量相加, 結果就是穿越灰色線框指向右上方的綠線.
// 如果將這條綠線正規化, 結果就是半途(halfway)向量了.
//
// 上圖中可以看出, 該 fragment 的半途(halfway)向量, 與該 fragment 的
// 法線方向是相同的. 因此該 fragment 為全高光區(full specular lighting);
// 這是有道理的, 因為光源方向與攝影機方向互為 90 度夾角, 而 fragment 的
// 法線方向在 45 度角的位置.
vec3 halfAngle = normalize(cameraDir + lightDir);
// 給鏡射光各顏色分量, 分別增加 0.5 讓其看起來更亮,
// 並利用 min 函式, 讓加法後的各顏色分量不會超過 1.0.
vec3 specularColor = min(lightColor[i] + 0.5, 1.0);
// 計算鏡射光的內積
float specularDot = dot(normal, halfAngle);
// 接著, 將內積的結果加總到 specular 向量上.
// 在此對 specularDot 作指數運算(16次方), 指數越大, 高光區域越集中.
specular += specularColor * pow(clamp(specularDot, 0.0, 1.0), 16.0) * distFactor;
}
// sample 是一個 vec4 類型的變數, 代表 fragment 原來的自然顏色, 在此為白色.
vec4 sample = vec4(1.0, 1.0, 1.0, 1.0);
// 輸出 fragment 的顏色
//
// 將 sample, diffuse, AMBIENT 和 specular 組合在一起, 產生了該 fragment
// 最後的顏色, 將此值設定給內建的變數: gl_FragColor, fragment shader 就完成了.
gl_FragColor = vec4(clamp(sample.rgb * (diffuse + AMBIENT) + specular, 0.0, 1.0), sample.a);
}
沒有留言:
張貼留言
注意:只有此網誌的成員可以留言。