目录
[向量 / 矩阵函数(Shader 核心)](#向量 / 矩阵函数(Shader 核心))
[三角函数(角度 / 弧度相关)](#三角函数(角度 / 弧度相关))
[纹理采样函数(Shader 必备)](#纹理采样函数(Shader 必备))
1).延迟着色渲染路径.延迟着色渲染路径)
2).前向渲染路径.前向渲染路径)
3).两种渲染路径的特征对比.两种渲染路径的特征对比)
1).LightMode标签.LightMode标签)
2).PassFlags标签.PassFlags标签)
1).编写Shader.编写Shader)
2).通过Frame Debug分析渲染流程.通过Frame Debug分析渲染流程)
Lambert光照模型
兰伯特 光照模型
物体表面的光照颜色是由入射光、物体材质,以及材质和光的交互规律共同决定。
将真实世界中的光照交互简化 成了一些简单的数学公式,这些用于计算光照的公式被称为光照模型。
1.Lambert光照模型理论
当光线照射到表面粗糙的物体,例如:石灰墙壁,纸张,布料等,光线会向各个方向等强度的反射,这种现象称为光的漫反射现象。
漫反射满足Lambert定律:反射光线的强度与表面法线的光源方向之间的夹角成正比。产生漫反射的物体表面称为理想漫反射体,这种光照模型称为Lambert光照模型。
Lambert光照模型的计算公式为:

这个公式的意思是:
最终的漫反射颜色 = (光的颜色 × 物体的颜色) × (光线与表面法线的夹角的余弦值,但最小为0)
1. 左边部分:
- 最终算出来的漫反射颜色值 。它决定了屏幕上这个像素点显示什么颜色。
2. 中间部分:
这是基础颜色 ,代表物体本身的材质颜色 和光的颜色相乘的结果。
:光源的颜色(比如白色的灯就是白色)。
:物体表面的材质颜色(比如红色的球就是红色)。
这一步相当于确定"光打在物体上,物体原本应该是什么颜色"。
3. 右边部分:saturate(n⋅l)
这是亮度调节因子 ,决定了光线照在物体上的强度。
n(Normal):物体表面该点的法线向量(垂直于表面的方向)。
l(Light):从表面指向光源的光线方向向量。
n⋅l:这两个向量的点积(Dot Product)。
垂直照射(θ=0°) :
cosθ = 1,最亮。斜着照射(0°<θ<90°) :
cosθ在 0 到 1 之间,变暗。从背面照射(θ≥90°) :
cosθ ≤ 0,无光照(被saturate函数设为0)。saturate(...):这是一个夹钳函数,意思是把结果限制在 0 到 1 之间。
如果点积是负数,强制变成 0(表示没有光照贡献)。
如果是正数,保持原样。
简单来说,这个公式用来告诉你:在光照条件下,物体表面某一点到底有多亮。
2.在Shader中获取灯光变量
既然光照模型是跟灯光打交道,那么在Shader中如何获取灯光的各个属性呢?
实际上,灯光参数如何传递给Shader,取决于当前unity项目使用的渲染路径以及Shader中用于声明灯光模型的PassTag.
如何查看当前项目的渲染路径?

