3、光源模型:点光源、线光源、面光源、体积光源的定义与数学描述
做照明系统建模这些年,我越来越觉得——选对光源模型,比调对参数更重要。模型选错了,后面算得再精细,结果也是错的。
这一章,咱们就把四种基本光源模型掰开揉碎讲清楚。点、线、面、体,每种都有它的数学定义、适用场景,还有我踩过的坑。
3.1 点光源——最基础的起点
点光源,说白了就是假设光源缩成一个点,向所有方向均匀发光。这是最简单的模型,也是我入行时第一个接触的。
数学定义:
点光源在空间某点 P₀ 处,向各方向辐射光通量。在距离 r 处的照度 E 为:
E = I / r²
其中 I 是光源的光强(单位:cd),r 是距离(单位:m)。
如果考虑光线方向与表面法线的夹角 θ,则:
E = I · cos(θ) / r²
这就是经典的平方反比定律。你想想看,距离翻倍,照度变成四分之一。这个规律在近场时特别明显。
3.2 线光源——从点到线的延伸
荧光灯管、LED灯带,这些长条形的光源,用点光源算就不太准了。这时候需要线光源模型。
数学定义:
线光源可以看作无数个点光源沿一条直线排列。假设线光源长度为 L,单位长度光强为 Iₗ(单位:cd/m),则线上某微元 dl 在目标点产生的照度为:
dE = Iₗ · dl · cos(θ) / r²
整条线光源的贡献,就是对 dl 从 0 到 L 积分:
E = ∫₀ᴸ Iₗ · cos(θ) / r² · dl
这个积分有解析解,但说实话,我平时更常用数值积分。尤其是当线光源不是直线,或者有弯曲的时候。
3.3 面光源——二维平面辐射
面板灯、发光顶棚、格栅灯……这些是面光源的典型代表。面光源模型比点光源复杂不少,但更贴近真实。
数学定义:
面光源可以看作无数个点光源在一个二维平面上分布。假设面光源面积为 A,单位面积光强为 Iₐ(单位:cd/m²),则面上某微元 dA 在目标点产生的照度为:
dE = Iₐ · dA · cos(θ) / r²
整个面的贡献是二重积分:
E = ∬ₐ Iₐ · cos(θ) / r² · dA
这里有个关键概念——朗伯体。朗伯体表面向各方向发光亮度相同,遵循余弦定律。大多数漫反射面光源可以近似为朗伯体。
| 光源类型 | 辐射特性 | 数学描述 | 典型场景 |
|---|---|---|---|
| 朗伯面光源 | 各方向亮度相同 | I(θ) = I₀ · cos(θ) | 漫反射面板灯 |
| 非朗伯面光源 | 有方向性辐射 | I(θ) = I₀ · f(θ) | 格栅灯、射灯面板 |
3.4 体积光源——三维空间发光体
体积光源是最复杂的模型,也是我近几年用得越来越多的。雾灯效果、火焰、云层、半透明材质的自发光……这些都需要体积光源。
数学定义:
体积光源在三维空间 V 内连续发光。单位体积的光强密度为 Iᵥ(单位:cd/m³),则体积内某微元 dV 在目标点产生的照度为:
dE = Iᵥ · dV · cos(θ) / r²
整个体积的贡献是三重积分:
E = ∭ᵥ Iᵥ · cos(θ) / r² · dV
嗯,这个积分基本没有解析解。我都是用蒙特卡洛方法或者体素化方法来做数值计算。
1) 光源本身有半透明介质(如磨砂灯泡内部)
2) 需要模拟大气散射效果(如雾灯、体积光)
3) 光源尺寸与场景尺寸在同一量级(如发光天花板整体)
3.5 四种模型的对比与选择
说了这么多,到底什么时候用哪种?我整理了一个对比表,方便你快速决策。
| 模型 | 维度 | 计算量 | 精度 | 适用距离 | 典型误差来源 |
|---|---|---|---|---|---|
| 点光源 | 0D | 极低 | 低 | 远场(r > 10×光源尺寸) | 近场误差大,忽略光源尺寸 |
| 线光源 | 1D | 低 | 中 | 中远场 | 两端发光不均匀 |
| 面光源 | 2D | 中 | 高 | 近中远场 | 朗伯假设偏差 |
| 体积光源 | 3D | 高 | 最高 | 全距离 | 计算资源消耗大 |
我个人习惯是:能用简单模型就别用复杂的。先问自己三个问题:
- 光源尺寸相对于照射距离,能不能忽略?能 → 点光源
- 光源是不是长条形?是 → 线光源
- 光源是不是平面发光?是 → 面光源
如果以上都不是,或者需要模拟介质内部发光,才考虑体积光源。
3.6 代码实现:四种光源的照度计算
理论讲完了,咱们上点实际的。下面是一个简单的 Python 实现,演示四种光源在目标点产生的照度计算。
import numpy as np
class PointLight:
"""点光源模型"""
def __init__(self, position, intensity):
self.pos = np.array(position) # 光源位置 [x, y, z]
self.I = intensity # 光强 (cd)
def illuminance_at(self, point, normal=None):
r_vec = np.array(point) - self.pos
r = np.linalg.norm(r_vec)
if r == 0:
return 0
E = self.I / (r ** 2)
if normal is not None:
cos_theta = np.dot(-r_vec / r, normal)
E *= max(0, cos_theta)
return E
class LineLight:
"""线光源模型(数值积分)"""
def __init__(self, start, end, intensity_per_meter, n_segments=100):
self.start = np.array(start)
self.end = np.array(end)
self.I_l = intensity_per_meter # 单位长度光强 (cd/m)
self.n = n_segments
def illuminance_at(self, point, normal=None):
total = 0.0
L = np.linalg.norm(self.end - self.start)
dl = L / self.n
for i in range(self.n):
t = (i + 0.5) / self.n
p = self.start + t * (self.end - self.start)
r_vec = np.array(point) - p
r = np.linalg.norm(r_vec)
if r == 0:
continue
dE = self.I_l * dl / (r ** 2)
if normal is not None:
cos_theta = np.dot(-r_vec / r, normal)
dE *= max(0, cos_theta)
total += dE
return total
class AreaLight:
"""面光源模型(朗伯体,数值积分)"""
def __init__(self, corners, intensity_per_area, nx=20, ny=20):
self.corners = np.array(corners) # 4个角点 [左上, 右上, 右下, 左下]
self.I_a = intensity_per_area # 单位面积光强 (cd/m²)
self.nx, self.ny = nx, ny
def illuminance_at(self, point, normal=None):
total = 0.0
# 计算面光源的局部坐标系
vx = self.corners[1] - self.corners[0]
vy = self.corners[3] - self.corners[0]
dx = np.linalg.norm(vx) / self.nx
dy = np.linalg.norm(vy) / self.ny
ux = vx / np.linalg.norm(vx)
uy = vy / np.linalg.norm(vy)
dA = dx * dy
for i in range(self.nx):
for j in range(self.ny):
p = self.corners[0] + (i + 0.5) * dx * ux + (j + 0.5) * dy * uy
r_vec = np.array(point) - p
r = np.linalg.norm(r_vec)
if r == 0:
continue
dE = self.I_a * dA / (r ** 2)
if normal is not None:
cos_theta = np.dot(-r_vec / r, normal)
dE *= max(0, cos_theta)
total += dE
return total
class VolumeLight:
"""体积光源模型(蒙特卡洛采样)"""
def __init__(self, bounds, intensity_per_volume, n_samples=5000):
self.bounds = np.array(bounds) # [[xmin, xmax], [ymin, ymax], [zmin, zmax]]
self.I_v = intensity_per_volume # 单位体积光强 (cd/m³)
self.n = n_samples
def illuminance_at(self, point, normal=None):
total = 0.0
vol = np.prod(self.bounds[:, 1] - self.bounds[:, 0])
for _ in range(self.n):
p = np.array([
np.random.uniform(self.bounds[0, 0], self.bounds[0, 1]),
np.random.uniform(self.bounds[1, 0], self.bounds[1, 1]),
np.random.uniform(self.bounds[2, 0], self.bounds[2, 1])
])
r_vec = np.array(point) - p
r = np.linalg.norm(r_vec)
if r == 0:
continue
dE = self.I_v * vol / self.n / (r ** 2)
if normal is not None:
cos_theta = np.dot(-r_vec / r, normal)
dE *= max(0, cos_theta)
total += dE
return total
# 使用示例
if __name__ == "__main__":
# 点光源
pl = PointLight([0, 0, 2], 1000)
print(f"点光源照度: {pl.illuminance_at([1, 0, 0], [0, 0, 1]):.2f} lx")
# 线光源
ll = LineLight([-1, 0, 2], [1, 0, 2], 500)
print(f"线光源照度: {ll.illuminance_at([0, 1, 0], [0, 0, 1]):.2f} lx")
# 面光源
al = AreaLight([[-0.5, -0.5, 2], [0.5, -0.5, 2], [0.5, 0.5, 2], [-0.5, 0.5, 2]], 200)
print(f"面光源照度: {al.illuminance_at([0, 0, 0], [0, 0, 1]):.2f} lx")
# 体积光源
vl = VolumeLight([[-0.3, 0.3], [-0.3, 0.3], [1.8, 2.2]], 100)
print(f"体积光源照度: {vl.illuminance_at([0, 0, 0], [0, 0, 1]):.2f} lx")
- 点光源直接用公式,最快。
- 线光源和面光源用数值积分,精度由分段数控制。
- 体积光源用蒙特卡洛方法,采样数越多越准,但计算量也越大。
- 实际项目中,我会根据精度需求动态调整分段数或采样数。
好了,四种光源模型就讲到这里。记住一句话:模型是工具,不是真理。选对工具,你的照明设计就成功了一半。