Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

第8章:为什么近轴光学仍然重要

我们刚刚构建了真实光线追迹。

这感觉是进步,确实是。现在,一条光线可以从一个光瞳坐标出发,传播到一个球面,找到局部法线,通过玻璃折射,离开透镜,并落在像平面上。由这些光线组成的光束可以产生一个点列图。

所以现在退一步可能感觉很奇怪。

为什么在有了真实光线之后还要讨论近轴光学?

为什么在已经有了更精确的光线追迹器时,还要回到薄透镜、小角度、焦距、F数、数值孔径和ABCD矩阵?

因为近轴光学并不是真实光线追迹的原始替代品。

它是骨架。

真实光线追迹告诉我们光线实际做了什么。近轴光学告诉我们系统在一阶形式下试图做什么。没有这个骨架,点列图可能会变成孤立的图片。你可能会看到光线分散,但不知道它们在围绕什么分散。你可能会看到焦点变化,但不知道真实光线偏离的是哪一个一阶焦点。

一个点列图说:

这是采样真实光线落点的位置。

近轴光学说:

这是系统所围绕的理想一阶像结构。

两者都需要。

本章介绍近轴参考系统:光线高度、光线斜率、传输矩阵、薄透镜、有效焦距、后焦距、F数、数值孔径和放大倍率。该模型故意保持小巧。它给我们下一章所需的参考框架,而不是取代光学设计教科书。

第9章将提出一个问题:

为什么真实光线会偏离近轴光线?

那个问题是像差的开始。

所以,我们首先需要理解近轴光线。

近轴光线是一种简化的描述

我们代码中的一条真实光线携带一个3D位置和一个3D方向:

位置  = [x, y, z]
方向 = [dx, dy, dz]

近轴光线更简单。

在一个横向平面内,它可以表示为:

y = 光线高度
u = 光线角度或斜率

这里y是光轴上方的高度,u是光线与光轴的小角度。

对于小角度:

sin u ≈ u
tan u ≈ u

u以弧度为单位时。

这个微小角度近似是近轴光学的核心。

这个近似并不意味着近轴光学是粗心的。它意味着近轴光学有意研究靠近光轴且角度很小的光线。这足以揭示系统的一阶行为。

一条真实光线回答:

这条实际光线在实际表面上会走向哪里?

一条近轴光线回答:

在一阶近似下,一条近轴光线如何表现?

这些是不同的问题。

两者都有用。

近轴光线矢量

我们将一条近轴光线写成一个两元素矢量:

[y, u]

其中:

y = 光轴上方的高度,单位毫米
u = 斜率或小角度,单位弧度

在代码中:

from dataclasses import dataclass
import numpy as np
import math

@dataclass
class ParaxialRay:
    y: float
    u: float  # 弧度,或小角度近似下的斜率

    def as_vector(self):
        return np.array([self.y, self.u], dtype=float)

    @classmethod
    def from_vector(cls, v):
        return cls(y=float(v[0]), u=float(v[1]))

一条平行于光轴、高度为5毫米的光线是:

ray = ParaxialRay(y=5.0, u=0.0)

一条以微小向下角度穿过光轴的光线可能是:

ray = ParaxialRay(y=5.0, u=-0.05)

这比真实光线包含的信息少得多。这正是关键。近轴光线是一阶行为的简化载体。

近轴形式的传播

如果一条近轴光线在均匀介质中传播一段距离d,它的高度会根据其斜率变化:

y_new = y + d u

其斜率保持不变:

u_new = u

这可以写成一个矩阵:

[ y_new ]   [ 1  d ] [ y ]
[ u_new ] = [ 0  1 ] [ u ]

这是第一个ABCD矩阵。

传播矩阵为:

T(d) = [[1, d],
        [0, 1]]

在代码中:

def translation_matrix(distance):
    """近轴光线在均匀介质中通过距离d的传播。"""
    return np.array([
        [1.0, distance],
        [0.0, 1.0],
    ], dtype=float)

def apply_matrix(M, ray):
    """将一个2x2近轴矩阵应用于一个ParaxialRay。"""
    return ParaxialRay.from_vector(M @ ray.as_vector())

测试:

ray = ParaxialRay(y=5.0, u=-0.1)

M = translation_matrix(20.0)
out = apply_matrix(M, ray)

print(out)

结果应该是:

y = 3.0
u = -0.1

因为:

5 + 20 × (-0.1) = 3

这不是粗糙的草图。它是用代码写的一阶光线传输。

薄透镜矩阵

薄透镜会改变光线的斜率,但不会改变光线在透镜平面处的高度。

对于焦距为f的薄透镜(在空气中):

