4. 光线追迹基础:斯涅尔定律的向量形式、光线与自由曲面求交算法(牛顿迭代法)、光线微分方程

光线追迹,说白了就是模拟光线怎么走。做自由曲面设计,这步要是搞不清楚,后面全是空中楼阁。我刚开始接触自由曲面那会儿,总觉得光线追迹是现成工具的事,自己写代码纯属浪费时间。直到有一次,一个非序列系统怎么调都追不准,我才老老实实把底层逻辑啃了一遍。

嗯,今天咱们就聊聊光线追迹的三个核心:斯涅尔定律怎么用向量算、光线和自由曲面怎么求交、以及光线微分方程到底在说什么。

4.1 斯涅尔定律的向量形式

传统教材里,斯涅尔定律长这样:n₁ sin θ₁ = n₂ sin θ₂。但在自由曲面里,我们面对的是任意方向的入射光和任意朝向的曲面法线。这时候,向量形式才是王道。

我个人习惯用这个形式:

n₁ (I × N) = n₂ (T × N)

其中 I 是入射光方向单位向量,T 是折射光方向单位向量,N 是法线单位向量。叉积代表什么?代表光线和法线构成的平面。说白了,折射光、入射光和法线必须在同一个平面内。

但实际计算时,我们更常用的是这个形式:

T = (n₁/n₂) I + [ - (n₁/n₂) (I·N) + sqrt(1 - (n₁/n₂)² (1 - (I·N)²)) ] N

看着复杂,其实逻辑很简单:折射光 = 入射光在切向的分量 + 法向分量的修正。我在项目中遇到过一个问题——当根号里面是负数时,说明发生了全反射。这时候光线就出不去了。

注意: 全反射的判断条件:当 n₁ > n₂ 且入射角大于临界角时,根号内为负。代码里一定要加这个判断,否则追迹结果会出鬼。

反射的向量形式更简单:

R = I - 2 (I·N) N

你想想看,反射其实就是把入射光在法线方向的分量反向,切向分量不变。这个公式我闭着眼睛都能写出来,因为太常用了。

4.2 光线与自由曲面求交算法

自由曲面没有解析表达式,没法像球面那样直接解二次方程。怎么办?牛顿迭代法。

基本思路是这样的:

  1. 给一个初始猜测点 P₀
  2. 计算 P₀ 到曲面的有符号距离 f(P₀)
  3. 如果 |f(P₀)| 小于阈值,认为找到了交点
  4. 否则,沿梯度方向更新:P₁ = P₀ - f(P₀) / (∇f(P₀) · D)

其中 D 是光线方向,∇f 是曲面的梯度(也就是法线方向)。

我曾经踩过一个坑:初始点选得太远,牛顿迭代直接发散。后来我学乖了,先用包围盒做粗筛,再用牛顿迭代精修。

经验之谈: 初始点最好选在曲面附近,距离不要超过曲面局部曲率半径的 10 倍。如果曲面变化剧烈,可以先用少量光线做一次预追迹,找到大概位置。

代码实现大概是这样的:

def ray_surface_intersection(ray_origin, ray_dir, surface_func, grad_func, tol=1e-8, max_iter=20):
    # 初始猜测:沿光线方向走一步
    t = 0.1
    for i in range(max_iter):
        P = ray_origin + t * ray_dir
        f = surface_func(P)
        if abs(f) < tol:
            return P, t
        grad = grad_func(P)
        # 沿梯度方向修正
        dt = f / (np.dot(grad, ray_dir) + 1e-12)
        t = t - dt
        if abs(dt) < tol:
            return P, t
    # 迭代不收敛
    return None, None

这里有个细节:分母加了个小量 1e-12,防止除零。光线和曲面相切的时候,梯度方向和光线方向垂直,点积为零。这种情况虽然少见,但遇到了就是大麻烦。

4.3 光线微分方程

光线微分方程描述的是光线在非均匀介质中的传播路径。说白了,就是光线在折射率连续变化的空间里怎么走。

方程长这样:

d/ds (n dr/ds) = ∇n

其中 s 是光程,r 是位置向量,n 是折射率。这个方程的意思是:光线会朝折射率增大的方向弯曲。你想想看,海市蜃楼就是这个原理——空气温度不同导致折射率变化,光线就弯了。

在自由曲面设计中,我们通常假设介质是均匀的(空气或玻璃),所以 ∇n = 0。这时候方程退化为:

d²r/ds² = 0

说白了就是直线传播。但如果你做渐变折射率透镜(GRIN lens),这个方程就派上用场了。

我记得有一次做自由曲面导光板设计,需要模拟光线在微结构中的传播。一开始我用直线近似,结果和实验对不上。后来老老实实解光线微分方程,才把问题搞定。

核心要点:
  • 斯涅尔定律向量形式:记住 T = (n₁/n₂) I + [...] N 这个公式
  • 牛顿迭代求交:初始点选好,迭代就稳
  • 光线微分方程:均匀介质中退化为直线,非均匀介质中需要数值求解

4.4 知识体系总览

下面这张图把本章的核心逻辑串起来了。从光线出发,经过曲面求交,再通过斯涅尔定律计算折射/反射,最后更新光线状态。循环往复,就是完整的光线追迹。

光线追迹核心流程 光线出发 曲面求交(牛顿迭代法) 初始猜测 → 迭代修正 → 收敛判断 斯涅尔定律(向量形式) 计算折射/反射方向 更新光线状态 循环追迹 关键数学工具 • 向量运算 • 梯度计算 • 牛顿迭代 • 微分方程 • 数值求解 注:虚线箭头表示循环追迹,直到光线出射或达到终止条件

这张图里,我把整个流程分成了四个阶段。你从「光线出发」开始,经过「曲面求交」找到交点,再用「斯涅尔定律」算出新方向,最后「更新光线状态」。如果光线还没出射,就继续循环。

我个人觉得,初学者最容易卡在「曲面求交」这一步。因为自由曲面没有标准方程,每次都要自己写求交函数。我的建议是:先把球面和平面的求交写熟,再扩展到自由曲面。一步一步来,别想一口吃成胖子。

避坑指南: 我曾经在写牛顿迭代时,忘记检查迭代次数上限。结果光线在曲面附近来回震荡,程序直接卡死。现在我的代码里,迭代次数上限和收敛阈值都是必设参数。

好了,这一章的内容就到这儿。光线追迹是自由曲面设计的基石,后面的优化、公差分析都建立在这之上。把这些基础打牢,后面会轻松很多。

专注资料整理