
简介这份光学Matlab模拟资源面向物理光学学习者、高校师生及科研入门者聚焦薄膜干涉、双缝干涉和衍射传输等经典现象的数值仿真弥补实验室中难以直观呈现的光学过程。压缩包共29个文件以17个m脚本为主辅以11个ppt和1个doc文档m脚本对应各节模拟程序ppt以动画展示干涉、衍射、偏振等抽象原理doc为章节小结便于对照学习。资源包仅3.32MB轻量易用已有1039人学习下载。借助傅里叶变换、快速傅里叶变换等数值方法读者可亲手调整薄膜厚度、双缝间距、波长等参数观察条纹变化并理解瑞利判据、双折射、光栅衍射等内容。脚本注释清晰、配套文档完善适合边改代码边验证理论可作为光学课程实验补充或自学Matlab光学仿真的入门参考帮助将抽象波动光学公式转化为可视化图形提升对干涉、衍射、偏振规律的直观认识。 做光学仿真很多年我一直觉得Matlab是上手最快、也最容易把物理图像“跑出来”的工具。光学领域有个特点理论公式写起来漂亮但一旦涉及到实际光束传播、干涉、衍射这类问题解析解往往只存在于少数理想模型里而数值模拟就成了连接理论与实验的桥梁。最近整理了一个光学Matlab模拟的完整实践笔记从最核心的数学工具到可以直接跑的代码再到我踩过的各种坑一并分享出来。这篇内容适合正在写光学大作业、做科研计算、或者刚入坑光学仿真的朋友无论你是第一次接触傅里叶光学还是已经在用FDTD这类商业软件应该都能从中找到点有用的东西。1. 为什么选择Matlab做光学模拟方案选型背后的逻辑1.1 光学模拟的几条路线对比光学仿真这行当工具选择其实挺多的。传统成像设计有Zemax、CODE V这类专业软件微纳结构有Lumerical FDTD、COMSOL等时域/频域求解器开源路线还有Python的diffractio、LightPipes等库。但如果你要解决的是“光的传播、干涉、衍射、傅里叶光学”这一大类问题Matlab的优势依然明显。核心原因在于光学场分布本质上就是一个二维复数矩阵。Matlab的矩阵运算能力天然匹配这个需求对光场做一次傅里叶变换本质上就是对矩阵做一次FFT逻辑极其直接。相比Zemax这种黑箱软件Matlab最起码让你明确知道每一步在算什么不会出现“软件给了结果但不知道怎么解释”的尴尬局面。1.2 Matlab做光学模拟的三个核心优势第一开发效率极高。光学模拟通常都是先快速验证想法Matlab脚本语言改起来几乎没有编译成本。我经常一晚上迭代几十个版本的参数这在C或者其他编译型语言里是不可想象的。第二可视化工具链成熟。imagesc、pcolor、surf这几个函数就能把二维复振幅的强度、相位分布完整展现出来配合colormap和colorbar出图质量完全可以用于论文发表。第三工具箱完善。除了基础的光学模拟图像处理工具箱可以处理实验采集的光学图像信号处理工具箱可以做时域脉冲分析统计工具箱还能做随机介质中的光束传播模拟一套环境覆盖全流程。1.3 环境准备与版本选择的几个提醒很多人卡在第一步Matlab装好了但启动闪退或者某些光学相关函数找不到。我遇到过几次类似问题基本都是工具箱没装全或者许可证失效导致的。建议做光学模拟时至少保证以下组件完整安装MATLAB 本体R2020b 以上均可功能差异不大Signal Processing ToolboxFFT相关辅助函数Image Processing Toolbox图像读取、滤波操作Curve Fitting Toolbox拟合衍射光斑、高斯光束参数如果你用的是Linux服务器跑仿真需要注意Matlab在Linux下的OpenGL版本兼容性。我之前在CentOS上遇到过图窗打不开的问题后来改用软件渲染启动参数matlab -softwareopengl就解决了算是一个不太起眼但很实用的经验。2. 光学模拟的核心数学工具与Matlab实现要点2.1 复振幅、光强与相位底层数据的理解做光学模拟永远要记住一个核心概念光场是复数。Matlab里通常用一个二维复数矩阵E来表示光场的复振幅分布比如E(x,y) A(x,y) * exp(1i * phi(x,y))其中A是振幅phi是相位。光强分布则是复数模长的平方I abs(E).^2;这里有个新手非常容易踩的坑abs(E).^2和E.^2完全是两回事。前者是模长平方物理上代表光强后者是复数平方会得到完全错误的结果。我之前帮人调试程序发现衍射图样怎么都不对最后定位到就是把abs(E).^2写成了E.^2。相位分布也值得注意。直接对复数矩阵取相位得到的结果会被限制在-pi到pi之间也就是所谓的“包裹相位”。如果你想看波前如何演化建议用unwrap函数把相位展开否则会在相位跳变处看到一堆虚假的边缘线。2.2 坐标网格与采样间隔所有模拟的地基Matlab处理二维光场坐标网格一般是这么生成的L 5e-3; % 模拟区域边长单位米 N 1024; % 采样点数 dx L / N; % 采样间隔 x (-N/2 : N/2-1) * dx; [X, Y] meshgrid(x, x);这个网格有几个关键点。第一采样点数N最好选2的幂次比如512、1024、2048这样FFT效率最高。第二模拟区域L和采样间隔dx直接决定了模拟精度采样间隔必须满足奈奎斯特采样定理——至少能分辨最小空间结构。比如你模拟波长为632.8nm的光那网格间隔dx应该远小于波长除以数值孔径之类的关键尺寸否则模拟结果会出现混叠伪影。还有很多人搞不清.*和*的区别。如果你要表示一个光场通过某个掩膜必须用E_mask E .* mask;。这里的.*是逐元素相乘正确的物理含义是每个空间点上的场乘以那个点的透过率。如果用E * maskMatlab会把它当成矩阵乘法瞬间把你辛辛苦苦构造的光场变成一团乱麻。这个点我反复跟别人强调过因为错误太隐蔽了报错信息还不明显运行结果只是诡异很难定位。2.3 傅里叶变换与角谱传播衍射计算的灵魂傅里叶光学里面衍射计算的本质就是光场在频域中传播v 一个相位因子。最常用的角谱法公式如下% 假设 E0 是初始平面的复振幅分布 wavelength 是波长 z 是传播距离 k 2*pi / wavelength; fx (-N/2 : N/2-1) / (N*dx); % 空间频率坐标 [FX, FY] meshgrid(fx, fx); H exp(1i * k * z * sqrt(1 - (wavelength*FX).^2 - (wavelength*FY).^2)); E_fft fftshift(fft2(E0)); Ez ifft2(ifftshift(E_fft .* H));这段代码看起来简单但是注意三个地方。第一fftshift和ifftshift的顺序很容易搞混一定要在FFT之前做频移否则频谱中心不在原点。第二fx的计算公式是(-N/2 : N/2-1) / (N*dx)注意不是(0:N-1)/(N*dx)前者才是对称的频域坐标。第三当(wavelength*FX).^2 (wavelength*FY).^2 1的时候传播因子会变成衰减项倏逝波这部分在自由空间传播中通常要截断掉不然数值会发散。角度谱法和菲涅尔衍射积分本质上是同一个物理过程在不同的近似精度下的表达但Matlab实现起来角谱法更直接只需要两次FFT加一次复数乘法。我个人的偏好是近场、远场、任意距离都用角谱法它对距离没有限制只是要注意在极小距离下相位因子变化剧烈需要更细的采样。2.4 可视化输出出图细节决定论文质量不少人的模拟算法没问题但出图效果一言难尽。光学模拟的可视化我个人几个小习惯强度图用pcolor或imagesc并配合axis image保持宽高比颜色映射优先选colormap(hot)或者colormap(jet)但要避免在正式报告里用彩虹色colorbar一定要加而且标注单位坐标轴字体大小调到合适set(gca, FontSize, 12)有次我发现polarplot的坐标轴字体总是被图形覆盖怎么设置都不生效。后来查了半天发现需要单独设置坐标区属性ax.ThetaAxis.FontSize和ax.RAxis.FontSize而不是直接用set(gca, FontSize)。这种小坑如果在演示当天遇到还是很尴尬的。3. 两个可直接复现的光学模拟实例3.1 实例一杨氏双缝干涉的完整实现杨氏双缝是光学干涉的经典入门案例也是验证模拟程序逻辑是否正确的黄金标准。模拟前先明确物理模型波长为λ的平面波垂直照射到缝宽为a、缝间距为d的双缝上在距离z处的观察屏上形成干涉条纹。理论条纹间距是Δx λ*z/d这个解析值可以直接用来验证模拟结果。% 参数设置 lambda 632.8e-9; % 氦氖激光波长 d 0.5e-3; % 双缝间距 0.5mm a 0.05e-3; % 缝宽 0.05mm z 1; % 观察屏距离 1m L 8e-3; % 模拟区域 N 2048; % 采样点 dx L / N; x (-N/2 : N/2-1) * dx; [X, Y] meshgrid(x, x); % 构造双缝掩膜在X方向 mask zeros(N, N); mask(:, abs(X) (d/2 - a/2) abs(X) (d/2 a/2)) 1; mask(:, abs(X) (d/2 - a/2) abs(X) (d/2 - (a/2 a))) 0; % 初始平面光场通过双缝 E0 mask; % 平面波振幅设为1 % 角谱法传播 k 2*pi / lambda; fx (-N/2 : N/2-1) / (N*dx); [FX, FY] meshgrid(fx, fx); H exp(1i * k * z * sqrt(1 - (lambda*FX).^2 - (lambda*FY).^2)); E_fft fftshift(fft2(E0)); Ez ifft2(ifftshift(E_fft .* H)); % 光强分布与绘图 I abs(Ez).^2; I_norm I / max(I(:)); imagesc(x*1e3, x*1e3, I_norm); colormap(hot); colorbar; xlabel(x (mm)); ylabel(y (mm)); title(sprintf(双缝干涉图样, z %.2f m, z));跑完这个程序你会看到很漂亮的条纹图样而且中心条纹最亮两边逐渐衰减。用[~, idx] max(I_norm(N/21, :))找到条纹峰值位置计算相邻峰间距会发现和理论值lambda*z/d 1.266mm完成一致。这一步验证通过说明你的角谱传播代码没有大问题可以放心用作后续更复杂模拟的基础。一个常见问题是条纹消失或者密度不对。如果你把d和a设置得太接近干涉条纹会被单缝衍射包络调制甚至在主极大位置出现缺级这是正常的物理现象。如果现象和你预期不符先回去检查d和a的数量级很多模拟失败都是输错参数。3.2 实例二高斯光束的任意距离传播模拟高斯光束是激光光学里最常遇到的模型模拟它的传播比双缝干涉更能体现角谱法的实用价值。高斯光束初始复振幅分布为% 高斯光束参数 lambda 1064e-9; % Nd:YAG 激光波长 w0 0.5e-3; % 束腰半径 0.5mm z_R pi * w0^2 / lambda; % 瑞利距离 L 10e-3; N 1024; dx L/N; x (-N/2 : N/2-1) * dx; [X, Y] meshgrid(x, x); % 束腰处的场分布 r2 X.^2 Y.^2; E0 exp(-r2 / w0^2); % 传播距离取1倍、5倍、10倍瑞利距离 z_list [z_R, 5*z_R, 10*z_R]; k 2*pi / lambda; fx (-N/2 : N/2-1) / (N*dx); [FX, FY] meshgrid(fx, fx); for i 1:3 z z_list(i); H exp(1i * k * z * sqrt(1 - (lambda*FX).^2 - (lambda*FY).^2)); E0_fft fftshift(fft2(E0)); Ez ifft2(ifftshift(E0_fft .* H)); I abs(Ez).^2; figure; imagesc(x*1e3, x*1e3, I/max(I(:))); colormap(hot); colorbar; axis image; title(sprintf(z %.2f z_R, z/z_R)); end % 提取束宽和理论值对比 % 理论光束半径 w(z) w0 * sqrt(1 (z/z_R)^2)高斯光束传播的模拟重点看两个东西。第一传播后光斑是否仍然保持高斯分布第二光斑半径是否按照w(z) w0*sqrt(1(z/z_R)^2)增长。我在实测中惯用手法是在输出平面上找光强下降到峰值1/e^2处的半径然后和理论值比较误差在1%以内说明模拟精度够用。这里要注意网格大小L的选择。高斯光束发散角为theta lambda/(pi*w0)传播到远场后光斑直径会超过初始模拟区域。如果L设置得太小一部分光场被截断得到的光斑半径会偏小。我一般会先估算一下最大传播距离处的光斑大小再反推需要设置的模拟区域L。通常设置L至少是最大预期光斑直径的三倍以上。3.3 参数调整与典型参数速查做了大量模拟之后我整理了一个典型参数表方便快速参考模拟类型波长范围典型区域尺寸推荐采样点数关键注意事项杨氏双缝干涉400-700nm1-10mm1024-2048缝宽远小于缝间距条纹间隔解析验证高斯光束传播可见光到近红外5-20mm512-1024模拟区域要大于3倍最大光斑平面波通过圆孔衍射可见光0.1-1mm1024孔边缘采样要足够避免锯齿任意相位掩膜成像可见光1-5mm1024-2048相位变化剧烈处需加密网格这些参数不是死的但初始值可以按这个表来设跑通再调整。4. 常见问题与排查技巧实录4.1 频域坐标错误导致衍射图样不对这是光学模拟里出现频率最高的问题没有之一。很多人直接用(0:N-1)/(N*dx)作为频域坐标结果得到的频谱中心不在原点传播后的光场直接错位看起来就像条纹被切了一半。正确的做法是使用负对称坐标(-N/2 : N/2-1)/(N*dx)并且配合fftshift使用。我建议写一个独立的辅助函数把这个频域坐标计算封装起来每次都调用避免重复犯错。4.2 结果全是NaN或者Inf如果模拟结果全是NaN最可能的原因是射向传播时1 - (lambda*FX).^2 - (lambda*FY).^2出现了负值然后又开了平方根生成了一堆复数指数发散项。解决办法是对这个表达式加一个上限保护real_part max(1 - (lambda*FX).^2 - (lambda*FY).^2, 0);或者直接对大于1的部分强制置零。另一个常见原因是初始光场振幅量级设置太大比如高斯光束峰值振幅直接设成1e10传播几步后指数因子直接溢出这类问题用realmax检查一下就知道。4.3 模拟结果边界出现明显的干涉伪影模拟区域是有限的但实际光场是无限的这就会导致边缘处的光场被硬性截断截断处产生非物理的衍射波向模拟区域内传播表现为边缘处多了一圈条纹。我的常用处理是零填充或者加窗函数。最简单的做法是在初始光场的边缘乘以一个Hann窗函数% 简单边界吸收窗 win1d hanning(N); win2d win1d * win1d; E0_apodized E0 .* win2d;代价是窗函数本身会影响模拟区域内边缘场的完整性所以窗函数一般只作用在最外边5%-10%的区域。更严格的做法是用高斯吸收层但那套逻辑比较复杂通常光通信仿真里才会用到日常光学模拟加个窗就完全够了。4.4 与外部数据的对接问题有些光学模拟需要读入实验测量数据比如波前传感器输出的相位文件或者气象卫星的光学遥感数据。Matlab读取GRIB格式数据是一个典型需求相对直接的方法是使用ncread配合相关的格式转换工具或者直接安装Mapping Toolbox但工具箱没有覆盖所有GRIB版本。我遇到的坑是下载的GRIB2文件用ncread读取时变量名和坐标维度经常对不上解决办法是先执行ncdisp(filename.grib2)查看具体的变量结构再写提取代码。另一个外部联动场景是Matlab和FDTD SolutionsLumerical联合仿真。FDTD擅长微纳结构Matlab擅长后处理。很多人碰到“FDTD cannot open a connection to Matlab”这类报错原因通常是Matlab的COM接口没启动或者没装对应版本的Runtime。早年用FDTD 2018版时我装Matlab R2017a两者版本必须匹配才能联调。后来我学乖了不再让两者直接联动而是用FDTD导出近场数据.mat或HDF5格式再用Matlab离线处理稳定性提升很多。4.5 图像处理大作业里的光学技巧看到不少人在做Matlab图像处理大作业时也涉及光学模拟相关任务最常见的是频域滤波。比如把一张图片做傅里叶变换在频域把高频分量滤掉再逆变回去就能实现模糊效果。这本质上就是傅里叶光学的二维版本只是把空间坐标换成了像素坐标。一个小技巧是做频域滤波前一定要fftshift再显示频谱不然低频成分在四个角上你很难直观判断要滤掉哪个区域。另外matlab自带的fspecial配合imfilter可以快速实现高斯模糊是图像处理大作业的好帮手。4.6 光学工具箱选择与版本兼容Matlab官方有个“Optics Toolbox”属于第三方贡献工具箱不是MathWorks官方发布的。如果要用直接下载解压后把路径加入Matlab搜索路径addpath(genpath(D:\toolbox\OpticsToolbox)); savepath;需要注意的是这类第三方工具箱在不同Matlab版本下的兼容性参差不齐。我用R2022b跑老版本的Optics Toolbox时就遇到过polarplot相关的函数名冲突提醒“Warning: Function polarplot conflicts with a function of the same name”。解决方法是减少工具箱的路径添加范围或者直接改名调用。这方面最稳妥的方式还是自己写核心函数工具箱只用来参考实现逻辑。5. 一些使用心得写了这么多最后分享一点个人经验。做光学模拟最忌讳的是“过于相信模拟结果”图像好看不等于结果正确一定要有验证环节。每写一个模拟程序我做的第一件事就是用已知解析解去验证比如双缝干涉验证条纹间距高斯光束验证发散角都对了才敢放心跑新场景。还有个习惯是把衍射计算涉及的核心函数写成独立脚本模块包括频域坐标计算、角谱传播算子、可视化封装不同项目间直接复用能省下大量重复调代码的时间。光学模拟的核心不在于代码量有多大而在于物理图像是否清晰如果你的模拟结果和理论对不上多半是物理本质还没吃透这时候不要急着改代码先回去看看公式推导更有效。本文还有配套的精品资源点击获取