y_new = y
u_new = u - y / f

矩阵为:

L(f) = [[1,    0],
        [-1/f, 1]]

在代码中:

def thin_lens_matrix(focal_length):
    """空气中薄透镜矩阵。"""
    f = float(focal_length)

    if f == 0:
        raise ValueError("焦距不能为零。")

    return np.array([
        [1.0, 0.0],
        [-1.0 / f, 1.0],
    ], dtype=float)

测试一条平行光线以5毫米高度入射到50毫米薄透镜:

ray = ParaxialRay(y=5.0, u=0.0)

L = thin_lens_matrix(50.0)
out = apply_matrix(L, ray)

print(out)

出射斜率为:

u_new = -5 / 50 = -0.1

这意味着光线现在向下朝光轴传播。如果它再传播50毫米,就会到达光轴:

T = translation_matrix(50.0)

after_focus = apply_matrix(T, out)
print(after_focus)

高度应接近零。

这是近轴版本的聚焦。

它没有使用真实的表面相交,没有使用精确的球面法线。它使用的是透镜的一阶模型。

这就是它如此紧凑的原因。

矩阵乘积即光学系统

一个光学系统可以表示为一组近轴矩阵的乘积。

如果一条光线依次通过:

传播 d1
薄透镜 f
传播 d2

则有:

ray_out = T(d2) L(f) T(d1) ray_in

最右边的矩阵最先作用。

因此系统矩阵是:

M = T(d2) @ L(f) @ T(d1)

在代码中:

def system_matrix(*matrices):
    """按照光线作用顺序组合矩阵。

    system_matrix(A, B, C) 返回 C @ B @ A,如果光线先看到A,然后B,然后C。
    """
    M = np.eye(2)

    for element in matrices:
        M = element @ M

    return M

这个版本让我们可以按物理顺序编写元素:

M = system_matrix(
    translation_matrix(10.0),
    thin_lens_matrix(50.0),
    translation_matrix(40.0),
)

光线先看到10 mm的传播,然后是透镜,然后是40 mm的传播。

这个顺序约定值得明确说明。矩阵光学很紧凑,但顺序很重要。交换两个元素会改变系统。

ABCD矩阵

一个通用的一阶系统矩阵写为:

M = [[A, B],
     [C, D]]

它将输入光线高度和斜率映射为输出光线高度和斜率:

[ y_out ]   [ A  B ] [ y_in ]
[ u_out ] = [ C  D ] [ u_in ]

因此:

y_out = A y_in + B u_in
u_out = C y_in + D u_in

每个系数都有含义。

A 表示输入高度对输出高度的贡献。

B 表示输入角度对输出高度的贡献。

C 表示输入高度对输出角度的贡献。

D 表示输入角度对输出角度的贡献。

对光学光焦度最重要的系数通常是C

对于薄透镜:

M = [[1,    0],
     [-1/f, 1]]

因此:

C = -1/f

这就是为什么,对于空气中的系统,有效焦距通常由下式得到:

EFL = -1 / C

C不为零时。

我们来编写这些。

def matrix_elements(M):
    A, B = M[0, 0], M[0, 1]
    C, D = M[1, 0], M[1, 1]
    return A, B, C, D

def effective_focal_length(M):
    """空气中系统的有效焦距,使用 [y, u] 约定的返回值。"""
    A, B, C, D = matrix_elements(M)

    if abs(C) < 1e-12:
        return math.inf

    return -1.0 / C

测试:

L = thin_lens_matrix(50.0)
print(effective_focal_length(L))

应该打印50.0

这令人安心。在这个简化模型中,一个50毫米的薄透镜具有50毫米的有效焦距。

后焦距

有效焦距和后焦距相关但并非总是相同。

有效焦距与系统的光焦度和主平面有关。后焦距是从最后一个表面或最后一个参考平面到后焦点的距离。

对于一个系统矩阵 M = [[A, B], [C, D]],考虑一条入射准直光线:

u_in = 0

在系统之后:

y_out = A y_in
u_out = C y_in

现在让光线在系统后传播一段距离s。它的高度变为:

y(s) = y_out + s u_out

后焦点是这条准直入射光线与光轴相交的位置:

0 = A y_in + s C y_in

对于非零的y_in

s = -A / C

因此,在这个简单的同一介质约定下:

BFL = -A / C

编写它:

def back_focal_length(M):
    """从输出参考平面测量的后焦距,在空气中。"""
    A, B, C, D = matrix_elements(M)

    if abs(C) < 1e-12:
        return math.inf

    return -A / C

对于一个参考平面在透镜本身的薄透镜:

L = thin_lens_matrix(50.0)

