2012年5月26日 星期六

OpenGL: Starting with the Shading Language-3

since: 2012/05/26
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.
         // 紅線代表正規化的光源方向之向量.
         // 藍線代表正規化的攝影機方向之向量.
         //
         // 如果把紅, 藍二個向量相加, 結果就是穿越灰色線框指向右上方的綠線.
         // 如果將這條綠線正規化, 結果就是半途(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);
}

沒有留言:

張貼留言

注意:只有此網誌的成員可以留言。