1. 程式人生 > >Shader學習筆記(三):Shader中的光照

Shader學習筆記(三):Shader中的光照

這篇文章討論shader中用到的光照。

理解光照

Unity中的光照技術包括:

  • 實時光照(realtime lighting)
  • 烘焙後的光照貼圖(baked lightmaps)
  • 預計算的實時全域性光照(precomputed realtime global illumination)
實時光照

Unity最基礎的光照方式,能夠隨光線和物體移動實時變化。但是隻能處理直射光,無法處理反射光,所以只是一種區域性光照。

烘焙後的光照貼圖

Unity可以將靜態物體的光照資訊(包括直射光和反射光)預先烘焙到一張貼圖(lightmap)上,供執行時使用,從而避免動態計算。

lightmap的原理是:預先計算整個光照路徑(只除去物體表面到攝像機的一段)。

它的優點是省去了執行時計算的代價,缺點是執行時無法改變。

另外一種烘焙方式叫光照探針(light probe)。與lightmap的不同點在於:lightmap烘焙的是射到物體表面的光;而light probe烘焙的是空間中的傳播光,可用於靜態物體到動態物體的反射。

Unity將光照資料儲存在名為Lighting Data Asset的檔案中。它是Unity 5.3新增的檔案,替代原來的Snapshot。用於儲存GI資料,以及render、lightmap、light probe等的引用。

預計算的實時全域性光照

預計算靜態物體之間所有可能的反射並編碼,在執行時再生成間接光照。執行時可以隨光源位置、方向、顏色的改變而改變。Unity 5.0支援的新功能,需要設定開啟。

優點是既能隨光源動態改變,也能降低執行時消耗;缺點是隻能針對靜態物體。

各種光照技術比較

三種技術典型的應用場景包括:

  • 實時光照:移動的角色。
  • 烘焙後的光照貼圖:場景中靜態的物體。
  • 預計算的實時全域性光照:一天之內的陽光,隨時間變化而角度、強度各不相同。

在移動裝置上適合用baked lightmaps,在PC上適合用precomputed realtime GI。

光源型別

名字 英文名 解釋
點光源 point light 從一個點向四面八方發射,類似電燈泡
錐形光 spot light 從一個點以錐形發出,類似手電筒
有向光 directional light 以固定方向平行發出,類似太陽
區域光 area light 光線限制在一個矩形區域內,固定從一側以任意角度發出
環境光 ambient light 光在場景中無處不在,不從特定物體發出

理解陰影

unity中的陰影分為實時陰影和陰影貼圖。

  • 實時陰影:實時光產生的陰影。要使用這種陰影,需要在產生的物體上開啟投射(cast),在投影到的物體上開啟接收(receive)。
  • 陰影貼圖:以貼圖方式實現的陰影。這種方式可以降低執行時消耗,但是陰影的大小、形狀都是固定的。

shader中的光照

光照是如何影響我們的視覺的呢?事實上,我們之所以能看到物體,除去那些能自發光的物體外(如燈泡),都是因為光線射到物體表面再反射到我們眼睛中。一個物體之所以呈現不同顏色,那是因為它對光的不同顏色分量的反射率不一樣。例如,紅色的物體對於紅光幾乎全部反射,而對於綠光和藍光幾乎全部吸收。所以,物體最終呈現在我們眼中的顏色,取決於反射光和自發光的疊加。

不同的光照模型對於反射光的處理大不一樣。Unity中內建了兩種光照模型:Lambert光照是漫反射,光線射過去向四面八方反射,適合於普通較粗糙的材質;BlingPhong光照是鏡面反射,適合光滑如鏡的材質。這兩種光照就可以涵蓋絕大多數情況。除此之外,我們也可以根據需求自己定製光照模型。

Unity中可以使用Surface Shader或者Vertex/Fragment Shader來處理光照。對於前者,需要自定義光照處理函式;對於後者,則需要在vert函式中做varying變數的賦值,而在frag函式中處理光照。對於Surface Shader和光照函式在渲染管線中的位置,可以看圖(來自知乎):
這裡寫圖片描述

可以看到,surf函式和光照都位於Fragment Shader中,兩者是緊挨著的關係。

例項解析

以下均為Unity Manual中的例子。

Surface Shader中的光照
  • DiffuseTexture:與Lambert光照等效的實現。自定義光照型別SimpleLambert,對應的函式在surf之後執行。計算頂點顏色涉及到的引數:片元的法向量、光入射角度、光的rgba和衰減、片元的Albedo。

