Shader 編程(二)使用光照模型來計算某個點的光照效果

什么是光照模型
光照模型就是一個公式,使用這個公式來計算在某個點的光照效果

標準光照模型
在標準光照模型里面,我們把進入攝像機的光分為下面四個部分
自發光
高光反射
Blinn光照模型
Specular=直射光 * pow( max(cosθ,0),10) θ:是反射光方向和視野方向的夾角
Blinn-Phong光照模型
Specular=直射光 * pow( max(cosθ,0),10) θ:是法線和x的夾角 x 是平行光和視野方向的平分線
漫反射 Diffuse = 直射光顏色 * max(0,cos夾角(光和法線的夾角) ) cosθ = 光方向· 法線方向
環境光

Tags{ "LightMode"="ForwardBase" }
只有定義了正確的LightMode才能得到一些Unity的內置光照變量
include "Lighting.cginc"
包含unity的內置的文件,才可以使用unity內置的一些變量

normalize() 用來把一個向量,單位化(原來方向保持不變,長度變為1)
max() 用來取得函數中最大的一個
dot 用來取得兩個向量的點積
_WorldSpaceLightPos0 取得平行光的位置
_LightColor0取得平行光的顏色
UNITY_MATRIX_MVP 這個矩陣用來把一個坐標從模型空間轉換到剪裁空間
_World2Object 這個矩陣用來把一個方向從世界空間轉換到模型空間
UNITY_LIGHTMODEL_AMBIENT用來獲取環境光

(1)蘭伯特光照模型計算公式(漫反射計算方式)

(漫反射)Diffuse = 直射光顏色 * max(0,cos夾角(光和法線的夾角) ) cosθ = 光方向· 法線方向
下面我們使用逐頂點光照和逐像素光照來實現蘭伯特光照模型計算公式。

逐頂點光照

兩種方式可以實現漫反射 第一種是逐頂點實現漫反射,第二種方式是逐像素實現漫反射。
逐頂點就是將光照的計算方式放在頂點函數里面,因為頂點個數是有限的,而像素個數是無限的,逐頂點計算是頂點與頂點之間的計算時是使用了插值的計算方式計算,這樣比較平和,但是頂點是直接計算出來的所以頂點上不會太平和。下面我們來實現第一種逐頂點。

Shader"Diffuse"{
    properties{
        _Diffuse("Color",Color) = (1,1,1,1)
    }
        SubShader{

        Pass {

        //定義正確的LightMode得到內置的光照變量
        Tags{"LightMode" = "ForwardBase"}

        CGPROGRAM
        //包含unity的內置的文件,才可以使用unity內置的一些變量  相當于引入內置的光照模型
        #include "Lighting.cginc"http://取得第一個直射光的顏色  _LightColor0第一個值是光的位置_WorldSpaceLightPos0

        #pragma vertex vert
        #pragma fragment frag

                   fixed4 _Diffuse;

            struct a2v {
               float4 vertex:POSITION;
               float3 normal:NORMAL; 
              };
        struct v2f {
        
            float4 position:SV_POSITION;
            fixed3 color : COLOR;
        }; 
        v2f vert(a2v v) {
                v2f f;
                //UNITY_MATRIX_MVP 這個矩陣用來把一個坐標從模型空間轉換到剪裁空間
                f.position = UnityObjectToClipPos(v.vertex);
                //環境光
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb;

                //_World2Object 這個矩陣用來把一個方向從世界空間轉換到模型空間
                fixed3 normalDir = normalize(mul(v.normal, (float3x3)unity_WorldToObject));//相當于把模型空間轉換到世界空間
                //normalize() 用來把一個向量,單位化(原來方向保持不變,長度變為1)
                //_WorldSpaceLightPos0 取得平行光的位置
                fixed3 lightDir = normalize( _WorldSpaceLightPos0.xyz);//對于每一個頂點來說 光的位置就是光的方向  因為光是平行光
                //_LightColor0取得平行光的顏色
                //max() 用來取得函數中最大的一個
                //dot 用來取得兩個向量的點積
                fixed3 diffuse = _LightColor0.rgb*max(0, dot(normalDir, lightDir))*_Diffuse.rgb;//取得漫反射的顏色
                    f.color = diffuse+ ambient;
                return f;
            }

        fixed4 frag(v2f f) :SV_Target{
            return fixed4(f.color,1);

        }
            ENDCG
       }
    }
        Fallback"VertexLit"
}

