在Unity Shader里学习阴影,关键是理解其核心------Shadow Map(阴影贴图),并掌握一套清晰的学习路径。你可以从了解全局开始,然后逐步深入到各个技术点。
🧭 先构建全局视角:阴影技术概览
在深入代码之前,建议先理清这些基础概念,它们能帮你更好地理解后续的具体实现:
-
Shadow Map 原理:它是现代实时阴影技术的基石。过程分两步:第一步,从光源视角将场景深度渲染到一张贴图里;第二步,在正常渲染时,将当前片段的位置转换到光源空间,比较其深度值与Shadow Map中存储的深度值,以此判断该点是否处于阴影中。
-
硬阴影 vs. 软阴影 :光线完全被遮挡会产生边缘锐利的硬阴影 ;而现实中光源有体积,会造成半影区,产生边缘过渡柔和的软阴影,真实感更强,但性能开销也更大。
-
阴影级联 (Shadow Cascades):专为解决方向光(如太阳光)在近处阴影出现的"透视锯齿"问题。它将摄像机的视锥体从近到远划分成多个区域(级联),为近处区域分配更高的阴影贴图分辨率,来保证近处阴影的清晰度。
-
关键API和宏 :编写Shader时,Unity提供了便捷的宏。例如,
SHADOW_COORDS用于定义阴影坐标,TRANSFER_SHADOW用于计算阴影坐标,SHADOW_ATTENUATION用于获取阴影衰减值。 -
不同渲染管线的差异:
-
内置渲染管线:实现方式较直接,但宏较多,容易让新手混淆。
-
URP:推荐新手从URP开始。它的API和命名更清晰,Shader结构也更现代化,是Unity未来的主流方向。
-
HDRP:主要用于追求顶尖画质的项目,默认阴影效果已经很完善,通常不需要开发者从头编写阴影Shader。
-
📚 循序渐进:分步学习指南
1. 产生阴影(投射阴影)
这是实现阴影功能的基础,让物体能够在地面或其他物体上留下影子。
-
核心:
ShadowCasterPass一个物体要向场景投射阴影,它的Shader必须包含
ShadowCaster这个Pass。Unity在渲染阴影贴图时,就是通过它来获取物体从光源视角看到的"样子"。 -
快速实现
-
方法一(推荐新手) :使用
UsePass命令。它能直接复用Unity内置Shader中的ShadowCasterPass,是生成可投射阴影Shader最简单的方法。 -
方法二(手动实现) :在Shader中显式定义一个
ShadowCasterPass。这能让你更深入地理解阴影贴图的渲染过程,但代码会复杂一些。
-
2. 接受阴影
这是让你的Shader能够正确地响应其他物体投射过来的阴影。
-
核心步骤
-
声明阴影坐标 :在顶点着色器的输出结构体(
v2f)中,使用SHADOW_COORDS宏来定义一个用于存储阴影贴图采样坐标的成员。 -
计算阴影坐标 :在顶点着色器中,使用
TRANSFER_SHADOW宏来计算阴影坐标并传递给片元着色器。 -
采样并应用阴影 :在片元着色器中,使用
SHADOW_ATTENUATION宏来获取当前像素的阴影强度(0为全阴影,1为全光照),然后用这个值乘以光照颜色即可得到阴影效果。
-
-
完整的内置管线示例
下面是一个内置管线中实现了接受阴影的完整Shader示例,结合了漫反射光照:
cs
Shader "Lit/Diffuse With Shadows" {
Properties {
_MainTex ("Texture", 2D) = "white" {}
}
SubShader {
Pass {
Tags {"LightMode"="ForwardBase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
#pragma multi_compile_fwdbase nolightmap nodirlightmap nodynlightmap novertexlight
#include "AutoLight.cginc"
struct v2f {
float2 uv : TEXCOORD0;
SHADOW_COORDS(1) // 1. 声明阴影坐标
fixed3 diff : COLOR0;
fixed3 ambient : COLOR1;
float4 pos : SV_POSITION;
};
v2f vert (appdata_base v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = v.texcoord;
half3 worldNormal = UnityObjectToWorldNormal(v.normal);
half nl = max(0, dot(worldNormal, _WorldSpaceLightPos0.xyz));
o.diff = nl * _LightColor0.rgb;
o.ambient = ShadeSH9(half4(worldNormal,1));
TRANSFER_SHADOW(o); // 2. 传递阴影坐标
return o;
}
sampler2D _MainTex;
fixed4 frag (v2f i) : SV_Target {
fixed4 col = tex2D(_MainTex, i.uv);
fixed shadow = SHADOW_ATTENUATION(i); // 3. 获取阴影衰减
fixed3 lighting = i.diff * shadow + i.ambient;
col.rgb *= lighting;
return col;
}
ENDCG
}
UsePass "Legacy Shaders/VertexLit/SHADOWCASTER" // 复用投射阴影的Pass
}
FallBack "Diffuse"
}
3. 级联阴影 (Cascaded Shadow Maps)
主要用于解决方向光阴影在近处的"透视锯齿"问题,通过为不同距离的区域分配不同分辨率的阴影贴图,来保证近处阴影清晰。
-
配置与可视化 :在项目的Quality Settings (内置管线)或URP Asset (URP管线)中设置级联数量(0、2或4)。可以使用Rendering Debugger (Window > Analysis > Rendering Debugger)的Lighting > Shadow Cascades选项来可视化每个级联的覆盖范围,方便调优。
-
Shader支持 :在Shader中,只需要添加对应的预处理宏(如
_MAIN_LIGHT_SHADOWS_CASCADE),引擎会自动处理级联的选择和采样。
4. 软阴影 (Soft Shadows)
通过模拟半影区域让阴影边缘看起来更柔和,增强真实感。
-
如何在项目中开启 :在光源的Inspector面板中,将Shadow Type 设置为Soft Shadows 。在URP Asset中,确保Soft Shadows选项已勾选。
-
性能与技巧:软阴影开销更大,因为它需要对阴影贴图进行多重采样滤波。一个优化技巧是,使用较低的阴影分辨率(如1024)并开启软阴影,反而能获得视觉上更柔和的阴影效果。
-
进阶:PCSS (Percentage-Closer Soft Shadows):这是一种更高级的软阴影技术,能根据遮挡物距离光源的远近,动态改变阴影的软硬程度。PCSS的性能开销很高,需要你编写复杂的自定义Shader或在Asset Store中寻找解决方案。
⚡ 高级技巧与优化
-
材质与物体设置:在编辑器中,物体的Mesh Renderer组件可单独控制是否"Cast Shadows"(投射阴影)和"Receive Shadows"(接受阴影)。
-
处理透明材质 :Alpha Test(镂空)材质需要在
ShadowCasterPass中进行裁剪,以投射出符合形状的阴影。 -
阴影顽疾 (Shadow Artifacts) 的解决 :常见问题包括阴影痤疮 (表面出现条纹状伪影)和Peter Panning (阴影与物体分离),可以通过调整光源设置里的Bias参数来修复。
-
Bias 的深刻影响 :
Bias分为Depth Bias(深度偏移)和Normal Bias(法线偏移)。Depth Bias主要解决阴影痤疮,而Normal Bias主要解决Peter Panning。调整这两个值是阴影调试中最关键的一步,需要根据你的场景反复试验。 -
性能调优
-
级联阴影性能 :级联数量增加会成倍增加渲染开销,建议在
Quality Settings中严格限制阴影的Shadow Distance(阴影距离)。 -
移动平台优化:在移动设备上,尽量使用硬阴影并限制阴影距离。如果必须使用软阴影,务必做好性能分析(Profiling)。
-
复杂几何体阴影:对于高模或远处的物体,可以考虑使用低模(LOD)来投射阴影,以降低GPU负担。
-
🚀 总结与学习路径建议
-
从URP开始:建立新项目,选择URP模板。
-
理解原理:学习Shadow Map的基本原理,通过光源Shadow Type的不同设置,直观对比硬阴影与软阴影、有无级联的视觉效果。
-
编写Shader:使用Shader Graph或手写HLSL Shader,从最基本的"接受阴影"功能开始,确保物体能正确响应光照和阴影。
-
进阶实现 :手动添加
ShadowCasterPass,实现完整的阴影投射与接收。 -
优化与调试:学习调整光源的Bias、Shadow Distance等参数,并使用Frame Debugger和Rendering Debugger来分析和优化阴影性能。