2. 光线追迹入门:光线向量表示法、平面与球面折射、反射定律的矩阵形式
光线追迹,说白了就是给光线画出一条精确的路径。你想想看,一束光从物体出发,经过透镜、棱镜,最后落到像面上——我们得知道它每一步往哪走、偏了多少。这就是追迹的核心。
我个人习惯把光线追迹分成两类:近轴追迹和实际追迹。近轴追迹假设光线与光轴的夹角很小,可以用线性近似;实际追迹则要考虑真实的三角函数。咱们这门课先啃近轴,因为它简单、快,而且能解决大部分工程问题。
2.1 光线的向量表示法
一条光线怎么描述?两个要素就够了:位置和方向。
位置用坐标点 P = (x, y, z) 表示,方向用单位向量 d = (α, β, γ) 表示。合起来就是一条光线的完整状态:
光线 = (P, d) = (x, y, z, α, β, γ)
这里 α, β, γ 是方向余弦,满足 α² + β² + γ² = 1。为什么用方向余弦?因为后续做折射、反射计算时,点积运算特别方便。
(pos, dir) 的元组对,清晰又好扩展。
举个例子,一条沿 z 轴正方向传播的光线,经过点 (0, 0, 0):
P = (0, 0, 0)
d = (0, 0, 1) # 方向余弦:α=0, β=0, γ=1
如果光线与 z 轴有个小夹角 θ,那方向余弦就是 (sinθ, 0, cosθ)。近轴近似下,sinθ ≈ θ,cosθ ≈ 1,这就简化了很多计算。
2.2 平面折射与反射
平面是最简单的界面。光线遇到平面,要么折射过去,要么反射回来。
2.2.1 平面折射——斯涅尔定律的向量形式
斯涅尔定律大家都知道:n₁ sinθ₁ = n₂ sinθ₂。但用向量怎么算?
设入射光线方向为 d,界面法向量为 n(指向入射介质),折射率为 n₁ 和 n₂。折射方向 d' 的计算公式是:
d' = (n₁/n₂) * d + [ - (n₁/n₂) * (d·n) + sqrt(1 - (n₁/n₂)² * (1 - (d·n)²)) ] * n
看着有点吓人?其实拆开看就三步:
- 计算入射角余弦:cosθ₁ = - (d·n)
- 计算折射角余弦:cosθ₂ = sqrt(1 - (n₁/n₂)² * sin²θ₁)
- 组合得到折射方向
2.2.2 平面反射
反射比折射简单多了。反射定律说:入射角等于反射角,且入射光线、法线、反射光线共面。
向量形式就一句话:
d' = d - 2 * (d·n) * n
这个公式漂亮又简洁。你想想看,它把反射光线的方向直接算出来了,不需要任何三角函数。
| 操作 | 输入 | 输出 | 关键公式 |
|---|---|---|---|
| 平面折射 | d, n, n₁, n₂ | d' | d' = (n₁/n₂)d + [ - (n₁/n₂)(d·n) + cosθ₂ ] n |
| 平面反射 | d, n | d' | d' = d - 2(d·n)n |
2.3 球面折射与反射
球面是光学系统里最常见的曲面。透镜的两个面基本都是球面(非球面是进阶内容,咱们后面再聊)。
2.3.1 球面折射
球面折射比平面多一步:先找到光线与球面的交点,再在交点处计算法线,最后用斯涅尔定律。
设球面曲率半径为 R,球心在 C = (0, 0, R)(假设球面顶点在原点)。光线从 P 点出发,沿方向 d 传播:
- 求交点:解参数方程 P + t·d 与球面方程 |P + t·d - C|² = R² 的二次方程
- 取有效解:取 t > 0 且最小的那个根
- 计算法线:n = (交点 - 球心) / R,归一化
- 应用折射公式:用 2.2.1 节的向量公式计算折射方向
def sphere_refract(P, d, R, n1, n2):
# 球心在 (0, 0, R)
C = np.array([0.0, 0.0, R])
# 解二次方程求交点
oc = P - C
a = d @ d
b = 2 * (oc @ d)
c = (oc @ oc) - R**2
disc = b**2 - 4*a*c
if disc < 0:
return None # 光线没碰到球面
t = (-b - np.sqrt(disc)) / (2*a)
if t < 0:
t = (-b + np.sqrt(disc)) / (2*a)
if t < 0:
return None
# 交点
Q = P + t * d
# 法线(指向入射介质)
n = (Q - C) / R
# 折射
cosI = - (d @ n)
if cosI < 0:
n = -n
cosI = - (d @ n)
ratio = n1 / n2
sinI2 = ratio**2 * (1 - cosI**2)
if sinI2 > 1:
return None # 全反射
cosT = np.sqrt(1 - sinI2)
d_prime = ratio * d + (ratio * cosI - cosT) * n
return Q, d_prime
2.3.2 球面反射
球面反射和球面折射的前三步完全一样——求交点、算法线。最后一步换成反射公式就行。
def sphere_reflect(P, d, R):
# 求交点(同折射)
C = np.array([0.0, 0.0, R])
oc = P - C
a = d @ d
b = 2 * (oc @ d)
c = (oc @ oc) - R**2
disc = b**2 - 4*a*c
if disc < 0:
return None
t = (-b - np.sqrt(disc)) / (2*a)
if t < 0:
t = (-b + np.sqrt(disc)) / (2*a)
if t < 0:
return None
Q = P + t * d
n = (Q - C) / R
# 反射
d_prime = d - 2 * (d @ n) * n
return Q, d_prime
2.4 反射定律的矩阵形式
近轴光学里,我们喜欢用矩阵。为什么?因为矩阵可以串联——多个光学面就是多个矩阵相乘,最后得到一个系统矩阵。
反射矩阵长什么样?对于平面反射镜,光线向量用 (y, θ) 表示(y 是光线高度,θ 是光线与光轴的夹角):
[ y' ] = [ 1 0 ] [ y ]
[ θ' ] [ 0 1 ] [ θ ]
等等,这不就是单位矩阵吗?没错,平面反射镜不改变光线的高度和角度(只改变传播方向,但近轴近似下角度符号不变)。
对于球面反射镜,情况不同了:
[ y' ] = [ 1 0 ] [ y ]
[ θ' ] [ -2/R 1 ] [ θ ]
这里的 -2/R 就是球面反射镜的光焦度。R 是曲率半径,符号规则是:光线入射方向为正,球心在入射侧 R 为正。
| 光学元件 | 传输矩阵 | 说明 |
|---|---|---|
| 平面反射镜 | [[1, 0], [0, 1]] | 单位矩阵,不改变光线状态 |
| 球面反射镜 | [[1, 0], [-2/R, 1]] | -2/R 是光焦度,R 有符号 |
| 平面折射 | [[1, 0], [0, n₁/n₂]] | 改变角度,不改变高度 |
| 球面折射 | [[1, 0], [(n₁-n₂)/(n₂·R), n₁/n₂]] | 同时改变高度和角度 |
2.5 本章知识体系
下面这张图把本章的核心逻辑串起来了。从光线向量表示出发,分平面和球面两条线,最后汇到矩阵形式——这就是近轴光线追迹的骨架。
2.6 代码实战:一个完整的平面折射示例
光说不练假把式。咱们写个完整的 Python 函数,把平面折射走一遍。
import numpy as np
def plane_refract(d, n, n1, n2):
"""
平面折射计算
参数:
d: 入射方向 (3元组或数组)
n: 法向量 (指向入射介质)
n1, n2: 折射率
返回:
折射方向 d',若全反射返回 None
"""
d = np.array(d)
n = np.array(n)
# 归一化
d = d / np.linalg.norm(d)
n = n / np.linalg.norm(n)
cosI = -np.dot(d, n)
# 如果光线从背面入射,翻转法线
if cosI < 0:
n = -n
cosI = -np.dot(d, n)
ratio = n1 / n2
sinI2 = ratio**2 * (1 - cosI**2)
if sinI2 > 1:
print("全反射发生!")
return None
cosT = np.sqrt(1 - sinI2)
d_prime = ratio * d + (ratio * cosI - cosT) * n
return d_prime
# 测试:空气(n=1)到玻璃(n=1.5),入射角30度
d_in = np.array([np.sin(np.radians(30)), 0, -np.cos(np.radians(30))])
n_vec = np.array([0, 0, 1]) # 法线指向z正方向
d_out = plane_refract(d_in, n_vec, 1.0, 1.5)
print(f"入射方向: {d_in}")
print(f"折射方向: {d_out}")
运行这段代码,你会看到折射角大约是 19.47 度——和斯涅尔定律算出来的一样。
numba 的 jit 装饰器。一条光线追几十个面,速度能快 10 倍以上。但调试阶段别加,不然报错信息看不懂。
好了,这一章的内容就到这儿。向量表示法、平面和球面的折射反射、矩阵形式——这三块是近轴光线追迹的基石。后面几章我们会在这个基础上搭更复杂的系统。