因為在程序里面設置了環境光所以在暗部會受到環境光的影響


效果展示.png

這里設置受SkyBox環境光的影響,也可以自己將其他物體或者顏色設置成環境光顏色


Paste_Image.png

逐像素光照

逐頂點光照沒有逐像素光照渲染的平和 ,但是逐像素光照渲染更加耗費性能
Shader"Fragment"{
    properties{
        _Diffuse("Color",Color) = (1,1,1,1)
    }
        SubShader{
        Pass {
        //定義正確的LightMode得到內置的光照變量
        Tags{"LightMode" = "ForwardBase"}
        CGPROGRAM
        //包含unity的內置的文件,才可以使用unity內置的一些變量  相當于引入內置的光照模型
        #include "Lighting.cginc"http://取得第一個直射光的顏色  _LightColor0第一個值是光的位置_WorldSpaceLightPos0
        #pragma vertex vert
        #pragma fragment frag

                   fixed4 _Diffuse;

            struct a2v {
               float4 vertex:POSITION;
               float3 normal:NORMAL; 
              };
        struct v2f {
        
            float4 position:SV_POSITION;
            fixed3 worldNormalDir : COLOR0;

        }; 
        v2f vert(a2v v) {
                v2f f;
                //UNITY_MATRIX_MVP 這個矩陣用來把一個坐標從模型空間轉換到剪裁空間
                f.position = mul(UNITY_MATRIX_MVP, v.vertex);   
                f.worldNormalDir = mul(v.normal, (float3x3)unity_WorldToObject);
                return f;
            }

        fixed4 frag(v2f f) :SV_Target{
            //環境光
            fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb;

        //_World2Object 這個矩陣用來把一個方向從世界空間轉換到模型空間
        fixed3 normalDir = normalize(f.worldNormalDir);//相當于把模型空間轉換到世界空間

        //normalize() 用來把一個向量,單位化(原來方向保持不變,長度變為1)
        //_WorldSpaceLightPos0 取得平行光的位置
        fixed3 lightDir = normalize(_WorldSpaceLightPos0.xyz);//對于每一個頂點來說 光的位置就是光的方向  因為光是平行光

         //_LightColor0取得平行光的顏色
        //max() 用來取得函數中最大的一個
        //dot 用來取得兩個向量的點積
        fixed3 diffuse = _LightColor0.rgb*max(0, dot(normalDir, lightDir))*_Diffuse.rgb;//取得漫反射的顏色
        fixed3 tempColor  = diffuse + ambient;
            return fixed4(tempColor,1);
        }
            ENDCG
       }
    }
        Fallback"Diffuse"
}
逐頂點逐像素對比.png

(2)半蘭伯特光照模型計算公式(漫反射計算方式)

Diffuse = 直射光顏色 *( cosθ *0.5 +0.5 )
因為蘭伯特光照模型在背光面會完全看不清,所以半蘭伯特光照模型應時而生。


