我第一次认真写迈克尔逊干涉的MATLAB仿真,是在一个连阴雨的周末。当时正在复习波动光学,公式推得头头是道,可一闭上眼睛,脑子里只飘着几个词:光程差、相位差、干涉条纹。至于条纹为什么是一圈一圈的圆环,为什么移动镜子时圆心会“吐出”或“吞入”亮环,我完全没有直觉。于是我用一个下午写了不到一百行MATLAB代码,把迈克尔逊干涉仪搬进了屏幕。也正是那次经历让我意识到,与其在草稿纸上反复画光路,不如让每一束光都变成屏幕上看得见、动得起来的像素。
这个仿真项目特别适合三类人:学过干涉原理但还没进过实验室的学生,想在课程设计里加一个漂亮光学demo的人,以及单纯想练一练MATLAB矩阵操作和可视化的朋友。它可以帮你回答很多“如果”问题:如果两束光的强度不相等,条纹会怎样?如果光源不是严格单色,条纹会怎样?如果两面镜子不是完全垂直,又会看到什么?这些问题在实验台上调起来费时费力,在MATLAB里却只是一行参数的改变。
下面我按自己的实现顺序,从物理模型、代码实现、参数调优到踩坑实录,完整记录这场“奇幻之旅”。
1. 奇幻旅程的地图:为什么要用MATLAB复现迈克尔逊干涉
1.1 这个仿真到底在做什么
迈克尔逊干涉仪的核心结构不复杂:一束光打在半透半反的分束器上,被分成两束,一束射向固定反射镜,一束射向移动反射镜,两束光返回后在分束器处重新汇合,形成干涉。因为两束光走过的路径不同,它们之间存在一个光程差,而这个光程差直接决定了屏幕上某一点是亮还是暗。
MATLAB仿真做的,就是把“光程差→相位→强度”这条链变成一张可视化的灰度图。我们不再需要幻想某个公式长什么样,而是直接看到一组同心圆环在屏幕上铺开。更为重要的是,改变镜面位置或角度,图样会实时变化,这种动态反馈能让人很快建立起物理直觉。
1.2 适合谁,能解决什么问题
对于初学者来说,最头疼的问题是:干涉条纹的间距为什么不是均匀的?中心为什么有时候是暗的?两个镜子的微小夹角如何把圆环变成平行条纹?这些问题如果只靠书上的静态插图,理解起来很吃力。
做这个仿真,你只需要会一点MATLAB基础:linspace生成坐标、meshgrid构造网格、cos函数计算亮度、imshow显示图像。难度不高,但收益很大。跑通之后,你手里相当于有一套“光学干涉演示器”,随时可以调节波长、臂长、镜面倾角、光源线宽等参数,观察相应的变化。
1.3 为什么“看见”比“背公式”重要
我在写这个仿真之前,一直觉得迈克尔逊干涉的公式已经背了不少:光程差、相位差、条纹级次……可真到让我预测“镜子移动半波长,中心会从亮变暗还是会从暗变亮”的时候,我发现自己会卡住。后来在仿真里看到中心强度随镜面距离呈余弦振荡,才彻底明白:光程差变化一个波长,相位差变化2π,中心点的干涉状态完整循环一次。这种“视觉记忆”一旦形成,再回去看公式就轻松得多。
2. 物理模型:让公式变成可执行的逻辑
2.1 两条光路的几何关系
在迈克尔逊干涉仪中,分束器到两面反射镜的距离分别为d1和d2,两束光各走了两次对应的臂长,所以它们的光程差是:
Δ = 2(d2 - d1) = 2Δd
这个“2倍”非常关键。实际实验中,如果你移动反射镜1mm,光程差变化的是2mm,而不是1mm。也就是说,屏上某一固定点从亮变暗再变亮的一个完整周期,对应的镜面移动距离只有半个波长。
如果是严格垂直的两个反射镜,等效于一块厚度为d=|d2-d1|的空气平行平板。当光源扩展、入射角为θ时,两束光在观察面上相遇的光程差为:
Δ = 2d·cosθ
θ越大,光程差越小。这个cosθ项,正是等倾干涉圆环产生的根源。
2.2 两束光叠加后的强度
若两束相干光的电场振幅分别为E1和E2,叠加后的强度可以写成:
I = I1 + I2 + 2√(I1I2)·cos(2πΔ/λ)
仿真中最常用的是等强度情况,取I1=I2=0.5,于是:
I = 0.5 + 0.5·cos(2πΔ/λ)
可见度定义为(Vmax - Vmin)/(Vmax + Vmin)。当两束光强度相等时,可见度等于1;一旦强度不相等,暗条纹不再完全变黑,可见度就会下降。这个结论在实验里很常见,仿真里改一下I1和I2的值就能立刻验证。
2.3 从连续光场到像素网格
计算机不能处理连续坐标,必须把观察平面离散成网格。假设屏幕范围为L,像素数为N,那么横纵坐标是:
x = linspace(-L/2, L/2, N)
[X, Y] = meshgrid(x, x)
屏幕上的每个像素,对应一个空间坐标。对等倾干涉,用半径r = sqrt(X² + Y²)计算入射角θ;对等厚干涉,用X坐标计算镜面间距变化。最后生成一个N×N的强度矩阵I,用imshow显示即可。
2.4 模型的理想化边界
这个仿真默认了几个前提:光源是理想单色点光源或扩展单色光源,分束器和反射镜没有吸收损耗,镜面是理想平面。实际实验台会有灰尘、振动、镜面镀膜损耗、光源线宽等问题,因此仿真结果往往比真实条纹更“干净”。这没关系,仿真的目标是理解主物理过程,而不是复刻所有工程细节。
3. MATLAB实现:从点光源到可视化干涉图
3.1 先设定基础参数
仿真的第一步是把真实世界的物理量映射成MATLAB变量。我通常这样设置:
lambda = 632.8e-9; % 氦氖激光波长,单位m N = 512; % 像素数,越大条纹越平滑 L = 3e-3; % 观察面边长,单位m,约3mm d = 1e-3; % 等效空气膜厚度,单位m,1mm f = 0.2; % 观察透镜焦距,单位m选择N=512是因为在普通屏幕上显示足够细腻,计算速度也够快。L取3mm是为了在默认参数下看到两三圈圆环,太大会使条纹集中在中心,太小则圆环太稀疏。d取1mm是因为此时中心光程差2d=2mm,约为3158个波长,圆环数量适中。
3.2 第一幅图:等倾干涉圆环
直接用公式生成等倾圆环的完整代码很短:
% 参数区 lambda = 632.8e-9; N = 512; L = 3e-3; d = 1e-3; f = 0.2; % 坐标网格 x = linspace(-L/2, L/2, N); [X, Y] = meshgrid(x, x); r2 = X.^2 + Y.^2; % 入射角与光程差 theta = atan(sqrt(r2) / f); Delta = 2 * d * cos(theta); % 干涉强度 I = 0.5 + 0.5 * cos(2 * pi * Delta / lambda); % 显示 imshow(I, []); colormap(gray); axis off;运行之后,你会看到屏幕中心有一组同心圆环。中心对应θ=0,光程差最大,干涉级次最高。向外走,θ增大,cosθ减小,光程差减小,所以每往外一圈,干涉级次降一。圆环间距并不均匀,越靠外越密,这也是等倾干涉的经典特征。
3.3 动态演示:让反射镜慢慢移动
静态图不够“奇幻”,把d变成变量,就能看到圆环不断从中心冒出来,这个过程叫“条纹吞吐”。代码可以这样写:
d0 = 1e-3; d_delta = linspace(0, 5e-6, 300); % 一共移动5微米,分成300帧 figure('Color','black'); for k = 1:length(d_delta) Delta = 2 * (d0 + d_delta(k)) * cos(theta); I = 0.5 + 0.5 * cos(2 * pi * Delta / lambda); imshow(I, []); title(sprintf('d = %.3f um', (d0 + d_delta(k)) * 1e6), 'Color', 'w'); drawnow; pause(0.05); end这里的关键是步长。如果步长太大,相邻两帧之间相位跳变超过π,画面会出现“闪烁”,看起来像是条纹乱跳而不是连续吞吐。一般让总帧数在200帧以上,移动距离不超过几个波长,就能得到平滑动画。
3.4 第二幅图:倾斜镜面的等厚条纹
迈克尔逊干涉仪中,如果两臂反射镜不再严格垂直,而是有一个微小夹角,等效于在光路中插入一个劈尖。此时光程差主要沿着某个方向线性变化,条纹也从圆环变成近似平行线。
在仿真里加入一个沿X方向的倾角alpha即可:
alpha = 2e-4; % 两镜夹角,单位rad h0 = 1e-5; % 中心处等效空气厚度,单位m Delta = 2 * (h0 + alpha * X); I = 0.5 + 0.5 * cos(2 * pi * Delta / lambda); imshow(I, []); colormap(gray);条纹间距可以估算:当光程差变化一个λ时,对应X变化λ/(2α)。alpha=2e-4时,间距约为1.58mm。在3mm宽的屏幕上大约能看到两条明纹和两条暗纹。这个线性间距和等倾圆环的非均匀间距形成鲜明对比,是区分两类干涉的好方法。
3.5 封装成函数,方便批量调参
如果每次都要从头写一遍坐标和强度公式,会很啰嗦。我建议把它封装成一个函数:
function I = mike_interference(d, alpha, lambda, L, N, f) x = linspace(-L/2, L/2, N); [X, Y] = meshgrid(x, x); if alpha == 0 theta = atan(sqrt(X.^2 + Y.^2) / f); Delta = 2 * d * cos(theta); else Delta = 2 * (d + alpha * X); end I = 0.5 + 0.5 * cos(2 * pi * Delta / lambda); end这样在命令行里只要改变参数,就能快速对比不同状态:
figure; subplot(1,2,1); imshow(mike_interference(1e-3, 0, 632.8e-9, 3e-3, 512, 0.2), []); subplot(1,2,2); imshow(mike_interference(1e-5, 2e-4, 632.8e-9, 3e-3, 512, 0.2), []);4. 参数调优与定量对照:别让仿真只是“看个热闹”
4.1 关键参数与物理现象对照表
仿真不是随便出个图就算完,参数和视觉变化之间的关系才是精髓。我整理了一张常用对照表:
| 参数 | 常用符号 | 典型值 | 视觉变化 |
|---|---|---|---|
| 波长 | λ | 400~700nm | 波长越大,条纹间距越大 |
| 等效空气厚度 | d | 0.1~2mm | d越大,圆环越多越密 |
| 镜面夹角 | α | 0~1mrad | α越大,等厚条纹越密 |
| 像素数 | N | 256~1024 | N过低时条纹锯齿严重 |
| 观察面尺寸 | L | 1~10mm | L决定能看到多少个条纹 |
| 两束光强度比 | I1:I2 | 1:1到1:10 | 比值越大,可见度越低 |
我建议拿到代码后,先不要改复杂的东西,只固定一个参数扫一遍。比如固定其他参数,把d从0.1mm逐渐增加到2mm,你会看到圆环密度明显增加。再由“为什么d越大条纹越密”倒推公式,就能把数学和图像联系起来。
4.2 用仿真测量波长:一个小实验
迈克尔逊干涉仪最经典的应用之一是用条纹吞吐数来测量波长。仿真里我们可以模拟这个过程。
思路是:让移动镜从某个初始位置d0开始,每次移动一个很小的步长delta_step,统计中心点亮度变化了多少个完整周期。因为光程差变化2Δd,所以当Δd变化λ/2时,中心亮度循环一次。也就是说,如果总共移动了Δd_total,中心明暗翻转了N次,那么:
λ = 2·Δd_total / N
用代码验证一下。我先取一个已知波长lambda_true = 632.8e-9,然后写一个计数循环:
lambda_true = 632.8e-9; d0 = 0.5e-3; d_total = 10e-6; % 总移动量10微米 step = lambda_true / 200; % 每次移动半个波长的1/100 d_vec = d0 : step : d0 + d_total; phase = 2 * pi * 2 * d_vec / lambda_true; center_intensity = 0.5 + 0.5 * cos(phase); % 统计峰值数 [pks, locs] = findpeaks(center_intensity, 'MinPeakProminence', 0.5); visible_cycles = length(pks); % 完整亮条纹次数 lambda_calc = 2 * d_total / visible_cycles;这里把d_total设为10微米,理论上翻转次数为2*d_total/λ,约31.6次。由于峰值检测可能少算半个边缘周期,计算结果会有小误差,但数量级和数值都很接近真实波长。这个例子能说明,仿真中的“移动距离”和“条纹吞吐数”确实是严格的线性关系。
4.3 像素尺寸与奈奎斯特:条纹太密为什么会“糊掉”
仿真的分辨率不是想设多高就设多高。当条纹间距小于几个像素时,采样点无法还原正弦变化的强度,画面上就会出现混叠或摩尔纹。判断方法很简单:测量相邻明条纹中心之间的像素数。若小于3个像素,说明条纹已经“欠采样”。
常用解决办法有三个:
- 增大N,也就是提高像素数;
- 减小观察面尺寸L,让同样数量的条纹摊在更大分辨率上;
- 降低d或alpha,让条纹本身变得稀疏。
我在实际中一般保证每个条纹周期至少有8到10个像素,这样图案看起来平滑,后续做空间频谱分析时也不会出现虚假频率峰。
4.4 光源非单色:可见度下降的仿真
现实光源不是绝对理想单色,而是一个以中心波长λ0为中心的窄带分布。不同波长的光各自形成一套条纹,叠加之后对比度下降。用MATLAB模拟高斯线型光源非常简单:
lambda0 = 632.8e-9; sigma_lambda = 0.2e-9; % 线宽参数 lambdas = lambda0 + linspace(-5*sigma_lambda, 5*sigma_lambda, 101); weights = exp(-(lambdas - lambda0).^2 / (2*sigma_lambda^2)); I_total = zeros(N); for i = 1:length(lambdas) I_total = I_total + weights(i) * (0.5 + 0.5*cos(2*pi*Delta/lambdas(i))); end I_total = I_total / sum(weights); imshow(I_total, []);把不同波长的干涉图按权重相加后,你会看到边缘处条纹变淡,可见度下降。光程差越大,这种下降越明显。这是因为不同波长的相位差差异会随光程差线性增大,当差异接近π时,亮暗互相抵消。对应到物理里,就是光源的相干长度有限。
5. 实战排坑:我在这个项目里踩过的问题与解法
5.1 圆环偏心或变成椭圆
这是最常见的诡异现象。明明公式里用的是X和Y,圆环却不以屏幕中心为圆心。原因通常是坐标原点没有放在屏幕中心。
如果你写的是:
[X, Y] = meshgrid(1:N, 1:N);那么中心就在(N/2, N/2),而半径计算会整体偏移,圆环自然偏心。正确做法是使用linspace(-L/2, L/2, N),让坐标原点在阵列正中心。另一个容易忽略的问题是:如果显示时用了imagesc而没有指定数据范围,图像可能会被拉伸变形,看起来像椭圆。用axis equal或直接imshow可以避免。
5.2 中心点不是亮也不是暗,而是灰蒙蒙
当你将I1和I2设为1和0.5时,暗条纹最暗值约为0.146,而不是0。因为两束光强度不相等,干涉项无法完全抵消背景光。很多初学者误以为程序写错了,其实这正是“可见度下降”的物理表现。把强度改为I1=I2=0.5,并让I = 0.5 + 0.5·cos(...),暗纹就会接近0。
5.3 动态动画闪烁,条纹像在“跳”
这个问题几乎每个人都遇到过。原因无非两个:帧数太少,或步长太大。当相邻两帧的相位差超过π时,余弦值会从正跳到负,看起来像是条纹在抖动。
解决办法是给相位变化加一个上限。比如总移动2μm,用300帧,每帧相位变化量是:
2π·2·(2e-6/300)/λ ≈ 0.042π
这个值远小于π,画面就会很平滑。如果希望动画更长,可以增加帧数而不是加大步长。另外,用drawnow而不是pause(0.05)可以保证渲染及时刷新,避免卡顿累积。
5.4 多波长叠加时运行太慢
用for循环逐波长计算干涉图,在N=512、波长样本数较多时会有些慢。可以把波长维度向量化,用MATLAB的隐式扩展:
Delta_3d = reshape(Delta, N, N, 1); lambda_3d = reshape(lambdas, 1, 1, []); weight_3d = reshape(weights, 1, 1, []); I_total = sum(weight_3d .* (0.5 + 0.5*cos(2*pi*Delta_3d./lambda_3d)), 3); I_total = I_total / sum(weights);这样内存开销会大一些,但速度快很多。如果屏幕分辨率可以用256,内存压力更小。
5.5 常见错误速查表
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 条纹不圆 | 坐标原点不在中心 | 用linspace生成对称坐标 |
| 条纹太密成网格状 | N太小或L太大 | 增大N,或减小L |
| 动态图抖动 | 步长过大 | 增加帧数,缩小每帧移动量 |
| 暗纹不够黑 | 两束光强度不等 | 检查I1、I2设置 |
| 圆环中心不在屏幕中央 | 显示时坐标轴拉伸 | 使用imshow并设置axis equal |
| 多波长程序卡顿 | for循环太重 | 用三维数组与隐式扩展 |
6. 更多玩法:从奇幻到实用
6.1 加入高斯光束剖面的影响
真实激光束是高斯光束,中心亮、边缘暗。仿真中只需在干涉强度上乘一个横向包络:
w = 1e-3; % 光斑半径 I = exp(-r2 / w^2) .* (0.5 + 0.5*cos(2*pi*Delta/lambda));这样屏幕边缘的条纹对比度会下降,更接近真实光路。也可以把包络只加在其中一束光上,观察可见度不均的现象。
6.2 用FFT自动测量条纹间距
对干涉图做二维傅里叶变换,可以在频域看到一对亮峰,峰值点与中心之间的距离对应条纹的空间频率。这个方法很适合自动分析仿真或实验图片:
F = fftshift(fft2(I)); F_log = log(1 + abs(F)); imshow(F_log, []);在频域图像中,找到亮峰的位置,就可以反推条纹间距。如果你正在做“自动读取干涉条纹数”的题目,这会是一个很好的起点。
6.3 做成GUI或App Designer演示
如果你打算把仿真分享给同学或用于课堂演示,可以用MATLAB App Designer做一个带滑杆的界面。用两个滑杆分别控制d和alpha,回调函数里重新生成强度矩阵并刷新图像。代码结构和前面几乎一样,只需把参数绑定到滑杆数值。实测下来,这种交互方式比在命令行里改参数直观得多,尤其在讲“光程差如何影响条纹密度”时,效果拔群。
6.4 当作实验前的“预演工具”
我强烈建议在进实验室之前,先用仿真把参数范围摸一遍。比如你希望在实验里看到5圈圆环,那就在仿真里调d,看看需要把反射镜移动到什么位置。到了实验台上,你就有明确预期,不会像无头苍蝇一样反复找条纹。仿真不是实验的替代品,而是实验的预习材料。
最后聊一点我自己的感受。这个项目表面叫“奇幻之旅”,实际上最不奇幻的部分恰恰是代码本身:先想清楚光程差,再写相位,再画强度图。可当你把d从0.1mm一路滑到1mm,看着圆环从稀疏变成密集、从中心不断吐出新的亮环时,那种视觉冲击会让你突然明白迈克尔逊干涉为什么能测出那么微小的长度变化。这就是仿真的意义——它不替代实验,但它让你在动手之前先拥有直觉。如果你也正在写这个仿真,我建议你千万别停留在把别人的代码跑通,而是把每个参数都改一遍,看看会发生什么。那些意外现象,往往比标准答案更值得记录。