2. 折射定律与斯涅尔定律:光从空气进入水中的路径计算
光从空气射入水中,路径为什么会弯折?这个问题,我在刚入行时就被问过。当时我随口答了一句“因为水的折射率大”,结果被老工程师追问:“大多少?弯多少?你给我算出来。”嗯,从那以后,我就老老实实把斯涅尔定律刻在了脑子里。
说白了,折射定律就是描述光在两种介质交界处“拐弯”的数学关系。你想想看,光在不同材料里跑的速度不一样,到了界面就得调整方向——这就是折射的本质。
2.1 斯涅尔定律的数学形式
斯涅尔定律的表达式很简单,但用起来门道不少:
n₁ · sin(θ₁) = n₂ · sin(θ₂)
其中:
- n₁、n₂ —— 两种介质的折射率(无量纲)
- θ₁ —— 入射角(光线与法线的夹角,单位:度或弧度)
- θ₂ —— 折射角(光线与法线的夹角)
我个人习惯把入射角和折射角都换算成弧度再代入计算,这样在后续做微分或优化时不容易出错。当然,如果你只是做单次验算,用角度制配合计算器也行。
核心要点:折射定律的本质是“光程最短”原理的推论。光在两点之间走的路径,一定是耗时最少的——这就是费马原理。斯涅尔定律只是费马原理在平面界面下的特解。
2.2 空气-水界面的典型计算
咱们来算一个实际案例。假设一束光从空气射入水中,入射角为 30°。空气折射率取 1.0003(近似为 1.0),水折射率取 1.333(标准值)。
计算步骤:
- 确定已知量:n₁ = 1.0,θ₁ = 30°,n₂ = 1.333
- 代入公式:1.0 × sin(30°) = 1.333 × sin(θ₂)
- 计算 sin(30°) = 0.5
- 得到 sin(θ₂) = 0.5 / 1.333 ≈ 0.3751
- 求反函数:θ₂ = arcsin(0.3751) ≈ 22.02°
结果:折射角约为 22.0°,比入射角小了约 8°。光从空气进入水中,确实向法线方向偏折了。
工程小技巧:我在做水下成像系统时,经常需要快速估算折射角。记住一个经验值:空气到水的折射,入射角每增加 10°,折射角大约增加 7°~8°。这个近似在 0°~40° 入射角范围内误差不超过 1°。
2.3 不同入射角的折射结果对比
为了让你更直观地理解,我整理了一组典型数据。这些数据我在做水下激光测距项目时反复验算过:
| 入射角 θ₁ (°) | sin(θ₁) | sin(θ₂) | 折射角 θ₂ (°) | 偏折量 (°) |
|---|---|---|---|---|
| 0 | 0.0000 | 0.0000 | 0.00 | 0.00 |
| 10 | 0.1736 | 0.1303 | 7.49 | 2.51 |
| 20 | 0.3420 | 0.2566 | 14.87 | 5.13 |
| 30 | 0.5000 | 0.3751 | 22.02 | 7.98 |
| 40 | 0.6428 | 0.4822 | 28.82 | 11.18 |
| 50 | 0.7660 | 0.5747 | 35.09 | 14.91 |
| 60 | 0.8660 | 0.6498 | 40.52 | 19.48 |
从表格里能看出两个规律:第一,入射角越大,偏折量越大;第二,偏折量不是线性增长的——入射角从 0° 到 30° 偏折了约 8°,但从 30° 到 60° 偏折了约 11.5°。为什么会这样?因为正弦函数本身就不是线性的。
2.4 折射定律的矢量形式
在实际工程中,尤其是做光线追迹时,我很少用标量形式的斯涅尔定律。为什么?因为光线在三维空间里传播,入射角、折射角都是相对于法线定义的,标量形式处理起来很麻烦。
我更推荐用矢量形式:
n₁ · (I × N) = n₂ · (T × N)
其中 I 是入射光线单位矢量,T 是折射光线单位矢量,N 是界面法线单位矢量。这个公式的物理意义是:入射光线、折射光线和法线三者共面,且它们之间的夹角满足折射关系。
实际计算时,我通常用这个等价形式:
T = (n₁/n₂) · I + [ (n₁/n₂) · cos(θ₁) - cos(θ₂) ] · N
这个形式的好处是:只要知道入射光线方向 I 和法线方向 N,就能直接算出折射光线方向 T,不需要先求角度再求方向。
注意:矢量形式中,cos(θ₂) 必须通过 sin(θ₂) 计算,而 sin(θ₂) = (n₁/n₂) · sin(θ₁)。如果 (n₁/n₂) · sin(θ₁) > 1,说明发生了全反射——此时没有折射光线,只有反射光线。我在做光纤耦合器时就被这个坑过,算出来的折射角是复数,一开始还以为是程序写错了。
2.5 折射定律的知识体系
下面这张图是我自己总结的折射定律知识框架,帮你理清各个概念之间的关系:
2.6 工程中的避坑指南
我在实际项目中踩过不少折射相关的坑,挑几个典型的说说:
- 折射率取值问题:水的折射率不是固定的 1.333。温度每升高 10°C,折射率大约下降 0.0005。海水比淡水高约 0.005。我在做水下光学通信时,就因为没考虑海水盐度,导致链路预算差了 2 个 dB。
- 波长依赖性:折射率随波长变化——这就是色散。蓝光在水中的折射率约 1.340,红光约 1.331。如果你做的是宽光谱系统,必须用对应波长的折射率,否则成像会有色差。
- 界面不平整:实际水面不是理想平面。有波浪时,法线方向在变化,折射角也在变化。这种情况下,标量形式的斯涅尔定律只能做统计平均,精确计算需要用波动光学。
我的习惯做法:在做折射计算前,先确认三个参数:界面两侧的折射率(含波长和温度修正)、入射光线的方向矢量、界面法线的方向矢量。这三个参数对了,剩下的就是代数运算。我曾经因为法线方向取反,导致整个光学系统仿真结果完全错误,查了两天才发现。
2.7 代码实现示例
下面是我常用的折射计算函数,用 Python 写的。这个函数我用了好几年,在各种项目里验证过:
import numpy as np
def snell_refraction(I, N, n1, n2):
"""
计算折射光线方向矢量
参数:
I : 入射光线单位矢量 (3元素数组)
N : 界面法线单位矢量 (3元素数组,指向入射介质)
n1: 入射介质折射率
n2: 折射介质折射率
返回:
T : 折射光线单位矢量 (3元素数组)
如果发生全反射,返回 None
"""
# 计算入射角余弦
cos_theta1 = -np.dot(I, N)
# 计算折射角正弦
sin_theta1 = np.sqrt(1 - cos_theta1**2)
sin_theta2 = (n1 / n2) * sin_theta1
# 判断全反射
if sin_theta2 > 1.0:
return None # 全反射,无折射光线
# 计算折射角余弦
cos_theta2 = np.sqrt(1 - sin_theta2**2)
# 计算折射光线方向
T = (n1 / n2) * I + (cos_theta2 - (n1 / n2) * cos_theta1) * N
return T
# 示例:空气到水,入射角30°
I = np.array([0.0, -0.5, 0.8660]) # 入射方向
N = np.array([0.0, 1.0, 0.0]) # 法线方向
n1, n2 = 1.0, 1.333
T = snell_refraction(I, N, n1, n2)
if T is not None:
theta2 = np.degrees(np.arccos(-np.dot(T, N)))
print(f"折射角: {theta2:.2f}°")
else:
print("发生全反射")
这段代码的核心逻辑就三步:算入射角、算折射角正弦、判断全反射后算方向。我建议你在自己的项目里直接复用这个函数,它处理了所有边界情况。
注意:代码中的法线方向 N 指向入射介质。如果法线指向折射介质,cos_theta1 的计算符号会反,结果全错。我在做双目立体视觉标定时就犯过这个错,标定出来的折射参数全是错的,折腾了一周才发现是法线方向约定不一致。
折射定律看起来简单,但真正用好它,需要理解背后的物理原理、掌握矢量计算方法、注意工程中的各种细节。希望这一章的内容能帮你少走一些弯路——至少别像我当年那样,因为法线方向搞反了白加班一周。