实时阴影ShadowMap 实时阴影的计算:
ShadowMap:传统实时阴影 我们假设场景中只有一个主光源平行光,我们假设在这个光源架设一个摄像机,顺着光的方向,这时会采集到一张“光源相机”的深度图,这个图就是ShadowMap。场景中的普通摄像机观察到的每一个片元,都会计算它在“光源相机”中的深度(Unity会提供一个unity_WorldToShadow
的矩阵),如果当前片元的深度比ShadowMap的深度要小,那么说明当前片元能被光照亮。
打开Project Settings——Quality,从最下面的Shadows中将Shadow Distance改为15,Shadow Cascades关闭。然后进入Project Settings——Graphic,将High(Tier 3)的Use Defaults取消勾选,再将里面的Cascaded Shadows取消勾选。
然后我们使用下面阴影内置函数一,就能实现最基础的实时阴影。
此时打开Frame Debugger,选择到Shadows.RenderShadowMap
展开看到里面的Shadows.RenderJob
,就能观察到当前场景每个物体的Shadow Map了。此时如果我们修改Project Settings——Quality里面的Shadow Distance ,会法线这个参数其实控制的就是当前物体到“光源相机”的距离 ,对应的Shadow Map也会被缩放。
在Frame Debugger中可以看到,每一个模型的ShadowMap其实都是在SHADOWCASTER这个Pass中计算的,如果我们需要自定义阴影(自定义半透明阴影),也需要在这个Pass中计算。
Screen Space ShadowMap Screen Space ShadowMap在传统实时阴影的基础上,模仿联级阴影做了一些改进。
想要实现屏幕空间ShadowMap,先按照上面传统实时阴影设置场景,然后再进入Project Settings——Graphic,将High(Tier 3)的Use Defaults勾选上 ,此时我们打开Frame Debugger,会发现多了UpdateDepthTexture
这一步,这一步就会先渲染屏幕空间的深度图,接下来依然会在Shadows.RenderShadowMap
里面渲染各个模型的Shadow Map,只不过接下来,有RenderForwardOpaque.CollectShadow
这一步,它将屏幕空间的深度图和ShadowMap结合了起来,经过这一步,我们能将屏幕空间的全阴影计算完毕,最后渲染时只需要读取这一张结合后的图即可。
深一些来讲就是,通过屏幕空间深度图,我们不仅能展现每个片元的深度,还能还原每个片元的世界坐标 ,这个坐标通过unity_WorldToShadow
矩阵,得到在“光源相机”的深度,从而得到屏幕空间的全阴影。
联级阴影(CSM)Cascaded Shadow Mapping CSM在传统实时阴影的基础上,根据视锥体生成多张ShadowMap,优化性能。
按照上面的Screen Space ShadowMap设置场景,然后再在Project Settings——Quality中将Shadow Cascades设为Four Cascades
打开Frame Debugger,在Shadows.RenderShadowMap
中我们可以发现每个模型的ShadowMap有四种
我们可以在Scene窗口中选择Shade Mode为Shadow Cascade,能够看到在视锥体内分成了四块区域(距离模型过远时可能看不见四块区域),根据距离的远近区分了ShadeMap,所以会出现四种ShadowMap,这样能够在屏幕空间ShadowMap的基础上进一步节省性能。
阴影相关内置函数
函数
说明
SHADOW_COORDS(3)
TRANSFER_SHADOW(o)
SHADOW_ATTENUATION(i);
只计算实时阴影
LIGHTING_COORDS(3,4)
TRANSFER_VERTEX_TO_FRAGMENT(o);
LIGHT_ATTENUATION(i)
处理投影、而且帮你判断灯光类型、从而算出光照的衰减范围、cookies等
UNITY_SHADOW_COORDS(2)
UNITY_TRANSFER_SHADOW(o, v.uv1);
UNITY_LIGHT_ATTENUATION(atten,i,s.posWorld);
计算投影、处理实时投影和静态投影的混合、光照范围和衰减、cookies等
ShadowCaster
这是一个Pass,想要使用ShadowMap渲染阴影必须要这个Pass,不写的话可以直接使用Fallback "Diffuse"
,会自动补全。
函数一的使用 SHADOW_COORDS(3)
放在v2f构造体内,括号内的是空闲的Texcoord编号。注意没有分号
TRANSFER_SHADOW(o)
放在vert方法体内。注意没有分号
SHADOW_ATTENUATION(i);
放在frag方法体内。
在最后加上Fallback "Diffuse"
,注意和SubShader同级。
如果想要应用阴影,我们需要将阴影和diffuse颜色进行混合,这里的混合是让阴影和NdotL的结果取最小值。
1 2 half diff_term = min(shadow, max(0.0 , dot(normal_dir, light_dir))); half3 diffuse_color = diff_term * _LightColor0.xyz * base_color.xyz;
同时,我们也需要让高光也乘diff_term,防止出现在暗部不受光的地方会出现高光的错误。
1 half3 spec_color = pow (max(0.0 , NdotH),_Shininess) * diff_term * _LightColor0.xyz * _SpecIntensity * spec_mask.rgb;
ForwardBase Pass 经过一番更改后,我们目前的ForwardBase Pass如下:
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 Pass { Tags {"LightMode" = "ForwardBase" } CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma multi_compile_fwdbase #include "UnityCG.cginc" #include "AutoLight.cginc" struct appdata { float4 vertex : POSITION; float2 texcoord : TEXCOORD0; float3 normal : NORMAL; float4 tangent : TANGENT; }; struct v2f { float4 pos : SV_POSITION; float2 uv : TEXCOORD0; float3 normal_dir : TEXCOORD1; float3 tangent_dir : TEXCOORD3; float3 binormal_dir : TEXCOORD4; float3 pos_world : TEXCOORD2; SHADOW_COORDS(5 ) }; sampler2D _MainTex; float4 _MainTex_ST; sampler2D _AOMap; sampler2D _SpecMask; sampler2D _NormalMap; sampler2D _ParallaxMap; float _Parallax; float4 _LightColor0; float _Shininess; float _SpecIntensity; float _NormalIntensity; float3 ACESFilm (float3 x) { float a = 2.51f ; float b = 0.03f ; float c = 2.43f ; float d = 0.59f ; float e = 0.14f ; return saturate((x*(a*x + b)) / (x*(c*x + d) + e)); } v2f vert (appdata v) { v2f o; o.pos = UnityObjectToClipPos(v.vertex); o.uv = TRANSFORM_TEX(v.texcoord, _MainTex); o.normal_dir = normalize(mul(float4(v.normal,0.0 ), unity_WorldToObject).xyz); o.tangent_dir = normalize(mul(unity_ObjectToWorld, float4(v.tangent.xyz, 0.0 )).xyz); o.binormal_dir = normalize(cross(o.normal_dir, o.tangent_dir)) * v.tangent.w; o.pos_world = mul(unity_ObjectToWorld, v.vertex).xyz; TRANSFER_SHADOW(o) return o; } half4 frag (v2f i) : SV_Target { half3 view_dir = normalize(_WorldSpaceCameraPos.xyz - i.pos_world); half3 normal_dir = normalize(i.normal_dir); half3 tangent_dir = normalize(i.tangent_dir); half3 binormal_dir = normalize(i.binormal_dir); float3x3 TBN = float3x3(tangent_dir, binormal_dir, normal_dir); half3 view_tangentspace = normalize(mul(TBN, view_dir)); half2 uv_parallax = i.uv; for (int j = 0 ; j < 10 ; j++) { half height = tex2D(_ParallaxMap, uv_parallax); uv_parallax = uv_parallax - (0.7 - height) * (view_tangentspace.xy / view_tangentspace.z + 0.42 ) * _Parallax * 0.01 ; } half4 base_color = tex2D(_MainTex, uv_parallax); base_color = pow (base_color,2.2 ); half4 ao_color = tex2D(_AOMap, uv_parallax); half4 spec_mask = tex2D(_SpecMask, uv_parallax); half4 normal_map = tex2D(_NormalMap, uv_parallax); half3 normal_data = UnpackNormal(normal_map); normal_data.xy = normal_data.xy * _NormalIntensity; normal_dir = normalize(mul(normal_data, TBN)); half shadow = SHADOW_ATTENUATION(i); half3 light_dir = normalize(_WorldSpaceLightPos0.xyz); half diff_term = min(shadow, max(0.0 , dot(normal_dir, light_dir))); half3 diffuse_color = diff_term * _LightColor0.xyz * base_color.xyz; half3 half_dir = normalize(light_dir + view_dir); half NdotH = dot(normal_dir, half_dir); half3 spec_color = pow (max(0.0 , NdotH),_Shininess) * diff_term * _LightColor0.xyz * _SpecIntensity * spec_mask.rgb; half3 ambient_color = UNITY_LIGHTMODEL_AMBIENT.rgb * base_color.rgb; half3 final_color = (diffuse_color + spec_color + ambient_color) * ao_color; half3 tone_color = ACESFilm(final_color); tone_color = pow (tone_color,1.0 /2.2 ); return half4(tone_color, 1.0 ); } ENDCG }
ForwardAdd Pass ForwardAdd Pass和ForwardBase Pass相比,修改了几个地方,并且使用了上面表格中第二套计算阴影的方式。同时,使用Lerp函数来判断_WorldSpaceLightPos0.w
分量,从而区分平行光和点光,并且由于第二套阴影计算已经包含了灯光衰减,这里区分平行光和点光仅仅是为了计算漫反射。
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 Tags {"LightMode" = "ForwardAdd" } Blend One One #pragma multi_compile_fwdadd struct v2f{ float3 pos_world : TEXCOORD2; LIGHTING_COORDS(5 , 6 ) }; v2f vert (appdata v) { v2f o; o.pos_world = mul(unity_ObjectToWorld, v.vertex).xyz; TRANSFER_VERTEX_TO_FRAGMENT(o) return o; } half4 frag (v2f i) : SV_Target { half atten = LIGHT_ATTENUATION(i); half3 view_dir = normalize(_WorldSpaceCameraPos.xyz - i.pos_world); half3 light_dir_point = normalize(_WorldSpaceLightPos0.xyz - i.pos_world); half3 light_dir = normalize(_WorldSpaceLightPos0.xyz); light_dir = lerp(light_dir, light_dir_point, _WorldSpaceLightPos0.w); half diff_term = min(atten, max(0.0 , dot(normal_dir, light_dir))); half3 final_color = (diffuse_color + spec_color) * ao_color; return half4(final_color, 1.0 ); }
完整的ForwardAdd Pass
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 Pass { Tags {"LightMode" = "ForwardAdd" } Blend One One CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma multi_compile_fwdadd #include "UnityCG.cginc" #include "AutoLight.cginc" struct appdata { float4 vertex : POSITION; float2 texcoord : TEXCOORD0; float3 normal : NORMAL; float4 tangent : TANGENT; }; struct v2f { float4 pos : SV_POSITION; float2 uv : TEXCOORD0; float3 normal_dir : TEXCOORD1; float3 tangent_dir : TEXCOORD3; float3 binormal_dir : TEXCOORD4; float3 pos_world : TEXCOORD2; LIGHTING_COORDS(5 , 6 ) }; sampler2D _MainTex; float4 _MainTex_ST; sampler2D _AOMap; sampler2D _SpecMask; sampler2D _NormalMap; sampler2D _ParallaxMap; float _Parallax; float4 _LightColor0; float _Shininess; float _SpecIntensity; float _NormalIntensity; v2f vert (appdata v) { v2f o; o.pos = UnityObjectToClipPos(v.vertex); o.uv = TRANSFORM_TEX(v.texcoord, _MainTex); o.normal_dir = normalize(mul(float4(v.normal,0.0 ), unity_WorldToObject).xyz); o.tangent_dir = normalize(mul(unity_ObjectToWorld, float4(v.tangent.xyz, 0.0 )).xyz); o.binormal_dir = normalize(cross(o.normal_dir, o.tangent_dir)) * v.tangent.w; o.pos_world = mul(unity_ObjectToWorld, v.vertex).xyz; TRANSFER_VERTEX_TO_FRAGMENT(o) return o; } half4 frag (v2f i) : SV_Target { half atten = LIGHT_ATTENUATION(i); half3 view_dir = normalize(_WorldSpaceCameraPos.xyz - i.pos_world); half3 normal_dir = normalize(i.normal_dir); half3 tangent_dir = normalize(i.tangent_dir); half3 binormal_dir = normalize(i.binormal_dir); float3x3 TBN = float3x3(tangent_dir, binormal_dir, normal_dir); half3 view_tangentspace = normalize(mul(TBN, view_dir)); half2 uv_parallax = i.uv; for (int j = 0 ; j < 10 ; j++) { half height = tex2D(_ParallaxMap, uv_parallax); uv_parallax = uv_parallax - (0.7 - height) * (view_tangentspace.xy / view_tangentspace.z + 0.42 ) * _Parallax * 0.01 ; } half4 base_color = tex2D(_MainTex, uv_parallax); half4 ao_color = tex2D(_AOMap, uv_parallax); half4 spec_mask = tex2D(_SpecMask, uv_parallax); half4 normal_map = tex2D(_NormalMap, uv_parallax); half3 normal_data = UnpackNormal(normal_map); normal_data.xy = normal_data.xy * _NormalIntensity; normal_dir = normalize(mul(normal_data, TBN)); half3 light_dir_point = normalize(_WorldSpaceLightPos0.xyz - i.pos_world); half3 light_dir = normalize(_WorldSpaceLightPos0.xyz); light_dir = lerp(light_dir, light_dir_point, _WorldSpaceLightPos0.w); half diff_term = min(atten, max(0.0 , dot(normal_dir, light_dir))); half3 diffuse_color = diff_term * _LightColor0.xyz * base_color.xyz; half3 half_dir = normalize(light_dir + view_dir); half NdotH = dot(normal_dir, half_dir); half3 spec_color = pow (max(0.0 , NdotH),_Shininess) * diff_term * _LightColor0.xyz * _SpecIntensity * spec_mask.rgb; half3 final_color = (diffuse_color + spec_color) * ao_color; return half4(final_color, 1.0 ); } ENDCG }