US3S6L3——让物体投射阴影
感受 Fallback 的作用
我们新建一个材质球,将其的 Shader 设置为我们之前实现的多种光源综合实现 Shader
并将该材质球赋值给较大的立方体使用,我们会发现该立方体不再投射阴影也不再接受阴影,如下图
左边的立方体使用 Unity 的默认 Shader,既可以接受阴影和也可以投射阴影
右边的立法体使用之前实现的 多种光源综合实现 Shader,可以发现其既不接受阴影和也不投射阴影
- 不投射阴影的原因:该 Shader 中没有
LightMode
为 ShaderCaster
的 Pass
,无法参与光源的阴影映射纹理的计算
- 不接收阴影的原因:该 Shader 并没有对阴影映射相关纹理进行采样,没有进行阴影相关颜色运算
我们之前学习理论知识时提到过,Unity 会寻找 LightMode
为 ShaderCaster
的 Pass
来进行处理,
如果该 Shader
没有该 Pass
,会在它 FallBack
指定的 Shader
中寻找,直到找到为止
我们现在在该 Shader
最后加上 FallBack "Specular"
,便可以让该立方体投射阴影
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| Shader "TeachShader/Lesson64" { Properties { } SubShader { Pass { }
Pass { } }
Fallback "Specular" }
|
显示效果:
可见,现在使用我们的自己实现的 Shader 的立方体可以投射阴影了
让物体投射阴影
物体向其它物体投射阴影的关键点是:
-
需要实现 LightMode
(灯光模式)为 ShadowCaster
(阴影投射)的 Pass
(渲染通道)
这样该物体才能参与到光源的阴影映射纹理计算中
-
一个编译指令,一个内置文件,三个关键宏
-
编译指令:#pragma multi_compile_shadowcaster
该编译指令时告诉 Unity 编译器生成多个着色器变体,用于支持不同类型的阴影(SM,SSSM等等)
可以确保着色器能够在所有可能的阴影投射模式下正确渲染
-
内置文件:#include "UnityCG.cginc"
UnityCG.cginc 中包含了关键的阴影计算相关的宏
-
三个关键宏:
-
V2F_SHADOW_CASTER
:顶点到片元着色器阴影投射结构体数据宏
这个宏定义了一些标准的成员变量,这些变量用于在阴影投射路径中传递顶点数据到片元着色器,我们主要在结构体中使用
-
TRANSFER_SHADOW_CASTER_NORMALOFFSET
:转移阴影投射器法线偏移宏
用于在顶点着色器中计算和传递阴影投射所需的变量,主要做了:
- 将对象空间的顶点位置转换为裁剪空间的位置
- 考虑法线偏移,以减轻阴影失真问题,尤其是在处理自阴影时
- 传递顶点的投影空间位置,用于后续的阴影计算
我们主要在顶点着色器中使用
-
SHADOW_CASTER_FRAGMENT
:阴影投射片元宏
将深度值写入到阴影映射纹理中,我们主要在片元着色器中使用
-
利用这些内容在 Shader 中实现代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| Pass { Tags { "LightMode" = "ShadowCaster" }
CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma multi_compile_shadowcaster
#include "UnityCG.cginc"
struct v2f { V2F_SHADOW_CASTER; };
v2f vert(appdata_base v) { v2f data; TRANSFER_SHADOW_CASTER_NORMALOFFSET(data); return data; }
fixed4 frag(v2f i) : SV_Target { SHADOW_CASTER_FRAGMENT(i); } ENDCG }
|
显示效果:
可见,现在我们不使用 Unity 已经实现的 Shader 就可让立方体投射阴影了
完整 Shader 实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197
| Shader "TeachShader/Lesson66ForwardLighting" { Properties { _MainColor("MainColor", Color) = (1, 1, 1, 1) _SpecularColor("SpecularColor", Color) = (1, 1, 1, 1) _SpecularNum("SpecularNum", Range(0, 20)) = 0.5 } SubShader { Pass { Tags { "LightMode" = "ForwardBase" }
CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma multi_compile_fwdbase
#include "UnityCG.cginc" #include "Lighting.cginc"
struct v2f { float4 svPos : SV_POSITION; float3 wNormal : NORMAL; float3 wPos : TEXCOORD0; };
fixed4 _MainColor; fixed4 _SpecularColor; float _SpecularNum;
fixed3 getFragLambertColor(in float3 wNormal) { float3 lightDir = normalize(_WorldSpaceLightPos0.xyz); fixed3 color = _LightColor0.rgb * _MainColor.rgb * max(0, dot(wNormal, lightDir)); return color; }
fixed3 getFragSpecularColor(in float3 wPos, in float3 wNormal) { float3 viewDir = normalize(_WorldSpaceCameraPos.xyz - wPos); float3 lightDir = normalize(_WorldSpaceLightPos0.xyz); float3 halfA = normalize(viewDir + lightDir); fixed3 color = _LightColor0.rgb * _SpecularColor.rgb * pow(max(0, dot(wNormal, halfA)), _SpecularNum); return color; }
v2f vert (appdata_base v) { v2f v2fData; v2fData.svPos = UnityObjectToClipPos(v.vertex); v2fData.wNormal = UnityObjectToWorldNormal(v.normal); v2fData.wPos = mul(unity_ObjectToWorld, v.vertex).xyz; return v2fData; }
fixed4 frag (v2f i) : SV_Target { fixed3 lambertColor = getFragLambertColor(i.wNormal); fixed3 specularColor = getFragSpecularColor(i.wPos, i.wNormal); fixed atten = 1; fixed3 blinnPhongColor = UNITY_LIGHTMODEL_AMBIENT.rgb + (lambertColor + specularColor) * atten; return fixed4(blinnPhongColor.rgb, 1); } ENDCG }
Pass { Tags { "LightMode" = "ForwardAdd" } Blend One One
CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma multi_compile_fwdadd
#include "UnityCG.cginc" #include "Lighting.cginc" #include "AutoLight.cginc"
struct v2f { float4 svPos : SV_POSITION; float3 wNormal : NORMAL; float3 wPos : TEXCOORD0; };
fixed4 _MainColor; fixed4 _SpecularColor; float _SpecularNum;
v2f vert (appdata_base v) { v2f v2fData; v2fData.svPos = UnityObjectToClipPos(v.vertex); v2fData.wNormal = UnityObjectToWorldNormal(v.normal); v2fData.wPos = mul(unity_ObjectToWorld, v.vertex).xyz; return v2fData; }
fixed4 frag (v2f i) : SV_Target { fixed3 worldNormal = normalize(i.wNormal); #if defined(_DIRECTIONAL_LIGHT) fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz); #else fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz - i.wPos); #endif fixed3 diffuse = _LightColor0.rgb * _MainColor.rgb * max(0, dot(worldNormal, worldLightDir));
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.wPos.xyz); fixed3 halfDir = normalize(worldLightDir + viewDir); fixed3 specular = _LightColor0.rgb * _SpecularColor.rgb * pow(max(0, dot(worldNormal, halfDir)), _SpecularNum);
#ifdef USING_DIRECTIONAL_LIGHT fixed atten = 1; #else #if defined(POINT) float3 lightCoord = mul(unity_WorldToLight, float4(i.wPos, 1)).xyz; fixed atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).xx).UNITY_ATTEN_CHANNEL; #elif defined(SPOT) float4 lightCoord = mul(unity_WorldToLight, float4(i.wPos, 1)); fixed atten = (lightCoord.z > 0) * tex2D(_LightTexture0, lightCoord.xy / lightCoord.w + 0.5).w * tex2D(_LightTextureB0, dot(lightCoord, lightCoord).xx).UNITY_ATTEN_CHANNEL; #else fixed atten = 1; #endif #endif
return fixed4((diffuse + specular) * atten, 1); } ENDCG }
Pass { Tags { "LightMode" = "ShadowCaster" }
CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma multi_compile_shadowcaster
#include "UnityCG.cginc"
struct v2f { V2F_SHADOW_CASTER; };
v2f vert(appdata_base v) { v2f data; TRANSFER_SHADOW_CASTER_NORMALOFFSET(data); return data; }
fixed4 frag(v2f i) : SV_Target { SHADOW_CASTER_FRAGMENT(i); } ENDCG } } }
|
投射阴影的实现建议
由于投射阴影相关的代码较为通用,因此建议大家不用自己去实现相关 Shader 代码
直接通过 FallBack
调用 Unity 中默认 Shader 中的相关代码即可
除非需要自定义投影的效果,或者 Shader 执行了模型顶点的偏移,需要让阴影同步呈现效果,例如:
- US3S9L6——顶点动画的注意事项 中的让顶点动画物体投射正确的阴影部分,就使用了自定义的投影通道
- US4L8-3——消融效果 中让阴影和模型同步出现消融效果