iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 19
0
自我挑戰組

初見Unity Shader系列 第 19

簡單的陰影實作方法

文章內使用Unity 2019 LTS

目標

  • 投射陰影
  • 接收陰影

陰影貼圖

這是一個常見的製作陰影的技術,Unity也是採用這種方法。

想像光源為一台攝影機,往下一照,獲取這個場景的深度緩衝,形成陰影貼圖(Shadow Map),這個陰影貼圖會是存取最靠近這個攝影機的深度資訊。

獲取陰影貼圖後,會把場景物體移動到光源空間進行計算,沒錯,又是一個「換原點」,記得上一步只是獲得由光源所製作出的陰影貼圖,接下來要讓「實際攝影機」所獲取的深度資訊與陰影貼圖進行對比,最後覺得是否要渲染出來。

from LearnOpenGL

一個物體的陰影可以分為兩種,由物體自身投射出來的與別的物體打在該物體上的,以下以這兩種分別作出講解。

投射陰影

要使物體投射出陰影,需要將LightMode設定為ShadowCaster,即陰影投射。Unity有提供完整的巨集指令,簡單幾行就可以讓物體投影出陰影,但還有一個更快的方法,就是利用Fallback。

Shader "Learning/CastShadow"
{
    Properties
    {
        
    }
    SubShader
    {

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            

            #include "UnityCG.cginc"

           
            struct v2f
            {               
                float4 vertex : SV_POSITION;
            };

            v2f vert (appdata_base v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                return fixed4(1.0, 1.0, 1.0, 1.0);
            }
            ENDCG
        }
    }
    Fallback "Specular"
}

把上面的Shader複製貼上,只是將物體變為白色,就可以完整的將陰影呈現出來,秘密就在於這個Fallback,他去呼叫了底下在Specular更下面的VertexLit,而VertexLit就有實作LightMode為ShadowCaster的Pass。

接收陰影

與投射一樣,Unity也有提供巨集指令,把上面的修改一下:

Shader "Learning/SimpleShadow"
{
    Properties
    {
        
    }
    SubShader
    {

        Pass
        {
            Tags {"LightMode"="ForwardBase"}

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            
            #pragma multi_compile_fwdbase

            #include "UnityCG.cginc"
            #include "Lighting.cginc"
            #include "AutoLight.cginc"

            struct v2f
            {   
                // 上面的頂點為vertex這邊的改為pos
                // 是因為TRANSFER_SHADOW巨集需要綁定
                // 「變數名稱」叫做pos的頂點座標
                float4 pos : SV_POSITION;
                float3 worldNormal : TEXCOORD0;
                float3 worldPos : TEXCOORD1;
                // 需要一組紋理座標給接收的的陰影貼圖
                SHADOW_COORDS(2)
            };

            v2f vert (appdata_base v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.worldNormal = UnityObjectToWorldNormal(v.normal);
                o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
                // 計算陰影貼圖的紋理座標
                TRANSFER_SHADOW(o);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                fixed3 worldNormal = normalize(i.worldNormal);
                fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);

                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;

                fixed3 diffuse = _LightColor0.rgb * fixed3(1.0, 1.0, 1.0) * saturate(dot(worldNormal, worldLightDir));

                // 計算陰影
                fixed shadow = SHADOW_ATTENUATION(i);

                return fixed4((ambient + diffuse) * shadow, 1.0);
            }
            ENDCG
        }
    }
    Fallback "Specular"
}

成了一個「漫反射 + 陰影」的Shader,理由是物體需要一些環境的明暗才可以看的出來物體接收陰影的效果。

實際上,我們只需要使用AutoLight.cgincSHADOW_COORDSTRANSFER_SHADOW(o)SHADOW_ATTENUATION(i)就可以達成這個功能。

Reference


上一篇
Unity中的光源
下一篇
透明物體的陰影
系列文
初見Unity Shader30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言