蘭伯特光照模型.png
Shader"Lambert"{
    properties{
        _Diffuse("Color",Color) = (1,1,1,1)
    }
        SubShader{

        Pass {

        //定義正確的LightMode得到內置的光照變量
        Tags{"LightMode" = "ForwardBase"}

        CGPROGRAM
        //包含unity的內置的文件,才可以使用unity內置的一些變量  相當于引入內置的光照模型
        #include "Lighting.cginc"http://取得第一個直射光的顏色  _LightColor0第一個值是光的位置_WorldSpaceLightPos0

        #pragma vertex vert
        #pragma fragment frag

                   fixed4 _Diffuse;

            struct a2v {
               float4 vertex:POSITION;
               float3 normal:NORMAL; 
              };
        struct v2f {
        
            float4 position:SV_POSITION;
            fixed3 worldNormalDir : COLOR0;

        }; 
        v2f vert(a2v v) {
                v2f f;
                //UNITY_MATRIX_MVP 這個矩陣用來把一個坐標從模型空間轉換到剪裁空間
                f.position = mul(UNITY_MATRIX_MVP, v.vertex);
                
                f.worldNormalDir = mul(v.normal, (float3x3)unity_WorldToObject);

                return f;
            }

        fixed4 frag(v2f f) :SV_Target{
            //環境光
            fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb;

        //_World2Object 這個矩陣用來把一個方向從世界空間轉換到模型空間
        fixed3 normalDir = normalize(f.worldNormalDir);//相當于把模型空間轉換到世界空間


        //normalize() 用來把一個向量,單位化(原來方向保持不變,長度變為1)
        //_WorldSpaceLightPos0 取得平行光的位置
        fixed3 lightDir = normalize(_WorldSpaceLightPos0.xyz);//對于每一個頂點來說 光的位置就是光的方向  因為光是平行光

        float halfLambert = dot(normalDir, lightDir)*0.5 + 0.5;

         //_LightColor0取得平行光的顏色
        //max() 用來取得函數中最大的一個
        //dot 用來取得兩個向量的點積
        fixed3 diffuse = _LightColor0.rgb*halfLambert*_Diffuse.rgb;//取得漫反射的顏色
        fixed3 tempColor  = diffuse + ambient;
            return fixed4(tempColor,1);

        }
            ENDCG
       }
    }
        Fallback"Diffuse"
}
Paste_Image.png

這樣對比一下背光面蘭伯特光照模型和半蘭伯特光照模型。可以看到很明顯的差別,半蘭伯特光照模型即使暗部就不會是全黑。

(3)高光反射計算方式

Blinn光照模型 計算公式
Specular=直射光 * pow( max(cosθ,0),10) θ:是反射光方向和視野方向的夾角
這里使用逐頂點光照模型寫出高光反射。

Shader"Specular"{
    properties{
        _Diffuse("Color",Color) = (1,1,1,1)
        _Specular("Specular Color",Color)=(1,1,1,1)//控制高光顏色
       _Gloss("Gloss",Range(8,200))=10//控制高光強度

    }
        SubShader{

        Pass {

        //定義正確的LightMode得到內置的光照變量
        Tags{"LightMode" = "ForwardBase"}

        CGPROGRAM
        //包含unity的內置的文件,才可以使用unity內置的一些變量  相當于引入內置的光照模型
        #include "Lighting.cginc"http://取得第一個直射光的顏色  _LightColor0第一個值是光的位置_WorldSpaceLightPos0

        #pragma vertex vert
        #pragma fragment frag

                   fixed4 _Diffuse;
                   half _Gloss;
                   fixed4 _Specular;

            struct a2v {
               float4 vertex:POSITION;
               float3 normal:NORMAL; 
              };
        struct v2f {
        
            float4 position:SV_POSITION;
            fixed3 color : COLOR;

        }; 
        v2f vert(a2v v) {
                v2f f;
                //UNITY_MATRIX_MVP 這個矩陣用來把一個坐標從模型空間轉換到剪裁空間
                f.position = mul(UNITY_MATRIX_MVP, v.vertex);
                
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb;//環境光

                //_World2Object 這個矩陣用來把一個方向從世界空間轉換到模型空間
                fixed3 normalDir = normalize(mul(v.normal, (float3x3)unity_WorldToObject));//相當于把模型空間轉換到世界空間
             
                fixed3 lightDir = normalize( _WorldSpaceLightPos0.xyz);//對于每一個頂點來說 光的位置就是光的方向  因為光是平行光
        
                fixed3 diffuse = _LightColor0.rgb*max(0, dot(normalDir, lightDir))*_Diffuse.rgb;//取得漫反射的顏色

                fixed3 reflectDir = normalize(reflect(-lightDir, normalDir));
                fixed3 vlewDir = normalize(_WorldSpaceCameraPos.xyz - mul(v.vertex, unity_WorldToObject).xyz);

                fixed3 specular = _LightColor0.rgb*_Specular.rgb*pow(max(dot( reflectDir, vlewDir), 0), _Gloss);//計算高光

                f.color = diffuse+ ambient+ specular;
                return f;
            }

        fixed4 frag(v2f f) :SV_Target{
            return fixed4(f.color,1);

        }
            ENDCG
       }
    }
        Fallback"Diffuse"
}
高光強度調節.gif

