1. 程式人生 > >Unity3D The Blacksmith 角色陰影技術使用心得

Unity3D The Blacksmith 角色陰影技術使用心得

這個demo放出來之後,對其中的角色陰影部分的技術十分感興趣,我就趕緊下過來研究了一下。官方的技術部落格裡有對這一部分技術的介紹,連結在這裡:獨特的角色陰影

其中寫到只要在其他shader裡新增

#pragma multi_compile _ UNIQUE_SHADOW UNIQUE_SHADOW_LIGHT_COOKIE  
#include "UniqueShadow_ShadowSample.cginc"  

這兩行程式碼就能使用這個高階陰影了。 我之前也因為在做一些demo的時候發現unity預設陰影達不到理想效果,和ue4比起來還是有一定差距的。於是就想到使用這個陰影技術了。於是我興致勃勃的把這兩行程式碼和幾個庫檔案加到我的shader和專案裡去,一執行發現,高階陰影並沒有出現。哎,看樣子只能自己研究了。

   既然是陰影,那麼著手點就是unity裡的陰影部分的程式碼,手動寫過自定義陰影的人應該都知道,unity陰影計算:SHADOW_COORDS、TRANSFER_SHADOW、SHADOW_ATTENUATION三個函數了,unity把陰影生成的程式碼都寫在了這三個函式裡,方便呼叫。我看The Blacksmith的技術部落格上寫著:  我們發現原來有一個非常簡單的方法來對常見的Unity shader所用的陰影方法進行重寫。 於是我琢磨著應該是他們也用了這三個方法,於是開啟 UniqueShadow_ShadowSample.cginc 檔案,果不其然,在最下面找到了這麼一段程式碼

#if defined(UNITY_PASS_FORWARDBASE) || defined(UNITY_PASS_FORWARDADD) || defined(UNIQUE_SHADOW_FORCE_REPLACE_BUILTIN)  
    #undef SHADOW_COORDS  
    #undef TRANSFER_SHADOW  
    #undef SHADOW_ATTENUATION  
    #define SHADOW_COORDS(i)                    UNIQUE_SHADOW_INTERP(i)  
    #define TRANSFER_SHADOW                     o.uniqueShadowPos = mul(u_UniqueShadowMatrix, float4(worldPos.xyz, 1.f));  
    #define SHADOW_ATTENUATION(i)               UNIQUE_SHADOW_SAMPLE(i);  
#endif  

這裡的意思就是,如果判斷pass的名字是
UNITY_PASS_FORWARDBASE、NITY_PASS_FORWARDADD、UNIQUE_SHADOW_FORCE_REPLACE_BUILTIN

這三個的話,就會把   

SHADOW_COORDS(i) 替換成UNIQUE_SHADOW_INTERP(i),
SHADOW_ATTENUATION(i)替換成UNIQUE_SHADOW_SAMPLE(i);
TRANSFER_SHADOW的值替換成後面的 o.uniqueShadowPos
這樣的話就好說了,之後只要把自己的shader裡生成陰影的pass名字改成三個裡的任意一個,生成陰影的程式碼還是按照原來的方式寫就可以了。
於是我立馬新建了一個Unlit Shader,新增上生成陰影的程式碼後,把pass的名字改成了UNITY_PASS_FORWARDBASE,程式碼如下

