今天我们来学习unity中的自定义光照

Unity 中的菲涅尔效应

核心原理

菲涅尔效应描述的是:物体表面的反射率随观察角度变化

  • 正对观察(视线与法线夹角小)→ 反射弱,漫反射/折射强
  • 掠射观察(视线与法线夹角大,接近平行表面)→ 反射强

典型例子:看一池水,垂直往下看能看到水底;远处水面像镜子一样反射天空。

在 Unity 中的实现方式

方式 说明
菲涅尔方程(物理准确) 基于折射率计算,用于 PBR(基于物理的渲染)材质
Schlick 近似 最常用的简化公式:F = F0 + (1 - F0) * (1 - cosθ)^5
内置 Shader Standard/URP/HDRP 的金属度/光滑度工作流程已自动包含

实际应用场景

  • 边缘光(Rim Light):角色轮廓发光
  • 玻璃/水面:边缘反射更强
  • 全息/护盾效果:视角相关的透明度变化
  • 卡通渲染:描边或边缘染色

简而言之就是物体在你观察时变得更易反射的原理

我们有的时候从unity商店下载的资源会显示不出来,这个是因为渲染管不适配导致的,有的材质之前是用Built in渲染管,而我们使用的是URP渲染管

image-20260530110534831

我们只需要选中这个material然后改成URP渲染即可

image-20260530110731455

image-20260530111131209

这样就可以使用我们的材质了

事实上shadergraph已经内置了菲涅尔反射的支持

比如我们将光滑度smoothness改成0,此时的材质就是一张不反光的普通贴图

但是如果我们将其数值调大就可以看到明显的反光

image-20260530111418053

image-20260530111536410

我们可以自己创建一个material

首先像之前那样创建基础色和基础纹理,选择无光照unlit着色器

image-20260530112826361

然后创建菲涅尔节点和浮点类型的Fresnelpower节点控制fresnel的强度

image-20260530112917414

强度默认为1 改为slider滑块,范围0~20

image-20260530113027407

然后用FresnelColor控制颜色,使用HDR

image-20260530113202944

image-20260530112746177

我们改动强度值就可以看到这个材质的反光了

image-20260530113537246

漫反射光

漫反射光的原理
image-20260531104133890

我们可以知道漫反射就是法向量夹角成反比

漫反射的光简化为 n · l

随着角度的增大,随着角度的增大,点积逐渐减少

image-20260531104451185

核心公式

漫反射光照强度 ∝ n · l

其中:

  • n = 表面法向量(垂直于表面的单位向量)
  • l = 指向光源的单位向量
  • · = 点积(dot product)

点积的几何定义是:

nl=∣n∣∣l∣cosθ=cosθ

(因为都是单位向量,长度为1)

夹角 θ cosθ 漫反射强度
0°(正对光源) 1.0 最强
30° 0.87 较强
45° 0.71 中等
60° 0.50 较弱
90°(切线方向) 0 无光
>90° 负值 0(背面无光照)

直观理解

想象一束光照射到地面:

  • 正午(太阳在头顶):光线垂直地面,单位面积接收的光最多 → 最亮
  • 傍晚(太阳斜射):同样的光束”铺开”在更大的面积上,单位面积能量减少 → 变暗

这就是”与法向量夹角成反比“的本质——夹角越大,有效光照面积越大,单位面积能量越分散,所以看起来越暗。

我们像之前那样创建颜色和纹理节点

image-20260531105626713

实现卡通渲染效果

然后我们实现光源的计算, 这里需要介绍几个节点

节点功能

Main Light Direction 是 Unity Shader Graph 中的一个内置节点(Shader Graph 13+ / Unity 2022+ 引入),用于获取场景中**主方向光(Main Directional Light)**的方向

属性 说明
输出 Direction (Vector3) — 世界空间中主方向光的归一化方向
主光源定义 投射阴影的方向光;如果没有,则回退到第一个不投射阴影的方向光

关键注意点:方向是反的

这是最容易踩坑的地方。Unity 官方讨论中确认:

Main Light Direction 节点输出的方向是 从光源指向表面 的向量(即 GetMainLight().direction 被取反了)。

但在光照计算中,我们需要的是 从表面指向光源 的向量(即 l),所以:

1
实际光照方向 l = -MainLightDirection

这个节点经典用法就是这样

1
2
3
[Main Light Direction] → [Negate] ───┐
├──→ [Dot Product]
[Normal Vector] (World) ─────────┘

而其他的几个就是

  1. Main Light Direction 获取光源方向
  2. Negate 取反,得到从表面指向光源的向量 l
  3. Normal Vector(世界空间)获取表面法线 n
  4. Dot Product 计算 n · l

这里简单说一下

1. [Normal Vector](世界空间)

属性 说明
输入 无(自动从模型顶点法线获取)
输出 Vector3 — 世界空间中的表面法线 n
作用 告诉你”表面朝哪个方向”

法线是什么? 想象一个球体,每个点的法线就是从球心指向该点的方向。平面的话,法线就是垂直于平面的向量。

注意:必须选择 World 空间(而非 Object/Tangent),因为光照方向也是在世界空间中定义的,两者要在同一坐标系下才能做点积。


2. [Main Light Direction]

属性 说明
输出 Vector3 — 主方向光的归一化方向
方向含义 从光源指向表面(光传播的方向)

plain