高光顏色調節.gif

下面我們用逐像素來實現高光反射

Shader"Specular Fragment"{
    properties{
        _Diffuse("Color",Color) = (1,1,1,1)
        _Specular("Specular Color",Color)=(1,1,1,1)//控制高光顏色
       _Gloss("Gloss",Range(8,200))=10//控制高光強度

    }
        SubShader{

        Pass {

        //定義正確的LightMode得到內置的光照變量
        Tags{"LightMode" = "ForwardBase"}

        CGPROGRAM
        //包含unity的內置的文件,才可以使用unity內置的一些變量  相當于引入內置的光照模型
        #include "Lighting.cginc"http://取得第一個直射光的顏色  _LightColor0第一個值是光的位置_WorldSpaceLightPos0

        #pragma vertex vert
        #pragma fragment frag

                   fixed4 _Diffuse;
                   half _Gloss;
                   fixed4 _Specular;

            struct a2v {
               float4 vertex:POSITION;
               float3 normal:NORMAL; 
              };
        struct v2f {
        
            float4 position:SV_POSITION;
            float3 WorldNomormal : TEXCOORD0;
            float3 WorldVertex:TEXCOORD1;
        }; 
        v2f vert(a2v v) {
                v2f f;
                //UNITY_MATRIX_MVP 這個矩陣用來把一個坐標從模型空間轉換到剪裁空間
                f.position = mul(UNITY_MATRIX_MVP, v.vertex);
    
                f.WorldNomormal = mul(v.normal, (float3x3)unity_WorldToObject);
                f.WorldVertex = mul(v.vertex,_World2Object).xyz;
                return f;
        };

        fixed4 frag(v2f f) :SV_Target{

        fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb;//環境光
         //_World2Object 這個矩陣用來把一個方向從世界空間轉換到模型空間
        fixed3 normalDir = normalize(f.WorldNomormal);//相當于把模型空間轉換到世界空間

        fixed3 lightDir = normalize(_WorldSpaceLightPos0.xyz);//對于每一個頂點來說 光的位置就是光的方向  因為光是平行光

        fixed3 diffuse = _LightColor0.rgb*max(0, dot(normalDir, lightDir))*_Diffuse.rgb;//取得漫反射的顏色

        fixed3 reflectDir = normalize(reflect(-lightDir, normalDir));
        fixed3 vlewDir = normalize(_WorldSpaceCameraPos.xyz - f.WorldVertex);

        fixed3 specular = _LightColor0.rgb*_Specular.rgb*pow(max(dot(reflectDir, vlewDir), 0), _Gloss);//計算高光

        fixed3 tempColor = diffuse + ambient + specular;
    

            return fixed4(tempColor,1);

        }
            ENDCG
       }
    }
        Fallback"Diffuse"
}

可以看到逐像素更加柔和平滑

對比圖.png

Blinn-Phong光照模型
下面對我們對Blinn光照模型又作了一種改進,其計算方式為:
Specular=直射光 * pow( max(cosθ,0),10) θ:是法線和x的夾角 x 是平行光和視野方向的平分線。