Shader "Custom/simpleSuperShadow"  
{  
    Properties  
    {  
        _MainTex ("Texture", 2D) = "white" {}  
    }  
    SubShader  
    {  
        Tags { "RenderType"="Opaque" }        
        LOD 100  
        Pass  
        {  
            Tags{ "LightMode" = "ForwardBase" }  
            CGPROGRAM  
            #pragma vertex vert  
            #pragma fragment frag  
            #pragma multi_compile_fwdbase  
            #include "UnityCG.cginc"  
            #include "AutoLight.cginc"  
 
#define UNITY_PASS_FORWARDBASE  
#pragma multi_compile _ UNIQUE_SHADOW UNIQUE_SHADOW_LIGHT_COOKIE  
#include "UniqueShadow/UniqueShadow_ShadowSample.cginc"  
                      
            struct v2f  
            {  
                float2 uv : TEXCOORD0;  
                float4 pos : SV_POSITION;  
                float3 worldPos : TEXCOORD1;  
                SHADOW_COORDS(2)  
  
            };  
  
            sampler2D _MainTex;  
            float4 _MainTex_ST;  
              
            v2f vert (appdata_full v)  
            {  
                v2f o;  
                o.pos = mul(UNITY_MATRIX_MVP, v.vertex);  
                o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);       
                o.worldPos = mul(_Object2World, v.vertex).xyz;  
                TRANSFER_SHADOW(o);  
                return o;  
            }  
              
            fixed4 frag (v2f i) : SV_Target  
            {  
                // sample the texture  
                fixed4 col = tex2D(_MainTex, i.uv);  
                fixed shadow = SHADOW_ATTENUATION(i);  
                return col * shadow;  
            }  
            ENDCG  
        }  
    }  
        FallBack "Diffuse"  
}  

點選執行之後,shader報錯,提示我頂點函式裡worldPos未定義,報錯的地方是在TRANSFER_SHADOW(o)這一行,但是我明明在v2f裡定義了這個變數,並且在上一行賦值了才對。這個提示不應該出現才對。對於這個問題,我毫無頭緒,不知道自己哪裡出了問題。於是我又新建了一個standard surface shader,這次我只在裡面添加了
#pragma multi_compile _ UNIQUE_SHADOW UNIQUE_SHADOW_LIGHT_COOKIE
#include "UniqueShadow/UniqueShadow_ShadowSample.cginc"
兩行,執行一下,居然成功了,於是我在編輯器裡點開了surface shader的原始程式碼,通過仔細對比陰影生成的三個函式發現,surface shader在
頂點函式使用worldPos的時候,並不是直接給o.worldPos賦值的,而是先定義了一個float3的worldPos變數,計算出來以後再讓o.worldPos = worldPos,這裡感覺應該是一樣的才是,並沒有什麼特別的地方啊。雖然我不是很清楚surface shader裡為啥要這麼寫,但是抱著試一試的心情,我把自己的shader的頂點函式從原來的
v2f vert (appdata_full v)  
{  
    v2f o;  
    o.pos = mul(UNITY_MATRIX_MVP, v.vertex);  
    o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);       
    o.worldPos = mul(_Object2World, v.vertex).xyz;  
    TRANSFER_SHADOW(o);  
    return o;  
}  

這樣,改成了
v2f vert (appdata_full v)  
{  
    v2f o;  
    o.pos = mul(UNITY_MATRIX_MVP, v.vertex);  
    o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);  
    float3 worldPos = mul(_Object2World, v.vertex).xyz;  
    o.worldPos = worldPos;  
    TRANSFER_SHADOW(o);  
    return o;  
}  

這樣,於是我執行一下。臥槽竟然成功了。按道理來說定義一個臨時變數和直接賦值應該沒有區別才是,為啥這裡不定義一個就不行呢,
mul(_Object2Wrold,v.vertex).xyz返回的本來就是一個float3的變數才是,為毛非要這麼做才能正確顯示呢。實在是不知道要怎麼解釋。希望知道的大大們能夠幫我解釋一下。
總結一下這個高階陰影的使用方法
1.給計算陰影的pass新增

#define UNITY_PASS_FORWARDBASE
#pragma multi_compile _ UNIQUE_SHADOW UNIQUE_SHADOW_LIGHT_COOKIE
#include "UniqueShadow/UniqueShadow_ShadowSample.cginc"


這三行程式碼,然後在頂點函式使用TRANSFES_SHADOW的地方改成
float3 worldPos = mul(_Object2World, v.vertex).xyz;
o.worldPos = worldPos;
TRANSFER_SHADOW(o);
這樣自定義的shader也能夠使用這個超高解析度的陰影了。

這裡我用了一個卡通shader做測試

未使用高階陰影:


使用高階陰影:


這樣就能做出媲美ue4的高階陰影來了。