渲染路径决定了Unity的渲染引擎如何收集、打包灯光数据 ,以及如何将这些数据传递给Shader。Shader必须使用与渲染路径相匹配的语法和内置变量/函数来读取这些数据。
核心原则对比表
| 渲染路径 / 管线 | 核心思想 | Shader中获取灯光属性的主要方式 | 关键代码/结构 |
|---|---|---|---|
| 内置管线 - 前向渲染 | 逐物体/逐光源计算。每个逐像素光可能触发额外的绘制Pass。 | 通过内置变量 和宏 。在着色器中声明 #pragma multi_compile变体,使用 UnityLightingCommon.cginc等中的变量,如 _LightColor0, _WorldSpaceLightPos0。 |
uniform float4 _LightColor0; #include "Lighting.cginc" |
| 内置管线 - 延迟渲染 | 光照计算在屏幕空间进行,与场景复杂度解耦。Shader不直接处理多个实时光。 | 在几何Pass 中,将材质信息(漫反射颜色、法线、高光等)写入G-Buffer 。在光照Pass中,从G-Buffer读取数据,并使用屏幕空间坐标和灯光参数进行计算。 | G-Buffer是一组渲染纹理(RT)。光照Pass通常是全屏四边形或计算着色器。 |
| **URP (通用渲染管线)** | 前向渲染的现代化、可配置版本。使用可编程渲染管线(SRP)。 | 通过URP Shader库函数 。在HLSL代码中包含 Lighting.hlsl,调用如 GetMainLight()的函数,返回一个结构化的 Light数据。 |
Light mainLight = GetMainLight(); float3 lightColor = mainLight.color; |
| **HDRP (高清渲染管线)** | 基于物理的复杂渲染,支持大量先进特性。 | 通过HDRP特定的Shader Graph节点或复杂的HLSL代码,使用HDRP的灯光数据结构、光照评估函数和材质系统。 | 高度封装,通常通过 FragInputs, PositionInputs, LightLoop等结构体和函数交互。 |
关键结论
-
Pass的LightMode标签是桥梁 :Shader中
Tags { "LightMode" = "..." }指明了这个Pass希望以哪种光照模式 被渲染引擎调用(如ForwardBase,ForwardAdd,Deferred等)。引擎会根据这个标签来决定传递哪些灯光数据。 -
数据传递方式不同:
-
前向类 :灯光属性通常通过
uniform变量或结构体直接设置到Shader中。 -
延迟类 :灯光属性通常被收集到屏幕空间的缓冲区 中,在光照Pass中统一读取和计算。
-
SRP(URP/HDRP) :通过精心设计的Shader库函数提供数据,隐藏底层细节,更易用但不够灵活。
-
-
你不能随意读取"所有灯光" :即使在URP的
GetMainLight()中,也只是获取了"最重要的"一个方向光。要处理多个动态光源,在前向渲染 中代价极高(每个额外光一个Pass),在前向+ 或延迟渲染中才更高效。
因此,当你问"Shader中如何获取灯光属性"时,第一个要回答的问题是: "你用的是哪种渲染管线/路径?" 因为这将决定你全部的代码写法。
前向渲染中可以使用的灯光属性变量
|----------------------------------------------------------|---------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 变量 | 类型 | 说明 |
| _LightColor0 | fixed4 | 当前处理的逐像素光的颜色(含强度)。这是最重要的灯光颜色变量。在Base Pass中是主平行光,在Add Pass中是当前额外的逐像素光。.rgb分量是Color * Intensity,.a分量通常为1。 |
| _WorldSpaceLightPos0 | float4 | 当前光源的位置/方向向量 。这是一个"多功能"变量: 1. 对于平行光 :.xyz是从世界空间原点指向光源方向 的单位向量(即光的方向),.w分量为0 。 2. 对于点光源/聚光灯 :.xyz是光源在世界空间中的位置坐标 ,.w分量为1 。 关键 :在着色器中,你需要用.w值来判断光源类型,并据此计算光线方向向量L。 |
| _LightMatrix0 | float4×4 | 从世界空间到光源空间的变换矩阵 。主要用于: 1. 采样灯光Cookie (一种投射在物体上的遮罩纹理)。 2. 采样衰减纹理 (一张预计算的、模拟光源距离衰减和聚光灯角度衰减的纹理)。 这个矩阵定义了一个以光源为原点的坐标系,用于快速查找当前片元在光源"视角"下的位置。 |
| unity_4LightPosX0, unity_4LightPosY0, unity_4LightPosZ0, | float4 | 这是四个"非重要"顶点光在世界空间的位置,被拆成了三个float4。X0, Y0, Z0分别存储了四个光源的x, y, z坐标分量。仅用于ForwardBase Pass,且通常用于逐顶点光照,效率高但质量低。 |
| unity_4LightAtten() | float4 | 对应上述四个顶点光的衰减系数 。这是一个预计算好的值,考虑了光源范围和距离。仅用于ForwardBase Pass。 |
| unity_LightColor | half4[4] | 对应上述四个顶点光的颜色 (含强度),是一个数组。仅用于ForwardBase Pass。 |
| unity_WorldToShadow | float4×4[4] | 世界空间到阴影贴图纹理空间的变换矩阵数组 。这是阴影计算的核心。 |
之所以在前向渲染 中需要"逐物体/逐光源计算 ",并且"每个逐像素光可能触发额外的绘制Pass ",其根本原因在于它的工作原理和性能瓶颈。
核心原理:前向渲染的工作流程
遍历每个物体:渲染引擎确定一个需要渲染的物体(比如一个角色模型)。
为这个物体计算每个光源的影响:
第一步:计算最重要/最特殊的光源(通常是第一个平行光,并包含环境光、光照贴图等)。
Shader 通过
Pass { Tags {"LightMode"="ForwardBase"} }执行一次。在这个 Pass 中,可以获取到这个主光源的属性(
_LightColor0等),计算它对整个物体的漫反射 + 高光,并输出结果到屏幕缓冲区。第二步:计算其他额外的光源(如场景中的点光源、聚光灯、额外的平行光)。
对每个额外光源 ,物体都可能需要再绘制一次!
Shader 通过另一个
Pass { Tags {"LightMode"="ForwardAdd"} }执行,但这次计算会针对一个新的光源。最关键的是:这个 Pass 的混合模式 被设置为
Blend One One。这意味着它不再是覆盖屏幕颜色,而是将当前这个光源的计算结果,叠加(Add)到上一步主光源的结果之上。为什么必须这么做?
因为在前向渲染的片元着色器阶段,计算光照的公式是:
最终颜色 = 材质颜色 × (光源1贡献 + 光源2贡献 + ... + 光源N贡献)这个"× "操作是非线性 的。你不能先把所有光源的属性(位置、颜色、方向)打包好,在着色器里一次性读取,然后算出一个"总和贡献",再乘到材质颜色上。正确的物理过程是:
每个光源独立地与材质发生作用(先做乘法计算),然后再把所有光源的结果加起来。
在着色器的一次执行中,它只能访问一组有限的光源数据 (通常是1-4个,由硬件和引擎优化决定)。为了模拟N个光源的效果,引擎就必须让这个着色器为同一个物体执行N次,每次代入不同的光源数据,每次都将结果叠加到帧缓冲区。
这会带来什么?
性能代价巨大 :如果一个物体被M个光源照射,它就可能需要被绘制 M+1 次(1个Base Pass + M个Add Pass)。这就是著名的 **"Draw Call暴增"** 问题。光源数量和物体数量一多,性能会急剧下降。
GPU的过度绘制严重:同一个屏幕像素会被同一个物体的不同Pass反复计算和写入多次,浪费了大量的GPU填充率。
3.基于Lambert光照模型的Shader
基础数学函数
| 函数名 | 函数原型 | 说明 | 典型场景 |
|---|---|---|---|
| abs | float abs(float x)``float2 abs(float2 x) |
计算绝对值,支持标量 / 向量(逐分量计算) | 避免数值为负(如距离、强度) |
| sqrt | float sqrt(float x) |
计算平方根 | 开平方(如从平方距离算实际距离) |
| rsqrt | float rsqrt(float x) |
计算平方根的倒数(1/√x) | 归一化向量时优化性能(比 sqrt 快) |
| pow | float pow(float x, float y) |
计算 x 的 y 次方 | 伽马校正(pow (x, 2.2))、高光次幂 |
| exp | float exp(float x) |
计算自然指数 e^x | 物理衰减、雾效计算 |
| log | float log(float x) |
计算自然对数 ln (x) | 数值范围压缩(如 HDR 转 LDR) |
| max | float max(float a, float b)``float2 max(float2 a, float2 b) |
取最大值,支持标量 / 向量(逐分量) | 限制最小值(如 max (x, 0)) |
| min | float min(float a, float b)``float2 min(float2 a, float2 b) |
取最小值,支持标量 / 向量(逐分量) | 限制最大值(如 min (x, 1)) |
| clamp | float clamp(float x, float min, float max) |
限制 x 在 [min, max] 范围内 | 亮度 / 强度限制(如 clamp (ndotl, 0, 1)) |
| saturate | float saturate(float x) |
等价于 clamp (x, 0, 1),专用快捷函数 | 漫反射 / 高光强度限制(最常用) |
| sign | float sign(float x) |
返回符号:x>0→1,x<0→-1,x=0→0 | 方向判断(如正反面、灯光方向) |
| frac | float frac(float x) |
取小数部分(x - floor (x)) | 纹理平铺、动画偏移 |
| floor | float floor(float x) |
向下取整(如 2.9→2,-2.1→-3) | 网格对齐、整数化计算 |
| ceil | float ceil(float x) |
向上取整(如 2.1→3,-2.9→-2) | 边界对齐、整数化计算 |
| round | float round(float x) |
四舍五入取整 | 像素对齐、离散化效果 |
向量 / 矩阵函数(Shader 核心)
| 函数名 | 函数原型 | 说明 | 典型场景 |
|---|---|---|---|
| dot | float dot(float3 a, float3 b) |
计算向量点积(a・b =abcosθ) | 漫反射(法线・灯光方向)、高光、夹角判断 |
| cross | float3 cross(float3 a, float3 b) |
计算向量叉积(结果垂直于 a 和 b) | 切线空间构建、反射 / 折射方向 |
| normalize | float3 normalize(float3 v) |
归一化向量(长度变为 1) | 方向向量标准化(灯光 / 法线方向) |
| length | float length(float3 v) |
计算向量长度(√(x²+y²+z²)) | 距离计算(如点光源衰减) |
| distance | float distance(float3 a, float3 b) |
计算两点间距离(length (a-b)) | 点光源距离衰减、范围检测 |
| lerp | float lerp(float a, float b, float t)``float3 lerp(float3 a, float3 b, float t) |
线性插值:a + t*(b-a),t∈[0,1] | 颜色混合、过渡动画、纹理插值 |
| smoothstep | float smoothstep(float edge0, float edge1, float x) |
平滑插值:x<edge0→0,x>edge1→1,中间平滑过渡 | 软阴影、边缘羽化、平滑衰减 |
| reflect | float3 reflect(float3 i, float3 n) |
计算反射方向:i - 2*dot (i,n)*n | 镜面反射、反射纹理(如镜子、水面) |
| refract | float3 refract(float3 i, float3 n, float eta) |
计算折射方向,eta = 折射介质 / 入射介质折射率 | 透明物体折射(如水、玻璃) |
| mul | float4 mul(float4x4 m, float4 v)``float4x4 mul(float4x4 a, float4x4 b) |
矩阵乘法(向量 / 矩阵) | 坐标转换(模型→世界→裁剪空间) |
三角函数(角度 / 弧度相关)
| 函数名 | 函数原型 | 说明 | 典型场景 |
|---|---|---|---|
| sin | float sin(float x) |
正弦函数(x 为弧度) | 波浪动画、周期运动 |
| cos | float cos(float x) |
余弦函数(x 为弧度) | 圆周运动、旋转计算 |
| tan | float tan(float x) |
正切函数(x 为弧度) | 角度计算、投影变换 |
| asin | float asin(float x) |
反正弦函数(返回弧度) | 从点积反算夹角 |
| acos | float acos(float x) |
反余弦函数(返回弧度) | 从点积反算夹角(如 ndotl→角度) |
| atan | float atan(float y, float x) |
反正切函数(返回弧度) | 方向角计算 |
| radians | float radians(float deg) |
角度转弧度(CG 三角函数仅支持弧度) | 输入角度值转弧度计算 |
| degrees | float degrees(float rad) |
弧度转角度 | 输出角度值(如调试夹角) |
纹理采样函数(Shader 必备)
| 函数名 | 函数原型 | 说明 | 典型场景 |
|---|---|---|---|
| tex2D | float4 tex2D(sampler2D tex, float2 uv) |
2D 纹理采样(基础) | 基础贴图(Albedo、法线贴图) |
| tex2Dlod | float4 tex2Dlod(sampler2D tex, float4 uvlod) |
2D 纹理采样(指定 mipmap 层级) | 手动控制 mipmap(如视差贴图) |
| texCUBE | float4 texCUBE(samplerCUBE tex, float3 dir) |
立方体贴图采样 | 环境反射(Skybox、IBL) |
| tex2Dproj | float4 tex2Dproj(sampler2D tex, float4 uv) |
投影纹理采样 | 阴影贴图、投影仪效果 |
类型转换函数
| 函数名 | 函数原型 | 说明 | 典型场景 |
|---|---|---|---|
| float | float float(int x) |
整型转浮点型 | 数值计算(CG 优先用浮点) |
| int | int int(float x) |
浮点型转整型(截断小数) | 索引计算、离散化 |
| bool | bool bool(float x) |
浮点型转布尔型(0→false,非 0→true) | 条件判断 |
| step | float step(float edge, float x) |
阶跃函数:x<edge→0,x≥edge→1 | 硬边缘、开关效果 |
Half-Lambert光照模型
半兰伯特 光照
使用Lambert光照模型有一个明显的缺点,那就是物体背光面完全是黑的,看不到表面的任何细节,以至于只能再添加一盏辅助光照亮物体被光面,这非常不利于性能的优化。于是有人提出一种基于Lambert进行算法优化的Half-Lambert光照模型
Half-Lambert 是 Valve 为《半条命》改进的漫反射光照模型 ,用来解决传统 Lambert 背光面太黑、过渡太硬的问题,本质是对点积结果做一次缩放 + 偏移,把亮度范围重新映射。
Half-Lambert光照模型计算公式:


