在Unity Shader的LightMode
标签中,除了前向渲染和延迟渲染外,还支持多种渲染模式设置。以下是主要分类及用途:
一、核心渲染路径模式
-
前向渲染相关
ForwardBase
用于基础光照计算,处理环境光、主平行光、逐顶点/SH光源及光照贴图。ForwardAdd
处理额外逐像素光源,每个Pass对应一个光源(如点光源、聚光灯)。
-
延迟渲染相关
Deferred
用于现代延迟渲染路径,负责将几何数据写入G-Buffer(法线、位置、材质参数等)。
二、辅助功能模式
3. 阴影处理
ShadowCaster
将物体深度信息写入阴影贴图或深度纹理,用于生成动态阴影。
- 遗留模式
PrepassBase
旧版延迟渲染的第一阶段,渲染法线和高光反射的指数部分。PrepassFinal
旧版延迟渲染的最终阶段,合并光照和自发光生成最终颜色。
三、特殊用途模式
5. 无条件渲染
Always
无论当前渲染路径如何,该Pass总被执行,但不参与光照计算(如全屏后处理)。
- 顶点光照模式(已弃用)
Vertex
、VertexLMRGBM
、VertexLM
用于旧版顶点照明渲染路径,Unity 5.0后已废弃。
四、选择建议
- 多光源场景:优先使用延迟渲染(
Deferred
),通过G-Buffer优化计算。 - 移动端优化:前向渲染(
ForwardBase
+ForwardAdd
)更节省带宽。 - 阴影生成:必须包含
ShadowCaster
Pass以保证动态阴影正常渲染。
具体设置示例:
Pass {Tags { "LightMode" = "ForwardBase" } // 主光源Pass // Shader代码...
}Pass {Tags { "LightMode" = "ShadowCaster" } // 阴影投射Pass // 阴影生成逻辑...
}
前向渲染
一、核心渲染路径类型
前向渲染路径(Forward Rendering)
ForwardBase:处理主平行光(逐像素)、环境光、光照贴图及顶点光照,必须存在且仅调用一次。
ForwardAdd:处理附加逐像素光源(点光源、聚光灯、次要平行光),每盏光源触发一次 Pass 调用。
以下是 Unity 前向渲染中 ForwardAdd Pass 的关键设定与注意事项:
一、基础配置
混合模式
必须设置 Blend One One:叠加多光源贡献(默认 Blend Off 会导致后续光源覆盖先前结果)。
错误示例:未启用混合时,仅保留最后一次光源计算结果。
深度缓冲
关闭深度写入:设置 ZWrite Off,避免覆盖 ForwardBase 写入的深度值。
Pass 标签
强制声明 LightMode:
glsl
Copy Code
Tags { “LightMode” = “ForwardAdd” }
否则 Unity 无法识别为附加光源处理 Pass。
二、光源处理规则
光源分配逻辑
仅处理逐像素光源:包括点光源、聚光灯及次要方向光,且需满足以下条件之一:
光源的 Render Mode 设为 Important
光源强度在场景中排名靠前(受 QualitySettings.pixelLightCount 限制)
每光源触发一次 Pass:场景中有 N 个逐像素光源时,ForwardAdd 会被调用 N-1 次(主方向光由 ForwardBase 处理)。
光源数据获取
_WorldSpaceLightPos0:
方向光时为世界空间方向向量
点光源/聚光灯时为世界空间坐标
_LightColor0:当前光源颜色与强度(含衰减后的值)。
三、阴影与衰减
阴影支持
需显式启用宏:
glsl
Copy Code
#pragma multi_compile_fwdadd_fullshadows
否则附加光源的阴影不会生效。
使用 UNITY_LIGHT_ATTENUATION:自动计算光源衰减与阴影(需包含 AutoLight.cginc)。
衰减纹理
点光源/聚光灯依赖 _LightTexture0:Unity 自动根据光源类型生成衰减纹理。
四、性能优化
控制光源数量
减少 Important 模式光源:避免过多逐像素光源触发 ForwardAdd Pass。
调整 pixelLightCount:在 Project Settings > Quality 中限制最大逐像素光源数。
剔除不必要计算
禁用无关宏:若无需阴影,移除 _fullshadows 以减少 Shader 变体。
简化光照计算:在 ForwardAdd 中避免复杂运算(如 PBR 高光)。
五、调试建议
Frame Debugger
查看每个 ForwardAdd Pass 对应的实际光源及调用次数。
光源排序验证
通过 _LightColor0 输出颜色值,确认光源是否按强度降序处理。
六、典型代码示例
glsl
Copy Code
Pass {
Tags { “LightMode” = “ForwardAdd” }
Blend One One // 叠加模式
ZWrite Off
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_fwdadd_fullshadows // 阴影支持
#include "UnityCG.cginc"
#include "AutoLight.cginc" // 衰减与阴影计算struct v2f {float4 pos : SV_POSITION;float3 worldPos : TEXCOORD0;float3 normal : TEXCOORD1;UNITY_SHADOW_COORDS(2) // 阴影坐标
};v2f vert (appdata_base v) {v2f o;o.pos = UnityObjectToClipPos(v.vertex);o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;o.normal = UnityObjectToWorldNormal(v.normal);UNITY_TRANSFER_SHADOW(o, o.worldPos); // 传递阴影数据return o;
}fixed4 frag (v2f i) : SV_Target {float3 lightDir = normalize(_WorldSpaceLightPos0.xyz - i.worldPos); // 点光源方向float3 diffuse = _LightColor0.rgb * max(0, dot(i.normal, lightDir));UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos); // 衰减+阴影return fixed4(diffuse * atten, 1.0);
}
ENDCG
}
总结
ForwardAdd 的核心设定包括:混合模式、光源筛选规则、阴影宏启用、衰减计算及性能优化策略。正确配置后可实现高效多光源叠加,同时需避免因逐像素光源过多导致的性能瓶颈。
在ForwardAdd中,可能需要判断光源类型,距离,强度等,来得出正确的效果
// 片段着色器函数,输入结构体v2f,返回像素颜色
fixed4 frag(v2f i) : SV_Target {// 规范化世界空间法线(从顶点着色器插值得到)fixed3 worldNormal = normalize(i.worldNormal);// 根据光源类型计算光线方向#ifdef USING_DIRECTIONAL_LIGHT// 方向光:_WorldSpaceLightPos0直接存储方向向量fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);#else// 点光源/聚光灯:需要计算光源到片段的向量(_WorldSpaceLightPos0存储的是光源位置)fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz - i.worldPos.xyz);#endif// 漫反射计算(Lambert光照模型)// _LightColor0:当前光源颜色和强度// _Diffuse:材质漫反射颜色fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir));// 高光计算(Blinn-Phong模型)// 计算视线方向:摄像机位置 - 片段位置fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);// 计算半角向量:光线方向 + 视线方向fixed3 halfDir = normalize(worldLightDir + viewDir);// _Specular:材质高光颜色,_Gloss:高光指数fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);// 衰减计算#ifdef USING_DIRECTIONAL_LIGHT// 方向光没有衰减fixed atten = 1.0;#else#if defined (POINT)// 点光源衰减计算(使用立方体贴图衰减)// 将世界坐标转换到光源空间float3 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1)).xyz;// 根据距离采样衰减纹理(dot(lightCoord,lightCoord)得到距离平方)fixed atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;#elif defined (SPOT)// 聚光灯衰减计算(结合角度衰减和距离衰减)float4 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1));// 角度衰减:使用投影纹理坐标float spotAtten = (lightCoord.z > 0) * tex2D(_LightTexture0, lightCoord.xy / lightCoord.w + 0.5).w;// 距离衰减:使用二次衰减纹理float distAtten = tex2D(_LightTextureB0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;fixed atten = spotAtten * distAtten;#else// 未知光源类型默认无衰减fixed atten = 1.0;#endif#endif// 最终颜色 = (漫反射 + 高光) * 衰减return fixed4((diffuse + specular) * atten, 1.0);
}
二、阴影
开启阴影对硬件资源的影响分析
一、核心硬件影响维度
-
GPU负载显著提升
- 阴影渲染涉及大量光栅化计算和像素填充率消耗,尤其是动态阴影和高质量软阴影
- 实时光追阴影的计算复杂度更高,可能占用50%以上的GPU算力(如《黑神话:悟空》开启光追阴影时)
-
CPU参与计算
- 负责处理物体位置关系和光源投影逻辑等基础数据
- 多光源场景中,CPU需为每个光源生成阴影贴图计算指令
-
显存占用增加
- 高分辨率阴影贴图可能占用200MB+显存(如4K阴影贴图)
- 虚拟阴影技术可降低显存消耗但需要GPU支持
二、硬件影响程度对比
硬件类型 | 影响程度 | 典型场景案例 |
---|---|---|
GPU | ★★★★★ | 开启高质量软阴影后帧率下降40%+ |
CPU | ★★☆ | 百人同屏战斗时阴影计算导致CPU占用提升15% |
内存 | ★☆☆ | 8GB显存显卡开启最高阴影需预留2GB空间 |
-
阴影的基础知识
阴影是 3D 图形中用于表现物体遮挡光线的重要技术,能够增强场景的真实感。在 Unity 中,阴影主要分为以下几种类型:
实时阴影(Real-time Shadows):适用于动态物体和光源,实时计算阴影效果。
烘焙阴影(Baked Shadows):适用于静态物体和光源,预先计算并存储在光照贴图中。
阴影贴图(Shadow Maps):一种常见的实时阴影技术,通过渲染光源视角下的深度图来判断哪些区域被遮挡。
在 Unity Shader 中,阴影的实现主要依赖 阴影贴图(Shadow Maps) 技术。 -
Unity 中的阴影实现
1.渲染阴影投射:
Unity 会为每个投射阴影的光源(如方向光)生成一张阴影贴图。这张贴图记录了从光源视角看到的场景深度信息。
2,物体的 Shader 需要包含一个特殊的 Pass(称为 “ShadowCaster” Pass),以将物体的深度信息渲染到阴影贴图中。
采样阴影贴图:
在渲染物体时,Shader 会根据物体的世界坐标采样阴影贴图,判断当前像素是否处于阴影中。
Unity 提供了内置函数(如 UnitySampleShadow 或 SHADOW_ATTENUATION),方便开发者获取阴影信息。
3.应用阴影效果:
根据采样结果,Shader 调整光照强度(通常是乘以一个阴影衰减值),从而实现阴影的显示。 -
自定义阴影 Shader 的编写
要添加pass
Pass
{
Tags { “LightMode” = “ShadowCaster” }}
}
这样子就有阴影
3,阴影原理
深度比较原理与阴影判断逻辑
核心判断逻辑
当屏幕空间像素在光源空间下的深度值 > 阴影映射纹理中的对应值时,该像素处于阴影中。这一结论源于以下三维空间遮挡关系的数学表达:
-
光源视角的深度记录
阴影映射纹理记录了光源可见范围内所有物体表面的最近深度值(即光源到物体表面的最小距离)。 -
当前像素的深度计算
将屏幕空间像素的坐标转换到光源空间后,计算其到光源的距离(即当前深度值)。 -
遮挡关系判定
- 若当前深度值 > 阴影映射值 → 光源路径上存在更近的物体遮挡 → 处于阴影中
- 若当前深度值 ≤ 阴影映射值 → 无遮挡 → 未被阴影覆盖
(注:此处的比较方向需根据坐标系定义调整,部分API可能相反)
几何原理可视化
- 光源空间坐标系
- Z轴方向:通常指向光源正前方(左手或右手系根据引擎定义)
- 深度值:沿Z轴方向的距离(范围由投影矩阵决定)
- 实例分析
| 位置状态 | 光源深度图值 | 当前像素深度值 | 结果 |
|--------------------|--------------|----------------|----------|
| 遮挡物表面点 | 0.3 | 0.5 | 阴影区域 |
| 遮挡物自身表面点 | 0.3 | 0.3 | 非阴影 |
| 无遮挡区域 | 0.3 | 0.2 | 非阴影 |
技术实现关键点
- 坐标系对齐
- 需保证屏幕空间与光源空间的坐标转换矩阵一致(涉及VP矩阵链式乘法)
- 常见误差来源:浮点数精度问题、非均匀深度分布
Screen Space Shadow Mapping(SSSM)与传统Shadow Mapping对比解析
一、核心差异对比
-
处理阶段与数据来源
| 技术类型 | 处理阶段 | 数据来源 | 计算范围 |
|--------------------|-------------------|----------------------------|---------------------|
| 传统Shadow Mapping | 物体渲染阶段(Forward模式) | 光源视角生成的深度图(ShadowMap) | 场景中所有可能投射阴影的物体 |
| SSSM | 屏幕空间阶段(Deferred模式) | 摄像机深度图+光源ShadowMap | 仅通过深度测试的可见像素 | -
实现原理差异
-
传统Shadow Mapping
- 光源视角渲染场景生成ShadowMap(记录最近深度)
- 实际渲染时,将物体顶点转换到光源空间,与ShadowMap中的深度对比
- 必须处理所有可能遮挡的物体,即使这些物体在摄像机视角不可见
-
Screen Space Shadow Mapping
- 预生成摄像机深度图(屏幕空间可见像素的深度)
- 结合光源ShadowMap,仅在屏幕空间中对可见像素进行阴影计算
- 通过
CollectShadows
阶段生成屏幕空间阴影纹理,其他物体采样该纹理即可
二、性能与效率对比
- 传统Shadow Mapping的局限性
- Overdraw问题:在Forward渲染中,阴影计算可能因物体覆盖而被浪费
- 全场景计算:需处理所有可能投射阴影的物体,包括视锥体外不可见物体
- SSSM的优势
- 精准裁剪:仅处理摄像机可见像素,减少冗余计算
- 硬件优化:利用屏幕空间深度图(如
_CameraDepthTexture
),避免重复渲染 - 移动端友好:统计显示优化后的SSSM在移动端可提升30%+渲染效率
三、应用场景选择
- 推荐使用SSSM的情况
- Deferred渲染管线:需屏幕空间数据支持
- 复杂场景交互:当存在大量动态物体且需要高效剔除不可见阴影时
- 移动端项目:需减少GPU计算负载的场景
- 传统Shadow Mapping的适用场景
- 简单场景/低端设备:硬件不支持SSSM时
- 特殊效果需求:如需要自定义阴影投射逻辑(如透明物体阴影)
四、开发者注意事项
-
深度图生成
- SSSM依赖
_CameraDepthTexture
,需确保摄像机开启深度渲染 - 传统方法需实现
ShadowCaster
Pass生成光源ShadowMap
- SSSM依赖
-
坐标系转换
- 在SSSM中需将屏幕空间坐标转换到光源空间:
float4 lightSpacePos = mul(_LightMatrix, float4(worldPos, 1.0));
- 在SSSM中需将屏幕空间坐标转换到光源空间:
-
性能权衡
- SSSM在复杂场景中更高效,但会占用更多显存(存储屏幕空间阴影纹理)
- 传统方法在小场景中可能更节省资源
半透明物体的阴影技术解析
核心结论
是的,半透明物体在 Shadow Mapping 中可以通过特定方法生成阴影,但在 Screen Space Shadow Mapping 中通常无法正确显示阴影。具体原因如下:
- Shadow Mapping 中的表现
实现原理
- 传统阴影贴图技术:通过光源视角生成深度图(ShadowMap),在渲染时比较物体深度与ShadowMap深度判断是否处于阴影中。
- 半透明处理方式:
- 透明度测试(Alpha Test):通过
clip()
丢弃部分像素,保留深度信息,可在ShadowMap中生成硬边阴影 。 - 透明度混合(Alpha Blend):需自定义Shader,在
ShadowCaster
Pass中强制写入深度,使半透明物体参与阴影计算 。
- 透明度测试(Alpha Test):通过
代码示例
// 自定义ShadowCaster Pass处理半透明阴影
struct v2f_shadow {float4 pos : SV_POSITION;float2 uv : TEXCOORD0;
};v2f_shadow vert_shadow(appdata_base v) {v2f_shadow o;o.pos = UnityObjectToClipPos(v.vertex);o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);return o;
}fixed4 frag_shadow(v2f_shadow i) : SV_Target {fixed alpha = tex2D(_MainTex, i.uv).a;clip(alpha - _Cutoff); // 强制写入深度 return 0;
}
- Screen Space Shadow Mapping 中的限制
技术缺陷
- 依赖深度缓冲:屏幕空间阴影映射基于摄像机深度纹理,但半透明物体默认关闭深度写入(
ZWrite Off
),导致其无法正确参与阴影计算 。 - 混合模式冲突:Alpha混合的物体在屏幕空间中无法生成有效的深度数据,导致阴影缺失。
典型现象
- 半透明物体(如水面)可能投射不完整阴影(仅部分区域可见)或无阴影 。
- 阴影边缘可能出现伪影(Artifacts),因光线追踪算法无法正确处理透明区域 。
- 解决方案对比
| 技术类型 | 适用场景 | 实现难度 | 性能消耗 | 阴影质量 |
|-------------------------|------------------------|----------|----------|----------------|
| Shadow Mapping | 静态光源、硬边阴影 | 中等 | 低 | 高(需优化) |
| Screen Space Shadows| 动态场景、软阴影 | 高 | 中高 | 低(半透明失效)|
-
开发者注意事项
-
渲染队列设置
- 将半透明物体的
RenderQueue
设为AlphaTest
(2500以下),以兼容阴影系统 。
- 将半透明物体的
-
Shader优化
- 使用
alphatest
处理阴影投射,同时保留主Pass的alphablend
效果 。
- 使用
-
光照模式限制
- 屏幕空间阴影要求Shader为
Pixel-Lit
且使用Geometry
渲染队列 。
- 屏幕空间阴影要求Shader为