AutoLight.cginc文件包含了一系列用来进行照明和阴影计算的函数,本篇重点讲述此文件中关于聚光灯和有向平行光的计算原理。
#ifndef AUTOLIGHT_INCLUDED
#define AUTOLIGHT_INCLUDED
#include "HLSLSupport.cginc"
#include "UnityShadowLibrary.cginc"
// If none of the keywords are defined, assume directional?
//如果没有使用点光灯和聚光灯光源,没有定义一个有向平行光光源,没有使用点cookie和有向平行光cookie,则默认定义一个有向平行光
#if !defined(POINT) && !defined(SPOT) && !defined(DIRECTIONAL) && !defined(POINT_COOKIE) && !defined(DIRECTIONAL_COOKIE)
#define DIRECTIONAL
#endif
// ---- Screen space direction light shadows helpers (any version)
//如果在屏幕空间处理阴影
#if defined (SHADOWS_SCREEN)
//当屏幕阴影空间层叠式阴影(screen space cascaded shadow map)不启用时,宏UNITY_NO_SCREENSPACE_SHADOWS被启用,引擎的C#层代码有BuiltinShaderDefine.UNITY_NO_SCREENSPACE_SHADOWS对应控制设置
#if defined(UNITY_NO_SCREENSPACE_SHADOWS)
//声明一个名为_ShadowMapTexture的阴影纹理贴图
UNITY_DECLARE_SHADOWMAP(_ShadowMapTexture);
#define TRANSFER_SHADOW(a) a._ShadowCoord = mul( unity_WorldToShadow[0], mul( unity_ObjectToWorld, v.vertex ) );
TRANSFER_SHADOW宏定义了一个功能:首先把一个顶点从它的局部坐标系转换到世界坐标系下,即mul(unity_ObjectToWorld,v.vertex)语句的功能;接着把该世界坐标值变到阴影空间中,得到在阴影空间中的坐标值,并赋值给a._ShadowCoord。
从代码中可以看到,使用TRANSFER_SHADOW宏时,传递给它的参数a必然是一个结构体。并且含有一个名为_ShadowCoord的成员变量,而且该语句包含一块v.vertex的代码,所以说这个宏必须搭配其他代码语句使用,并且其他语句要包含一个变量名为v的结构体,且这个结构体必须包含一个4D向量类型的、名为vertex的成员变量。
_ShadowCoord变量再代码段中有定义,是一个unityShadowCoord4类型的变量,而unityShadowCoord4又在UnityShadowLibrary.cginc文件中有定义,为fixed4类型,通过宏SHADOW_COORDS可以声明_ShadowCoord变量绑定一个TEXTCOORD语义。
//阴影的强度
inline fixed unitySampleShadow (unityShadowCoord4 shadowCoord)
{
#if defined(SHADOWS_NATIVE)
fixed shadow = UNITY_SAMPLE_SHADOW(_ShadowMapTexture, shadowCoord.xyz);
shadow = _LightShadowData.r + shadow * (1-_LightShadowData.r);
return shadow;
#else
//在tegra处理器上,如果直接把_LightShadowData.x传给Cg库函数max,会因为参数类型精度的问题而导致混乱和不精确,所以在此要先把_LightShadowData.x
//复制给一个unityShadowCoord类型变量lightShadowData.x复制给一个unityShadowCoord类型变量lightShadowDataX,然后传递给max函数
unityShadowCoord dist = SAMPLE_DEPTH_TEXTURE(_ShadowMapTexture, shadowCoord.xy);
// tegra is confused if we use _LightShadowData.x directly
// with "ambiguous overloaded function reference max(mediump float, float)"
unityShadowCoord lightShadowDataX = _LightShadowData.x;
//比较当前片元的深度值和对应的贴图纹素中表示的深度值
unityShadowCoord threshold = shadowCoord.z;
//如果深度贴图中的深度值大于当前片元的深度值,表示当前片元在阴影之外,dist>threshold的值为1,这时max函数返回1
//如果小于,表示当前片元在阴影之内,dist>threshold的值为0,这时max函数返回的是lightShadowDataX
return max(dist > threshold, lightShadowDataX);
#endif
}
当关闭UNITY_NO_SCREENSPACE_SHADOWS宏时,即用基于屏幕空间的阴影时,阴影纹理贴图和阴影坐标的声明与定义方式如下:
//当启用屏幕空间层叠式阴影
#else // UNITY_NO_SCREENSPACE_SHADOWS
UNITY_DECLARE_SCREENSPACE_SHADOWMAP(_ShadowMapTexture);
#define TRANSFER_SHADOW(a) a._ShadowCoord = ComputeScreenPos(a.pos);
inline fixed unitySampleShadow (unityShadowCoord4 shadowCoord)
{
fixed shadow = UNITY_SAMPLE_SCREEN_SHADOW(_ShadowMapTexture, shadowCoord);
return shadow;
}
#endif
#define SHADOW_COORDS(idx1) unityShadowCoord4 _ShadowCoord : TEXCOORD##idx1;
#define SHADOW_ATTENUATION(a) unitySampleShadow(a._ShadowCoord)
#endif
//如果启用了和立体渲染相关的宏
#if defined(UNITY_STEREO_INSTANCING_ENABLED) || defined(UNITY_STEREO_MULTIVIEW_ENABLED)
#define UNITY_SAMPLE_SCREEN_SHADOW(tex, uv) UNITY_SAMPLE_TEX2DARRAY( tex, float3((uv).x/(uv).w, (uv).y/(uv).w, (float)unity_StereoEyeIndex) ).r
#define UNITY_DECLARE_SCREENSPACE_TEXTURE UNITY_DECLARE_TEX2DARRAY
#define UNITY_SAMPLE_SCREENSPACE_TEXTURE(tex, uv) UNITY_SAMPLE_TEX2DARRAY(tex, float3((uv).xy, (float)unity_StereoEyeIndex))
//如果没有启用立体渲染,那么基于屏幕空间的screen space的阴影贴图实质上就是一个普通的sampler类型,要对该纹理采样,调用tex2DProj即可
#else
#define UNITY_DECLARE_SCREENSPACE_SHADOWMAP(tex) sampler2D tex
#define UNITY_SAMPLE_SCREEN_SHADOW(tex, uv) tex2Dproj( tex, UNITY_PROJ_COORD(uv) ).r
#define UNITY_DECLARE_SCREENSPACE_TEXTURE(tex) sampler2D tex;
#define UNITY_SAMPLE_SCREENSPACE_TEXTURE(tex, uv) tex2D(tex, uv)
#endif
//此版本的函数根据传递进来的某片元在世界空间下的坐标、使用的光照贴图采样坐标,以及它所在的屏幕坐标,计算出该片元的阴影值为多少
half UnityComputeForwardShadows(float2 lightmapUV, float3 worldPos, float4 screenPos)
{
//fade value 阴影淡化值
//_WorldSpaceCameraPos是当前摄像机在世界空间中的位置值,用片元位置点和当前摄像机位置点的连线向量与当前摄像机的朝前方向向量做点积,得到的就是连线向量的实际长度值
float zDist = dot(_WorldSpaceCameraPos - worldPos, UNITY_MATRIX_V[2].xyz);
float fadeDist = UnityComputeShadowFadeDistance(worldPos, zDist);
//根据淡化距离,求得实时转烘焙阴影的阴影淡化值
half realtimeToBakedShadowFade = UnityComputeShadowFade(fadeDist);
//baked occlusion if any
//当使用阴影蒙版时,UnitySampleBakedOcclusion函数用来返回烘焙阴影的衰减值
half shadowMaskAttenuation = UnitySampleBakedOcclusion(lightmapUV, worldPos);
half realtimeShadowAttenuation = 1.0f;
//directional realtime shadow
//计算主有向平行光产生的实时阴影
#if defined (SHADOWS_SCREEN)
#if defined(UNITY_NO_SCREENSPACE_SHADOWS) && !defined(UNITY_HALF_PRECISION_FRAGMENT_SHADER_REGISTERS)
//如果不是基于屏幕空间生成阴影,把片元坐标从世界坐标系下变换到光源空间下后进行采样
realtimeShadowAttenuation = unitySampleShadow(mul(unity_WorldToShadow[0], unityShadowCoord4(worldPos, 1)));
#else
//否则把片元从世界坐标系下变换到屏幕空间中采样,下面这段用屏幕坐标进行采样的代码,只有LIGHTMAP_ON宏未被启用时会执行到,这时不能使用光照贴图
//Only reached when LIGHTMAP_ON is NOT defined (and thus we use interpolator for screenPos rather than lightmap UVs). See HANDLE_SHADOWS_BLENDING_IN_GI below.
realtimeShadowAttenuation = unitySampleShadow(screenPos);
#endif
#endif
#if defined(UNITY_FAST_COHERENT_DYNAMIC_BRANCHING) && defined(SHADOWS_SOFT) && !defined(LIGHTMAP_SHADOW_MIXING)
//avoid expensive shadows fetches in the distance where coherency will be good
//使用UNITY_BRANCH分支,明确告知着色器编译器生成真正的动态分支功能,编码执行性能消耗较大的阴影fetch操作
UNITY_BRANCH
if (realtimeToBakedShadowFade < (1.0f - 1e-2f))
{
#endif
//spot realtime shadow
//计算聚光灯光源产生的实时阴影,从实时阴影贴图中取得衰减值
#if (defined (SHADOWS_DEPTH) && defined (SPOT))
#if !defined(UNITY_HALF_PRECISION_FRAGMENT_SHADER_REGISTERS)
unityShadowCoord4 spotShadowCoord = mul(unity_WorldToShadow[0], unityShadowCoord4(worldPos, 1));
#else
unityShadowCoord4 spotShadowCoord = screenPos;
#endif
realtimeShadowAttenuation = UnitySampleShadowmap(spotShadowCoord);
#endif
//point realtime shadow
//计算点光源产生的实时阴影,从实时阴影贴图中取得衰减值
#if defined (SHADOWS_CUBE)
realtimeShadowAttenuation = UnitySampleShadowmap(worldPos - _LightPositionRange.xyz);
#endif
#if defined(UNITY_FAST_COHERENT_DYNAMIC_BRANCHING) && defined(SHADOWS_SOFT) && !defined(LIGHTMAP_SHADOW_MIXING)
}
#endif
//最后混合实时的、阴影蒙版的、以及实时转烘焙的阴影值
return UnityMixRealtimeAndBakedShadows(realtimeShadowAttenuation, shadowMaskAttenuation, realtimeToBakedShadowFade);
}
#if defined(SHADER_API_D3D11) || defined(SHADER_API_D3D12) || defined(SHADER_API_XBOXONE) || defined(SHADER_API_PSSL)
# define UNITY_SHADOW_W(_w) _w
#else
# define UNITY_SHADOW_W(_w) (1.0/_w)
#endif
#if !defined(UNITY_HALF_PRECISION_FRAGMENT_SHADER_REGISTERS)
# define UNITY_READ_SHADOW_COORDS(input) 0
#else
# define UNITY_READ_SHADOW_COORDS(input) READ_SHADOW_COORDS(input)
#endif
//如果定义了在全局照明下进行阴影混合的宏,使用这三个宏对应定义一个需要带坐标的版本
#if defined(HANDLE_SHADOWS_BLENDING_IN_GI) // handles shadows in the depths of the GI function for performance reasons
# define UNITY_SHADOW_COORDS(idx1) SHADOW_COORDS(idx1)
# define UNITY_TRANSFER_SHADOW(a, coord) TRANSFER_SHADOW(a)
# define UNITY_SHADOW_ATTENUATION(a, worldPos) SHADOW_ATTENUATION(a)
//如果定义了屏幕空间中处理阴影,且不使用贴图,没有使用层叠式屏幕空间阴影贴图
#elif defined(SHADOWS_SCREEN) && !defined(LIGHTMAP_ON) && !defined(UNITY_NO_SCREENSPACE_SHADOWS) // no lightmap uv thus store screenPos instead
// can happen if we have two directional lights. main light gets handled in GI code, but 2nd dir light can have shadow screen and mask.
// - Disabled on ES2 because WebGL 1.0 seems to have junk in .w (even though it shouldn't)
//当有两个有向平行光时,主有向平行光在全局照明的相关代码中进行处理,第二个有向平行光在屏幕空间中进行阴影计算,如果使用了阴影蒙版,且不是
//在D3D9和OpenGLES平台下,那就从烘焙出来的光照贴图中取得阴影数据
# if defined(SHADOWS_SHADOWMASK) && !defined(SHADER_API_GLES)
# define UNITY_SHADOW_COORDS(idx1) unityShadowCoord4 _ShadowCoord : TEXCOORD##idx1;
//因为阴影是在屏幕空间中进行处理,所以阴影坐标的x、y分量就是光照贴图的u、v贴图坐标换算而来的。对于用coord乘以unity_LightmapST.xy后再加
//上unity_LightmapST.zw的这样一个计算方式,当LIGHTMAP_ON为false时才能进入代码此处
# define UNITY_TRANSFER_SHADOW(a, coord) {a._ShadowCoord.xy = coord * unity_LightmapST.xy + unity_LightmapST.zw; a._ShadowCoord.zw = ComputeScreenPos(a.pos).xy;}
//计算阴影衰减,转调用了UnityComputeForwardShadows函数
# define UNITY_SHADOW_ATTENUATION(a, worldPos) UnityComputeForwardShadows(a._ShadowCoord.xy, worldPos, float4(a._ShadowCoord.zw, 0.0, UNITY_SHADOW_W(a.pos.w)));
# else
# define UNITY_SHADOW_COORDS(idx1) SHADOW_COORDS(idx1)
//如果不从主光照贴图unity_LightmapST中计算阴影坐标,就用TRANSFER_SHADOW计算
# define UNITY_TRANSFER_SHADOW(a, coord) TRANSFER_SHADOW(a)
# define UNITY_SHADOW_ATTENUATION(a, worldPos) UnityComputeForwardShadows(0, worldPos, a._ShadowCoord)
# endif
//其他条件下
#else
# define UNITY_SHADOW_COORDS(idx1) unityShadowCoord4 _ShadowCoord : TEXCOORD##idx1;
//如果使用阴影蒙版,那么根据光照贴图纹理uv坐标求出阴影坐标
# if defined(SHADOWS_SHADOWMASK)
# define UNITY_TRANSFER_SHADOW(a, coord) a._ShadowCoord.xy = coord.xy * unity_LightmapST.xy + unity_LightmapST.zw;
//如果使用立方体阴影,或者光照探针代理体等有体积空间的阴影实现,需要把在世界空间中的坐标也传递进去
# if (defined(SHADOWS_DEPTH) || defined(SHADOWS_SCREEN) || defined(SHADOWS_CUBE) || UNITY_LIGHT_PROBE_PROXY_VOLUME)
# define UNITY_SHADOW_ATTENUATION(a, worldPos) UnityComputeForwardShadows(a._ShadowCoord.xy, worldPos, UNITY_READ_SHADOW_COORDS(a))
# else
//否则给UnityComputeForwardShadows函数传递的worldPos参数为0
# define UNITY_SHADOW_ATTENUATION(a, worldPos) UnityComputeForwardShadows(a._ShadowCoord.xy, 0, 0)
# endif
# else //如果不使用阴影蒙版,就不用实现transfer shadow的操作
# if !defined(UNITY_HALF_PRECISION_FRAGMENT_SHADER_REGISTERS)
# define UNITY_TRANSFER_SHADOW(a, coord)
# else
# define UNITY_TRANSFER_SHADOW(a, coord) TRANSFER_SHADOW(a)
# endif
# if (defined(SHADOWS_DEPTH) || defined(SHADOWS_SCREEN) || defined(SHADOWS_CUBE))
# define UNITY_SHADOW_ATTENUATION(a, worldPos) UnityComputeForwardShadows(0, worldPos, UNITY_READ_SHADOW_COORDS(a))
# else
# if UNITY_LIGHT_PROBE_PROXY_VOLUME
# define UNITY_SHADOW_ATTENUATION(a, worldPos) UnityComputeForwardShadows(0, worldPos, UNITY_READ_SHADOW_COORDS(a))
# else
# define UNITY_SHADOW_ATTENUATION(a, worldPos) UnityComputeForwardShadows(0, 0, 0)
# endif
# endif
# endif
#endif
下面这张图把上面的这段代码图表化,更加清楚显示了在不同多样体关键字开启条件下各宏的实际定义:
#ifdef POINT
//存储点光源发出的光线在空间中各个位置值的衰减值纹理
sampler2D_float _LightTexture0;
unityShadowCoord4x4 unity_WorldToLight;
# define UNITY_LIGHT_ATTENUATION(destName, input, worldPos) \
//首先把世界坐标系的坐标值worldPos变换到光源空间中,这里得到的lightCoord是一个采样值,在对后面的光源衰减纹理和采样时使用
unityShadowCoord3 lightCoord = mul(unity_WorldToLight, unityShadowCoord4(worldPos, 1)).xyz; \
//求出这一点的阴影衰减值
fixed shadow = UNITY_SHADOW_ATTENUATION(input, worldPos); \
//从光源衰减信息纹理中取出此处的衰减值,然后在UNITY_ATTEN_CHANNEL通道中输出,再和对应的阴影衰减值相乘,得到最后结果
fixed destName = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).r * shadow;
#endif
_LightTexture0是一张包含光源衰减信息的衰减纹理,lightCoord做一个和自身的点积操作,实际上就是计算出光源空间中位置点lightCoord到光源位置点的距离的平方;然后利用这个距离值重组(swizzle)出一个二维向量,用做衰减纹理的索引坐标。
从纹理中取得衰减值后,再乘以该处的阴影值,就可以得到光线在这点worldPos处的光亮度衰减值。
#ifdef SPOT
sampler2D_float _LightTexture0;
unityShadowCoord4x4 unity_WorldToLight;
sampler2D_float _LightTextureB0;
inline fixed UnitySpotCookie(unityShadowCoord4 LightCoord)
{
return tex2D(_LightTexture0, LightCoord.xy / LightCoord.w + 0.5).w;
}
inline fixed UnitySpotAttenuate(unityShadowCoord3 LightCoord)
{
return tex2D(_LightTextureB0, dot(LightCoord, LightCoord).xx).r;
}
#if !defined(UNITY_HALF_PRECISION_FRAGMENT_SHADER_REGISTERS)
#define DECLARE_LIGHT_COORD(input, worldPos) unityShadowCoord4 lightCoord = mul(unity_WorldToLight, unityShadowCoord4(worldPos, 1))
#else
#define DECLARE_LIGHT_COORD(input, worldPos) unityShadowCoord4 lightCoord = input._LightCoord
#endif
# define UNITY_LIGHT_ATTENUATION(destName, input, worldPos) \
DECLARE_LIGHT_COORD(input, worldPos); \
fixed shadow = UNITY_SHADOW_ATTENUATION(input, worldPos); \
fixed destName = (lightCoord.z > 0) * UnitySpotCookie(lightCoord) * UnitySpotAttenuate(lightCoord.xyz) * shadow;
#endif
在点光源下,存储着光亮度随着距离而衰减的习性的纹理由代码中的_LightTexture0负责读取;而在聚光灯下,这张纹理由_LightTextureB0读取。其采样坐标的计算方法和点光源有相似的地方。在UnitySpotAttenuate函数中,求得位置点中光源位置点的距离的平方,得到衰减值。通常聚光灯用“光亮度随着距离而衰减的信息”的纹理和点光源是一样的。
聚光灯光源照射范围内某一点的光亮度,它的衰减值除了和它与光源点的距离有关外,还和它与光源点的连线和光源照射方向的夹角有关。同样的到光源点的距离,夹角越大的位置点其衰减值就越大。
#ifdef DIRECTIONAL
# define UNITY_LIGHT_ATTENUATION(destName, input, worldPos) fixed destName = UNITY_SHADOW_ATTENUATION(input, worldPos);
#endif
有向平行光的光亮度不会随着光的传播距离的变化而发生衰减,因此此处所示的UNITY_LIGHT_ATTENUATION宏只需要内部转调UNITY_SHADOW_ATTENUATION宏,计算阴影产生的衰减结果即可。
在Light组件中,当选择Type属性项为Point时,可以向Cookie属性项指定一个立方体纹理。Light组件所表征的点光源使用此立方体纹理产生cookie投影效果。此时点光源的光亮度衰减值除了取决于光源自身发出的光线之外,还与产生cookie效果的立方体纹理中的纹素值有关。
#ifdef POINT_COOKIE
//产生cookie效果的立方体纹理采样器,在Light组件中的Cookie属性项中指定
samplerCUBE_float _LightTexture0;
unityShadowCoord4x4 unity_WorldToLight;
sampler2D_float _LightTextureB0; //点光源光线亮度衰减值纹理图
# if !defined(UNITY_HALF_PRECISION_FRAGMENT_SHADER_REGISTERS)
//把坐标转换从世界坐标系下转换到光源坐标系下
# define DECLARE_LIGHT_COORD(input, worldPos) unityShadowCoord3 lightCoord = mul(unity_WorldToLight, unityShadowCoord4(worldPos, 1)).xyz
# else
# define DECLARE_LIGHT_COORD(input, worldPos) unityShadowCoord3 lightCoord = input._LightCoord
# endif
# define UNITY_LIGHT_ATTENUATION(destName, input, worldPos) \
DECLARE_LIGHT_COORD(input, worldPos); \
fixed shadow = UNITY_SHADOW_ATTENUATION(input, worldPos); \
//计算光源发出光线的衰减方式,和点光源一样
fixed destName = tex2D(_LightTextureB0, dot(lightCoord, lightCoord).rr).r * texCUBE(_LightTexture0, lightCoord).w * shadow;
#endif
#ifdef DIRECTIONAL_COOKIE
sampler2D_float _LightTexture0; //产生cookie效果的纹理采样器
unityShadowCoord4x4 unity_WorldToLight;
# if !defined(UNITY_HALF_PRECISION_FRAGMENT_SHADER_REGISTERS)
# define DECLARE_LIGHT_COORD(input, worldPos) unityShadowCoord2 lightCoord = mul(unity_WorldToLight, unityShadowCoord4(worldPos, 1)).xy
# else
# define DECLARE_LIGHT_COORD(input, worldPos) unityShadowCoord2 lightCoord = input._LightCoord
# endif
# define UNITY_LIGHT_ATTENUATION(destName, input, worldPos) \
DECLARE_LIGHT_COORD(input, worldPos); \
fixed shadow = UNITY_SHADOW_ATTENUATION(input, worldPos); \
fixed destName = tex2D(_LightTexture0, lightCoord).w * shadow;
#endif
和点光源一样,有向平行光源也可以指定cookie纹理以产生cookie阴影效果,和不带cookie的类似,带cookie的有向平行光源的光亮度衰减,其光源本身是不随距离的变化而产生亮度衰减的,对衰减值有影响的是产生cookie的纹理。