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) = (1, 1, 1, 1)
    _Specular ("Specular", Color) = (1, 1, 1, 1)
    _Gloss ("Gloss", Range(8.0, 256)) = 20
}
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(2)
    };

注意:为保证内置宏运行正确,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(0, dot(worldNormal, worldLightDir));

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


    UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);

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

这种内置宏可以简化我们某个工作

透明度物体的阴影

对于大多数不透明物体,fallback设为VertexLit就可以得到正确的阴影,但不透明物体就要小心了。

配置

Properties {
_Color ("Color Tint", Color) = (1, 1, 1, 1)
_MainTex ("Main Tex", 2D) = "white" {}
_Cutoff ("Alpha Cutoff", Range(0, 1)) = 0.5
}
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(3)
    };

注意:SHADOW_COORDS的参数是3,意味着它将占有第四个插值寄存器TEXCOORD3

FallBack "Transparent/Cutout/VertexLit"
}

还有fallback,这一次不能间接调用Vertexlit了,我们需要直接调用Transparent/Cutout/VertexLit,只有这样才能调用含透明度测试的shader

注意:Unity内置的半透明shader都不会添加阴影,因为半透明物体关闭了深度写入,必须处理好渲染顺序,否则阴影处理会非常复杂