Phong光照模型
冯 光照模型
Lambert模型能够较好地模拟出粗糙物体表面的光照效果,但再真是环境中还存在很多表面光滑的物体,例如金属、陶瓷、塑料等,而Lamber光照模型缺无法对此进行很好地表现,因此引入表面镜面反射的光照模型------Phong光照模型
1.Phong光照模型理论
Phong 光照模型是由 Bui Tuong Phong 在 1975 年提出的一个经典、高效的经验光照模型。它通过将光线的反射分为三个部分来模拟物体表面的光照效果:环境光 、漫反射 和高光反射。
核心计算公式
Phong 模型的光照颜色是以下三个分量的总和:

最终颜色 = 环境光 + 漫反射 + 高光反射
核心原理
这个公式遵循 ** 经典光照模型(Phong 模型)** 的思路:
- 环境光(Ambient):提供基础亮度,避免背光面完全变黑,是场景全局光照的简化体现。
- 漫反射(Diffuse):模拟光线在粗糙表面的均匀反射,决定了物体的基础明暗和色调。
- 高光(Specular):模拟光滑表面的镜面反射,体现物体的质感(如金属、塑料的光泽感)。
- 三者线性相加,得到物体表面在光照下的最终视觉颜色。
镜面反射计算公式为:
为灯光亮度;
为物体材质的镜面反射颜色
为视角方向(由顶点指向摄像机)
为光线的反射方向
为物体材质的光泽度
高光 = 灯光颜色 × 高光颜色 × pow( saturate( dot( 反射方向, 视线方向 ) ), 光泽度 )
物理与渲染逻辑
- 核心原理 :镜面反射强度由视角方向与反射方向的夹角决定,两者越接近,高光越明显。
- saturate 作用:防止点积为负时出现 "反向高光"(即视角在反射方向另一侧时不应有高光)。
- 指数 Mshininess :
- 小值(如 10~30):高光范围大、柔和,模拟塑料、布料等材质
- 大值(如 100~200+):高光范围小、锐利,模拟金属、抛光玻璃等材质
2.在Shader中获取环境光变量
unity提供的可以直接使用的环境光变量,表中变量在UnityShaderVariables.cginc中被定义
Shader中可以使用的环境光变量
需要注意的是,要把默认的Skybox类型改为Gradient类型,才能使用上述的变量
1. 只有 Ambient Mode = Gradient(渐变环境光)
这三个变量才会生效:
unity_AmbientSkyunity_AmbientEquatorunity_AmbientGround
2. 如果是 Skybox 或 Color 模式
- 这三个变量不会正确赋值
- 只能用旧版:
UNITY_LIGHTMODEL_AMBIENT
如何查看当前场景中的环境光呢?
window------>Rendering------>Lighting

3.基于Phong光照模型的Shader
核心代码:

