天天看點

shader篇-陰影shader篇-陰影

shader篇-陰影

标簽(空格分隔): shader

  • shader篇-陰影
    • 簡介
    • 投射陰影實作
    • 接受陰影
    • Unity統一管理光照衰減和陰影
    • 透明度物體的陰影

簡介

陰影的實作需要2個過程:

一、接受陰影的物體,需要在shader中采樣陰影映射紋理,并把采樣後結果與光照結果相乘獲得陰影效果。

二、一個物體如果想投射陰影,就需要将該物體加入光照映射紋理的計算中,以便被采樣時可以擷取該物體相關資訊。Unity Shader中該過程是又LightgMode 為ShadeowCaster的Pass實作。

投射陰影實作

Unity中,是否讓物體投射或接受陰影,是由mesh renderer元件的Cast Shadows和Receive Shadows的設定來實作

不過即便沒有設定,如果fallback能調用含LightMode 為ShadowCaster的Pass時,一樣能投射陰影,比如說 Fallback “Specualr”,通過回調内置的Specular可以間接調用含有LightMode 為ShadowCaster的Pass。

接受陰影

編寫接受陰影的shader時需要#include “AutoLight.cginc”,計算陰影的宏都是來自這個檔案。

配置

Properties {
    _Diffuse ("Diffuse", Color) = (, , , )
    _Specular ("Specular", Color) = (, , , )
    _Gloss ("Gloss", Range(, )) = 
}
SubShader {
Tags { "RenderType"="Opaque" }

Pass {

    Tags { "LightMode"="ForwardBase" }

    CGPROGRAM


    #pragma multi_compile_fwdbase   

    #pragma vertex vert
    #pragma fragment frag


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

    fixed4 _Diffuse;
    fixed4 _Specular;
    float _Gloss;

    struct a2v {
        float4 vertex : POSITION;
        float3 normal : NORMAL;
    };

    struct v2f {
        float4 pos : SV_POSITION;
        float3 worldNormal : TEXCOORD0;
        float3 worldPos : TEXCOORD1;
        SHADOW_COORDS()
    };
           

注意:為保證内置宏運作正确,a2v結構體頂點坐标變量名必須

頂點着色器

v2f vert(a2v 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;
}
           

之後,我們使用内置宏SHADOW_ATTENUATION在片元着色器中計算陰影值

Unity統一管理光照衰減和陰影

unity内置了一個宏計算光照衰減和陰影UNITY_LIGHT_ATTENUATION

v2f vert(a2v 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(UnityWorldSpaceLightDir(i.worldPos));

    fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(, dot(worldNormal, worldLightDir));

    fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
    fixed3 halfDir = normalize(worldLightDir + viewDir);
    fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(, dot(worldNormal, halfDir)), _Gloss);


    UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);

    return fixed4((diffuse + specular) * atten, );
}
           

這種内置宏可以簡化我們某個工作

透明度物體的陰影

對于大多數不透明物體,fallback設為VertexLit就可以得到正确的陰影,但不透明物體就要小心了。

配置

Properties {
_Color ("Color Tint", Color) = (, , , )
_MainTex ("Main Tex", D) = "white" {}
_Cutoff ("Alpha Cutoff", Range(, )) = 
}
SubShader {
Tags {"Queue"="AlphaTest" "IgnoreProjector"="True" "RenderType"="TransparentCutout"}

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

    Cull Off

    CGPROGRAM

    #pragma multi_compile_fwdbase

    #pragma vertex vert
    #pragma fragment frag

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

    fixed4 _Color;
    sampler2D _MainTex;
    float4 _MainTex_ST;
    fixed _Cutoff;

    struct a2v {
        float4 vertex : POSITION;
        float3 normal : NORMAL;
        float4 texcoord : TEXCOORD0;
    };

    struct v2f {
        float4 pos : SV_POSITION;
        float3 worldNormal : TEXCOORD0;
        float3 worldPos : TEXCOORD1;
        float2 uv : TEXCOORD2;
        SHADOW_COORDS()
    };
           

注意:SHADOW_COORDS的參數是3,意味着它将占有第四個插值寄存器TEXCOORD3

FallBack "Transparent/Cutout/VertexLit"
}
           

還有fallback,這一次不能間接調用Vertexlit了,我們需要直接調用Transparent/Cutout/VertexLit,隻有這樣才能調用含透明度測試的shader

注意:Unity内置的半透明shader都不會添加陰影,因為半透明物體關閉了深度寫入,必須處理好渲染順序,否則陰影處理會非常複雜

繼續閱讀