print("EFL:", effective_focal_length(L))
print("BFL:", back_focal_length(L))

两者都是50 mm

现在将透镜之后的一段传播加入系统矩阵:

M = system_matrix(
    thin_lens_matrix(50.0),
    translation_matrix(10.0),
)

print(M)
print("EFL:", effective_focal_length(M))
print("BFL from output plane:", back_focal_length(M))

有效焦距仍然是50毫米,因为光焦度没有改变。但是从新的输出平面测量的后焦距发生了变化。如果输出平面在薄透镜后10毫米处,那么到焦点的剩余距离大约是40毫米。

这是一个关键的区别。

一个系统可以具有相同的有效焦距,但根据最后一个参考平面或最后一个表面位置的不同,后焦距会不同。

这就是为什么光学处方关心表面位置,而不仅仅是光焦度。

由矩阵推导薄透镜公式

熟悉的薄透镜公式是:

1/s + 1/s' = 1/f

其中:

s  = 透镜前的物距
s' = 透镜后的像距
f  = 焦距

符号约定各不相同,所以这个公式常常引起混淆。我们不在抽象层面争论符号,而是从矩阵出发,推导一个简单的情况。

假设一个物点位于薄透镜前距离s处。一条光线从物点以高度0出发,以高度y到达透镜。它在透镜处的斜率大约是:

u = y / s

在薄透镜之后:

u_after = u - y/f
        = y/s - y/f

为了使光线在距离s' 后到达光轴上的像点:

0 = y + s' u_after

代入:

0 = y + s'(y/s - y/f)

除以 y

0 = 1 + s'(1/s - 1/f)

整理得:

1/s' = 1/f - 1/s

或:

1/s + 1/s' = 1/f

在这种正距离设置下。

在代码中:

def thin_lens_image_distance(object_distance, focal_length):
    """对于薄透镜,物距为s,返回像距s'。

    使用 1/s + 1/s' = 1/f。
    采用简单的实物体、实像正距离约定。
    """
    s = float(object_distance)
    f = float(focal_length)

    denom = 1.0 / f - 1.0 / s

    if abs(denom) < 1e-12:
        return math.inf

    return 1.0 / denom

测试:

for s in [100.0, 200.0, 1000.0, math.inf]:
    if math.isinf(s):
        sp = 50.0
    else:
        sp = thin_lens_image_distance(s, 50.0)

    print("物距:", s, "像距:", sp)

随着物体向远处移动,像距趋近于焦距。

这就是为什么物体在无穷远的例子如此方便。在近轴薄透镜模型中,平行入射光线会聚到焦平面。

放大倍率

对于一个物距为s、像距为s'的薄透镜,横向放大倍率为:

m = -s' / s

负号表示在通常的实物体、实像约定下,像是倒立的。

在代码中:

def thin_lens_magnification(object_distance, image_distance):
    return -image_distance / object_distance

例子:

s = 100.0
f = 50.0
sp = thin_lens_image_distance(s, f)
m = thin_lens_magnification(s, sp)

print("s' =", sp)
print("m =", m)

对于s = 100 mmf = 50 mm,像距为100 mm,放大倍率为-1

同样,这是一个一阶结果。真实透镜可能存在畸变、场曲、像差以及有限厚度效应。但近轴放大倍率提供了参考结构。

没有它,“畸变”就没有明确的含义。畸变不仅仅是“图像看起来奇怪”。它是实际像位置与预期的近轴物像映射之间的偏离。

我们将在第9章回到这一点。

F数

F数是最常见的光学量之一。

对于一个对无穷远调焦的透镜,一个有用的一阶定义是:

F/# = f / D

其中:

f = 有效焦距
D = 入瞳直径

一个50毫米透镜,入瞳直径为10毫米,有:

F/# = 50 / 10 = 5

或称为f/5。

在代码中:

def f_number(effective_focal_length, entrance_pupil_diameter):
    D = float(entrance_pupil_diameter)

    if D <= 0:
        raise ValueError("入瞳直径必须为正。")

    return effective_focal_length / D

使用:

f = 50.0
D = 10.0

print(f_number(f, D))

这很简单,但它直接与光线束相联系。

入瞳直径控制着光锥。较大的孔径意味着一个更快的系统,更小的F数,并且通常对像差校正的要求更高。较小的孔径意味着一个更慢的系统,更大的F数,并且通常几何像差减小,但衍射模糊增加。

这是几何思维和衍射思维交汇的地方之一。

大孔径可能改善集光能力和衍射受限分辨率,但它也要求透镜处理更极端的光线。如果设计未校正,真实光线像差可能占主导。

因此,F数不仅仅是相机镜头上的一个标签。它是关于孔径相对于焦距的一阶概括。

