UnityShader之漫反射

前言

近些年,電子競技行業發展迅速,游戲的品質也是逐年提升。從用戶體驗的角度來講,畫質渲染及畫質特效方面非常重要。任何渲染特效都主要是光影的變幻,從而達到美輪美奐的效果。漫反射是最簡單的光照效果,所有的光照特效都是以漫反射為基礎的。本篇就來詳細介紹,漫反射著色器如何實現。

  • 首先我們來看一下什么漫反射
    • 漫反射,是投射在粗糙表面上的光向各個方向反射的現象。當一束平行的入射光線射到粗糙的表面時,表面會把光線向著四面八方反射,所以入射線雖然互相平行,由于各點的法線方向不一致,造成反射光線向不同的方向無規則地反射,這種反射稱之為“漫反射”或“漫射”。這種反射的光稱為漫射光。很多物體,如植物、墻壁、衣服等,其表面粗看起來似乎是平滑,但用放大鏡仔細觀察,就會看到其表面是凹凸不平的,所以本來是平行的太陽光被這些表面反射后,彌漫地射向不同方向。


      漫反射
    • 以簡單人物為例,在Unity中的漫反射是這樣的


      人物漫反射
  • 關于漫反射的光照模型,常見的是蘭伯特(Lambert)半蘭伯特(Half-Lambert)
    • Lambert的光照公式是(C-light · M-diffuse)max(0,n·I)

    • 光照顏色 * 漫反射顏色 * max(0,法向量*光照方向)

    • 下面以球形2D圖像解析一下原理

      • 第一步,我們確認公式中的兩個方向向量,注意光照方向是指向光的方向,即入射光向量的逆向量,紅色光照方向,藍色頂點法向量。


        光照方向及法向量方向
      • 第二步,計算不同頂點兩向量的夾角,我們可以得到面向光的部分角度在0-90度之間,背向光的角度在90-180度之間。


        計算不同頂點兩向量的夾角
      • 第三步,Lambert漫反射需要的面向光的部分渲染,背向光的地方不渲染,且渲染部分需要做曲線性的遞減。公式中兩個向量做點乘計算,即n · I,點乘公式是|n|*|I|*Conθ,其中θ是兩個向量的夾角,下面先看一下余弦圖。
        余弦函數曲線
      • 第四步,我們的夾角范圍是0-180度,因此我們裁掉后半部分


        0°-180°余弦圖
      • 第五步,我們需要0度時顏色最顯眼,到90度逐漸遞減,余弦前半段得到的結果與我們的預期相符,但90度到180度區間內,漫反射不做任何渲染,即純黑色代表陰暗面效果,因此得出下圖。


        90°--180°渲染比例為0
      • 最終我們在兩向量夾角為0°到90°范圍內按比例渲染,就形成了最終的漫反射效果。


        最終比例
    • 使用Lambert進行漫反射渲染,在Shader中有兩種寫法,一種是逐頂點著色,另一種是逐像素著色。從代碼中我們可以看出,逐頂點是在頂點著色器中進行漫反射計算,而逐像素則在片元著色器中進行漫反射計算,下面來看逐頂點代碼。

      //蘭伯特Lambert漫反射(逐頂點)
      Shader "AlbertShader/VertexDiffuse"
      {
          Properties
          {
              //漫反射顏色
              _DiffuseColor("Color",Color)=(1,1,1,1)
          }
          SubShader
          {
              Pass
              {
                  //正向渲染
                  Tags{ "LightMode"="ForwardBase" }
                  CGPROGRAM//------------------CG語言開始-------------------
                  //聲明頂點函數
                  #pragma vertex vert
                  //聲明片段函數
                  #pragma fragment frag
                  //引入光照函數庫
                  #include "Lighting.cginc"
                  //定義外部屬性-漫反射顏色
                  float4 _DiffuseColor;
                  //頂點輸入結構體
                  struct appdata
                  {
                      //頂點坐標
                      float4 vertex : POSITION;
                      //頂點法線
                      float3 normal : NORMAL;
                  };
                  //頂點輸出結構體
                  struct v2f
                  {
                      //像素坐標
                      float4 vertex : SV_POSITION;
                      //臨時變量:顏色
                      fixed3 color : COLOR;
                  };
                  //頂點函數實現
                  v2f vert (appdata v)
                  {
                      //定義頂點輸出結構體對象
                      v2f o;
                      //頂點坐標轉換到屏幕像素坐標
                      o.vertex = UnityObjectToClipPos(v.vertex);
                      //獲取環境光
                      float3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
                      //將頂點法線轉換到世界空間下,并做歸一化處理
                      float3 worldNormal = normalize(mul(v.normal,(float3x3)unity_WorldToObject));
                      //將光照方向轉換到世界空間下,并做歸一化處理
                      float3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
                      //帶入公式運算
                      fixed3 diffuse = _LightColor0.rgb * _DiffuseColor.rgb * saturate(dot(worldNormal,worldLight));
                      //結合漫反射和環境光
                      o.color = ambient + diffuse;
                      //返回結果
                      return o;
                  }
                  
      
                  fixed4 frag (v2f i) : SV_Target
                  {
                      //將結果返回
                      return fixed4(i.color,1.0);
                  }
                  ENDCG//------------------CG語言結束-------------------
              }
          }
      }
      
    • 下面來看逐像素代碼

      //蘭伯特Lambert漫反射(逐像素)
      Shader "AlbertShader/PixelDiffuse"
      {
          Properties
          {
              //漫反射顏色
              _DiffuseColor("Color",Color)=(1,1,1,1)
          }
          SubShader
          {
              Pass
              {
                  //正向渲染
                  Tags{ "LightMode"="ForwardBase" }
                  CGPROGRAM//------------------CG語言開始-------------------
                  #pragma vertex vert
                  #pragma fragment frag
                  
                  #include "Lighting.cginc"
      
                  float4 _DiffuseColor;
                  struct appdata
                  {
                      float4 vertex : POSITION;
                      //頂點法線
                      float3 normal : NORMAL;
                  };
      
                  struct v2f
                  {
                      float4 vertex : SV_POSITION;
                      //世界空間下的頂點法線
                      fixed3 worldNormal : TEXCOORD0;
                  };
      
                  v2f vert (appdata v)
                  {
                      v2f o;
                      o.vertex = UnityObjectToClipPos(v.vertex);
                      //通過矩陣運算,得到世界空間下的頂點法線
                      o.worldNormal = mul(v.normal,(float3x3)unity_WorldToObject);
                      return o;
                  }
                  
                  fixed4 frag (v2f i) : SV_Target
                  {
                      float3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
                      fixed3 worldNormal = normalize(i.worldNormal);
                      fixed3  worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
                      //使用蘭伯特光照模型公式
                      fixed3 diffuse = _LightColor0.rgb * _DiffuseColor.rgb *
                          saturate(dot(worldNormal,worldLightDir));
                      fixed3 color = diffuse + ambient;
                      return fixed4(color,1.0);
                  }
                  ENDCG//------------------CG語言結束-------------------
              }
          }
      }
      
  • Lambert漫反射效果不錯,但背光的一面全是黑色,若鏡頭在后面照射,則渲染出來的就是一個黑色平面,視覺效果很差,因此,有時我們也常常使用半蘭伯特(Half-Lambert)的光照模型。

  • Half-Lambert的公式與Lambert相似,只是人為改動了公式中的渲染比例,半蘭伯特沒有太多的物理證明,可以理解為測試出來的一種實現方式。公式即(C-light · M-diffuse)(α(n·I)+β),通常情況下αβ的值為0.5。具體代碼如下:

    //半蘭伯特HalfLambert漫反射(逐像素)
    Shader "Hidden/HalfPixelDiffuse"
    {
        Properties
        {
            _DiffuseColor("Color",Color)=(1,1,1,1)
        }
        SubShader
        {
            Pass
            {
                Tags{ "LightMode"="ForwardBase" }
                CGPROGRAM
                #pragma vertex vert
                #pragma fragment frag
                
                #include "Lighting.cginc"
    
                float4 _DiffuseColor;
                struct appdata
                {
                    float4 vertex : POSITION;
                    float3 normal : NORMAL;
                };
    
                struct v2f
                {
                    float4 vertex : SV_POSITION;
                    //臨時變量:世界法線
                    fixed3 worldNormal : TEXCOORD0;
                };
    
                v2f vert (appdata v)
                {
                    v2f o;
                    o.vertex = UnityObjectToClipPos(v.vertex);
                    //世界法線
                    o.worldNormal = mul(v.normal,(float3x3)unity_WorldToObject);
                    return o;
                }
                
                fixed4 frag (v2f i) : SV_Target
                {
                    //環境光
                    float3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
                    //世界法線
                    fixed3 worldNormal = normalize(i.worldNormal);
                    //世界入射光
                    fixed3  worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
                    //半蘭伯特漫反射公式
                    fixed3 diffuse = _LightColor0.rgb * _DiffuseColor.rgb *
                        (0.5 * (dot(worldNormal,worldLightDir)) + 0.5);
                    fixed3 color = diffuse + ambient;
                    return fixed4(color,1.0);
                }
                ENDCG
            }
        }
    }
    
  • Half-Lambert顯的更透亮一點,背光面也不是全黑的,三種Shader的渲染效果對比如下:

    逐頂點Lambert、逐像素Lambert、逐像素Half-Lambert

結束語

漫反射是光影效果的基礎,通過對漫反射底層實現原理,以及對漫反射公式的圖像剖析,大家有沒有更清晰的認識呢?Shader就是如此奇妙,這就是所謂的數學之美、編程之美。想成為邏輯開發和圖形學開發的雙料大師嗎?一起加油吧!??????

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,106評論 6 542
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,441評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,211評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,736評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,475評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,834評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,829評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,009評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,559評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,306評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,516評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,038評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,728評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,132評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,443評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,249評論 3 399
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,484評論 2 379

推薦閱讀更多精彩內容