Unity3D 編寫Shader實作光照模型 Phong Model

Avatar of misk.
Avatar of misk.

Unity3D 編寫Shader實作光照模型 Phong Model

Unity3D 練習編寫Shader實作 Phong Model

Ambient、Diffuse、Specular

Unity對圖形底層進行包裝,並定義一系列的Render Pipeline,使用者可使用Cg/HLSL語言在各渲染階段操作物體頂點、像素等,這次使用Cg語言實作光照模型Phong Model,包括Ambient、Diffuse和Specular


漫反射 (Diffuse)

實作漫反射須知道物體表面是否正向光源,因此可使用物體表面與光源的向量R,並與表面法向量N做內積,若光源在物件表面正上方,則R.N為1,若光源與表面平行或在背後,則 R.N <= 0(前提R、N均是Normalize),計算內積後即可知道光源與表面漫反射的相關性,最後加入光源強度和Diffuse程度,整合公式如下:


Diffuse = 光強度 * Diffuse程度 * (R.N)

大功告成!但仔細看會發現,沒照到光的部分通通是黑色的,因為R.N為0,這不符合現實光照,因此須對物件增加一層Ambient Light。


漫反射 + 環境光 (Ambient)

現實中,若沒照到光的部分仍然會有其他物件反射後的環境光,所以不可能是全黑的,但電腦很難計算所有光的反射,因此公式很簡單,只需要乘上光照強度和Ambient程度就好。


Ambient = 光強度 * Ambient程度


加上Ambient後,沒有光源的地方就不會是全黑了。最後,只剩下Specular了!


漫反射 + 環境光 + 高光 (Specular)

Phong Model中的Specular,先獲得光源經過表面反射的反射向量r,接著與相機視角向量V內積,目的是獲得反射光打到眼睛的相關程度,若反射出來的光不直視眼睛,反射量則遞減,而內積仍然在1 ~ -1之間,V與r均是Normalize。

之後可針對物體粗糙程度,設置粗糙變數n,若物體越粗糙,反射程度會指數遞減(高光會不明顯)。


Specular = 光強度 * Specular程度 * (V.r)^n


實作完成,截圖三張狀態觀察它們的差別。

 

程式碼 (Cg)

Shader "Unlit/Phong"

{

  Properties

  {

    _MainTex ("Texture", 2D) = "white" {}

    _Color("Color", Color) = (0.25, 0.5, 0.5, 1)

    _Diffuse("Diffuse", float) = 1

    _Gloss("Gloss", float) = 1

    _Specular("Specular", float) = 1

  }

  SubShader

  {

    Tags { "RenderType"="Opaque" }


    Pass

    {

      CGPROGRAM

      #pragma vertex vert

      #pragma fragment frag


      #include "UnityCG.cginc"


      struct appdata

      {

        float4 vertex : POSITION;

        float2 uv : TEXCOORD0;

        float3 normal : NORMAL;

      };


      struct v2f

      {

        float2 uv : TEXCOORD0;

        float4 vertex : SV_POSITION;

        float4 worldPos : TEXCOORD1;

        float3 normal : NORMAL;

      };


      sampler2D _MainTex;

      float4 _MainTex_ST;

      float4 _Color;

      float _Gloss;

      float _Diffuse;

      float _Specular;


      v2f vert (appdata v)

      {

        v2f o;


        // 將物件座標轉為螢幕座標

        o.vertex = UnityObjectToClipPos(v.vertex);


        // 取得texture的UV,之後要貼貼圖在像素上

        o.uv = TRANSFORM_TEX(v.uv, _MainTex);


        // 計算Diffuse需要頂點世界座標,所以拿世界座標轉換矩陣跟頂點座標相乘

        o.worldPos = mul(unity_ObjectToWorld, v.vertex);


        // 獲得頂點法向量

        o.normal = v.normal;


        return o;

      }


      fixed4 frag(v2f i) : SV_Target

      {

        /*  Ambient  */

        fixed4 ambient = UNITY_LIGHTMODEL_AMBIENT;




        /*  Diffuse  */

        // 獲得點的法向量 (normalize)

        fixed3 worldNormalDir = normalize(i.normal);


        // 獲得點對光源的向量 (normalize)

        fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));


        // 取光向量與表面法向量夾角,並算出強度

        fixed4 diffuse = saturate(dot(worldNormalDir, worldLightDir)) * _Diffuse;




        /*  Specular  */

        // 計算光對表面的反射向量

        // (worldLightDir是負的,因為是從光為起點到表面的向量)

        fixed3 reflectDir = normalize(reflect(-worldLightDir, worldNormalDir));


        // 算出視角與表面的向量

        fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos);


        // dot算出specular

        fixed4 specular = pow(saturate(dot(reflectDir, viewDir)), _Gloss) * _Specular;





        // 取得貼圖在該UV下的顏色

        fixed4 textureColor = tex2D(_MainTex, i.uv) * _Color;


        return textureColor * (diffuse);

        //return textureColor * (diffuse + ambient);

        //return textureColor * (diffuse + ambient + specular);

      }

      ENDCG

    }

  }

}

(圖片參考:https://blog.csdn.net/arag2009/article/details/78083065)