资源大湿 发表于 2022-12-7 09:14

【unity Shader 学习笔记】5-2 阴影

现实生活中,一个物体除了接收光照外,它还会生成和接收阴影。在渲染中,如果一个物体没有阴影,那么看起来就像浮空一样,也很假很奇怪。


生成阴影



现实生活中,阴影是光线被阻挡的结果。
光线被阻挡,会生成阴影,那也就是说,当这个位置有物体遮挡(有被写入深度)的时候,物体背后会生成阴影。那么没有写入深度的物体(透明物体)又是怎样生成阴影的呢?
在unity 中,使用的是 ShadowMapping,是一种常用的实时阴影生成方法。



且物体上需要开启阴影的生成与接收


光源无法达到的位置,但是相机能看得到的,那么就认为这个位置是在阴影下。这个位置通过比较,到光源和到摄像机的深度得到结果。
然后,要用一个东西记录这些信息,也就是阴影映射纹理(shadowmap),它从本质上来说,是一张深度图。它记录了,从该光源的位置出发、能看到的场景中距离它最近的表面信息(深度信息)。
不透明物体的阴影

最简单的一种生成阴影办法,则是在shader 中添加 FallBack
Shader
{
......
    SubShader {}
    // 生成阴影
    FallBack "Diffuse"
}它会找到内置的 Diffuse shader 文件,使用里面的生成阴影的方法。具体原理嘛,还不太懂。这样做的好处是可以减少自己写的 shader 中的pass 的数量。
另外一种方法,则是添加一个pass来绘制阴影。直接上代码
Pass
      {
            // 生成阴影
            Tags {"LightMode" = "ShadowCaster"}
            CGPROGRAM

            #pragma vertex vert
            #pragma fragment frag
            // 定义在LightMode=ShadowCaster的Pass中,会自动生成两个宏:
            // 1. SHADOWS_DEPTH :用于生成直线光和聚光灯阴影.
            // 2. SHADOW_CUBE :用于生成点光源阴影.
            #pragma multi_compile_shadowcaster
            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex :POSITION;
                half3 normal : NORMAL;
            };


            struct v2f
            {
                // 用于声明需要传送到片断的数据.
                V2F_SHADOW_CASTER;
            };


            v2f vert(appdata v)
            {
                v2f o;
                //主要是计算阴影的偏移以解决不正确的Shadow Acne和Peter Panning现象
                TRANSFER_SHADOW_CASTER_NORMALOFFSET(o)

                return o;
            }

            fixed4 frag(v2f i):SV_TARGET
            {
                // 内置的定义宏,没有查看到具体的逻辑
                SHADOW_CASTER_FRAGMENT(i)
            }
            ENDCG
      }
[*] 添加"LightMode" = "ShadowCaster"的Pass.
[*]appdata中声明float4 vertex:POSITION;和half3 normal:NORMAL;这是生成阴影所需要的语义
[*]v2f中添加V2F_SHADOW_CASTER;   用于声明需要传送到片断的数据.
[*]在顶点着色器中添加TRANSFER_SHADOW_CASTER_NORMALOFFSET(o),主要是计算阴影的偏移以解决不正确的Shadow Acne和Peter Panning现象.
[*]在片断着色器中添加SHADOW_CASTER_FRAGMENT(i)
在 UnityCG.cginc 中,我们能找到相关的注释,但是并没有具体函数的实现
V2F_SHADOW_CASTER
// Declare all data needed for shadow caster pass output (any shadow directions/depths/distances as needed),
// plus clip space position.
#define V2F_SHADOW_CASTER V2F_SHADOW_CASTER_NOPOS UNITY_POSITION(pos)TRANSFER_SHADOW_CASTER_NORMALOFFSET
// Vertex shader part, with support for normal offset shadows. Requires
// position and normal to be present in the vertex input.
#define TRANSFER_SHADOW_CASTER_NORMALOFFSET(o) TRANSFER_SHADOW_CASTER_NOPOS(o,o.pos)SHADOW_CASTER_FRAGMENT
#if defined(SHADOWS_CUBE) && !defined(SHADOWS_CUBE_IN_DEPTH_TEX)
    // Rendering into point light (cubemap) shadows
    #define V2F_SHADOW_CASTER_NOPOS float3 vec : TEXCOORD0;
    #define TRANSFER_SHADOW_CASTER_NOPOS_LEGACY(o,opos) o.vec = mul(unity_ObjectToWorld, v.vertex).xyz - _LightPositionRange.xyz; opos = UnityObjectToClipPos(v.vertex);
    #define TRANSFER_SHADOW_CASTER_NOPOS(o,opos) o.vec = mul(unity_ObjectToWorld, v.vertex).xyz - _LightPositionRange.xyz; opos = UnityObjectToClipPos(v.vertex);
    #define SHADOW_CASTER_FRAGMENT(i) return UnityEncodeCubeShadowDepth ((length(i.vec) + unity_LightShadowBias.x) * _LightPositionRange.w);