Shader"Learning/Specular Fragment BlinnPhone"{
   properties{
       _Diffuse("Color",Color) = (1,1,1,1)
       _Specular("Specular Color",Color)=(1,1,1,1)//控制高光顏色
      _Gloss("Gloss",Range(8,200))=10//控制高光強度

   }
       SubShader{

       Pass {

       //定義正確的LightMode得到內置的光照變量
       Tags{"LightMode" = "ForwardBase"}

       CGPROGRAM
       //包含unity的內置的文件,才可以使用unity內置的一些變量  相當于引入內置的光照模型
       #include "Lighting.cginc"http://取得第一個直射光的顏色  _LightColor0第一個值是光的位置_WorldSpaceLightPos0

       #pragma vertex vert
       #pragma fragment frag

                  fixed4 _Diffuse;
                  half _Gloss;
                  fixed4 _Specular;

           struct a2v {
              float4 vertex:POSITION;
              float3 normal:NORMAL; 
             };
       struct v2f {
       
           float4 position:SV_POSITION;
           float3 WorldNomormal : TEXCOORD0;
           float4 WorldVertex:TEXCOORD1;
       }; 
       v2f vert(a2v v) {
               v2f f;
               //UNITY_MATRIX_MVP 這個矩陣用來把一個坐標從模型空間轉換到剪裁空間
               f.position = mul(UNITY_MATRIX_MVP, v.vertex);
   
               /*f.WorldNomormal = mul(v.normal, (float3x3)unity_WorldToObject);*/

               //UnityObjectToWorldNormal(float3 norm)  把法線方向 模型空間 == 》世界空間
               f.WorldNomormal = UnityObjectToWorldNormal(v.normal);

               f.WorldVertex = mul(v.vertex,unity_WorldToObject);
               return f;
       };

       fixed4 frag(v2f f) :SV_Target{
       fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb;//環境光
        //_World2Object 這個矩陣用來把一個方向從世界空間轉換到模型空間
       fixed3 normalDir = normalize(f.WorldNomormal);//相當于把模型空間轉換到世界空間

       //fixed3 lightDir = normalize(_WorldSpaceLightPos0.xyz);//對于每一個頂點來說 光的位置就是光的方向  因為光是平行光

       //WorldSpaceLightDir(float4 v)  模型空間中的頂點坐標 == 》世界空間中從這個點到光源的方向
       fixed3 lightDir = normalize(WorldSpaceLightDir(f.WorldVertex).xyz);

       fixed3 diffuse = _LightColor0.rgb*max(0, dot(normalDir, lightDir))*_Diffuse.rgb;//取得漫反射的顏色
   
       //fixed3 vlewDir = normalize(_WorldSpaceCameraPos.xyz - f.WorldVertex);

       //UnityWorldSpaceViewDir(float4 v) 世界空間中的頂點坐標==》世界空間從這個點到攝像機的觀察方向
       fixed3 vlewDir = normalize(UnityWorldSpaceViewDir(f.WorldVertex));

       fixed3 halfDir = normalize(vlewDir + lightDir);
       fixed3 specular = _LightColor0.rgb*_Specular.rgb*pow(max(dot(normalDir, halfDir), 0), _Gloss);//計算高光
       fixed3 tempColor = diffuse + ambient + specular;
           return fixed4(tempColor,1);
       }
           ENDCG
      }
   }
       Fallback"Diffuse"
}

在這里我們可以看到Blinn-Phong光照模型的高光更加大更加亮,并且在背光面B模型因為一些計算所以背光面也會有一些高光,但是B—P模型就不會出現這種現象,一般我們更傾向于Blinn-Phong的計算方式.


Paste_Image.png
Paste_Image.png
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 一.標準光照模型OpenGL與Direct3D提供了幾乎相同的固定功能光照模型。什么是固定功能光照模型?在過去只有...
    karma085閱讀 3,661評論 0 2
  • 一、Surface Output (表面著色器的標準輸出結構)Surface Shader的標準輸出結構-第一要素...
    CarlDonitz閱讀 948評論 0 1
  • 版本記錄 前言 OpenGL 圖形庫項目中一直也沒用過,最近也想學著使用這個圖形庫,感覺還是很有意思,也就自然想著...
    刀客傳奇閱讀 6,144評論 0 2
  • 現實世界的光照是極其復雜的,而且會受到諸多因素的影響,這是以目前我們所擁有的處理能力無法模擬的。因此OpenGL的...
    IceMJ閱讀 2,002評論 1 6
  • 如果有一天我要去流浪不是因為我厭倦了家鄉不是難忍這里的冬天太長而是我終于得知了你的方向如果有一天我不再感傷不是因為...
    lulucia閱讀 160評論 0 0