這裡寫圖片描述

   Shader "Example/Diffuse Texture" {
        Properties {
            _MainTex ("Texture", 2D) = "white" {}
        }
        SubShader {
        Tags { "RenderType" = "Opaque" }
        CGPROGRAM
          #pragma surface surf SimpleLambert

          half4 LightingSimpleLambert (SurfaceOutput s, half3 lightDir, half atten) {
              half NdotL = dot (s.Normal, lightDir);
              half4 c;
              c.rgb = s.Albedo * _LightColor0.rgb * (NdotL * atten);
              c.a = s.Alpha;
              return c;
          }

        struct Input {
            float2 uv_MainTex;
        };

        sampler2D _MainTex;

        void surf (Input IN, inout SurfaceOutput o) {
            o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
        }
        ENDCG
        }
        Fallback "Diffuse"
    }
  • SimpleSpecular:與BlingPhong光照等效的實現。計算頂點顏色涉及到的引數:視角、片元的法向量、光入射角度、光的rgba和衰減、片元的Albedo。

這裡寫圖片描述

    ...ShaderLab code...
    CGPROGRAM
    #pragma surface surf SimpleSpecular

    half4 LightingSimpleSpecular (SurfaceOutput s, half3 lightDir, half3 viewDir, half atten) {
        half3 h = normalize (lightDir + viewDir);

        half diff = max (0, dot (s.Normal, lightDir));

        float nh = max (0, dot (s.Normal, h));
        float spec = pow (nh, 48.0);

        half4 c;
        c.rgb = (s.Albedo * _LightColor0.rgb * diff + _LightColor0.rgb * spec) * atten;
        c.a = s.Alpha;
        return c;
    }

    struct Input {
        float2 uv_MainTex;
    };

    sampler2D _MainTex;

    void surf (Input IN, inout SurfaceOutput o) {
        o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
    }
    ENDCG
    ...ShaderLab code...
Vertex/Fragment Shader中的光照

要使用光照,首先需要在pass tag中設定LightMode,從而定義各種rendering path。常見的選擇有:

關鍵字 意義 解釋
ForwardBase forward rendering 使用關鍵光照資訊立即渲染
Deferred deferred shading 使用所有光照資訊延遲渲染
ShadowCaster shadow caster 投射陰影所必需,通常與ForwardBase或Deferred配合使用
  • SimpleDiffuse:與Lambert光照等效。需要在vert和frag中做處理。

這裡寫圖片描述

Shader "Lit/Simple Diffuse"
{
    Properties
    {
        [NoScaleOffset] _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Pass
        {
            // indicate that our pass is the "base" pass in forward
            // rendering pipeline. It gets ambient and main directional
            // light data set up; light direction in _WorldSpaceLightPos0
            // and color in _LightColor0
            Tags {"LightMode"="ForwardBase"}

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc" // for UnityObjectToWorldNormal
            #include "UnityLightingCommon.cginc" // for _LightColor0

            struct v2f
            {
                float2 uv : TEXCOORD0;
                fixed4 diff : COLOR0; // diffuse lighting color
                float4 vertex : SV_POSITION;
            };

            v2f vert (appdata_base v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = v.texcoord;
                // get vertex normal in world space
                half3 worldNormal = UnityObjectToWorldNormal(v.normal);
                // dot product between normal and light direction for
                // standard diffuse (Lambert) lighting
                half nl = max(0, dot(worldNormal, _WorldSpaceLightPos0.xyz));
                // factor in the light color
                o.diff = nl * _LightColor0;
                return o;
            }

            sampler2D _MainTex;

            fixed4 frag (v2f i) : SV_Target
            {
                // sample texture
                fixed4 col = tex2D(_MainTex, i.uv);
                // multiply by lighting
                col *= i.diff;
                return col;
            }
            ENDCG
        }
    }
}
  • ShadowCasting:實現陰影投射。定義了兩個Pass,LightMode分別是ForwardBase和ShadowCaster。

這裡寫圖片描述

Shader "Lit/Shadow Casting"
{
    SubShader
    {
        // very simple lighting pass, that only does non-textured ambient
        Pass
        {
            Tags {"LightMode"="ForwardBase"}
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"
            struct v2f
            {
                fixed4 diff : COLOR0;
                float4 vertex : SV_POSITION;
            };
            v2f vert (appdata_base v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                half3 worldNormal = UnityObjectToWorldNormal(v.normal);
                // only evaluate ambient
                o.diff.rgb = ShadeSH9(half4(worldNormal,1));
                o.diff.a = 1;
                return o;
            }
            fixed4 frag (v2f i) : SV_Target
            {
                return i.diff;
            }
            ENDCG
        }

        // shadow caster rendering pass, implemented manually
        // using macros from UnityCG.cginc
        Pass
        {
            Tags {"LightMode"="ShadowCaster"}

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_shadowcaster
            #include "UnityCG.cginc"

            struct v2f { 
                V2F_SHADOW_CASTER;
            };

            v2f vert(appdata_base v)
            {
                v2f o;
                TRANSFER_SHADOW_CASTER_NORMALOFFSET(o)
                return o;
            }

            float4 frag(v2f i) : SV_Target
            {
                SHADOW_CASTER_FRAGMENT(i)
            }
            ENDCG
        }
    }
}