一、阴影与全局照明系统的关系
Unity3D引擎可以根据宏SHADOWS_SCREEN和LIGHTMAP_ON是否启用决定是否在全局照明系统下对阴影进行混合处理。如果这两个宏同时启用,则HANDLE_SHADOWS_BLENDING_IN_GI定义为1,即宣告在全局照明下也对阴影进行处理。宏SHADOWS_SCREEN本质上是一个着色器多样体,表示是否在屏幕空间中处理阴影计算,如下所示:
#if defined( SHADOWS_SCREEN ) && defined( LIGHTMAP_ON )
#define HANDLE_SHADOWS_BLENDING_IN_GI 1
#endif
二、聚光灯光源生成的阴影
Unity3D引擎会根据不同类型的光源,用不同的计算方式对应计算光源所产生的引擎。引擎提供的阴影计算库中,有用聚光灯光源生成和用点光源生成的阴影。当启用SPOT宏时,表示使用聚光灯生成,如下代码:
2.1启用SPOT宏
#if defined (SHADOWS_DEPTH) && defined (SPOT)
// declare shadowmap
//如果没有声明shadowmap,则声明一个阴影贴图纹理ShadowMapTexture
#if !defined(SHADOWMAPSAMPLER_DEFINED)
UNITY_DECLARE_SHADOWMAP(_ShadowMapTexture);
#define SHADOWMAPSAMPLER_DEFINED
#endif
// shadow sampling offsets and texel size
//阴影贴图纹理的偏移量和纹素的大小
#if defined (SHADOWS_SOFT)
float4 _ShadowOffsets[4];
float4 _ShadowMapTexture_TexelSize;
#define SHADOWMAPSAMPLER_AND_TEXELSIZE_DEFINED
#endif
如果启用了SHADOWS_SOFT,即启用了软阴影效果,就还需要其他采样点去进行阴影的柔化操作。变量_ShadowOffsets[4]取出了阴影某个采样点的4个偏移采样点。
2.2 UNITY_DECLARE_SHADOWMAP宏的定义
UNITY_DECLARE_SHADOWMAP宏在HLSLSupport.cginc文件中定义,用来声明一个阴影纹理。除此之外,在文件中还定义了其他对阴影进行操作的宏,如进行采样操作的宏UNITY_SAMPLE_SHADOW、进行投影计算的宏UNITY_SAMPLE_SHADOW_PROJ。这些宏在不同的平台下使用不同版本的着色器编译器,有着不同的实际定义。
2.3 SAMPLE_DEPTH_TEXTURE 及类似的宏
如果能使用着色器本身支持的阴影相关的操作函数,即宏SHADOW_NATIVE被启用,就直接使用这些函数,如果不支持,就使用普通2D纹理函数对纹理进行采样。这些函数Unity3D已经用宏包装了一层。
#if defined(SHADER_API_PSP2)
half SAMPLE_DEPTH_TEXTURE(sampler2D s, float4 uv) { return tex2D<float>(s, (float3)uv); }
half SAMPLE_DEPTH_TEXTURE(sampler2D s, float3 uv) { return tex2D<float>(s, uv); }
half SAMPLE_DEPTH_TEXTURE(sampler2D s, float2 uv) { return tex2D<float>(s, uv); }
# define SAMPLE_DEPTH_TEXTURE_PROJ(sampler, uv) (tex2DprojShadow(sampler, uv))
# define SAMPLE_DEPTH_TEXTURE_LOD(sampler, uv) (tex2Dlod<float>(sampler, uv))
# define SAMPLE_RAW_DEPTH_TEXTURE(sampler, uv) SAMPLE_DEPTH_TEXTURE(sampler, uv)
# define SAMPLE_RAW_DEPTH_TEXTURE_PROJ(sampler, uv) SAMPLE_DEPTH_TEXTURE_PROJ(sampler, uv)
# define SAMPLE_RAW_DEPTH_TEXTURE_LOD(sampler, uv) SAMPLE_DEPTH_TEXTURE_LOD(sampler, uv)
half SAMPLE_DEPTH_CUBE_TEXTURE(samplerCUBE s, float3 uv) { return texCUBE<float>(s, uv); }
#else
// Sample depth, just the red component.
//只采样深度值,所以只需要用到depth texture的texel的red分量即可
# define SAMPLE_DEPTH_TEXTURE(sampler, uv) (tex2D(sampler, uv).r)
# define SAMPLE_DEPTH_TEXTURE_PROJ(sampler, uv) (tex2Dproj(sampler, uv).r)
# define SAMPLE_DEPTH_TEXTURE_LOD(sampler, uv) (tex2Dlod(sampler, uv).r)
// Sample depth, all components.
//需要用到depth texture的texel的所有分量
# define SAMPLE_RAW_DEPTH_TEXTURE(sampler, uv) (tex2D(sampler, uv))
# define SAMPLE_RAW_DEPTH_TEXTURE_PROJ(sampler, uv) (tex2Dproj(sampler, uv))
# define SAMPLE_RAW_DEPTH_TEXTURE_LOD(sampler, uv) (tex2Dlod(sampler, uv))
# define SAMPLE_DEPTH_CUBE_TEXTURE(sampler, uv) (texCUBE(sampler, uv).r)
#endif
2.4 UnitySampleShadowMap函数版本 1
理解了宏的定义,继续回到UnityShadowLibrary.cginc的代码分析UnitySampleShadowmap函数,该函数的功能就是根据给定的阴影坐标,在阴影深度贴图中进行采样,获取阴影坐标对应的贴图纹素的深度值,如下:
//float4 shadowCoord是场景中某个空间位置点,该位置点已经变换到产生阴影的那个光源的光源空间。判断shadowCoord是否在阴影下,以及判断该阴影的浓度
inline fixed UnitySampleShadowmap (float4 shadowCoord)
{//如果使用软阴影
#if defined (SHADOWS_SOFT)
half shadow = 1;
// No hardware comparison sampler (ie some mobile + xbox360) : simple 4 tap PCF
//如果着色器不支持原生的阴影操作函数
#if !defined (SHADOWS_NATIVE)
//除以w,进行透视除法,把坐标转换到一个NDC坐标上执行操作
float3 coord = shadowCoord.xyz / shadowCoord.w;
float4 shadowVals;
//获取到本采样点四周的四个偏移采样点的深度值,然后存储到shadowVals变量中
shadowVals.x = SAMPLE_DEPTH_TEXTURE(_ShadowMapTexture, coord + _ShadowOffsets[0].xy);
shadowVals.y = SAMPLE_DEPTH_TEXTURE(_ShadowMapTexture, coord + _ShadowOffsets[1].xy);
shadowVals.z = SAMPLE_DEPTH_TEXTURE(_ShadowMapTexture, coord + _ShadowOffsets[2].xy);
shadowVals.w = SAMPLE_DEPTH_TEXTURE(_ShadowMapTexture, coord + _ShadowOffsets[3].xy);
//如果本采样点四周的4个采样点的z值都小于阴影贴图采样点的z值,就表示该点不处于阴影区域。_LightShadowData的r分量,即x分量表示阴影的强度值
half4 shadows = (shadowVals < coord.zzzz) ? _LightShadowData.rrrr : 1.0f;
//阴影值为本采样点四周的4个采样点的阴影值的平均值
shadow = dot(shadows, 0.25f);
#else
// Mobile with comparison sampler : 4-tap linear comparison filter
//如果是在移动平台上,使用tex2D函数进行采样
#if defined(SHADER_API_MOBILE)
float3 coord = shadowCoord.xyz / shadowCoord.w;
half4 shadows;
shadows.x = UNITY_SAMPLE_SHADOW(_ShadowMapTexture, coord + _ShadowOffsets[0]);
shadows.y = UNITY_SAMPLE_SHADOW(_ShadowMapTexture, coord + _ShadowOffsets[1]);
shadows.z = UNITY_SAMPLE_SHADOW(_ShadowMapTexture, coord + _ShadowOffsets[2]);
shadows.w = UNITY_SAMPLE_SHADOW(_ShadowMapTexture, coord + _ShadowOffsets[3]);
shadow = dot(shadows, 0.25f);
// Everything else
//其他未特别声明的平台
#else
float3 coord = shadowCoord.xyz / shadowCoord.w;
float3 receiverPlaneDepthBias = UnityGetReceiverPlaneDepthBias(coord, 1.0f);
shadow = UnitySampleShadowmap_PCF3x3(float4(coord, 1), receiverPlaneDepthBias);
#endif
shadow = lerp(_LightShadowData.r, 1.0f, shadow);
#endif
2.5 unitySampleShadowmap函数版本 2
#else
// 1-tap shadows
//如果不用软阴影,就不需要对阴影进行柔化
//使用着色器支持的阴影操作函数shadow2proj来进行纹理投影,在这里宏UNITY_SAMPLE_SHADOW_PROJ即是shadow2Dproj
#if defined (SHADOWS_NATIVE)
half shadow = UNITY_SAMPLE_SHADOW_PROJ(_ShadowMapTexture, shadowCoord);
//进行线性插值,让阴影值落在当前阴影强度0和1之间
shadow = lerp(_LightShadowData.r, 1.0f, shadow);
#else
//如果没有着色器内建的阴影操作函数,就直接比较当前判断点的z值和阴影图中对应点的z值,然后返回
half shadow = SAMPLE_DEPTH_TEXTURE_PROJ(_ShadowMapTexture, UNITY_PROJ_COORD(shadowCoord)) < (shadowCoord.z / shadowCoord.w) ? _LightShadowData.r : 1.0;
#endif
#endif
三、点光源生成的阴影
当启用SHADOWS_CUBE 宏时,将使用点光源生成阴影。和聚光灯光源不同的是,点光源生成的阴影,其阴影深度贴图存储在一个立方体纹理(cube texture中)。贴图中某一点纹素所存储的深度值,即某处离光源最远且光线能照射到的那个位置的深度值。
3.1 从立方体纹理贴图中取得纹素对应的深度值
下面的代码展示了如何从一个立方体纹理贴图中取得某纹素对应的深度值,通常有一个有着RGBA通道的纹理,如果仅用来存储深度值,一般只用到其中一个通道,就是Red通道。
代码段 SampleCubeDistance
Unity3D引擎充分利用了R、G、B、A这4个通道的存储空间,把一个浮点数编码进4个通道中,以提高深度值的精度:
#if defined (SHADOWS_CUBE)
#if defined(SHADOWS_CUBE_IN_DEPTH_TEX)
UNITY_DECLARE_TEXCUBE_SHADOWMAP(_ShadowMapTexture);
#else
//如果使用立方体纹理映射贴图作为一个阴影深度纹理
UNITY_DECLARE_TEXCUBE(_ShadowMapTexture);
//vec是从原点发出,指向立方体上某点位置的连线向量,也是立方体纹理的贴图坐标
inline float SampleCubeDistance (float3 vec)
{
return UnityDecodeCubeShadowDepth(UNITY_SAMPLE_TEXCUBE_LOD(_ShadowMapTexture, vec, 0));
}
#endif
上述代码中调用了UnityDecodeCubeShadowDepth函数。texCUBE函数和texCUBElod函数的区别在于:前者如果使用两个参数版本,是不带mipmap采样的;而后者则会依据不同的mipmap进行不同精度的采样。
3.2 对采样值进行混合计算
实现了从立方体贴图中进行采样之后就可以根据当前位置进行阴影判断了。UnitySampleShadowmap函数接受一个float3类型的值,此值是当前待判断是否在阴影中的偏移在光源空间中的坐标。计算出该坐标到光源之间的距离,并且归一化,然后在该坐标位置点的右上前方、左下前方、左上后方、右下后方偏移,各取得对应纹素的深度值:
inline half UnitySampleShadowmap (float3 vec)
{
#if defined(SHADOWS_CUBE_IN_DEPTH_TEX)
float3 absVec = abs(vec);
float dominantAxis = max(max(absVec.x, absVec.y), absVec.z); // TODO use max3() instead
dominantAxis = max(0.00001, dominantAxis - _LightProjectionParams.z); // shadow bias from point light is apllied here.
dominantAxis *= _LightProjectionParams.w; // bias
float mydist = -_LightProjectionParams.x + _LightProjectionParams.y/dominantAxis; // project to shadow map clip space [0; 1]
#if defined(UNITY_REVERSED_Z)
mydist = 1.0 - mydist; // depth buffers are reversed! Additionall