1. 法线转到世界空间
float3 n = UnityObjectToWorldNormal(v.normal); n = normalize(n);
为什么?
- 模型法线是模型空间的
- 灯光、视角是世界空间的
- 不统一空间,光照计算全错
normalize保证方向正确,不受缩放影响
2. 灯光方向
fixed3 l = normalize(_WorldSpaceLightPos0.xyz);
为什么?
_WorldSpaceLightPos0= Unity 给的主平行光方向- 必须归一化,才能正确计算点积
3. 视角方向(看相机的方向)
fixed3 view = normalize(WorldSpaceViewDir(v.vertex));
为什么?
- Phong 高光必须知道 "眼睛看向哪里"
- 只有视线和反射光重合时,才会出现高光
- 这是高光存在的前提!
4. 漫反射计算(Lambert)
fixed ndotl = saturate(dot(n,l));
fixed4 dif = _LightColor0 * _MainColor * ndotl;
为什么这么写?
- dot(n,l):法线与灯光夹角 → 决定亮度
saturate:把值限制在 0~1,防止背光变负数_LightColor0:灯光颜色_MainColor:物体本身颜色- 最终得到物体被照亮的颜色
- ✨ Phong 高光核心(最重要)
float3 ref = reflect(-l, n);
fixed rdotv = saturate(dot(ref, view));
fixed4 spec = _LightColor0 * _SpecularColor * pow(rdotv, _Shininess);
① reflect(-l, n)
计算光线的反射方向
- 光射向物体 → 反弹出去
- 必须写
-l,因为 reflect 函数要求入射光指向顶点② dot(ref, view)
反射光 和 视角方向 越接近,高光越强
- 重合 = 1(最亮)
- 不重合 = 0(无高光)
③ pow(rdotv, _Shininess)
控制高光大小
- 指数越大 → 高光越小越集中
- 指数越小 → 高光越大越散
这就是 Phong 高光的核心公式!
环境光效果与方向无关,物体任意部位的强度都一样;漫反射效果与Lambert一样,强度随物体表面方向的不同而改变;镜面反射效果比较聚焦,但强度很大。
1. 环境光:到处都一样亮
"环境光效果与方向无关,物体任意部位的强度都一样"
- 就像阴天的天空光,没有明确的光源方向。
- 物体正面、侧面、背面亮度都差不多,不会有明显明暗变化。
- 作用:防止物体完全黑掉,给一个基础亮度。
2. 漫反射:正对光最亮,侧着就暗
"漫反射效果与 Lambert 一样,强度随物体表面方向的不同而改变"
Lambert 就是最经典的漫反射规则:
- 光有固定方向(比如太阳从左边照)。
- 物体表面正对光的地方最亮。
- 表面一歪、一转,亮度就慢慢变暗。
- 看起来是柔和、均匀的明暗过渡,比如磨砂塑料、墙面、皮肤。
3. 镜面反射:一小块特别亮,很刺眼
"镜面反射效果比较聚焦,但强度很大"
- 不是整个面都亮,只在一小块高光点特别亮。
- 像镜子、金属、光滑瓷砖那种刺眼的光斑。
- 范围小、很集中,但亮度极高。
环境光+漫反射+镜面反射=完整光照效果
逐像素光照
Phong光照模型应用到Shader之后,不难发现这样一个现象:高光部位为什么跟平常看到的不太一样呢?印象中高光点应该是边缘很圆滑,而现在却很不清晰,难道是算法有问题?
之所以出现这样的现象是因为光照模型的计算一直使用的是逐顶点光照,而不是逐像素光照。
什么是逐顶点光照呢?
其实就是在顶点着色器中计算光照颜色。计算过程中,GPU将为多边形的每个顶点执行一遍光照计算,得到顶点颜色,然后通过顶点在多边形上所占的范围对像素颜色进行线性插值。
逐顶点光照(Vertex Lighting)
官方解释: 在顶点着色器 里算光照 → 只给每个顶点算颜色 →中间的颜色 ** 自动插值(渐变)** 出来。
人话解释:
你可以把模型想象成用铁丝搭的架子 ,架子的交点就是顶点。
- GPU 只在这些交点(顶点)上算光照亮不亮
- 顶点之间的面,GPU 不重新计算 ,直接从一个顶点颜色渐变到另一个顶点颜色
举个超级简单的例子:
一个三角形,3 个顶点:
- 顶点 A:白色(最亮)
- 顶点 B:灰色(中等)
- 顶点 C:黑色(最暗)
逐顶点光照的结果:
- 只算这 3 个点的颜色
- 三角形内部所有颜色 → 自动从白渐变到灰再渐变到黑
优点缺点
- 优点:超级快,算得少
- 缺点:效果粗糙,高光会糊、会块、会不自然
什么是逐像素光照呢?
其实就是在像素着色器中计算光照颜色。在像素着色器中,颜色的计算就不再是基于顶点,而是基于像素的计算,所以最终屏幕的分辨率越高计算量越大。
逐像素光照(Pixel Lighting)
官方解释: 在像素着色器 里算光照 → 屏幕上每一个像素都单独算一遍颜色 → 分辨率越高,算得越多。
人话解释:
逐像素光照不是 "算顶点再渐变",而是屏幕上每一个小点(像素)都独立算一次光照。
就像画画:
- 逐顶点:画几个点 → 连线涂色
- 逐像素:一个点一个点认真画,每个点都算亮不亮
例子:
同样一个三角形:
- 逐顶点:3 次计算
- 逐像素:几百、几千次计算(每个像素都算)
所以:
- 1080P 屏幕 = 200 万像素
- 2K 屏幕 = 400 万像素分辨率越高,计算量越大,越吃性能。
优点缺点
- 优点:超级平滑、真实、高光细腻
- 缺点:性能消耗大
逐顶点光照和逐像素光照的优缺点分别是什么?
逐顶点光照(Vertex Lighting)
优点
- 计算量极小,GPU 压力小,运行速度快
- 对低性能设备、老旧手机 / 电脑非常友好
- 模型面数再多,也只算顶点,开销稳定
缺点
- 光照效果粗糙,明暗过渡生硬
- 高光会块状、错位、不圆滑
- 曲面看起来有明显的色块感
- 复杂光照(法线贴图、精细高光)基本做不出来
逐像素光照(Pixel Lighting)
优点
- 光照极度细腻、真实,每个像素单独计算
- 高光平滑自然,法线贴图效果好
- 能实现复杂真实的材质:金属、皮肤、玻璃等
- 画面质感强,现代游戏主流方案
缺点
- 计算量巨大,分辨率越高越卡
- 非常吃 GPU 性能,低端设备容易掉帧
- 渲染开销大,同性能下能画的物体更少
超简记忆版
- 逐顶点:快 → 糙 → 省性能
- 逐像素:慢 → 美 → 耗性能
逐顶点光照和逐像素光照分别适用于哪些场景?
逐顶点光照 适用场景
特点:快、省性能、效果一般
适合:
性能极差的设备老旧手机、掌机、低配电脑、网页轻量小游戏。
远景物体、不重要的小物件远处的树、石头、小兵、场景杂物,反正看不清细节。
低模风格、卡通简模游戏模型本身面数很少,用逐顶点光照就够了,不违和。
大量同屏物体、粒子特效比如一堆箭矢、碎片、烟雾,数量多但不用精细光照。
移动端优化、性能压力大的场景能省一点性能是一点,保证流畅优先。
逐像素光照 适用场景
特点:效果好、真实、吃性能
适合:
主角、BOSS、重要角色镜头经常特写,必须细腻好看。
近景物体、精致场景地面、墙面、武器、载具,玩家能看清的地方。
需要法线贴图、高光、金属质感的物体逐顶点根本表现不出光滑金属、皮肤、镜面效果,必须逐像素。
写实类、3A 画质、高画质游戏现代主机、PC 游戏主流水准。
高画质渲染、PC / 主机平台设备性能强,追求画面质量。
超简总结
- 逐顶点:远处、杂物、低配、性能优先
- 逐像素:近处、主角、高画质、效果优先
使用逐像素计算不仅可以提升光照效果的精确度,还可以在渲染时添加多边形原本并不存在的表面细节,例如使用NormalMap可以在像素级别上使原本平坦的表面表现出凹凸效果
Blinn-Phong光照模型
布林 - 冯 光照模型
1.Blinn-Phong光照模型理论
Blinn-Phong光照模型不再使用反射向量r计算镜面反射,而是使用半角向量h代替r,h为表视角方向v和灯光方向l的角平分线方向

半角向量的计算公式为:
h=normalize(v+l)
Blinn-Phong镜面反射的计算公式为:

