第三章 光源模型编程:点光源、平行光源、面光源的数学模型与Python实现
做光学系统集成,光源模型是绕不开的第一道坎。
我记得刚入行那会儿,带我的老师傅跟我说过一句话:「你连光都描述不清楚,后面整个光路设计就是瞎蒙。」这话糙理不糙。光源的数学模型,说白了就是给光一个「身份证」——它从哪来、往哪去、能量怎么分布。
这一章,咱们就动手把三种最常用的光源模型用Python写出来。点光源、平行光源、面光源,一个都别落下。
3.1 点光源模型
点光源是最基础的光源模型。它假设光从一个点向四面八方均匀发射。
数学上怎么描述?很简单。假设光源位置在 P0 = (x0, y0, z0),那么空间中任意一点 P = (x, y, z) 接收到的光线方向就是:
direction = P - P0
# 然后归一化
direction_norm = direction / ||direction||
这里有个坑,我踩过。归一化的时候如果 P 和 P0 重合,分母就是0。嗯,要加个判断。
注意:点光源的辐照度随距离平方衰减。也就是说,距离光源2米处的光强只有1米处的1/4。这个在物理仿真里必须考虑。
来看代码实现:
import numpy as np
class PointLight:
def __init__(self, position, intensity=1.0):
self.position = np.array(position, dtype=float)
self.intensity = intensity
def get_direction(self, point):
"""计算从光源到目标点的方向向量"""
vec = np.array(point, dtype=float) - self.position
dist = np.linalg.norm(vec)
if dist < 1e-10:
return np.zeros(3), 0.0 # 重合点,返回零向量
direction = vec / dist
# 辐照度随距离衰减
irradiance = self.intensity / (4 * np.pi * dist**2)
return direction, irradiance
我个人习惯把位置和强度分开管理。这样后期如果要加光源阵列,改起来方便。
3.2 平行光源模型
平行光源模拟的是太阳光这类远距离光源。光线之间互相平行,没有发散角。
数学上更简单——只需要一个方向向量 D = (dx, dy, dz)。所有光线都沿着这个方向走。
你想想看,平行光源没有位置概念。所以它的辐照度不随距离变化。这一点和点光源完全不同。
class ParallelLight:
def __init__(self, direction, intensity=1.0):
# 归一化方向向量
self.direction = np.array(direction, dtype=float)
self.direction = self.direction / np.linalg.norm(self.direction)
self.intensity = intensity
def get_direction(self, point=None):
"""平行光方向与目标点无关"""
return self.direction, self.intensity
注意看,get_direction 方法里我加了个 point=None 的默认参数。这样做是为了和点光源保持接口一致。我在项目中吃过接口不统一的亏,后来所有光源类都统一了 get_direction(point) 这个接口。
小技巧:平行光源的方向向量通常指向光源方向,而不是光线传播方向。比如太阳在头顶,方向向量就是 (0, 1, 0)。这个约定在光线追踪里很常见。
3.3 面光源模型
面光源就复杂多了。它不是一个点,而是一个发光面。比如LED面板、荧光屏都属于面光源。
面光源的数学模型可以看作无数个点光源的集合。但实际计算时,我们不可能真的去算每一个点。常用的方法是蒙特卡洛采样。
假设面光源是一个矩形,长宽分别为 width 和 height,中心在 center,法线方向为 normal。
class AreaLight:
def __init__(self, center, width, height, normal, intensity=1.0):
self.center = np.array(center, dtype=float)
self.width = width
self.height = height
self.normal = np.array(normal, dtype=float)
self.normal = self.normal / np.linalg.norm(self.normal)
self.intensity = intensity
# 计算局部坐标系
# 这里我选了一个与法线不平行的向量做叉积
if abs(self.normal[0]) < 0.9:
self.u = np.cross(self.normal, [1, 0, 0])
else:
self.u = np.cross(self.normal, [0, 1, 0])
self.u = self.u / np.linalg.norm(self.u)
self.v = np.cross(self.normal, self.u)
def sample_point(self):
"""在面光源上随机采样一个发光点"""
u_offset = (np.random.random() - 0.5) * self.width
v_offset = (np.random.random() - 0.5) * self.height
sample = self.center + u_offset * self.u + v_offset * self.v
return sample
def get_direction(self, point, num_samples=16):
"""从目标点看向面光源的多个采样点"""
directions = []
total_irradiance = 0.0
for _ in range(num_samples):
sample = self.sample_point()
vec = sample - np.array(point, dtype=float)
dist = np.linalg.norm(vec)
if dist < 1e-10:
continue
direction = vec / dist
# 面光源的辐照度与采样点处的发光角度有关
cos_theta = abs(np.dot(direction, self.normal))
irradiance = self.intensity * cos_theta / (dist**2)
directions.append((direction, irradiance / num_samples))
total_irradiance += irradiance / num_samples
return directions, total_irradiance
这里我用了16个采样点。实际项目中,采样数量要根据精度要求来调。我曾经做过一个项目,采样数从16调到64,结果差异不到1%,但计算时间翻了两倍。所以别盲目追求高采样。
核心要点:面光源的辐照度计算要考虑两个因素——距离衰减和发光角度衰减。发光面法线方向的光最强,侧面看过去就暗了。这就是为什么LED面板正面看很亮,侧面看就一般。
3.4 光源参数化控制
写到这里,你可能已经发现了——三种光源有很多共同参数。比如位置、强度、方向。把这些参数统一管理,就是参数化控制的核心思想。
我建议用一个基类来定义公共接口:
class BaseLight:
def __init__(self):
self.name = ""
self.enabled = True
self.color = (1.0, 1.0, 1.0) # RGB
def get_direction(self, point):
raise NotImplementedError
def set_intensity(self, value):
self.intensity = max(0.0, value)
def toggle(self):
self.enabled = not self.enabled
然后让三种光源都继承这个基类。这样做的好处是,你在系统集成时可以用统一的方式控制所有光源。
举个例子,假设你要做一个多光源场景:
class LightManager:
def __init__(self):
self.lights = []
def add_light(self, light):
self.lights.append(light)
def compute_total_irradiance(self, point):
total = 0.0
for light in self.lights:
if not light.enabled:
continue
_, irradiance = light.get_direction(point)
total += irradiance
return total
你看,不管是什么类型的光源,到了 LightManager 这里都一样处理。这就是参数化控制的威力。
实战建议:在做系统集成时,把光源参数放到配置文件里。比如用YAML或JSON格式。这样调试时改参数不用重新编译代码。我现在的项目都是这么干的,效率提升很明显。
3.5 三种光源的对比总结
| 光源类型 | 数学模型 | 关键参数 | 辐照度衰减 | 典型应用 |
|---|---|---|---|---|
| 点光源 | 单点发射,球面波 | 位置、强度 | 1/r² | 小灯泡、LED单颗 |
| 平行光源 | 方向一致,平面波 | 方向、强度 | 无衰减 | 太阳光、准直光 |
| 面光源 | 面内多点发射 | 位置、尺寸、法线、强度 | 1/r² + 角度衰减 | LED面板、显示屏 |
最后说一句。光源模型这东西,理论看着简单,但真正集成到光学系统里,坑不少。比如点光源和面光源的接口统一问题,我当年改了三版才满意。别急,慢慢来,代码写多了自然就顺手了。
下一章咱们聊聊光线追迹的基础算法。到时候这些光源模型就要真正派上用场了。