#else
    // Rendering into directional or spot light shadows
    #define V2F_SHADOW_CASTER_NOPOS
    // Let embedding code know that V2F_SHADOW_CASTER_NOPOS is empty; so that it can workaround
    // empty structs that could possibly be produced.
    #define V2F_SHADOW_CASTER_NOPOS_IS_EMPTY
    #define TRANSFER_SHADOW_CASTER_NOPOS_LEGACY(o,opos) \
      opos = UnityObjectToClipPos(v.vertex.xyz); \
      opos = UnityApplyLinearShadowBias(opos);
    #define TRANSFER_SHADOW_CASTER_NOPOS(o,opos) \
      opos = UnityClipSpaceShadowCasterPos(v.vertex, v.normal); \
      opos = UnityApplyLinearShadowBias(opos);
    #define SHADOW_CASTER_FRAGMENT(i) return 0;
#endif透明物体的阴影

我们知道阴影的生成和物体的深度信息有关系,可以通过 Frame Debug 来查看阴影的信息







Alpha Test (生成/接收阴影)   Alpha Blend (不生成/接收阴影)

Alpha Blend 是需要关闭深度写入的,所以一般情况下,它是不会生成阴影的。因为要为这样的半透明物体生成正确的阴影非常的复杂,而且也影响性能。但是,也可以通过一些强制的耍手段令其生成阴影。
在shader内添加
FallBack "VertexLit"

Alpha Test 虽然看起来有生成阴影,但是阴影看起来不是很对。由于一些面完全背向光源,因此这些面的深度信息没有假如到阴影映射纹理的计算中。需要修改物体Mesh Renderer 的 Cast Shadows 属性设置为 Two Sided



接收阴影


[*]在v2f中添加UNITY_SHADOW_COORDS(idx),unity会自动声明一个叫_ShadowCoord的float4变量,用作阴影的采样坐标.
[*]在顶点着色器中添加TRANSFER_SHADOW(o),用于将上面定义的_ShadowCoord纹理采样坐标变换到相应的屏幕空间纹理坐标,为采样阴影纹理使用.
[*]在片断着色器中添加UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos),其中atten即存储了采样后的阴影.
Pass
      {
            // 采样阴影的时候需要修改渲染路径为 ForwarBase (因为拉近拉远时出现阴影消失的问题)
            Tags { "LightMode"="ForwardBase" }

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // make fog work
            #pragma multi_compile_fog

            // 采样阴影时需要对应的变体开关 (这些其实是涉及调用的宏,这些宏里面有需要用到当前这些变体的状态)
            #pragma multi_compile DIRECTIONAL
            #pragma multi_compile SHADOWS_SCREEN
            
            #include "UnityCG.cginc"
            #include "AutoLight.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
            };

            struct v2f
            {
                float4 worldPos : TEXCOORD1;
                float4 pos :SV_POSITION;   // 为什么要改名pos呢?因为TRANSFER_SHADOW 进入到实际取的是 .pos 的顶点
                // 这里取的参数实际是 TEXCOORD2 unity会自动声明一个叫_ShadowCoord的float4变量,用作阴影的采样坐标.
                // 需要引用AutoLight.cginc
                UNITY_SHADOW_COORDS(2)
            };

            v2f vert(appdata v)
            {
                v2f o = (v2f)0;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.worldPos = mul(unity_ObjectToWorld,v.vertex);
                // 用于将上面定义的_ShadowCoord纹理采样坐标变换到相应的屏幕空间纹理坐标,为采样阴影纹理使用.
                TRANSFER_SHADOW(o)
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                // 其中atten即存储了采样后的阴影.
                UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos)
                return atten;
            }
            ENDCG
      }

左侧是unity内置的默认shader,右侧是粗糙实现的阴影采样。从图中很明显看到,虽然阴影的位置大致是一样的,但是颜色却很明显不一样。atten 采样得到的是非黑即白的简单阴影数据。
搜索 UNITY_LIGHT_ATTENUATION 相关的阴影采样,在 UnityStandardCore.cginc 能看到比较复杂的计算。阴影采样得到的 atten ,还需要加入到一系列的计算中,最后才得到一个物体的最终颜色,这个颜色包括物体自己以及接收的阴影颜色。
half4 fragForwardBaseInternal (VertexOutputForwardBase i)
{
    UNITY_APPLY_DITHER_CROSSFADE(i.pos.xy);

    FRAGMENT_SETUP(s)

    UNITY_SETUP_INSTANCE_ID(i);
    UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(i);

    UnityLight mainLight = MainLight ();
    UNITY_LIGHT_ATTENUATION(atten, i, s.posWorld);

    half occlusion = Occlusion(i.tex.xy);
    UnityGI gi = FragmentGI (s, occlusion, i.ambientOrLightmapUV, atten, mainLight);

    half4 c = UNITY_BRDF_PBS (s.diffColor, s.specColor, s.oneMinusReflectivity, s.smoothness, s.normalWorld, -s.eyeVec, gi.light, gi.indirect);
    c.rgb += Emission(i.tex.xy);

    UNITY_EXTRACT_FOG_FROM_EYE_VEC(i);
    UNITY_APPLY_FOG(_unity_fogCoord, c.rgb);
    return OutputForward (c, s.alpha);
}
以上是unity 阴影的简单使用,需要了解阴影生成的原理和细节,需要进一步了解计算机图形学的渲染知识才行。

相关推荐
【GAMES101-现代计算机图形学入门-闫令琪】 【精准空降到 50:41】
【GAMES202-高质量实时渲染】 【精准空降到 04:41】
相关参考:
页: [1]
查看完整版本: 【unity Shader 学习笔记】5-2 阴影