2.Blinn-Phong光照模型的Shader
fixed4 frag(v2f i):SV_Target
{
//法线向量
float3 n=UnityObjectToWorldNormal(i.normal);
n=normalize(n);
//灯光方向向量
fixed3 l=normalize(_WorldSpaceLightPos0.xyz);
fixed3 view=normalize(WorldSpaceViewDir(i.vertex));
//漫反射部分
fixed ndotl=saturate(dot(n,l));
fixed4 dif=_LightColor0*_MainColor*ndotl;
//镜面反射部分
fixed3 h=normalize(l+view);//计算半角向量
fixed ndoth=saturate(dot(n,h));//计算法线与半角向量的点积,并钳制到[0,1]
fixed4 spec=_LightColor0*_SpecularColor*pow(ndoth,_Shininess);
//环境光+漫反射+镜面反射
return unity_AmbientSky+dif+spec;
}
Blinn-Phong效果与Phong效果在视觉上的差距并不是很大。
在性能方面,当观察者和灯光离被照射物体非常远时,Blinn-Phong的计算效率要远高于Phong。因为h是取决于视角方向以及灯光方向的,两者都很远时h可以被认为是常量,跟位置以及表面的曲率没关系,因此可以大大减少计算量。而Phong却要根据表面曲率去逐顶点或逐像素计算反射向量r,相对而言计算量比较大。
视觉差距不大 Blinn-Phong 和 Phong 肉眼看几乎一样,只有高光边缘稍微柔和一点点,不对比根本看不出区别。
远距离时,Blinn-Phong 性能碾压 Phong 当相机 + 光源都很远时:
- 视线方向 V 几乎不变
- 灯光方向 L 几乎不变 → 半角向量 H = normalize (L+V) 变成全局常量! → 不用逐顶点 / 逐像素计算 H→ 性能大幅提升
而 Phong 必须逐点计算反射向量 R ,和表面法线强相关,无法预计算,所以更慢。
Blinn-Phong 在远距离场景下能把半角向量 H 变成常量,省去大量计算;而 Phong 必须逐点算反射向量 R,无法优化,所以更慢。两者视觉效果几乎一样。
灯光阴影
如果想要把现在的Shader真正应用在游戏中,其实还是不太完美。例如:开启灯光的投射阴影选项,物体上没有投射任何阴影能够,也不会接受其他物体投射的阴影;
当灯光类型切换为聚光灯或者点光源的时候,光照效果依然跟平行光一样,只会受到方向的影响,更改灯光位置不会有任何光照变化。
1.渲染路径
1).延迟着色渲染路径
延迟着色 顾名思义是将着色步骤推迟处理的渲染方式,是实现最逼真光影效果的渲染路径,即使场景中有成百上千个实时灯光,依然可以保持比较流畅的渲染帧率。它是它需要硬件达到一定级别才能使用
延迟着色(Deferred Shading)到底是什么?
- 一句话核心定义(你说的完全对)
把 "着色计算" 推迟到 "所有几何体都渲染完之后" 再算。
2. 它解决了什么痛点?
传统前向渲染(Forward):
场景里有 100 个物体 × 100 个灯
每个物体 × 每个灯都要算一遍光照
灯越多 → 越卡,性能爆炸
延迟渲染(Deferred):
不管多少灯,只看屏幕上最终有多少像素
灯再多,帧率也不会崩 → 这就是你说的:成百上千个实时灯依然流畅
3. 它是怎么做到的?(两步走)
第一步:几何缓冲(G-Buffer)
只渲染位置、法线、颜色、深度 等数据,不计算光照!不计算光照!不计算光照!
把这些信息存到几张贴图里,叫 G-Buffer。
第二步:光照计算(真正的着色)
拿着 G-Buffer 里的数据,在屏幕空间计算光照。
好处:
只对最终可见的像素算一次光照
灯越多优势越大
复杂光影、软阴影、PBR 都好做
4. 你说的这几句,我帮你确认 + 强化
"将着色步骤推迟处理"
完全正确。 先存数据 → 后算光照。
"实现最逼真光影效果的渲染路径"
**正确。**延迟渲染天生适合:
大量实时光
软阴影
屏幕空间反射
高级 PBR 效果前向渲染很难做到这么干净、高效。
"成百上千个实时灯光依然流畅"
正确。 前向渲染:灯数 × 物体数 → 性能爆炸延迟渲染:只和屏幕像素数有关,灯多几乎不影响帧率。
"需要硬件达到一定级别"
**正确。**需要支持:
多渲染目标(MRT)
较高的显存带宽老旧手机 / 老显卡不支持。
5. 超简短终极总结(笔记 / 面试专用)
延迟着色 = 先渲染几何信息到 G-Buffer,再在屏幕空间统一计算光照。
优点:支持海量实时光、光影效果强、性能稳定。
缺点:耗显存、硬件要求高、不支持半透明。
当使用延迟着色时,灯光Pass基于G-Buffer(屏幕空间存储)和深度信息计算光照,光照在屏幕空间进行计算,因此计算量与场景的复杂程度无关,如此一来就可以避免计算因未通过深度测试而被丢弃的片段,从而减少性能浪费。并且所有灯光都可以逐像素渲染,这意味着所有灯光都可以与法线贴图等产生正确的交互,并且所有的灯光都可以使用阴影和cookies。
延迟着色在干嘛?
它先不着急算光照,而是先把画面拍一张 "信息照",存在 G-Buffer 里:
- 每个像素的颜色
- 每个像素的法线(朝向)
- 每个像素的深度(离相机多远)
这一步只画看得见的像素 ,被挡住的直接丢掉,不浪费算力。
然后再在这张照片上算光照 。重点来了:光照是在照片上算的,不是在 3D 场景里算的。
所以:
- 场景再复杂,不影响光照计算量
- 只算屏幕上看得见的像素
- 不会给被挡住的像素白算光照
- 每个像素都能精细算一遍灯光,效果更准
遗憾的是,延迟着色不支持真正的抗锯齿,也不能处理半透明物体,这些会自动使用前向渲染处理。使用延迟着色的模型不支持接受阴影选项,Culling Mask只能在限定条件下使用,且最多只能使用4个。
延迟着色虽然省了光照计算的性能,但它的 "信息照(G-Buffer)" 是按实心像素存的,而且格式 / 数量有限制,这就导致了下面这些 "短板":
1. 不支持真正的抗锯齿
- 通俗解释:普通抗锯齿是让画面边缘(比如斜线、文字)不出现锯齿,得 "模糊" 或 "混合" 相邻像素的颜色 / 深度。但延迟着色的 G-Buffer 是 "拍死" 的固定数据,没法灵活混合这些信息,强行做抗锯齿要么效果差,要么得额外加成本(等于白省性能)。
2. 不能处理半透明物体(自动切前向渲染)
- 通俗解释:半透明物体(比如玻璃、烟雾、水)的特点是 "能看透去",需要把背后的像素和自身像素混合计算。但延迟着色的 G-Buffer 只存 "最前面那一个像素" 的信息(比如玻璃挡住了背后的墙,G-Buffer 只记玻璃,不记墙),根本没法混合 "玻璃 + 墙" 的颜色。所以遇到半透明物体,只能切回老办法(前向渲染)单独算,等于 "一半场景用省性能的方法,一半用费性能的方法"。
3. 模型不支持 "接受阴影" 选项
- 通俗解释:延迟着色的阴影是 "屏幕空间统一算" 的,不是按单个模型算的。你想单独设置 "这个模型不接受阴影"(比如让某个角色不被影子遮),它做不到 ------ 因为阴影是整张图的效果,没法单独抠出一个模型改。
4. Culling Mask(图层剔除)限制多,最多用 4 个
- 通俗解释:Culling Mask 就是 "让相机只渲染某几个图层的物体"(比如只渲染玩家,不渲染敌人)。延迟着色的 G-Buffer 存储空间有限,只能存 4 个图层的信息,超过 4 个就存不下了;而且用法也受限(比如不能随便组合图层),不像前向渲染能随便选。
总结(核心关键点)
延迟着色的 "省性能" 是靠 "固定格式存屏幕信息" 实现的,代价是灵活性差(抗锯齿、半透明、图层 / 阴影定制都受限);
遇到半透明物体时会自动切回前向渲染,实际性能收益会打折扣;
它适合 "全是实心物体、灯光多、不需要精细定制阴影 / 图层" 的场景(比如开放世界),不适合 "半透明多、需要精细控制渲染" 的场景(比如 UI 多、特效多的游戏)。
- "所有灯光都能用阴影" = 这盏灯可以投射阴影
- "模型不支持接受阴影选项" = 你不能单独设置某个物体要不要被影子照到
在性能方面,光照对于性能的消耗不再受灯光数量或受光物体数量影响,而是与受灯光影响的像素数量或者灯光的照射范围有关,所以物体受灯光影响的数量不会再有限制。投射阴影的灯光依然比无投射的灯光更耗费性能,投射阴影的物体仍然需要为阴影投射灯光渲染多次。但是可以通过减少照射范围来降低性能消耗。
通俗翻译
1. 性能消耗跟什么有关?
以前(前向渲染):灯越多、物体越多 → 越卡。
现在(延迟着色):性能只跟两件事有关:
屏幕上被灯照到的像素有多少
每个灯照的范围有多大
跟场景里有多少物体、多少灯,没关系。
所以你可以堆几百盏灯,只要它们照的像素不多,就不卡。
2. "物体受灯光影响的数量不会再有限制"
意思就是:你不用再担心 "这物体被 10 盏灯照着会不会爆性能"。延迟着色不在乎物体被多少灯照,只在乎屏幕上最终亮了多少像素。
3. 但带阴影的灯还是更贵
这句话是补充现实:
普通灯:便宜
开了阴影的灯:贵很多
因为阴影要额外渲染、额外计算。不管是不是延迟,阴影永远比没阴影贵。
4. 投射阴影的物体仍然要渲染多次
比如一个角色要给 5 盏灯投阴影,那这个角色就要被渲染 5 次。这部分开销延迟着色省不掉。
5. 怎么优化?很简单:缩小灯的范围
灯照得越小 → 影响的像素越少 → 性能越省。你把点光源半径调小,性能立刻下降。
延迟着色只能在有多重渲染目标、Shader Model3.0或者以上,并且支持深度渲染贴图的显卡上运行。在移动端上,可以在OpenGL ES 3.0及以上的设备上运行。延迟着色不支持正交投影,当摄像机使用正交投影模式的时候,摄像机会自动使用前向渲染。
1. 延迟着色对显卡有要求
不是所有老显卡都能用,必须满足:
- 支持 MRT(多渲染目标)→ 就是能一次性往屏幕上存好几张图(G-Buffer)
- 支持 Shader Model 3.0 及以上→ 不算很高,现在电脑显卡基本都满足
- 支持 深度渲染纹理→ 能把深度信息存成一张图用
手机上:
- 必须是 OpenGL ES 3.0 及以上→ 近几年的手机基本都可以,老手机不行。
2. 延迟着色不支持正交相机
什么是正交相机?就是那种没有近大远小的相机,比如 2D 游戏、UI 相机。
延迟着色只能在透视相机 (有近大远小的 3D 相机)上跑。如果相机切成正交模式,引擎会自动放弃延迟着色,换回前向渲染。
2).前向渲染路径
前向渲染是传统的渲染路径,它支持所有的Unity图形功能,例如法线贴图、逐像素光照、阴影等。前向渲染路径使用一个或多个Pass渲染每个物体,这取决于影响到物体的灯光数量。并且灯光也会因为自身设置和强度不同而被区别对待。
通俗翻译
前向渲染 = 传统、最通用的渲染方式
它啥都支持法线贴图、阴影、半透明、抗锯齿、正交相机......Unity 里所有渲染功能它都能跑,没有限制。
**它是怎么渲染的?**一个物体一个物体地画。物体被多少盏灯照着,就要多画几遍(多几个 Pass)。
- 被 1 盏灯照 → 画 1 次
- 被 4 盏灯照 → 画 4 次灯越多,物体画得越频繁,性能越贵。
灯会被分成三六九等引擎不会傻到所有灯都逐像素算,它会自动分类:
- 最亮、最重要的灯 → 逐像素精细计算
- 次重要的灯 → 逐顶点粗略算
- 很弱的灯 → 直接忽略或用球谐光照算这样省性能,但效果就没那么统一。
在前向渲染中,一部分最亮的灯光以完全逐像素照明的方式渲染,然后4个点光源以逐顶点的方式渲染,其余的灯光以更快速的SH光照渲染。
SH光照可以被非常快速地渲染,它只消耗很少的CPU性能,几乎不消耗GPU性能。并且增加SH灯光的数量不会影响性能的消耗。
前向渲染里,灯光是分 "三档" 干活的
最重要、最亮的灯 逐像素算光照,效果最真实,能和法线贴图、阴影完美配合。但最费性能。
接下来最多 4 个点光源只在物体的顶点上算光照,中间像素靠插值糊出来。效果一般,比逐像素省性能。
剩下所有其他灯 统一用 SH 光照(球谐光照) 来算。
SH 光照是什么?
你可以把它理解成:只算一个大概的、模糊的环境亮度,不画具体光影。
没有清晰的亮暗边界
不跟法线贴图互动
没有阴影
但超级快
性能上的特点
SH 光照几乎不占 GPU,CPU 占用也很小
关键:你加再多 SH 灯,性能几乎不变10 个和 100 个 SH 灯,开销差不多
一个灯光是逐像素光照还是其他方式渲染?
取决于以下几点:
- 渲染模型设置为Not Important的灯光总是以逐顶点或者SH的方式渲染。
- 渲染模型设置为Important的灯光总是逐像素渲染。
- 最亮的平行光总是逐像素渲染。
- 如果逐像素光照的灯光数量少于项目质量设置中PixelLight的数量,那么其余比较亮的灯光会被逐像素渲染。 渲染模型在如下属性面板里设置------>