1
2
3
4
如果太阳在上方,输出大概是 (0, -1, 0)
↑ 太阳

↓ 光往下走

3. [Negate](取反)

输入 输出
(x, y, z) (-x, -y, -z)

为什么要取反?

因为光照公式 n · l 中的 l 必须是 从表面指向光源

plain

1
2
Main Light Direction:  光源 ──→ 表面   = (0, -1, 0)
Negate 后: 表面 ──→ 光源 = (0, 1, 0) ← 这才是 l

4. [Dot Product](点积)

这是核心运算。数学上:

nl=∣n∣∣l∣cosθ=cosθ

(因为都是单位向量,长度为1)

它把”角度关系”转换成”亮度数值”:

夹角 θ 点积结果 含义
0°(正对光源) 1.0 最亮
45° 0.707 较亮
60° 0.5 中等
90°(切线) 0 无光
120°(背面) -0.5 理论上负值,但光照不能为负

为了满足这个nl=∣n∣∣l∣ 我们需要这些节点

main light direction表示从光源到物体的方向,所以用negate节点取反获得从物体表面到光源的方向,接着计算nagate输出的反向向量与normal vector输出的法线向量的点积

image-20260531105932981

以上我们已经完成了光照的计算,有两种实现方式来实现渲染

image-20260531113646090

如图,我们有两个节点,分别为step节点和smoothstep节点,由图像你就可以很清楚的看出二者的区别

节点功能

Step 节点

功能

硬阈值切换 — 输入大于阈值输出1,小于等于阈值输出0。

plain

1
Step(Edge, In) = In > Edge ? 1 : 0
输入 In Edge=0.5 结果
0.2 0.5 0
0.5 0.5 0(或1,取决于实现,通常是0)
0.8 0.5 1

图形表示

plain

1
2
3
4
5
输出
1 │ ┌────────
0 │──────┘
└────┬────┬────→ 输入
0.5(Edge)

完全硬边,没有过渡。

典型用法

  • 制作硬边遮罩(如卡通渲染的明暗分界)
  • 将连续的灰度图转成纯黑白

Smoothstep 节点

功能

平滑阈值过渡 — 在 Edge1 和 Edge2 之间做平滑的 Hermite 插值

plain

1
2
3
4
Smoothstep(Edge1, Edge2, In):
如果 In ≤ Edge1: 输出 0
如果 In ≥ Edge2: 输出 1
如果 Edge1 < In < Edge2: 输出平滑的 0→1 曲线

图形表示

plain

1
2
3
4
5
6
输出
1 │ ┌──────
│ ╱
0 │──────╱
└────┬────┬────→ 输入
Edge1 Edge2

有过渡带,边缘柔和

和 Step 的对比

Step Smoothstep
参数 1个阈值 (Edge) 2个阈值 (Edge1, Edge2)
过渡 硬切,无过渡 平滑曲线过渡
用途 硬边、二值化 软边、抗锯齿、柔和遮罩

这两个最明显的区别就是step当in输入值小于edge阈值的时候节点输出0,0是黑色,1是白色,我们可以通过控制阈值实现,不过这个明暗过度非常生硬

所以smoothstep可以实现平滑过渡,这个函数具有缓冲区域输出值在此区间内会从0平滑过渡到1,所以这个拥有两个边界值,当 in输入值< edge1输出位0 ,当in输入值 > edge2输出为1,如果in输入值位于 edge1和edge2之间,那么输出将会是介于0~1之间的某个值

image-20260531133019055

我们添加一个vector2的值cutoff thresholds用split分割出x和y来控制两个阈值,分别设置为-0.05和0

不过为了更直观添加一个ambient light strength的浮点节点来控制环境光强度,我们将其设置为(0~1)的slider滑块

Lerp(Linear Interpolation,线性插值)是 Shader Graph 中最常用、最核心的混合节点,它的作用很简单:

在两个值之间,按一个百分比取中间值。

数学公式

Lerp(A,B,T)=A+(BAT=A×(1−T)+B×T

参数 含义
A 起始值(T=0 时的结果)
B 目标值(T=1 时的结果)
T 混合因子(0~1),也叫”权重”

直观理解

想象一条从 A 到 B 的线段,T 就是你走到的位置:

graph LR
    A["🅰️ A
T=0"] -->|"25% B"| P1["T=0.25
75%A+25%B"] P1 -->|"50% B"| P2["T=0.5
中间值
50%A+50%B"] P2 -->|"75% B"| P3["T=0.75
25%A+75%B"] P3 -->|"100% B"| B["🅱️ B
T=1"] style A fill:#e74c3c,stroke:#c0392b,stroke-width:3px,color:#fff style B fill:#3498db,stroke:#2980b9,stroke-width:3px,color:#fff style P1 fill:#c0392b,stroke:#a93226,stroke-width:2px,color:#fff style P2 fill:#8e44ad,stroke:#7d3c98,stroke-width:2px,color:#fff style P3 fill:#2980b9,stroke:#2471a3,stroke-width:2px,color:#fff

然后将我们smoothstep的输出值放入T槽,将ambient light strength值放入A槽,B槽保持默认的1,这样我们只需要改动A槽就可以了

我们将这个材质添加到物体上,但是你可以看到阴影面黑的离谱,这个只需要调整环境光强度即可

image-20260531141511789

这样就正常多了,这个可以自己调整x y 还有光照强度尝试一下

image-20260531141623711