作用:让物体能够投射阴影
原理:返回裁剪空间的坐标,写入depth buff,让摄像机在光源空间渲染出深度图,再进一步渲染出屏幕阴影纹理(某些平台并没有这一步骤https://blog.csdn.net/zengjunjie59/article/details/111356636),最后在光照pass里面对纹理进行采样。
例子:
Pass
{
Tags
{
"LightMode" = "ShadowCaster"
}
CGPROGRAM
#pragma target 3.0
// 定义相关key,具体看https://blog.csdn.net/zengjunjie59/article/details/111404824
#pragma multi_compile_shadowcaster
#include "CustomShadows.cginc"
#pragma vertex MyShadowVertexProgram
#pragma fragment MyShadowFragmentProgram
ENDCG
}
#if !defined(CUSTOM_SHADOWS_INCLUDE)
#define CUSTOM_SHADOWS_INCLUDE
#include "UnityCG.cginc"
//如果直接调用unity自带的宏,则可以这样写
struct Interpolators{
V2F_SHADOW_CASTER;
};
Interpolators MyShadowVertexProgram(appdata_base v){
Interpolators i;
TRANSFER_SHADOW_CASTER_NORMALOFFSET(i);
return i;
}
float4 MyShadowFragmentProgram(Interpolators i): SV_TARGET {
SHADOW_CASTER_FRAGMENT(i);
}
#endif
首先看第一个宏:V2F_SHADOW_CASTER
源码:
// On D3D reading screen space coordinates from fragment shader requires SM3.0
#define UNITY_POSITION(pos) float4 pos : SV_POSITION
#if defined(SHADOWS_CUBE) && !defined(SHADOWS_CUBE_IN_DEPTH_TEX)
// Rendering into point light (cubemap) shadows
#define V2F_SHADOW_CASTER_NOPOS float3 vec : TEXCOORD0;
#else
// Rendering into directional or spot light shadows
#define V2F_SHADOW_CASTER_NOPOS
// Let embedding code know that V2F_SHADOW_CASTER_NOPOS is empty; so that it can workaround
// empty structs that could possibly be produced.
#define V2F_SHADOW_CASTER_NOPOS_IS_EMPTY
#endif
// Declare all data needed for shadow caster pass output (any shadow directions/depths/distances as needed),
// plus clip space position.
#define V2F_SHADOW_CASTER V2F_SHADOW_CASTER_NOPOS UNITY_POSITION(pos)
其实就相当于:
#if defined(SHADOWS_CUBE) && !defined(SHADOWS_CUBE_IN_DEPTH_TEX)
float4 pos : SV_POSITION // 用于储存裁剪坐标
float3 vec : TEXCOORD0 // 用于储存光源到顶点的世界空间方向向量
#else
float4 pos : SV_POSITION
#endif
顶点着色器里用的宏:TRANSFER_SHADOW_CASTER_NORMALOFFSET(o)
源码:
#if defined(SHADOWS_CUBE) && !defined(SHADOWS_CUBE_IN_DEPTH_TEX)
// 计算世界空间光源到顶点的向量
// 计算顶点裁剪坐标
#define TRANSFER_SHADOW_CASTER_NOPOS(o,opos) o.vec = mul(unity_ObjectToWorld, v.vertex).xyz - _LightPositionRange.xyz; opos = UnityObjectToClipPos(v.vertex);
#else
// 转换到才裁剪空间齐次坐标,并执行顶点法向偏差
// 便宜裁剪齐次坐标的z值,并返回顶点裁剪坐标点
#define TRANSFER_SHADOW_CASTER_NOPOS(o,opos) \
opos = UnityClipSpaceShadowCasterPos(v.vertex, v.normal); \
opos = UnityApplyLinearShadowBias(opos);
#endif
// Vertex shader part, with support for normal offset shadows. Requires
// position and normal to be present in the vertex input.
#define TRANSFER_SHADOW_CASTER_NORMALOFFSET(o) TRANSFER_SHADOW_CASTER_NOPOS(o,o.pos)
片元着色器里用的宏:SHADOW_CASTER_FRAGMENT(i)
源码:
#if defined(SHADOWS_CUBE) && !defined(SHADOWS_CUBE_IN_DEPTH_TEX)
// _LightPositionRange.w是1/Range, Range是光照范围
// UnityEncodeCubeShadowDepth是如果检测到不能用浮点数则把float值的不同位存到rgba各个分量中
// 整段算的就是光源空间下顶点的深度,值域[0, 1], 期间也用了z值偏差(+unity_LightShadowBias.x)
#define SHADOW_CASTER_FRAGMENT(i) return UnityEncodeCubeShadowDepth ((length(i.vec) + unity_LightShadowBias.x) * _LightPositionRange.w);
#else
#define SHADOW_CASTER_FRAGMENT(i) return 0;
#endif
最后:
有个小疑问:点光源,定义SHADOWS_CUBE之后,在SHADOW_CASTER_FRAGMENT宏里的第一个if分支下直接返回0效果并没有什么区别,猜测是某些平台会有用。有知道的人,还望指教指教