unity会根据灯光的亮度以及物体的距离自动判断该灯光是否重要。
基础Pass包含一个逐像素的平行光和所有逐顶点或者SH的灯光,并且也会包含所有来自于Shader的光照贴图、环境光和自发光。平行光能够投射阴影,但是灯光贴图不能接受SH灯光的照明。
其他逐像素的灯光会在额外的Pass中渲染,每一个灯光会产生一个额外的Pass。在额外Pass中的灯光默认不会投射阴影。这意味着默认情况下,前向渲染只支持一个投射阴影的平行光。如果希望更多的灯光能够产生投影,就需要添加内置的multi_compile_fwdadd_fullshadows编译指令编译出不同的Shader变体(Variant)
先搞懂两个核心概念(基础版)
基础 Pass:渲染物体的 "底子",是必须跑的第一步,算最核心的光照
额外 Pass :基础 Pass 之外,为了算更多精细灯光额外跑的步骤,多一个灯就多一步
1. 基础 Pass 到底算啥?
它是给物体 "打基础光" 的,包含这些内容:
1 个逐像素的平行光(比如太阳光,效果最细,还能投阴影)
所有逐顶点的灯 + 所有 SH 灯光(就是之前说的 "粗略算 / 糊一下" 的灯)
光照贴图(烘焙好的固定光影)、环境光(整体氛围光)、自发光(物体自己亮的部分,比如霓虹灯)
注意两个坑:
烘焙的光照贴图,不会被 SH 灯光影响(SH 灯再亮,贴图里的明暗也不变)
基础 Pass 里只有这 1 个平行光能投阴影,其他灯都不行
2. 额外 Pass 是干嘛的?
如果想让除了那个平行光之外的灯 也逐像素精细算(比如玩家手里的手电筒),就得走额外 Pass:
每多 1 个要逐像素算的灯 → 多跑 1 个额外 Pass
默认情况下,额外 Pass 里的灯不能投阴影
3. 关键结论:前向渲染的阴影限制
默认规则:只有1 个平行光能投阴影(就是基础 Pass 里的那个)
想让更多灯投阴影(比如手电筒也有影子):必须在 Shader 里加一行代码
multi_compile_fwdadd_fullshadows→ 作用:让 Shader 编译出 "支持额外 Pass 灯光投阴影" 的版本(Shader 变体)
整段话浓缩成大白话
前向渲染分两步算光照:
第一步(基础 Pass):算 1 个能投阴影的太阳光(逐像素)+ 所有粗略算的灯 + 烘焙光 / 环境光,烘焙光不受 SH 灯影响;
第二步(额外 Pass):其他要精细算的灯,一个灯多算一步,但默认这些灯没阴影;
想让更多灯有阴影,就得给 Shader 加一行代码,让 Shader 支持这个功能。
总结(核心关键点)
前向渲染默认仅 1 个平行光能投阴影,其他逐像素灯需额外 Pass 且无阴影;
基础 Pass 包含了场景的 "基础光影",是性能开销的核心;
要多灯投阴影,必须给 Shader 加
multi_compile_fwdadd_fullshadows编译指令生成对应变体。
3).两种渲染路径的特征对比
| 对比维度 | 前向渲染(Forward Rendering) | 延迟渲染(Deferred Rendering) |
|---|---|---|
| 核心功能 | 1. 几何→着色→光栅化串行执行2. 逐物体、逐光源直接着色计算3. 支持透明物体原生渲染4. 可直接使用 MSAA 抗锯齿 | 1. 几何→G-Buffer 缓存→光照后处理2. 先存位置 / 法线 / 颜色等数据,再统一计算光照3. 透明物体需单独前向通道处理4. 原生不支持 MSAA,需 FXAA/TAA 等后处理抗锯齿 |
| 光照性能 | 1. 复杂度:**O (物体数 × 光源数)**2. 多光源下 DrawCall 激增、性能急剧下降3. 少量点光源 / 方向光时效率极高 | 1. 复杂度:**O (像素数 × 光源数)**2. 光源数量几乎不影响 DrawCall,适合大量动态光源3. 高分辨率、高 PPI 下带宽与显存压力显著增大 |
| 资源开销 | 1. 显存占用低,仅需常规颜色 / 深度缓冲2. 带宽压力小,计算集中在顶点 / 片元着色 | 1. 需多张 G-Buffer 纹理,显存占用高2. 读写 G-Buffer 带宽消耗大,对显存带宽敏感 |
| 材质与特性 | 1. 完美支持各类混合模式、双面渲染、几何裁剪2. 兼容所有传统 shader 写法,适配性强3. 支持硬件几何细分、曲面细分等原生特性 | 1. 不适合复杂混合,透明物体需额外渲染流程2. 材质数据需写入 G-Buffer,数据结构固定3. 难以直接实现复杂几何类硬件特性 |
| 平台支持 | 1. 全平台通用:PC / 主机 / 移动端 / 网页端2. 兼容 OpenGL ES 2.0/3.0、WebGL 1.0/2.03. 老旧设备、低配移动平台首选 | 1. 主要支持现代 GPU:PC (DX10+/OpenGL3.3+)、主机、中高端移动端2. 低端手机、WebGL 1.0、老旧集成显卡不支持 / 性能极差3. 移动端需谨慎使用,带宽敏感易发热降频 |
| 典型优势 | 1. 简单稳定、调试友好2. 透明渲染原生支持,抗锯齿成本低3. 低配平台兼容性拉满 | 1. 百级 / 千级动态光源场景性能优势极大2. 光照逻辑统一,便于实现全局光照、高级光照效果3. 复杂光照渲染架构更清晰 |
| 典型缺陷 | 1. 多动态光源场景性能崩坏2. 复杂光照架构扩展性差 | 1. 显存 / 带宽开销大2. 透明渲染、抗锯齿实现复杂3. 老设备兼容性差 |
| 适用场景 | 手游、 indie 小游戏、极简渲染、大量透明物体、低配设备 | 开放世界、3A 大作、大量动态灯光、夜景 / 室内复杂光照场景 |
2.Pass标签
Pass标签的使用语法跟SubShader是一样的,也是键值对的形式,并且没有数量的限制,语法结构如下:
Pass
{
Tags { "键名1"="值1" "键名2"="值2" "键名3"="值3" }
}
核心规则
- 标签必须用
{ }包裹- 格式:
"Key"="Value"(双引号必须加,键值区分大小写)- 多个标签直接空格分隔,无需逗号
- 可写任意数量自定义 / 内置标签
- 作用域:只对当前 Pass 生效(SubShader Tags 对整个子着色器生效)
1).LightMode标签
LightMode标签定义了Pass在光照渲染流水线中的渲染规则。
|--------------|-----------------------------------------|
| 标签值 | 作用 |
| Always | 除了主要平行光,其他灯光不会产生任何光照 |
| ForwardBase | 用于计算主要平行光、逐顶点或者SH灯光、环境光和光照贴图,只能在前向渲染中使用 |
| ForwardAdd | 为每一个逐像素灯光生成一个Pass进行光照计算,只能在前向渲染中使用 |
| Deferred | 用于渲染G-Buffer,只能在延迟着色中使用 |
| ShadowCaster | 将物体的深度渲染到阴影贴图或者深度贴图中 |
Tags{"LightMode"="ForwardBase"}
总结(必背)
- ForwardBase + ForwardAdd = 前向渲染
- Deferred = 延迟渲染
- ShadowCaster = 阴影
- Always = 无光照纯颜色
- 标签决定 Pass 在什么时候被引擎调用,是光照系统的核心
超详细官方级解释(和你前面知识点 100% 对应)
1. Always
作用 :只画颜色,没有任何光照交互
不会计算平行光、点光、聚光灯
适合:UI、无光照物体、特效、自发光纯色物体
2. ForwardBase(前向主 Pass)
作用 :负责基础光照
主平行光
环境光 / 自发光
光照贴图 Lightmap
SH 球面调和光 / 顶点光照
一个物体只会执行 1 次
是前向渲染必须有的 Pass
3. ForwardAdd(前向附加 Pass)
作用 :为每一个影响物体的像素光执行一次
多光源 = 多 Draw Call
性能消耗 = 光源数量
必须配合 Blend One One 使用
这就是前向渲染多光源性能差的根本原因!
4. Deferred(延迟渲染)
作用 :不直接算光照,只输出数据到 G-Buffer
世界空间位置
法线
反照率 Albedo
金属度 / 平滑度
光照在后处理阶段统一计算
多光源性能几乎不下降
5. ShadowCaster(阴影投射)
作用 :把物体深度信息写入阴影贴图
物体本身不显示,只用来投射阴影
所有渲染路径都必须用它
2).PassFlags标签
PassFlags标签用于更改渲染流水线传递数据给Pass的方式。目前仅可以使用的值为OnlyDirectional。当使用前向渲染的时候,这个标签使得只有主要平行光、环境光或灯光探针、光照贴图的数据才能传递给Shader,SH和逐顶点灯光不能传递数据。
Pass
{
Tags { "LightMode" = "ForwardBase" }
// PassFlags 写法
PassFlags <OnlyDirectional>
}
核心规则
- 不是 Tags,是独立指令 它不是 写在
Tags { }里,而是单独一行PassFlags- 目前只有一个可用值:OnlyDirectional没有其他选项
- 只对 ForwardBase Pass 生效其他 LightMode 用了也没用
OnlyDirectional 到底做了什么?
一句话总结: 让这个 Pass 只接收主平行光、环境光、光照贴图、灯光探针 ,屏蔽掉所有 SH 光照 和 逐顶点光。
为什么要用它?(核心用途)
1. 精简光照计算,提升性能
你不需要 SH / 顶点光时,开启它 → GPU 少算一堆数据 → 性能更优。
2. 避免光照叠加混乱
有些自定义 Shader 不处理 SH / 顶点光,开启后可以防止多余光照干扰。
3.内置的multi_compile
即前向渲染实现多光源、多阴影
默认前向渲染 = 只有 1 个平行光能投射阴影 想要多光源阴影 / 多类型光源 → 必须加这 3 条编译指令!
1)multi_compile_fwdbase:编译ForwardBase Pass中所有变体,用于处理不同类型的光照贴图,并为主要平行光开启或者关闭阴影。
2)multi_compile_fwdadd:编译ForwardAdd Pass中所有变体,用于处理平行光、聚光灯和点光源,以及它们的cookie纹理
3)multi_compile_fwdadd_fullshadows:与multi_compile_fwdadd类似,但是增加了灯光投射实时阴影的效果
| 指令 | 作用 | 适用 Pass | 支持光源 | 阴影支持 |
|---|---|---|---|---|
| multi_compile_fwdbase | 主光照变体:处理光照贴图、主光阴影 | ForwardBase | 主平行光 | 主光阴影 |
| multi_compile_fwdadd | 附加光源变体:点 / 聚 / 平行光 | ForwardAdd | 点光、聚光、其他平行光 | 无阴影 |
| multi_compile_fwdadd_fullshadows | 带阴影的附加光源 | ForwardAdd | 点光、聚光、其他平行光 | 全阴影 |
最关键结论(必背)
- ForwardBase = 主平行光 → 用
multi_compile_fwdbase - ForwardAdd = 其他所有光源 → 用
multi_compile_fwdadd或fullshadows - 想让多光源都投射阴影 → 必须用
multi_compile_fwdadd_fullshadows - 不加这些指令 → 只有 1 个主光能阴影,其他光都没阴影
4.实现阴影效果
1).编写Shader
Shader "Custom/ShadowShader"
{
Properties
{
_MainColor ("Main Color", Color) = (1,1,1,1)
}
SubShader
{
Pass
{
//添加Pass标签
Tags{"LightMode"="ForwardBase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_fwdbase
#include "UnityCG.cginc"
#include "Lighting.cginc"
#include "AutoLight.cginc"
struct v2f
{
float4 pos:SV_POSITION;
float3 normal:TEXCOORD0;
float4 vertex:TEXCOORD1;
//使用预定义宏保存阴影坐标
SHADOW_COORDS(2)
};
fixed4 _MainColor;
v2f vert(appdata_base v)
{
v2f o;
o.pos=UnityObjectToClipPos(v.vertex);
o.normal=v.normal;
o.vertex=v.vertex;
//使用宏定义变换阴影坐标
TRANSFER_SHADOW(o)
return o;
}
fixed4 frag (v2f i):SV_Target
{
//准备变量
float3 n=UnityObjectToWorldNormal(i.normal);
n=normalize(n);
float3 l=WorldSpaceLightDir(i.vertex);
l=normalize(l);
float4 worldPos=mul(unity_ObjectToWorld,i.vertex);
//Lambert光照
fixed ndotl=saturate(dot(n,l));
fixed4 color=_LightColor0*_MainColor*ndotl;
//加上4个点光源的光照
color.rgb+=Shade4PointLights(
unity_4LightPosX0,unity_4LightPosY0,unity_4LightPosZ0,
unity_LightColor[0].rgb,unity_LightColor[1].rgb,
unity_LightColor[2].rgb,unity_LightColor[3].rgb,unity_4LightAtten0,worldPos.rgb,n)*_MainColor;
//加上环境光照
color+=unity_AmbientSky;
UNITY_LIGHT_ATTENUATION(shadowmask,i,worldPos.rgb)
//阴影合成
color.rgb*=shadowmask;
return color;
}
ENDCG
}
Pass
{
//添加Pass标签
Tags{"LightMode"="ForwardAdd"}
Blend One One
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_fwdadd_fullshadows
#include "UnityCG.cginc"
#include "Lighting.cginc"
#include "AutoLight.cginc"
struct v2f
{
float4 pos:SV_POSITION;
float3 normal:TEXCOORD0;
float4 vertex:TEXCOORD1;
//使用预定义宏保存阴影坐标
SHADOW_COORDS(2)
};
fixed4 _MainColor;
v2f vert(appdata_base v)
{
v2f o;
o.pos=UnityObjectToClipPos(v.vertex);
o.normal=v.normal;
o.vertex=v.vertex;
//使用宏定义变换阴影坐标
TRANSFER_SHADOW(o)
return o;
}
fixed4 frag (v2f i):SV_Target
{
//准备变量
float3 n=UnityObjectToWorldNormal(i.normal);
n=normalize(n);
float3 l=WorldSpaceLightDir(i.vertex);
l=normalize(l);
float4 worldPos=mul(unity_ObjectToWorld,i.vertex);
//Lambert光照
fixed ndotl=saturate(dot(n,l));
fixed4 color=_LightColor0*_MainColor*ndotl;
//加上4个点光源的光照
color.rgb+=Shade4PointLights( unity_4LightPosX0,unity_4LightPosY0,unity_4LightPosZ0,
unity_LightColor[0].rgb,unity_LightColor[1].rgb,
unity_LightColor[2].rgb,unity_LightColor[3].rgb,unity_4LightAtten0,worldPos.rgb,n)*_MainColor;
//加上环境光照
color+=unity_AmbientSky;
UNITY_LIGHT_ATTENUATION(shadowmask,i,worldPos.rgb)
//阴影合成
color.rgb*=shadowmask;
return color;
}
ENDCG
}
}
FallBack "Diffuse"
}
2).通过Frame Debug分析渲染流程
帧调试器允许用户通过回放的方式查看显卡在渲染当前帧的过程中每个步骤所产生的计算结果。




:光源的颜色(比如白色的灯就是白色)。
:物体表面的材质颜色(比如红色的球就是红色)。

为灯光亮度;
为物体材质的镜面反射颜色
为视角方向(由顶点指向摄像机)
为光线的反射方向
为物体材质的光泽度
