1. 程式人生 > >標準光照模型之漫反射學習

標準光照模型之漫反射學習

標準光照模型之漫反射

逐片元漫反射光照實現

/*
    簡單的逐片元漫反射光照
*/
Shader "Volume 01/Diffuse/Simple Diffuse Per Frag" {
    Properties {
        // 材質本身的漫反射顏色 
        _Diffuse("Diffuse Color",Color) = (1, 1, 1, 1)
    }
    SubShader {

        // 設定渲染型別為不透明物體,渲染佇列也按不透明物體來
        Tags { "RenderType" = "Opaque" "Queue" = "Geometry" }

        Pass {
            // 設定渲染路徑,此處使用前向渲染路徑,用於讓Unity底層渲染引擎填充內建光照變數
            Tags{ "LightMode" = "ForwardBase" }

            CGPROGRAM    

            // 定義頂點/片元著色器
            #pragma vertex vert
            #pragma fragment frag

            // 要使用_LightColor0(場景平行光光源顏色)變數,
            // 需要匯入Lighting包
            #include "Lighting.cginc"

            // 材質漫反射顏色
            fixed4 _Diffuse;

            // 輸入結構體,Application To Vertex
            struct a2v{
                float4 vertex : POSITION;
                float3 normal : NORMAL;
            };
            // 頂點著色器的輸出結構體,Vertex To Fragment
            struct v2f{
                float4 pos : SV_POSITION;
                float3 worldNormal : TEXCOORD0;
                float3 worldPos : TEXCOORD1;
            };

            v2f vert(a2v v){
                v2f o;

                // 變換頂點到裁剪空間
                o.pos = UnityObjectToClipPos(v.vertex);

                // 獲得世界座標下的法線
                o.worldNormal = UnityObjectToWorldNormal(v.normal);

                // 獲得世界座標下的頂點座標
                o.worldPos = UnityObjectToWorldDir(v.vertex).xyz;

                return o;
            }

            fixed4 frag(v2f i) : SV_TARGET{
                // 歸一化法線
                fixed3 worldNormal = normalize(i.worldNormal);
                // 獲得歸一化光源方向(通過設定渲染路徑獲得)
                fixed3 worldDir = normalize(UnityWorldSpaceLightDir(i.worldPos));

                // 環境光
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb;

                // 計算漫反射光照
                fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0,dot(worldNormal,worldDir));

                return fixed4(ambient+diffuse,1);
            }

            ENDCG
        }
    }
    FallBack "Diffuse"
    
}

疑難解答

1. 為什麼漫反射光照結果計算時,要使用歸一化的法線以及單位光源方向?

首先明確一點,光照的輻照度與cosΘ成正相關,cosΘ表示單位法線與光源方向的夾角,詳情如下圖。

輻照度通過計算垂直於光的方向的單位面積上單位時間穿過的能量來計算,左圖是光源方向垂直於物體的情況,但大多數情況,物體不會與光源垂直,此時情況如右圖。

可以明顯觀察到右圖因為光線之間距離變大(由原來的d變為d/cosθ),光線變少,即輻照度變少。

輻照度與d/cosθ成反比,故與cosθ成正比。

當計算漫反射光照結果時,需要用到cosθ,故此時使用法線與光源方向點乘獲得cosθ,計算公式如下。

2. 為什麼Pass中要有Tags{ "LightMode" = "ForwardBase" }這一行?作用是什麼?

這一行的作用是設定該Pass的渲染路徑。

渲染路徑,其決定了光照是如何應用到Unity Shader中的,Unity內建光照的變數隨著設定的渲染路徑不同而不同。在這個簡單的diffuse shader中,其用於填充_LightColor0變數以及正確的光源方向的賦值。

如果不對渲染路徑進行設定,會出現錯誤結果,下圖總結了這個錯誤的結果。

3. 為什麼要使用max(0,reuslt)來約束漫反射光照結果?

  1. max(0,result)用於約束cosθ的結果為正數,需要注意的是,當光源處於物體(某一點)背面時,該點法線與光源方向點乘結果為負數,在最終結果diffuse+ambient中,因為diffuse是負數,會導致物體變暗。

  1. 第二點,在《Unity Shader 入門精要》中提到:

如果不對結果約束為正數,物體會被後面來的光照照亮。

實際測試中發現。。。。。貌似並沒有這種現象的產生,在這裡我推測一下,有可能是Unity版本不同導致的結果。根據樂樂大神在書中描述的現象,我不負責任的推測一下,可能是由於以前的Unity版本,在光照計算中遇到的負數全部都會自動變成其對應的正數。

下圖中,使用了abs函式將光照結果取正值,可以發現物體的確被背面來的光照亮了。

關於這個第二點,只是我的一個個人的小猜測。。。因為在目前查閱的資料了都描述了“會被後面來的光照亮”這一現象。。。。但是我沒有遇到,所以想可能是Unity版本的原因。如果大家知道是怎麼回事的話,跪求在評論區告訴樓主ORZ超級感激不盡。

參考資料

《Unity Shader入門精要》

【UnityShader從零開始】漫反射效果 http://gad.qq.com/article/detail/11725