简介:面向光学工程与信号处理学习者的达曼光栅仿真源码,以单个m文件实现从光栅建模到点阵绘制的全流程。程序将达曼光栅视为由周期性相位结构组成的衍射元件,覆盖光栅周期、透镜尺寸、相位延迟等关键参数定义,以及入射角、波长等条件设置,可模拟入射光干涉生成等间距点阵的核心原理。完成相位分布计算后,借助fft2进行二维傅里叶变换,将空间域相位分布转换到频域,得到不同衍射方向的光束强度;再对幅度谱提取主峰位置,并用imagesc或imshow直接显示点阵图像,帮助初学者将抽象光栅方程与可视化图案对应起来。压缩包仅含1个m文件,大小719B,轻量且便于修改运行,已有2324人浏览学习。读者可调整周期、相位延迟或入射条件,观察点阵密度、排列与亮度分布的变化,仿真光束整形、分束等应用,是理解计算光学和衍射光栅设计的实用入门示例。
1. 达曼光栅生成点阵的 MATLAB 程序:先解决“拿到 rar 能干什么”
激光分束点阵这件事,很多人第一反应是上空间光调制器或者直接摆一组激光器,但一块表面只刻了若干条 0/π 相位台阶的达曼光栅,就能把一束光分成几十个等强度点。标题里这份 MATLAB 程序,解决的是从“想要 N×N 点阵”到“得到一组可用光栅相位坐标”的完整仿真链路:一维跃变点优化、二维光栅合成、FFT 计算点阵强度、输出均匀性和衍射效率。
如果你在激光加工、结构光照明或并行光互连方向工作,这套程序的价值在于不用从零推导优化算法,直接改几个参数就能看到点阵图。新手可以跟着跑通整个流程,熟手可以拿它验证自己设计的坐标,或者把坐标导出成制版数据。
需要先说清楚:程序输出的是仿真点阵和相位坐标,不是可以直接拿去光刻的文件。把坐标换算成光栅周期内的物理尺寸,还需要一步制版数据转换,这个我放到最后一章专门讲。
2. 达曼光栅背后的衍射逻辑:先用傅里叶观点看穿点阵
2.1 一个周期里的 0/π 跳变,如何决定每个点亮度
达曼光栅本质上是一个周期性纯相位结构。一维情况下,一个周期内光栅相位只取两个值:0 和 π,对应透射函数是 +1 和 -1。因为它是周期结构,一束平面波照上去,远场能量只会出现在离散的方向,也就是光栅的各个衍射级次。每个级次的复振幅,恰好就是透射函数在一个周期内的傅里叶系数。这就是“点阵”的来源:级次就是点,傅里叶系数的模方就是点的亮度。
真正决定每个点亮度的,是周期内 0/π 相位区的切换位置。把周期归一化到 0 到 1,切换位置记为 x_cut = [x1, x2, ..., xK]。最简单的情况是 x_cut = [0.5],前半周期 0 相位、后半周期 π 相位,得到的频谱里 0 级完全消失,±1 级最强;如果把 x1 改成 0.3,0 级就会回来,±1 级强度也随之变化。所以设计 N×N 点阵,本质上就是找一组 x_cut,让目标级次的傅里叶系数模长相等。
这里要强调一点:达曼光栅不是唯一的“能把光分成多束”的元件,但二元相位的制造优势非常明显。它只需要一种刻蚀深度,对光刻和刻蚀误差的容忍度比连续浮雕高很多,衍射效率也远高于振幅型光栅。程序包里 optimize1d 做的事,就是把“找 x_cut”变成可计算的反问题:写一个成本函数,目标级次尽量等强,总衍射效率尽量高,然后搜参。这不玄学,只是多峰函数需要注意初值。
2.2 1×N 到 N×N:两个一维光栅相乘为什么可行
二维 N×N 点阵最直接的做法是直接搜索二维相位分布,也就是在 M×M 的矩阵上找每个像素是 0 还是 π。这个搜索空间太大,程序包里一般不会这么干。常见做法是拆成两个正交的一维达曼光栅,方向做 x,方向做 y,然后把透射函数相乘。
这个做法的物理依据是傅里叶变换的可分离性:如果 t(x,y) = t1(x) * t1(y),那么频谱就是 T(fx, fy) = T1(fx) * T1(fy)。于是 (m,n) 级次的强度,等于一维第 m 级强度乘以一维第 n 级强度。只要一维方向做到各级次等强度,二维所有组合点的强度也相等。这相当于把 N² 点阵的设计问题,降成两个 1×N 点阵的设计问题,计算量少了整整一个量级。
程序里实现这个相乘要小心,不是把两个方向的相位相加,而是把透射函数矩阵做外积:t2d = t1.' * t1。因为每个 t1 只有 +1 和 -1,外积结果仍然只有 +1 和 -1,对应的还是纯二元相位结构,工艺上依然只要刻一种深度。我之前见过有人把两个相位直接相加再取模,结果多出了中间相位,点阵均匀性怎么调都调不对,最后就是栽在这一步。
2.3 程序包里的核心参数:M、x_cut 与 uniform 到底指什么
拿到程序包以后,打开文件不要急着点运行,先把下面这张参数表过一遍。你后面所有修改和排查,都绕不开这张表:
| 参数名 | 作用 | 常见取值 |
|---|---|---|
| target | 一维目标分束数,二维点阵写为 [nx ny] | 3、5、9 等奇数 |
| M | 光栅一个周期内的采样像素数 | 128~512 |
| x_cut | 一维相位跃变点的归一化坐标 | 0~1 之间的升序数组 |
| repeat | 光栅周期重复次数 | 频谱分析用 1,口径模拟用 4~8 |
| uniform | 点阵均匀性,(Imax-Imin)/(Imax+Imin) | 小于 0.05 算不错 |
| eta | 衍射效率,目标级次能量占比 | 大于 0.7 才值得加工 |
target 的取值决定了优化的目标级次:5×5 点阵的一维目标级次是 -2、-1、0、1、2。这里 0 级比较特殊,中心点的强度和相邻点必须一致,等于要求它的傅里叶系数模长和旁边级次相等,所以达曼光栅的分束数几乎都是奇数,偶数分束需要额外引入相位偏置,程序里没那么好处理。
M 决定了仿真能算到多高级次。目标只关心 ±2 级时,M 取 64 就能看趋势,取 256 是稳妥值。repeat 只用于模拟实际光栅的有限口径效应,分析纯周期结构时用 1 就够了。uniform 和 eta 是判断结果好坏的唯一标准,程序跑完先看这两个数,再去看图。
3. 在本地跑通点阵仿真:rar 包结构、最小命令与 5×5 改造
3.1 解压后文件该怎么认
一份常见达曼光栅 MATLAB 程序包,解压后通常会有这几个文件:
| 文件名 | 职责 |
|---|---|
| main_dammann.m | 主入口,读完参数、调用各模块、出图 |
| optimize1d.m | 一维跃变点坐标优化 |
| build_grating2d.m | 根据一维坐标合成二维光栅透射函数 |
| verify_fft.m | 对光栅做 FFT,计算点阵强度、均匀性和效率 |
| params.m | 集中存放 target、M、repeat 等参数 |
| results/ | 存放输出图和数据 |
先打开 params.m 把 target 和 M 看懂,再运行 main_dammann。如果压缩包里没有 params.m,大概率是参数直接写在 main_dammann.m 头部,效果一样。如果 optimize1d.m 缺失,你就只能手动输入坐标,这种情况下我会先补一个随机搜索优化器,成本不高,后面 3.3 节我给出一个可用的演示版。
3.2 最小可跑验证:2×2 点阵的完整仿真
先跑一个最小例子验证环境和流程,用的是 x_cut = [0.5] 的最简单结构。这个结构的一维频谱里 ±1 级最强,两个方向相乘后,二维频谱会得到 2×2 的四个角点,正好用来确认“FFT 出点阵”这件事本身。
% demo_dammann_2x2.m M = 128; % 单周期采样像素数 xx = linspace(0, 1, M); % 归一化周期坐标 % 一维达曼光栅:前半周期 0 相位,后半周期 pi 相位 phase1d = zeros(1, M); phase1d(xx >= 0.5) = pi; t1 = exp(1i * phase1d); % 透射函数,取值 +1 / -1 % 二维光栅:两个正交方向的一维光栅做张量积 t2d = t1.' * t1; % 夫琅禾费衍射场:直接对透射函数做二维 FFT E = fftshift(fft2(t2d)); I2 = abs(E).^2; % 提取中心附近 3×3 区域,看 2×2 点阵是否出现 c = M/2 + 1; pts = I2(c-1:c+1, c-1:c+1); uni = (max(pts(:)) - min(pts(:))) / (max(pts(:)) + min(pts(:))); figure('Name', 'Dammann 2x2 simulation'); imagesc(I2); axis image; colormap hot; fprintf('2x2 点阵均匀性: %.4f\n', uni);这段代码的逻辑是:phase1d 设置二值相位,t1 变成透射函数;t2d = t1.' * t1 把两个方向的透射函数相乘,相当于正交放置两个一维光栅;fft2 直接给出衍射频谱,fftshift 把 0 级挪到图像中心。最后提取中心 3×3 区域,可以看到四个角有亮斑,就是 (±1, ±1) 级次,中心 0 级因为一维设计里 0 级被抑制而保持暗区。
M 在这里建议不要低于 64。M 太小,频谱中 ±1 级和 ±3 级的间隔变密,图像上容易出现混叠,新手会误以为是程序 bug。M=128 时运行不到一秒,足够验证环境。
3.3 改成 5×5 点阵:调用优化器并检查均匀性
2×2 点阵只是流程验证。要生成 5×5 点阵,需要先优化一维 5 级达曼光栅的坐标,再把坐标代入二维光栅。下面这个 demo 版优化器用随机搜索找坐标,结果精度不如正式包里的遗传算法加局部搜索,但胜在完整可跑,能让你理解整个数据流。
% demo_dammann_5x5.m cfg.target = [5 5]; cfg.M = 256; x_cut = optimize1d_demo(cfg.target(1), cfg.M); t2d = build_grating2d_demo(x_cut, x_cut, cfg.M); E = fftshift(fft2(t2d)); I2 = abs(E).^2; half = (cfg.target(1) - 1) / 2; c = cfg.M / 2 + 1; rows = c-half : c+half; sub = I2(rows, rows); uni = (max(sub(:)) - min(sub(:))) / (max(sub(:)) + min(sub(:))); figure('Name', 'Dammann 5x5 simulation'); imagesc(I2); axis image; colormap hot; fprintf('5x5 点阵均匀性: %.4f\n', uni);function x_cut = optimize1d_demo(Ntarget, M) if mod(Ntarget, 2) == 0 error('demo 版只支持奇数分束数'); end half = (Ntarget - 1) / 2; rng(2024); bestCost = inf; best = []; for trial = 1:300 y = sort(rand(1, half) * 0.48 + 0.01); xc = [y, 1 - fliplr(y)]; cost = dammann_cost_demo(xc, half, M); if cost < bestCost bestCost = cost; best = xc; end end x_cut = best; end function cost = dammann_cost_demo(xc, half, M) xx = linspace(0, 1, M); phi = zeros(1, M); for k = 1:numel(xc) phi(xx > xc(k)) = pi - phi(xx > xc(k)); end t = exp(1i * phi); spec = fftshift(fft(t)) / M; c = M / 2 + 1; idxs = (-half : half) + c; A = abs(spec(idxs)); eta = sum(A.^2); uni = (max(A) - min(A)) / max(A); cost = (1 - eta) + 2 * uni; end function t2d = build_grating2d_demo(xc, yc, M) tx = make1d_demo(xc, M); ty = make1d_demo(yc, M); t2d = tx.' * ty; end function t1 = make1d_demo(xc, M) xx = linspace(0, 1, M); phi = zeros(1, M); for k = 1:numel(xc) phi(xx > xc(k)) = pi - phi(xx > xc(k)); end t1 = exp(1i * phi); end这段代码里,optimize1d 只优化半个周期的跃变点,再对称复制到整个周期,这样光栅左右对称,避免最终频谱出现一边强一边弱。成本函数 dammann_cost 把两项压在一起:1 减目标级次能量占比,加上 2 倍均匀性误差。权重 2 的意思是,均匀性差一点点就狠罚,避免出现效率很高但几个点明显不齐的情况。
主程序里的 rows = c-half : c+half 就是提取 -half 到 +half 的级次区域,对 5×5 来说就是中心 5×5 像素。sub 矩阵里的数值直接对应点阵亮度,uni 小于 0.05 可以接受,demo 优化器一般能跑到 0.01~0.03。如果你发现结果里中心点和四角差异大,先把 trial 提高到 500,多数情况下是随机搜索没碰到好的初值,不是算法写错。
4. 常见问题与排查:仿真点阵不均匀与优化不收敛的五个原因
4.1 仿真点阵左右不对称:先查坐标和索引,别怀疑算法
现象:5×5 点阵里,左侧级次明显比右侧亮,或者上下两排强度不一致,怎么看都觉得光栅“歪了”。
原因:八成不是色散或物理问题,而是坐标不对称。优化器如果直接搜整个周期的跃变点,很容易搜出一组左右不对称的 x_cut;另一个常见坑是 FFT 下采样时目标级次索引没对齐,比如中心取成 M/2,结果把 0 级当成 +1 级,整个频谱看起来就偏了。
解决:先强制对称约束。我的习惯是只优化半个周期的 x_cut,然后通过 [y, 1-fliplr(y)] 扩展成完整周期。索引上统一用 c = M/2+1 作为 0 级,再往两边取级次。检查 E 矩阵中心是不是暗点,如果是,说明 0 级被抑制,你的索引偏移了,第一时间改回 M/2+1。
4.2 优化结果每次都不一样:多峰函数要加重启和固定种子
现象:同一套参数,优化器跑三次,三次给的 x_cut 不同,点阵均匀性也忽好忽坏。
原因:达曼光栅的成本函数是一个典型多峰函数,局部极小值非常多。单次 fmincon 从固定初值出发,很容易停在某个次优峰里,下次换个初值又停在另一个峰。这不是代码随机,而是优化问题本身的特性。
解决:至少做两件事。第一,随机搜索 200~300 次选成本最低的初值,再交给局部精调;第二,在脚本开头加 rng(固定种子),让结果可复现。如果项目要求高一致性,我会把随机搜索改成多起点并行,每个起点独立迭代,最后统一比较。demo 里的 300 次随机搜索就是这个目的。
4.3 采样点数太小,点阵里冒出额外高次点
现象:目标 5×5 点阵,结果图像里 7 级、9 级位置也出现明显亮斑,导致看起来像 9×9 甚至更多点。
原因:M 太小。FFT 频谱的级次范围只有 -M/2 到 +M/2,当你只想看 ±2 级时,M=16 也行,但高级次会溢出混叠,把相邻位置的强度抬高。另一个相关原因是 repeat 设得过大,有限口径的旁瓣叠加到了谱线上。
解决:目标 ±2 级时,M 至少取 64,推荐 256;目标 ±4 级时,M 至少 128,推荐 512。想模拟口径效应,repeat 从 1 慢慢加到 5,每加一次看一次点阵图,如果高级次杂散点重新出现,说明 repeat 已经大到让频谱泄漏变得明显,降回去就好。
4.4 均匀性达标但衍射效率上不去:目标函数权重不对
现象:uni 已经到 0.01,看起来每个点差不多亮,但 eta 只有 0.4,一半以上的能量散落在非目标级次。
原因:成本函数把均匀性权重压得太高,优化器宁可牺牲效率也要保证点阵齐。这是多目标优化最常见的翻车点,均匀性太好但整体很暗,做出来的光栅实际功率利用率极低。
解决:把成本函数改成加权式,cost = w1 * (1-eta) + w2 * uni,w1 和 w2 调平衡。我一般先各给 1,看 eta 和 uni 两个数;如果 uni 太差就加大 w2,eta 太低就加大 w1。判断门槛记死:eta 小于 0.7 的光栅,就算点阵再齐,激光加工里也没人愿意用,因为大部分功率浪费了。
4.5 仿真通过但实测对不上:相位误差和口径效应在捣乱
现象:仿真里 5×5 点阵均匀性 0.02,实际架上激光器一照,边缘点强度明显往下掉,中心点也不稳。
原因:两个主要差异。第一是实际刻蚀深度不可能刚好 π 相位,哪怕误差 5%,也会改变傅里叶系数;第二是仿真默认光栅无限大,实际照明光斑只覆盖有限周期数,边缘衍射会改变点阵包络。
解决:仿真阶段就主动加误差。作相位扰动项 phi_err = randn(size(phi)) * 0.05,观察均匀性是否还能撑住;同时把 repeat 加到实际光栅的周期数再仿真。如果 repeat=8 时均匀性还是很差,要么减少目标分束数,要么提高加工精度。程序跑得快,别只跑理想情况,实测前多折磨自己几次,比拿了样品再返工强得多。
5. 进阶验证技巧:从仿真点阵到实测光斑和制版坐标
仿真通过之后,下一步通常是两件事:第一,把实际 CCD 拍到的光斑图拿回来和仿真对比;第二,把 x_cut 坐标导出成制版文件。先看对照代码:
% 读取实测光斑并和仿真点阵对齐 img = imread('ccd_spot.tif'); % 实测光斑,8 位或 16 位灰度 img = double(img); % 找局部极大值,阈值根据背景估计 bg = median(img(:)); peaks = imregionalmax(img) & (img > bg * 1.5); [y, x] = find(peaks); % 按亮度排序,取前 N^2 个点作为光斑质心区域 Ival = img(sub2ind(size(img), y, x)); [~, idx] = sort(Ival, 'descend'); x = x(idx); y = y(idx); fprintf('实测光斑点数: %d\n', numel(x));这段代码只做到“找出所有光斑”,如果你想严格对比均匀性,还要对每个光斑区域按灰度重心做亚像素定位,再和仿真点阵的坐标比例对齐。对比时不要直接比绝对值,实测光斑有背景噪声和 CCD 响应差异,比相对强度更靠谱。
x_cut 导出制版坐标也简单:
% 把一维坐标导出为 CSV,单位是归一化周期 writematrix(x_cut(:), 'x_cut_5x5.csv');拿到 CSV 后,乘以实际光栅周期的物理尺寸,就得到每个相位跳变点在光刻版图上的位置。比如周期设计为 200 微米,坐标 0.25 对应的物理位置就是 50 微米。这一步不做,仿真坐标就永远停留在屏幕里。
我的习惯是拿新光栅先做一维优化,M 取 64 快速扫参数,确认均匀性和效率都能接受,再上 M=512 的精算,最后才导出制版坐标。之前有同行一上来就把 repeat 设成 32、M 设成 1024,结果 16GB 内存直接被二维 FFT 吃满,代码跑了一个多小时才报内存不足。后来我学乖了,小参数先看趋势,大参数只做最后确认。希望这个习惯也能帮到你,少交点内存学费。
本文还有配套的精品资源,点击获取