数值孔径

数值孔径(NA)描述了光锥的角度。

在像空间,对于折射率为n的介质:

NA = n sin θ

其中θ是边缘光线相对于光轴的角度。

对于空气中的小角度:

NA ≈ sin θ ≈ θ

对于空气中的系统,还有一个常用的近似关系:

NA ≈ 1 / (2 F/#)

当系统对无穷远调焦且角度不太大时成立。

让我们实现两者。

def numerical_aperture(n_medium, marginal_angle_rad):
    return n_medium * math.sin(marginal_angle_rad)

def na_from_f_number(fno):
    return 1.0 / (2.0 * fno)

对于一个50毫米焦距和10毫米入瞳直径的系统:

fno = f_number(50.0, 10.0)

print("F/#:", fno)
print("近轴NA:", na_from_f_number(fno))

近似NA是0.1

这对应于近轴近似下大约0.1弧度的边缘光线角度。

同样,这是一阶的。在高NA情况下,精确的定义和正弦行为更为重要。但对于许多入门光学系统,这个近似给出了一个有用的尺度。

F数与边缘光线关联

让我们将F数与一条近轴光线联系起来。

对于无穷远物体,一条边缘光线以与光轴平行、高度为:

y = D / 2

的状态入射。

经过焦距为f的薄透镜后,其斜率变为:

u = -y / f = -(D/2) / f

因此,边缘光线角度的大小约为:

|u| = D / (2f) = 1 / (2 F/#)

这与空气中近似的NA相同。

这是一个美妙的时刻,因为它连接了几个量:

光瞳直径
焦距
F数
边缘光线斜率
数值孔径

这些不是需要死记硬背的独立事实。它们是从不同角度观察一阶光锥。

在代码中:

def marginal_slope_from_f_and_d(f, D):
    return -(D / 2.0) / f

f = 50.0
D = 10.0

u_marginal = marginal_slope_from_f_and_d(f, D)

print("边缘斜率:", u_marginal)
print("近轴NA:", abs(u_marginal))
print("从F/#得到的NA:", na_from_f_number(f_number(f, D)))

在近轴近似下,这些数字一致。

这正是近轴光学有用的原因。它将几何关系压缩成你可以推理的关系。

真实光线边缘行为与近轴边缘行为的对比

现在将本章与前一章联系起来。

在真实光线追迹器中,位于光瞳边缘的边缘光线在球表面上碰到的法线不同于近轴光线。它按照精确的几何折射。

在近轴光学中,边缘光线由一个高度和一个小斜率表示。透镜以线性方式改变其斜率:

u_new = u - y/f

如果光线靠近轴且角度很小,两种描述应该接近。

如果光线远离轴,或者透镜有明显的像差,真实光线就会偏离近轴预测。

那个偏离并不是一件麻烦事。它是像差分析的开始。

一次真实光线追迹给出:

实际像面交点

近轴模型给出:

一阶预期交点

两者之差给出:

横向光线误差

第9章将直接建立在这思想上。

一个近轴焦点扫描

我们用矩阵做一个小的近轴焦点计算。

一条准直光线以高度y进入薄透镜,应该在焦距处与光轴相交。我们可以通过传播到不同的z位置并测量高度来验证这一点。

def trace_paraxial_thin_lens_to_plane(y_in, u_in, f, z_after_lens):
    ray = ParaxialRay(y=y_in, u=u_in)

    ray_after_lens = apply_matrix(thin_lens_matrix(f), ray)
    ray_at_plane = apply_matrix(translation_matrix(z_after_lens), ray_after_lens)

    return ray_at_plane

扫描像面位置:

f = 50.0
y_in = 5.0
u_in = 0.0

z_values = np.linspace(30.0, 70.0, 9)

for z in z_values:
    out = trace_paraxial_thin_lens_to_plane(y_in, u_in, f, z)
    print(f"z={z:.1f} mm, y={out.y:.4f} mm")

z = 50 mm处,高度应接近零。

这是焦点扫描的近轴版本。它使用了一条边缘光线而不是一整个光束。对于薄透镜,结果在近轴模型内是精确的。

在一个真实的球面单透镜中,边缘光线可能不会在与近轴光线完全相同的点与轴相交。这个差异就是球差。

同样,近轴光学提供了参考。

用一个简单的厚透镜近似矩阵

一个真实的单透镜有两个表面和厚度。我们也可以从折射和传播构建一个一阶模型,但最简洁的矩阵形式取决于所选的光线变量。

有两种常见的约定:

[y, u]
[y, n u]

第二种通常被称为约化角度约定。它使得在具有不同折射率的介质间折射矩阵特别简洁。

为了保持本章的可读性,我们不会构建一个带有介质变化的完整厚透镜折射矩阵系统。那需要仔细的符号约定,并且会偏离主要目的。

相反,我们会做两件事:

  1. 使用薄透镜矩阵以获得清晰的一阶直觉;
  2. 将矩阵结果作为真实光线追迹的一个参考概念。

这是一个有意的边界。

本章并不取代光学设计程序的近轴引擎。它展示了那个引擎试图计算的内容。

一个生产级光学库可以从完整的序列系统计算一阶特性,包括表面光焦度、厚度、折射率、光阑、光瞳和主平面。我们的教学代码展示的是骨架。

那个骨架足以理解输出。

主平面,不必假装它们是简单的图

对于薄透镜,主平面位于透镜平面处。这就是为什么焦距和后焦距相同。

对于厚透镜或多元件系统,有效光焦度的行为仿佛与某些主平面相关,这些主平面可能位于物理玻璃的内部或外部。

这就是为什么:

EFL 和 BFL 可能不同

有效焦距是从一个主平面测量的。后焦距是从最后一个表面或输出参考平面测量的。

这个区别在实际设计中很重要。

一个透镜可能具有很长的有效焦距,但后焦距很短。或者它可能被设计为长后焦距,以便为反射镜、传感器组件、滤光片、快门或机械安装座留出空间。

如果你只知道焦距,你就不知道像平面相对于最后一个玻璃表面的位置。

近轴系统矩阵有助于计算这些一阶距离。

在我们的同介质ABCD模型中:

EFL = -1 / C
BFL = -A / C

这两个值之间的差异表明,后主平面不在输出参考平面上。

这不仅仅是术语。它影响布局。

一个EFL和BFL不同的例子

让我们做一个简单的系统:一个薄透镜,然后一个10毫米的传播到输出参考平面。

M = system_matrix(
    thin_lens_matrix(50.0),
    translation_matrix(10.0),
)

A, B, C, D = matrix_elements(M)

print("M =")
print(M)
print("A, B, C, D =", A, B, C, D)
print("EFL:", effective_focal_length(M))
print("BFL:", back_focal_length(M))

有效焦距仍然是50毫米。从输出平面测量的后焦距是40毫米。

透镜的光焦度没有改变。只是参考平面向下游移动了10毫米。

这个例子很简单,但它传达了一个真实的教训:

一阶量总是与参考平面相关。

当光学软件报告EFL、BFL、FFL、主平面位置或光瞳位置时,它正在进行近轴簿记。这些值不是装饰性的标签。它们告诉你系统是如何组织的。

视场角和像高

对于无穷远物体,近轴像高约为:

y_image ≈ f tan θ

对于小角度:

tan θ ≈ θ

因此:

y_image ≈ f θ

其中θ以弧度为单位。

在代码中:

def paraxial_image_height_from_field(focal_length, field_angle_deg):
    theta = math.radians(field_angle_deg)
    return focal_length * math.tan(theta)

例子:

for angle in [0.0, 1.0, 3.0, 5.0]:
    print(angle, paraxial_image_height_from_field(50.0, angle))

这是一阶像高。

现在将此联系到第7章。对于离轴点列图,质心或主光线交点可能出现在某个像高处。近轴像高为我们预期那个视场点落点的位置提供了一个参考。

如果真实主光线落点与近轴预测不同,那个差异就与畸变有关。

我们现在还不准备完全分析畸变。但这种关系已经可见:

视场角
→ 近轴像高
→ 真实主光线像高
→ 畸变比较

这是近轴光学重要的另一个原因。它赋予“偏离”以意义。

一阶布局不是一个像质结果

这一点非常重要,需要明确说明。

一个近轴设计可能看起来不错,但仍可能产生糟糕的图像。

一阶光学可以告诉你:

大致焦距
像距
放大倍率
F数
NA
光瞳关系
视场高度

它本身不能告诉你:

球差
彗差
像散
场曲
具体的畸变
色差模糊
PSF
MTF

这些需要真实光线追迹以及波前或衍射分析。

因此,近轴光学并不是像质的最终评判者。

但它仍然是骨架。如果一阶系统有误,后续分析可能是在解决错误的问题。

一个焦距错误的透镜,不会因为在另一个尺度上有一个漂亮的MTF图而得救。一个后焦距不足的设计可能在光学上很有趣,但在机械上无法使用。一个F数错误的系统可能无法满足集光或衍射要求。

一阶设计搭建了舞台。

真实光线和波前光学分析告诉我们在那个舞台上系统表现如何。

一个小型近轴设计函数

让我们编写一个紧凑的函数,从焦距和光瞳直径总结一个薄透镜设计。

@dataclass
class ThinLensFirstOrder:
    focal_length: float
    entrance_pupil_diameter: float

    @property
    def efl(self):
        return self.focal_length

    @property
    def fno(self):
        return f_number(self.efl, self.entrance_pupil_diameter)

    @property
    def approx_na(self):
        return na_from_f_number(self.fno)

    def image_height(self, field_angle_deg):
        return paraxial_image_height_from_field(self.efl, field_angle_deg)

    def marginal_ray_slope(self):
        return marginal_slope_from_f_and_d(self.efl, self.entrance_pupil_diameter)

使用它:

design = ThinLensFirstOrder(
    focal_length=50.0,
    entrance_pupil_diameter=10.0,
)

print("EFL:", design.efl)
print("F/#:", design.fno)
print("近轴NA:", design.approx_na)
print("边缘斜率:", design.marginal_ray_slope())

for field in [0.0, 3.0, 5.0]:
    print("视场角", field, "像高", design.image_height(field))

这不是一个透镜优化工具。它是一个一阶计算器。

但即使是这个小类也能给出有用的预期:

系统有多快?
像高有多大?
我们应该期望多大的边缘锥角?
平行光应在哪里聚焦?

这些预期帮助我们解读真实光线的输出。

比较近轴焦点和真实焦点

让我们直接连接到第6章和第7章的单透镜。

我们的真实单透镜具有:

前半径 = +50 mm
后半径 = -50 mm
中心厚度 = 5 mm
材料 = N-BK7

对于空气中的双凸透镜,一个粗略的薄透镜估计是:

1/f ≈ (n - 1)(1/R1 - 1/R2)

对于R1 = +50R2 = -50,和n ≈ 1.5168

def thin_lens_focal_length_from_radii(n, R1, R2):
    power = (n - 1.0) * (1.0 / R1 - 1.0 / R2)

    if abs(power) < 1e-12:
        return math.inf

    return 1.0 / power

n_bk7_d = N_BK7.n(0.5875618)
f_est = thin_lens_focal_length_from_radii(n_bk7_d, R1=50.0, R2=-50.0)

print("薄透镜估计焦距:", f_est)

这个估计值将在48毫米左右。它忽略了厚度、精确的主平面和真实的球面光线行为,但它给出了一个尺度。

在第7章中,如果你扫描像面位置并在该尺度附近找到了一个RMS斑点最小值,那应该感觉合理。如果最佳焦点在500毫米或5毫米处,你会怀疑存在符号、单位或追迹错误。

这是近轴光学的另一个实际用途:

它为你提供了合理性检查。

近轴估计不必完美才有用。它告诉你真实光线结果是否在正确的范围内。

用矩阵代码进行薄透镜焦点检查

现在将估计焦距作为薄透镜使用,并比较不同视场下的近轴像高。

f_est = thin_lens_focal_length_from_radii(n_bk7_d, R1=50.0, R2=-50.0)
D = 10.0

design = ThinLensFirstOrder(
    focal_length=f_est,
    entrance_pupil_diameter=D,
)

print("估计EFL:", design.efl)
print("估计F/#:", design.fno)
print("估计NA:", design.approx_na)

for field in [0.0, 1.0, 3.0, 5.0]:
    print(
        f"field={field:.1f} deg, "
        f"近轴像高={design.image_height(field):.4f} mm"
    )

现在,你对于点列图中使用的相同类型的视场角有了一阶预期。

这有助于你避免一个常见的初学者困惑。

当一个离轴斑点出现在远离原点的位置时,这并不自动意味着是像差。那个位移的很大一部分仅仅是像高。像差是与理想像点的偏离,而不是像高本身。

这就是为什么中心点列图对于模糊形状有用,而绝对点列图对于像位置有用。

近轴光学告诉你“理想像位置”的含义。

Optiland近轴分析作为更高级的版本

一个真正的光学设计库不需要我们手写的薄透镜捷径。它可以基于实际的序列模型计算近轴特性。

对于一个Optiland系统,高层思路是:

from optiland import optic

lens = optic.Optic()

lens.surfaces.add(index=0, radius=float("inf"), thickness=float("inf"))
lens.surfaces.add(index=1, radius=50.0, thickness=5.0, material="N-BK7", is_stop=True)
lens.surfaces.add(index=2, radius=-50.0, thickness=45.0)
lens.surfaces.add(index=3)

lens.set_aperture(aperture_type="EPD", value=10.0)
lens.fields.set_type("angle")
lens.fields.add(y=0.0)
lens.wavelengths.add(value=0.5876, is_primary=True)

# 示意性示例:
# 使用库的分析工具检查近轴或一阶特性。
# 确切的属性名称可能因Optiland版本而异。

根据安装的版本,近轴特性可能通过分析类、系统属性或专用方法暴露出来。确切的API接口不如概念比较重要。

库级别的近轴引擎应该能够回答以下问题:

有效焦距是多少?
后焦距是多少?
F数是多少?
主平面在哪里?
近轴像高是多少?
孔径关系是什么?

我们的教学代码回答了关于薄透镜和简单ABCD系统的那些问题的缩小版。

这正是本书的双轨方法:

最小代码 → 理解骨架
Optiland工作流 → 查看实用工具

不要将它们混淆。我们的代码并不试图成为一个完整的近轴分析包。它试图使输出变得可读。

为什么近轴结果可能与简单薄透镜估计不匹配

如果你将库对一个真实单透镜的近轴结果与我们的薄透镜估计进行比较,数字可能会不同。

这是预期的。

原因包括:

有限的透镜厚度
主平面偏移
玻璃目录使用的精确折射率
波长选择
表面符号约定
参考平面定义
孔径定义
系统前后介质

薄透镜方程是一个近似。对一个厚序列系统的完整近轴分析仍然是一阶的,但它包含了更多实际处方信息。

因此,层级结构是:

薄透镜估计:
    快速尺度和合理性检查

ABCD矩阵模型:
    所选元件下的一阶系统行为

库近轴分析:
    从实际处方得到的一阶特性

真实光线追迹:
    采样光线的精确几何行为

波前 / PSF / MTF:
    含衍射的成像分析

每一层都有其职责。

当我们要求一层去完成另一层的工作时,问题就会出现。

近轴光学作为调试工具

近轴光学不仅仅是理论。它也是一个调试工具。

如果你的真实光线追迹器给出了奇怪的结果,近轴估计可以帮助隔离问题。

例如:

情况1:焦点错得很离谱

如果一个简单的双凸透镜的焦距应该在50毫米左右,但你的真实光线聚焦在5000毫米左右,请检查:

半径单位
折射率
表面符号
法线方向
斯涅尔 n1/n2 顺序
像面单位

情况2:离轴像高差了一个很大因子

检查:

视场角单位
度与弧度
方向矢量构建
像面位置
坐标约定

情况3:F数与光线锥角不匹配

检查:

孔径直径与半径
入瞳与物理光阑
边缘光线选择
焦距

情况4:真实光线与近轴光线在近轴区不一致

对于真实表面,有些差异是正常的,但近轴光线通常应与近轴预测接近。如果不接近,检查:

表面相交
表面法线
符号约定
矩阵约定
波长和材料折射率

近轴光学为你提供了第二条计算路径。这很有价值。

当两个独立的简单检查不一致时,你就知道该在哪里寻找问题。

一个小对比:近轴与真实边缘光线

让我们勾勒一个对比。

  1. 使用薄透镜估计预测焦点。
  2. 追踪一条小高度的真实光线通过单透镜。
  3. 观察它与光轴相交的位置。
  4. 比较交点距离。

一条小高度的光线应该比边缘光线更接近近轴行为。

下面是一个辅助函数,用于估计追迹一条真实光线离开透镜后在何处与光轴相交。

假设我们追踪光线通过两个单透镜表面,但尚未到达像面。

def trace_simple_singlet_to_exit(ray, front, back, glass_material):
    n_air = 1.0
    n_glass = glass_material.n(ray.wavelength_um)

    interact_refractive_surface(ray, front, n1=n_air, n2=n_glass)

    if ray.alive:
        interact_refractive_surface(ray, back, n1=n_glass, n2=n_air)

    return ray

def z_axis_crossing_after_exit(ray):
    """估计一条真实光线在x-z平面中与y=0相交的位置。

    这假设光线位于x-z平面内,并使用x作为横向坐标。
    """
    x0 = ray.position[0]
    z0 = ray.position[2]
    dx = ray.direction[0]
    dz = ray.direction[2]

    if abs(dx) < 1e-12:
        return math.inf

    t = -x0 / dx

    if t < 0:
        return math.inf

    return z0 + t * dz

现在比较小高度和大高度的光线:

for height in [0.5, 2.0, 5.0, 8.0]:
    ray = Ray(
        position=[height, 0.0, -20.0],
        direction=[0.0, 0.0, 1.0],
        wavelength_um=0.5875618,
    )

    trace_simple_singlet_to_exit(ray, front, back, N_BK7)
    z_cross = z_axis_crossing_after_exit(ray)

    print(f"输入高度={height:.1f} mm,与轴相交 z≈{z_cross:.3f} mm")

如果透镜有球差,不同光线高度与光轴的交点会在不同的z位置。

小高度的光线应该最接近近轴焦点。边缘光线可能偏离得更明显。

这是通往第9章的桥梁。

近轴光线提供了参考。真实光线揭示了偏离。

接下来“像差”的含义

现在,我们已准备好从实用计算的角度定义像差。

在一阶近似下,系统有一个理想的行为:

一个视场点映射到一个近轴像点
一个轴上准直光束聚焦在一个近轴焦平面
近轴光线遵循ABCD预测

真实光线可能并不完全遵循这个理想。

它们的偏离表现为:

不同光线高度有不同的焦点
离轴不对称性
不同的子午和弧矢行为
与视场相关的模糊
色差偏移
与理想像高的偏离(畸变)

这些偏离就是像差。

这并不意味着透镜在道义上是“坏的”。每个真实光学系统都有局限性。像差分析告诉我们我们正在看到的是哪种局限性。

一个点列图展示了结果。

一个近轴参考有助于解释这个结果。

这就是为什么本章刚好位于点列图之后、像差之前。

近轴光学中的常见错误

让我们汇总陷阱。

错误1:将近轴光学视为过时

近轴光学并不过时。它是系统的一阶骨架。真实光线追迹增加了细节;它并没有抹去骨架。

错误2:忘记弧度

小角度方程使用弧度。如果你在y = f θ中直接使用度,结果将相差大约57.3倍。

错误3:混合符号约定

薄透镜公式和矩阵光学依赖于符号约定。在相信一个公式之前,先定义你的约定。

当有疑问时,测试简单情况:

通过正透镜的平行光线应在下游聚焦
轴上光线应保持轴上
传播不应改变斜率

错误4:混淆EFL和BFL

有效焦距与光焦度和主平面相关。后焦距是从最后一个表面或输出参考平面测量的。

对于在参考平面处的薄透镜,它们是相等的。但一般情况不是。

错误5:混淆孔径直径和半径

F数使用入瞳直径,不是半径。

如果你误用了半径,你的F数会差两倍。

错误6:将近轴焦点视为最佳真实焦点

近轴焦点是一阶参考。一个真实透镜可能由于像差,在另一个平面上有最佳RMS点焦点。

错误7:期望薄透镜估计与厚透镜软件完全匹配

薄透镜估计是合理性检查。对实际处方的完整近轴分析包含更多信息。

错误8:将近轴指标用作最终像质指标

焦距、F数、NA和放大倍率本身不会告诉你PSF或MTF。它们描述了一阶布局。

错误9:忘记视场参考

离轴像高是预期的。问题不在于斑点是否远离原点。问题在于真实像位置与近轴预测相比如何。

本章给了我们什么

我们现在有了一阶参考框架。

我们可以:

将近轴光线表示为 [高度, 斜率]
用传播矩阵传播它
用薄透镜矩阵弯曲它
将元件组合成一个ABCD矩阵
从C计算有效焦距
从A和C计算后焦距
用薄透镜方程估计像距
计算放大倍率
计算F数
估计数值孔径
从视场角估计近轴像高
使用近轴结果作为真实光线追迹的合理性检查

这不是偏离计算光学。

它是一阶的计算光学。

公式比真实光线追迹代码短,但它们回答的是不同的问题。它们告诉我们在像差、衍射和采样细节使图像复杂化之前,光学系统是如何组织的。

那种组织是至关重要的。

前方的路

下一章会提出一个自然的问题:

如果近轴光学给出了一阶理想,为什么真实光线会错过它?

现在我们有了回答这个问题的工具。

一条真实光线可能开始于与一条近轴光线相同的光瞳坐标。它可能通过同样的名义上的透镜。但它遇到的是实际的弯曲表面、精确的局部法线以及真实的斯涅尔折射。在更高的光线高度和离轴视场下,这些精确的相互作用不再遵循简单的线性近轴模型。

其差异表现为像差。

我们不会把它埋在一大堆三阶公式之下。相反,我们将使用我们已经构建的计算:

追迹真实光线
计算近轴参考
比较像面误差
绘制横向光线像差曲线
解读图样

这将使像差不再像是教科书中的命名怪物,更像是光线数据中可测量的偏离。

近轴骨架现在已就位。

接下来,我们观察真实光线从中偏离。

生成的验证图

从第8章矩阵模型生成的近轴焦点

来源与验证说明

本章中的ABCD矩阵公式使用本章所述的符号和排序约定。其他光学书籍可能使用不同的坐标符号、光线矢量定义或乘法顺序。当对比另一来源或软件包的结果时,在比较数字之前,先比较约定。