一、代码结构
// Unity built-in shader source. Copyright (c) 2016 Unity Technologies. MIT license (see license.txt)
Shader "Hidden/Internal-ScreenSpaceShadows" {
Properties {
_ShadowMapTexture ("", any) = "" {} // 阴影贴图纹理(级联阴影图集)
_ODSWorldTexture("", 2D) = "" {} // 存储世界空间坐标或额外数据的纹理
}
CGINCLUDE
// 声明阴影贴图相关变量
UNITY_DECLARE_SHADOWMAP(_ShadowMapTexture);
float4 _ShadowMapTexture_TexelSize; // 阴影贴图纹素尺寸,用于计算偏移
#define SHADOWMAPSAMPLER_AND_TEXELSIZE_DEFINED
sampler2D _ODSWorldTexture; // 世界空间坐标纹理
#include "UnityCG.cginc" // Unity通用CG函数
#include "UnityShadowLibrary.cginc" // 阴影相关库函数
// 级联阴影混合配置(0 表示禁用混合)
#define UNITY_USE_CASCADE_BLENDING 0
#define UNITY_CASCADE_BLEND_DISTANCE 0.1 // 级联过渡距离
// 顶点着色器输入结构
struct appdata {
float4 vertex : POSITION;
float2 texcoord : TEXCOORD0;
#if defined(UNITY_STEREO_INSTANCING_ENABLED) || defined(UNITY_STEREO_MULTIVIEW_ENABLED)
float3 ray0 : TEXCOORD1;
float3 ray1 : TEXCOORD2;
#else
float3 ray : TEXCOORD1;
#endif
UNITY_VERTEX_INPUT_INSTANCE_ID
};
// 片段着色器输入结构
struct v2f {
float4 pos : SV_POSITION;
// xy uv / zw screenpos
float4 uv : TEXCOORD0;
// View space ray, for perspective case
float3 ray : TEXCOORD1;
// Orthographic view space positions (need xy as well for oblique matrices)
float3 orthoPosNear : TEXCOORD2;
float3 orthoPosFar : TEXCOORD3;
UNITY_VERTEX_INPUT_INSTANCE_ID
UNITY_VERTEX_OUTPUT_STEREO
};
// 顶点着色器(当前为空,需根据需求实现)
v2f vert (appdata v) {}
// 声明深度纹理(用于计算相机空间坐标)
UNITY_DECLARE_DEPTH_TEXTURE(_CameraDepthTexture);
// 级联投影的尺寸比例(相对于第一个级联)
float4 unity_ShadowCascadeScales;
// 根据阴影划分方式定义级联权重计算函数
#if defined (SHADOWS_SPLIT_SPHERES) // 使用球形分割的级联
#define GET_CASCADE_WEIGHTS(wpos, z) getCascadeWeights_splitSpheres(wpos)
#else // 使用默认的级联方式(基于距离)
#define GET_CASCADE_WEIGHTS(wpos, z) getCascadeWeights(wpos, z)
#endif
// 根据级联数量定义阴影坐标计算函数
#if defined (SHADOWS_SINGLE_CASCADE) // 单级联模式
#define GET_SHADOW_COORDINATES(wpos,cascadeWeights) getShadowCoord_SingleCascade(wpos)
#else // 多级联模式
#define GET_SHADOW_COORDINATES(wpos,cascadeWeights) getShadowCoord(wpos,cascadeWeights)
#endif
// 获取级联权重(多级联模式)
inline fixed4 getCascadeWeights(float3 wpos, float z) { }
// 获取级联权重(球形分割模式)
inline fixed4 getCascadeWeights_splitSpheres(float3 wpos) { }
// 获取阴影坐标(多级联模式)
inline float4 getShadowCoord( float4 wpos, fixed4 cascadeWeights ) { }
// 获取阴影坐标(单级联模式)
inline float4 getShadowCoord_SingleCascade( float4 wpos ) { }
// 根据深度和逆投影矩阵计算相机空间坐标(PS阶段计算)
inline float3 computeCameraSpacePosFromDepthAndInvProjMat(v2f i) { }
// 根据顶点着色器输出信息计算相机空间坐标(VS阶段计算)
inline float3 computeCameraSpacePosFromDepthAndVSInfo(v2f i) { }
// 计算相机空间坐标(根据子着色器配置选择实现方式)
inline float3 computeCameraSpacePosFromDepth(v2f i) { }
// 硬阴影片段着色器
fixed4 frag_hard (v2f i) : SV_Target { }
// 软阴影片段着色器(PCF)
fixed4 frag_pcfSoft(v2f i) : SV_Target { }
ENDCG
// 子着色器:硬阴影(SM 2.0 及以下)
SubShader {
Tags{ "ShadowmapFilter" = "HardShadow" }
Pass {
//具体代码在下方
}
}
// 子着色器:硬阴影(强制PS阶段逆投影,通用但精度较低)
SubShader {
Tags{ "ShadowmapFilter" = "HardShadow_FORCE_INV_PROJECTION_IN_PS" }
Pass{
//具体代码在下方
}
}
// 子着色器:软阴影(PCF,SM 3.0 及以上)
SubShader {
Tags {"ShadowmapFilter" = "PCF_SOFT"}
Pass {
//具体代码在下方
}
}
// 子着色器:软阴影(强制PS阶段逆投影,SM 3.0 及以上)
SubShader{
Tags{ "ShadowmapFilter" = "PCF_SOFT_FORCE_INV_PROJECTION_IN_PS" }
Pass{
//具体代码在下方
}
}
Fallback Off // 无备用着色器
}
二、HardShadow
// 子着色器:硬阴影(SM 2.0 及以下)
SubShader {
Tags{ "ShadowmapFilter" = "HardShadow" }
Pass {
ZWrite Off ZTest Always Cull Off // 禁用深度写入和剔除
CGPROGRAM
#pragma vertex vert
#pragma fragment frag_hard
#pragma multi_compile_shadowcollector // 多编译阴影收集器变体
// 使用顶点着色器传递的中间数据计算相机空间坐标
inline float3 computeCameraSpacePosFromDepth(v2f i) {
return computeCameraSpacePosFromDepthAndVSInfo(i);
}
ENDCG
}
}
1.vert
v2f vert (appdata v)
{
v2f o;
UNITY_SETUP_INSTANCE_ID(v); // 如果使用了GPU实例化技术,则设置实例ID,以便在顶点着色器中正确访问实例相关的数据。
UNITY_TRANSFER_INSTANCE_ID(v, o); // 将实例ID从输入结构体传输到输出结构体,使得片元着色器可以访问这个ID。
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o); // 初始化与立体渲染相关的输出数据,确保左右眼图像正确生成。
float4 clipPos;
#if defined(STEREO_CUBEMAP_RENDER_ON) // 如果定义了STEREO_CUBEMAP_RENDER_ON宏,表示正在对立体环境贴图进行渲染。
clipPos = mul(UNITY_MATRIX_VP, mul(unity_ObjectToWorld, v.vertex)); // 先将顶点坐标从对象空间转换到世界空间,然后转换到裁剪空间。
#else // 否则,默认情况下的处理方式。
clipPos = UnityObjectToClipPos(v.vertex); // 直接将顶点坐标从对象空间转换为裁剪空间。
#endif
o.pos = clipPos; // 将计算得到的裁剪空间坐标赋值给输出结构体的pos成员。
o.uv.xy = v.texcoord; // 从输入结构体中获取纹理坐标,并赋值给输出结构体的uv成员的xy分量。
// unity_CameraInvProjection at the PS level.
o.uv.zw = ComputeNonStereoScreenPos(clipPos); // 计算屏幕空间位置,并赋值给uv成员的zw分量,注意这里指的是非立体渲染的情况。
// Perspective case
#if defined(UNITY_STEREO_INSTANCING_ENABLED) || defined(UNITY_STEREO_MULTIVIEW_ENABLED)
// 如果启用了立体渲染的实例化或多重视图功能,根据当前是左眼还是右眼选择正确的光线向量。
o.ray = unity_StereoEyeIndex == 0 ? v.ray0 : v.ray1;
#else
// 默认情况下,直接使用传入的光线向量。
o.ray = v.ray;
#endif
}
2.frag_hard
fixed4 frag_hard (v2f i) : SV_Target
{
UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(i); // 在顶点处理后设置立体视觉的眼睛索引,这对于正确采样阴影贴图纹理数组中的对应切片是必需的。
float4 wpos; // 声明一个float4类型的变量wpos,用于存储世界空间位置。
float3 vpos; // 声明一个float3类型的变量vpos,用于存储相机空间位置。
#if defined(STEREO_CUBEMAP_RENDER_ON) // 如果定义了STEREO_CUBEMAP_RENDER_ON宏,则表示正在进行立体全景图渲染。
// 使用提供的世界坐标纹理来获取世界空间位置
wpos.xyz = tex2D(_ODSWorldTexture, i.uv.xy).xyz; // 从_ODSWorldTexture纹理中根据uv坐标采样得到世界空间位置的xyz分量。
wpos.w = 1.0f; // 设置w分量为1.0f,以确保正确的齐次坐标转换。
// 将世界坐标转换为相机空间坐标
vpos = mul(unity_WorldToCamera, wpos).xyz; // 通过乘以unity_WorldToCamera矩阵将wpos从世界空间转换到相机空间。
#else // 如果没有启用立体全景图渲染。
// 计算相机空间的位置
vpos = computeCameraSpacePosFromDepth(i); // 根据深度信息计算相机空间位置。
// 将相机空间位置转换回世界空间位置
wpos = mul (unity_CameraToWorld, float4(vpos,1)); // 通过乘以unity_CameraToWorld矩阵将vpos从相机空间转换回到世界空间。
#endif
// 获取级联权重
fixed4 cascadeWeights = GET_CASCADE_WEIGHTS(wpos, vpos.z); // 根据世界空间位置和深度计算级联阴影映射的权重。
// 获取阴影坐标
float4 shadowCoord = GET_SHADOW_COORDINATES(wpos, cascadeWeights); // 根据世界空间位置和级联权重计算阴影坐标。
// 执行单次采样以确定是否处于阴影中
fixed shadow = UNITY_SAMPLE_SHADOW(_ShadowMapTexture, shadowCoord); // 使用阴影坐标在_ShadowMapTexture上进行采样,判断当前像素是否处于阴影中。
// 根据阴影结果混合颜色
shadow = lerp(_LightShadowData.r, 1.0, shadow); // 使用线性插值(lerp)函数,基于阴影结果混合光照阴影数据和全亮的颜色。
fixed4 res = shadow; // 将shadow结果赋值给输出颜色res。
return res; // 返回最终的颜色结果。
}
3.computeCameraSpacePosFromDepthAndVSInfo
/**
* 根据深度纹理和视图相关的参数计算一个给定点在相机空间中的坐标
*/
inline float3 computeCameraSpacePosFromDepthAndVSInfo(v2f i)
{
// 从_CameraDepthTexture纹理中根据uv坐标采样得到当前像素的深度值zdepth。
float zdepth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv.xy);
// 将非线性深度值转换为0到1范围内的线性深度值。对于正交投影,直接使用原始深度值zdepth。
// unity_OrthoParams.w用来区分是否是正交投影:0表示透视投影,1表示正交投影。
float depth = lerp(Linear01Depth(zdepth), zdepth, unity_OrthoParams.w);
#if defined(UNITY_REVERSED_Z)
// 如果定义了UNITY_REVERSED_Z宏,则反转深度值,因为Unity在某些平台上使用反向Z缓冲区。
zdepth = 1 - zdepth;
#endif
// 计算视角位置,针对透视投影的情况:
// 使用i.ray(从顶点着色器传过来的光线方向)乘以depth(深度值),得到视角下的位置vposPersp。
float3 vposPersp = i.ray * depth;
// 针对正交投影的情况:
// 使用线性插值lerp函数,基于zdepth在i.orthoPosNear(近裁剪面位置)和i.orthoPosFar(远裁剪面位置)之间进行插值,得到视角下的位置vposOrtho。
float3 vposOrtho = lerp(i.orthoPosNear, i.orthoPosFar, zdepth);
// 根据是否为正交投影选择合适的视角位置:
// unity_OrthoParams.w为1时选择vposOrtho(正交投影情况),否则选择vposPersp(透视投影情况)。
float3 camPos = lerp(vposPersp, vposOrtho, unity_OrthoParams.w);
// 返回计算得到的相机空间坐标。
return camPos.xyz;
}
4.GET_CASCADE_WEIGHTS
// 如果定义了SHADOWS_SPLIT_SPHERES宏,则使用getCascadeWeights_splitSpheres函数计算级联权重。
#if defined (SHADOWS_SPLIT_SPHERES)
// 定义宏GET_CASCADE_WEIGHTS,当调用时将使用世界坐标wpos作为参数调用getCascadeWeights_splitSpheres函数。
#define GET_CASCADE_WEIGHTS(wpos, z) getCascadeWeights_splitSpheres(wpos)
#else
// 否则,默认情况下使用getCascadeWeights函数计算级联权重,该函数需要世界坐标wpos和深度值z作为参数。
#define GET_CASCADE_WEIGHTS(wpos, z) getCascadeWeights(wpos, z)
#endif
// 如果定义了SHADOWS_SINGLE_CASCADE宏,则处理单个级联的情况。
#if defined (SHADOWS_SINGLE_CASCADE)
// 定义宏GET_SHADOW_COORDINATES,当调用时将仅使用世界坐标wpos作为参数调用getShadowCoord_SingleCascade函数。
#define GET_SHADOW_COORDINATES(wpos,cascadeWeights) getShadowCoord_SingleCascade(wpos)
#else
// 否则,默认情况下使用getShadowCoord函数获取阴影坐标,该函数需要世界坐标wpos和级联权重cascadeWeights作为参数。
#define GET_SHADOW_COORDINATES(wpos,cascadeWeights) getShadowCoord(wpos,cascadeWeights)
#endif
5.getCascadeWeights_splitSpheres
/**
* 基于片段的世界位置以及每个级联分割球体的位置来获取级联权重。
* 返回一个仅有一个组件设置为对应适当级联的float4值。
*
* @param wpos 片元的世界坐标。
* @return 四个浮点数,每个代表一个级联是否适用(0或1)。
*/
inline fixed4 getCascadeWeights_splitSpheres(float3 wpos)
{
// 计算当前世界位置到每个分割球体中心的距离向量。
float3 fromCenter0 = wpos.xyz - unity_ShadowSplitSpheres[0].xyz;
float3 fromCenter1 = wpos.xyz - unity_ShadowSplitSpheres[1].xyz;
float3 fromCenter2 = wpos.xyz - unity_ShadowSplitSpheres[2].xyz;
float3 fromCenter3 = wpos.xyz - unity_ShadowSplitSpheres[3].xyz;
// 计算这些距离向量的平方长度。
float4 distances2 = float4(dot(fromCenter0, fromCenter0), dot(fromCenter1, fromCenter1), dot(fromCenter2, fromCenter2), dot(fromCenter3, fromCenter3));
// 如果距离平方小于相应的分割球体半径平方,则认为该级联适用。
fixed4 weights = float4(distances2 < unity_ShadowSplitSqRadii);
// 确保只有一个级联被选中,通过饱和减法消除其他级联的影响。
weights.yzw = saturate(weights.yzw - weights.xyz);
return weights;
}
6.getCascadeWeights
/**
* 根据片段的世界位置和深度值获取级联权重。
* 返回一个仅有一个组件设置为对应适当级联的float4值。
*
* @param wpos 片元的世界坐标。
* @param z 深度值。
* @return 四个浮点数,每个代表一个级联是否适用(0或1)。
*/
inline fixed4 getCascadeWeights(float3 wpos, float z)
{
// 检查深度z是否大于等于每个级联的近裁剪面距离。
fixed4 zNear = float4(z >= _LightSplitsNear);
// 检查深度z是否小于每个级联的远裁剪面距离。
fixed4 zFar = float4(z < _Light_SplitsFar);
// 计算级联权重,只有在z同时满足zNear和zFar条件时,权重才为1。
fixed4 weights = zNear * zFar;
return weights;
}
7.getShadowCoord_SingleCascade
/**
* 同getShadowCoord函数,但针对单个级联进行了优化。
*
* @param wpos 片元的世界坐标。
* @return 针对单个级联优化后的阴影贴图坐标。
*/
inline float4 getShadowCoord_SingleCascade(float4 wpos)
{
// 直接将世界坐标转换为第一个级联的阴影坐标,并设置w分量为0。
return float4(mul(unity_WorldToShadow[0], wpos).xyz, 0);
}
8.getShadowCoord
/**
* 根据给定的世界位置和z深度返回阴影映射坐标。
* 这些坐标属于包含所有级联的地图的阴影映射图集。
*
* @param wpos 片元的世界坐标。
* @param cascadeWeights 级联权重。
* @return 阴影贴图坐标。
*/
inline float4 getShadowCoord(float4 wpos, fixed4 cascadeWeights)
{
// 将世界坐标转换为每个级联的阴影坐标。
float3 sc0 = mul(unity_WorldToShadow[0], wpos).xyz;
float3 sc1 = mul(unity_WorldToShadow[1], wpos).xyz;
float3 sc2 = mul(unity_WorldToShadow[2], wpos).xyz;
float3 sc3 = mul(unity_WorldToShadow[3], wpos).xyz;
// 根据级联权重混合这些阴影坐标。
float4 shadowMapCoordinate = float4(sc0 * cascadeWeights[0] + sc1 * cascadeWeights[1] + sc2 * cascadeWeights[2] + sc3 * cascadeWeights[3], 1);
#if defined(UNITY_REVERSED_Z)
// 如果启用了反向Z缓冲区,则调整z坐标以避免精度问题。
float noCascadeWeights = 1 - dot(cascadeWeights, float4(1, 1, 1, 1));
shadowMapCoordinate.z += noCascadeWeights;
#endif
return shadowMapCoordinate;
}
三、HardShadow_FORCE_INV_PROJECTION_IN_PS
// ----------------------------------------------------------------------------------------
// 子着色器:硬阴影(强制PS阶段逆投影,通用但精度较低)
SubShader {
Tags{ "ShadowmapFilter" = "HardShadow_FORCE_INV_PROJECTION_IN_PS" }
Pass{
ZWrite Off ZTest Always Cull Off
CGPROGRAM
#pragma vertex vert
#pragma fragment frag_hard
#pragma multi_compile_shadowcollector
// 在PS阶段使用逆投影矩阵计算坐标(兼容性更高但性能较低)
inline float3 computeCameraSpacePosFromDepth(v2f i) {
return computeCameraSpacePosFromDepthAndInvProjMat(i);
}
ENDCG
}
}
1.vert
同HardShadow
2.frag_hard
同HardShadow
3.computeCameraSpacePosFromDepthAndInvProjMat
/**
* 从深度信息和逆投影矩阵获取相机空间坐标。
*/
inline float3 computeCameraSpacePosFromDepthAndInvProjMat(v2f i)
{
// 使用提供的_CameraDepthTexture纹理和uv坐标采样得到当前像素的深度值zdepth。
float zdepth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv.xy);
#if defined(UNITY_REVERSED_Z)
// 如果定义了UNITY_REVERSED_Z宏,则反转深度值,因为Unity在某些平台上使用反向Z缓冲区。
zdepth = 1 - zdepth;
#endif
// 视角位置计算,适用于倾斜裁剪的投影情况:
// 这种方法不如通过插值光线和深度计算的方法精确或快速,但它能处理更复杂的投影方式。
// 根据i.uv.zw(通常包含未变换的屏幕空间坐标)和zdepth构造clipPos。
float4 clipPos = float4(i.uv.zw, zdepth, 1.0);
// 将clipPos转换为NDC(标准化设备坐标),范围从[0,1]转换到[-1,1]。
clipPos.xyz = 2.0f * clipPos.xyz - 1.0f;
// 使用unity_CameraInvProjection矩阵将裁剪空间坐标转换回相机空间坐标。
float4 camPos = mul(unity_CameraInvProjection, clipPos);
// 透视除法:将xyz分量除以w分量,得到最终的相机空间坐标。
camPos.xyz /= camPos.w;
// 因为相机空间中的z轴方向通常是相反的,所以需要对z分量取反。
camPos.z *= -1;
// 返回计算得到的相机空间坐标。
return camPos.xyz;
}
四、PCF_SOFT
SubShader {
Tags {"ShadowmapFilter" = "PCF_SOFT"}
Pass {
ZWrite Off ZTest Always Cull Off
CGPROGRAM
#pragma vertex vert
#pragma fragment frag_pcfSoft
#pragma multi_compile_shadowcollector
#pragma target 3.0 // 需要SM 3.0 支持
// 使用顶点着色器传递的中间数据计算坐标
inline float3 computeCameraSpacePosFromDepth(v2f i) {
return computeCameraSpacePosFromDepthAndVSInfo(i);
}
ENDCG
}
}
1.vert
同HardShadow
2.frag_pcfSoft
/**
* 软阴影 (SM 3.0)
*/
fixed4 frag_pcfSoft(v2f i) : SV_Target // 定义片元着色器函数frag_pcfSoft,接收一个v2f类型的输入结构体i,并返回一个fixed4类型的结果。
{
UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(i); // 在顶点处理后设置立体视觉的眼睛索引,这对于正确采样阴影贴图纹理数组中的对应切片是必需的。
float4 wpos; // 声明一个float4类型的变量wpos,用于存储世界空间位置。
float3 vpos; // 声明一个float3类型的变量vpos,用于存储相机空间位置。
#if defined(STEREO_CUBEMAP_RENDER_ON) // 如果定义了STEREO_CUBEMAP_RENDER_ON宏,则表示正在进行立体全景图渲染。
wpos.xyz = tex2D(_ODSWorldTexture, i.uv.xy).xyz; // 使用提供的_ODSWorldTexture纹理和uv坐标采样得到世界空间位置的xyz分量。
wpos.w = 1.0f; // 设置w分量为1.0f,以确保正确的齐次坐标转换。
vpos = mul(unity_WorldToCamera, wpos).xyz; // 将世界坐标转换为相机空间坐标。
#else
vpos = computeCameraSpacePosFromDepth(i); // 根据深度信息计算相机空间位置。
// 样本属于的级联
wpos = mul(unity_CameraToWorld, float4(vpos,1)); // 将相机空间位置转换回世界空间位置。
#endif
fixed4 cascadeWeights = GET_CASCADE_WEIGHTS(wpos, vpos.z); // 获取级联权重,确定当前像素属于哪个阴影级联。
float4 coord = GET_SHADOW_COORDINATES(wpos, cascadeWeights); // 计算阴影坐标。
float3 receiverPlaneDepthBias = 0.0;
#ifdef UNITY_USE_RECEIVER_PLANE_BIAS
// 接收平面深度偏移:需要基于第一个级联中的阴影坐标计算,否则在级联边界处的导数会出错。
float3 coordCascade0 = getShadowCoord_SingleCascade(wpos); // 获取单个级联的阴影坐标。
float biasMultiply = dot(cascadeWeights, unity_ShadowCascadeScales); // 根据级联权重计算偏移乘数。
receiverPlaneDepthBias = UnityGetReceiverPlaneDepthBias(coordCascade0.xyz, biasMultiply); // 计算接收平面深度偏移。
#endif
#if defined(SHADER_API_MOBILE)
half shadow = UnitySampleShadowmap_PCF5x5(coord, receiverPlaneDepthBias); // 对于移动平台,使用5x5的PCF进行阴影采样。
#else
half shadow = UnitySampleShadowmap_PCF7x7(coord, receiverPlaneDepthBias); // 对于其他平台,使用7x7的PCF进行阴影采样。
#endif
shadow = lerp(_LightShadowData.r, 1.0f, shadow); // 使用线性插值混合阴影结果。
// 如果启用了级联混合并且不是使用分割球或单一级联的情况,则在此进行级联间的混合。
//
// 目前不支持分割球,并且当只有一个级联时不需要混合。
#if UNITY_USE_CASCADE_BLENDING && !defined(SHADOWS_SPLIT_SPHERES) && !defined(SHADOWS_SINGLE_CASCADE)
half4 z4 = (float4(vpos.z,vpos.z,vpos.z,vpos.z) - _LightSplitsNear) / (_LightSplitsFar - _LightSplitsNear); // 计算z值在各个级联中的比例。
half alpha = dot(z4 * cascadeWeights, half4(1,1,1,1)); // 计算当前像素所属的级联alpha值。
UNITY_BRANCH
if (alpha > 1 - UNITY_CASCADE_BLEND_DISTANCE) // 如果alpha值超过指定范围,则进行级联混合。
{
// 将alpha调整到0..1范围内,以便在混合距离内平滑过渡。
alpha = (alpha - (1 - UNITY_CASCADE_BLEND_DISTANCE)) / UNITY_CASCADE_BLEND_DISTANCE;
// 采样下一个级联
cascadeWeights = fixed4(0, cascadeWeights.xyz); // 更新级联权重,指向下一个级联。
coord = GET_SHADOW_COORDINATES(wpos, cascadeWeights); // 重新获取阴影坐标。
#ifdef UNITY_USE_RECEIVER_PLANE_BIAS
biasMultiply = dot(cascadeWeights, unity_ShadowCascadeScales); // 重新计算偏移乘数。
receiverPlaneDepthBias = UnityGetReceiverPlaneDepthBias(coordCascade0.xyz, biasMultiply); // 重新计算接收平面深度偏移。
#endif
half shadowNextCascade = UnitySampleShadowmap_PCF3x3(coord, receiverPlaneDepthBias); // 对下一个级联进行阴影采样。
shadowNextCascade = lerp(_LightShadowData.r, 1.0f, shadowNextCascade); // 混合阴影结果。
shadow = lerp(shadow, shadowNextCascade, alpha); // 根据alpha值在两个级联之间进行线性插值。
}
#endif
return shadow; // 返回最终的阴影结果。
}
3.computeCameraSpacePosFromDepthAndVSInfo
同HardShadow
五、PCF_SOFT_FORCE_INV_PROJECTION_IN_PS
SubShader{
Tags{ "ShadowmapFilter" = "PCF_SOFT_FORCE_INV_PROJECTION_IN_PS" }
Pass{
ZWrite Off ZTest Always Cull Off
CGPROGRAM
#pragma vertex vert
#pragma fragment frag_pcfSoft
#pragma multi_compile_shadowcollector
#pragma target 3.0
// 在PS阶段使用逆投影矩阵计算坐标
inline float3 computeCameraSpacePosFromDepth(v2f i) {
return computeCameraSpacePosFromDepthAndInvProjMat(i);
}
ENDCG
}
}
1.vert
同HardShadow
2.frag_pcfSoft
同PCF_SOFT
3.computeCameraSpacePosFromDepthAndInvProjMat
同HardShadow_FORCE_INV_PROJECTION_IN_PS