做光学系统设计的朋友应该都有这种经历:暗室里实测的杂散光分布,跟 Zemax 里仿真的结果怎么都对不上,尤其在镜头前加一道偏振片之后,鬼像的亮度和位置能差出好几个量级。很多人第一反应是测量条件有问题,但我在几个项目里反复排查后发现,问题往往出在仿真模型本身——我们默认了“散射不改变偏振”,可现实中的粗糙表面散射,几乎都是偏振敏感的。
这篇文章来聊聊在 Ansys Zemax OpticStudio 里怎么模拟偏振敏感的散射过程。我会把偏振散射的物理基础、Zemax 里几种能落地的实现路径、一套完整的实操流程,以及我踩过的坑都整理出来。重点面向正在做偏振成像系统、激光主动照明、光通信器件杂散光分析的朋友,标准序列仿真基本够了,但只要你碰到“散射路径上偏振态变化”这类需求,这篇可以直接给你一个起步框架。
1. 为什么必须关注偏振敏感的散射
1.1 散射如何破坏系统的偏振特性
偏振成像系统这几年用得越来越多,从工业检测里的应力分布测量,到车载镜头里的去眩光处理。这类系统的核心假设是:目标光经过光学系统后,偏振态的变化是可预测的。可只要系统里存在杂散光路径,光线在机械件表面、镜片边缘、结构内部发生散射,偏振态就会被打乱,最终在探测器上和目标光干涉叠加,产生奇怪的鬼像或者对比度下降。
很多人会问:散射不就是把光线打散到各个方向吗,跟偏振有什么关系?这里要分清楚两个层面。第一层是概率分布层面,即散射光往哪个方向跑,这个确实由表面微结构和入射角决定,传统 BSDF 就能描述。第二层是矢量层面,光在粗糙表面发生散射时,入射光的电场方向和散射光的电场方向并不一致,偏振分量会发生重新分配。也就是说,一束水平线偏振光打到一个粗糙表面上,散射出来后可能变成部分圆偏振,也可能变成线偏振但方向转了。
这种偏振重分配在抛光面的微小划痕、喷砂处理的金属表面、黑漆涂层上表现得特别明显。你要是做红外系统,探测器对偏振本来就不敏感,这个效应可能被忽略;但做可见光偏振相机或者激光雷达,忽略这个效应,仿真和实测之间就会出现系统性偏差。
1.2 传统散射模型的“偏振盲区”
我们平时在 Zemax 里用的散射模型,像 Lambertian 和 ABg,本质上都是标量模型。Lambertian 只告诉你散射光强按余弦分布,ABg 用一个数学公式拟合 BSDF 的角度衰减,但这两个模型都不触碰光线的偏振信息。
更麻烦的是,Zemax 的标准散射机制在散射事件发生时,默认把入射光线的偏振态原封不动地带到散射光线上。也就是说,你去追迹一束圆偏振光,光线打到散射面后,出射方向变了,但偏振态还是那个圆偏振,这在物理上是不对的。真实粗糙表面的散射会产生去偏振效应,Stokes 矢量的三个偏振分量都会变化。
这就引出一个关键认知:要在 Zemax 里模拟偏振敏感的散射,不能只靠“打开某个开关”,而是要在反射、透射这类规则光学路径之外,额外处理散射路径上的偏振变换。后面讲的几种方案,本质上都是在补这个缺口。
2. 偏振与散射结合的基本工具
2.1 用 Stokes 参数描述偏振状态
要处理偏振散射,先得统一语言。Zemax 的偏振追迹功能里,我们最常用到的就是 Stokes 矢量。它用四个实数描述一束光的偏振状态:
- S0:总光强
- S1:水平线偏振分量与垂直线偏振分量的差值
- S2:45 度线偏振分量与 -45 度线偏振分量的差值
- S3:右旋圆偏振与左旋圆偏振的差值
这套描述方式的好处是,它天然能处理部分偏振光。比如 S1 = 0.3 表示水平偏振成分比垂直多三成;如果 S1、S2、S3 都为零,就是完全非偏振光。跟 Jones 矢量只能描述完全相干偏振光相比,Stokes 矢量更适合散射这种“大量随机微面元叠加”的场景。
在 Zemax 里设置偏振光源时,可以直接输入 Stokes 参数,也可以用 Jones 矢量定义完全偏振光,软件内部会做转换。实际处理散射问题时,我习惯直接给 Stokes 参数,因为后续自定义散射模型时,输出更新也以 Stokes 参数为准,不用来回换算。
2.2 偏振相关的 BSDF 到底是个什么东西
标量 BSDF 是一个数值函数,给定入射角和散射角,返回散射强度。偏振敏感的 BSDF,也就是我们常说的 pBSDF,则是一个 4x4 的 Muller 矩阵函数。它的每一项都可能是入射角、散射角、方位角的函数。
理解 pBSDF 最简单的方式是把它想象成一组偏振通道。入射光的四个 Stokes 分量经过这个矩阵,混合输出为新的四个分量。如果你只关心线偏振,可以把矩阵简化,分别测 s 偏振入射出射 s 偏振(ss 通道)、s 入射出射 p(sp 通道)、p 入射出射 s(ps 通道)、p 入射出射 p(pp 通道),这样四个标量 BSDF 就能组合出一个 2x2 的线偏振传输矩阵。
工程上没有人直接测完整的 4x4 Muller-BSDF,太贵也太慢。最常见的做法是只测线偏振相关的四个通道,或者干脆只测两个主通道(ss 和 pp),然后用模型拟合出另外两个交叉通道。这样得到的数据,足够驱动一次偏振敏感散射仿真。
3. Zemax 里实现偏振敏感散射的三种路径
3.1 路径对比
我们在项目里尝试过三种不同深度的方法,各有取舍。把它们放在一起对比,方便你按自己的精度和成本需求选。
| 实现路径 | 原理 | 适用场景 | 精度 | 实现成本 |
|---|---|---|---|---|
| 基础偏振追迹 | 打开追迹开关,光源设置偏振态,散射事件仍保持偏振不变 | 系统级杂散光概算,判断偏振影响量级 | 中 | 低 |
| 分偏振 BSDF 通道 | 分别建立 ss/sp/ps/pp 四个 BSDF 数据文件,分次追迹后在后处理中合 | 有测量数据、需要工程级偏振散射评估 | 中高 | 中 |
| 自定义散射 DLL | 在散射事件中调用 Muller 矩阵更新 Stokes 矢量 | 科研级精确建模、需要完整偏振演化路径 | 高 | 高 |
3.2 基础偏振追迹能干什么
这个方法最省事,操作只在光源设置和系统开关两个地方。在非序列模式下给光源定义偏振态,然后打开系统层的偏振追迹开关,跑一遍追迹。散射模型照常用 ABg 或 Lambertian,但你必须清楚:散射只是改变光线方向,偏振态不变。
那这方法还有什么用?我的理解是,它主要用于快速评估系统里“偏振相关杂散光”的量级。比如你做一个镜头,想知道入射偏振方向改变时,某个机械结构产生的散射鬼像强度变化有多大。因为散射偏振不变,所以变化完全来自几何光程差异和后续表面的膜层效应。这个结果虽然不是一个完整的物理真相,但能帮你判断要不要投入资源去做更精细的偏振散射建模。
3.3 分偏振 BSDF 通道:工程上最划算的方案
这个方法的核心思想,前面提过,就是分别准备四个偏振通道的 BSDF 数据。实际操作时,我们通常准备两个极端通道就够了:ss 和 pp。如果入射光是 s 偏振,追迹中使用 ss 的 BSDF 权重;入射光为 p 偏振时,使用 pp 的 BSDF 权重。
具体做法是先测或用模型生成偏振分辨的 BRDF 数据,整理成 Zemax 能识别的 ASCII BSDF 文件格式。然后分两次跑追迹,一次用 s 偏振入射 + ss BSDF,一次用 p 偏振入射 + pp BSDF,最后把两次探测器结果导出来做差分。
这个方案的好处是不用写任何代码,所有操作都能在 Zemax 界面里完成。坏处是忽略了交叉通道(sp 和 ps),在线偏振照明、近似各向同性表面时误差可控。如果目标表面有明显的方向性纹理,比如单点金刚石车削表面,交叉通道就不能省了,这时就得走自定义 DLL 方案。
3.4 自定义散射 DLL:完整模拟的终极手段
Zemax 支持用户自定义散射模型,入口是对象属性的散射标签页,选择“用户自定义”,指向一个编译好的 DLL。这个 DLL 负责计算散射后的光线方向和偏振态。
DLL 的核心逻辑分两步。第一步,和普通散射模型一样,根据一个概率分布函数采样散射角,这个用表面粗糙度模型生成。第二步,根据入射偏振态和散射角,通过一个预置的 Muller 矩阵计算新的 Stokes 矢量,写入出射光线的偏振状态。
我在多个 Zemax 版本里做过这种 DLL,值得提醒的是:DLL 接口函数签名在不同版本间有差异,你在写代码之前,务必打开软件自带安装目录下的用户自定义示例源码,找到对应你版本的那个模板文件,基于它来改。不要在网上随便找一份 DLL 源码拿过来直接用,编译通过是一回事,Zemax 能不能正确解码变量是另一回事。
提示:自定义散射 DLL 生成后,建议先用一束简单线偏振光打到平面表面,通过探测器对比散射前后的 Stokes 变化,验证计算正确,再放到完整系统里。跳过这一步,后面查问题会非常痛苦。
4. 实操流程:偏振敏感散射仿真全记录
4.1 场景设定与前期准备
我这边的项目背景是一个紧凑型离轴光学系统,工作在 1064 nm,前组有个反射镜,镜筒内壁做了黑色阳极氧化处理。问题表现为:系统在强激光照明下,探测器边缘出现一个偏振相关的晕影,换用不同偏振方向的入射光,晕影强度变化接近一倍。
这个场景非常适合做偏振敏感散射分析。我把整个模型拆成几部分:光源、前组镜片、镜筒内壁散射面、后组镜片、探测器。散射主要集中在镜筒内壁,所以给镜筒内壁单独分配一个散射标签。
前期数据准备阶段,我给镜筒内壁测了偏振分辨的 BSDF。测量得到四个通道的数据后,用最小二乘法拟合出 ABg 参数。这里给出我拟合出来的典型值范围,你直接用可能不匹配你的表面,但它们能让你对数量级有个概念:
- 黑色阳极氧化铝,喷砂处理:
- ss 通道:A = 8e-4,B = 2e-3,g = 1.4
- pp 通道:A = 5e-4,B = 2e-3,g = 1.5
实际镜筒内壁的绝对散射水平受表面氧化层厚度影响很大,这批参数只能用于前期的量级摸底。正式仿真前,你最好根据供应商提供的样片标定数据重新拟合。
4.2 设置偏振光源与开启偏振追迹
在非序列模式下,我用了一个椭圆的 Source Ray,尺寸设定好以后,在光源属性里打开偏振设置。这里可以直接输入 Jones 矢量:水平线偏振就是 (1, 0),45 度线偏振是 (1/√2, 1/√2),圆偏振是 (1/√2, i/√2)。为了跟实测对齐,我用了水平线偏振。
系统层面,需要在系统选项里找到偏振相关的面板,勾选偏振追迹。这一步很多人会忘,位置不难找,但默认关闭。开了以后,Zemax 会对所有传播路径上的偏振状态做更新,包括膜层透射反射、镀膜效应、以及后续的偏振叠加。
每一步追迹中的偏振信息,Zemax 会按光线的进程保留,你可以在分析工具里查看后光线的 Stokes 参数。初始调试时,我建议把光线追迹数调低,先用几万根光线验证模型能正常工作,确认散射方向分布和偏振变化符合预期,再拉高光线数做最终统计。
4.3 定义偏振敏感的散射表面
这是整个流程里核心的一步。我先用最简单的方案验证:给镜筒内壁应用 ABg 散射模型,打开偏振追迹,跑一次完整的杂散光分析。结果和预想一样,探测器上的偏振分布只反映了系统几何导致的偏振变化,散射事件本身没有引入偏振依赖。
然后我切换到分偏振 BSDF 通道方案。准备两组 BSDF 文件,一组对应 ss 通道,一组对应 pp 通道。在散射标签页里加载 BSDF 数据文件,追迹两次,一次用 s 偏振入射加 ss 文件,一次用 p 偏振入射加 pp 文件。
这里有个操作细节:光源偏振不能只改 Jones 矢量的方向,因为系统里后续的偏振敏感光学元件会根据入射偏振改变光线权重。所以两次追迹中,除了散射面的 BSDF 文件不同,光源偏振态也必须对齐。最好把这两组配置存成两个 Zemax 文件,这样不容易搞混。
两次追迹结束后,把探测器数据分别导出成文本文件。我用 Python 做了个简单的后处理,计算每个像素的偏振响应对比度,代码如下:
import numpy as np # 两个文件分别对应 s 偏振和 p 偏振入射下的散射强度分布 I_s = np.loadtxt("scatter_s.txt") I_p = np.loadtxt("scatter_p.txt") # 避免除零,加一个极小量 denom = I_s + I_p + 1e-12 # 偏振对比度:正值为 s 偏振更强,负值为 p 偏振更强 contrast = (I_s - I_p) / denom np.savetxt("polarization_contrast.txt", contrast)这个对比度图非常直观,直接把散射路径上的偏振敏感区域标了出来。我们最终靠这张图,定位到了镜筒内壁一个特定角度区域,它正好对应实测的晕影位置。
4.4 参数化分析与验证
拿到对比度图后,还要验证结果的可靠性。我做了两个验证实验。
第一,把光源换成 45 度线偏振,重新跑两次追迹,看对比度图是否按预期旋转。如果模型正确,偏振敏感区域应该随入射偏振旋转而产生对称性变化。这一步能筛掉很多因为边界条件设置错误导致的假结果。
第二,在自定义 DLL 的方案下,用一组已知 Muller 矩阵的表面对模型做单点验证。做法是把 DLL 里的 Muller 矩阵设为单位阵,跑出来应该和基础偏振追迹完全一致;再把矩阵设成理想旋光器,对比度图应该出现固定的偏振旋转。这两个标准测试通过后,我才相信 DLL 输出的偏振更新是可靠的。
注意:在非序列模式下,光线追迹的随机性会导致对比度图上出现大量噪点。要得到平滑的偏振对比度分布,建议单次追迹光线数不低于 500 万,并配合使用低差异采样序列,否则像素级对比度会有明显的统计波动。
5. 常见问题与排查实录
5.1 开了偏振追迹但结果完全没变化
这个问题我见过太多次了。排查顺序很固定:先看光源偏振设置是否生效,再看系统偏振开关是否真的打开,最后看探测器是否记录了偏振相关输出。
还有一个隐蔽陷阱:如果你的散射面是“纯机械”表面,没有镀膜,那偏振追迹确实会正常执行,但散射事件保持偏振不变,所以结果看起来“没变化”。这不一定是出错,而是模型本身不含偏振散射机制。
另一个可能,是你没有在光源上设置偏振态。有些默认光源初始状态是自然光,Stokes 参数是 (1,0,0,0),入射光没有任何偏振信息,散射自然也就看不出偏振差异。务必用一个接近完全偏振的光源做验证。
5.2 BSDF 数据导入报错
Zemax 的 ASCII BSDF 导入对文件格式比较严格,常见的报错是“无法解析文件”。我总结了几个必查项:
- 数据文件保存为 UTF-8 无 BOM 格式,不要用记事本的默认编码
- 每列数据使用空格或 Tab 分隔,不要混用逗号
- 角度网格必须单调递增,且覆盖完整的入射和散射范围
- 散射数值不能出现负值或极端的离群值
我建议所有 BSDF 数据在导入前,先用 MATLAB 或 Python 画一下验证图,确认数据没有突变。导入之后,在 Zemax 的散射设置页面里查看数据预览,如果预览曲线和原始数据对不上,多半是文件头信息的索引格式没对上。
5.3 自定义 DLL 计算偏振更新不稳
自定义散射 DLL 在偏振更新的时候,最常见的毛病是 Stokes 参数的归一化问题。Zemax 内部光线追迹有个前提,就是光线的能量不能突变。你在 DLL 里更新 Stokes 矢量时,如果 S0 变了,意味着散射本身吸收了或放大了能量,这会影响能量守恒。
正确做法是:偏振变化只修改 S1、S2、S3 分量,S0 维持不变(或按 BSDF 权重独立处理)。如果模型里需要去偏振效应,就要把 S1/S2/S3 按比例缩小,同时保持 S0 不变,表示一部分偏振信息转化成了非偏振成分。
另外,DLL 里做浮点运算时,注意不要过度精确保留偏振分量。我在调试时就遇到过,某个方向散射后 S1 变成了一个非常小的负值,按理说这是数值噪声,但它被后续光线追迹当作真实偏振态传递,导致结果异常。解决方案是在更新 Stokes 后做一次钳位,把绝对值小于 1e-10 的分量直接置零。
5.4 仿真结果和实测对不上
如果偏振敏感散射仿真做完了,对比度图跟实测仍有偏差,先不要急着改模型。我建议按下面几步排查:
第一,检查光源偏振和实测装置是否一致,特别是偏振片快轴方向的角度误差。一个 1 到 2 度的偏振方向误差,在强偏振散射系统中可能造成百分之十几的对比度差异。
第二,确认 BSDF 测量数据和仿真器件状态是否一致。表面污染、氧化层厚度变化、温湿度差异都会明显改变偏振散射特性。最好在仿真前和实测前,用同一样片做一次校准测量。
第三,检查追迹光线数量是否足够。偏振对比度依赖两个大数的差值,如果统计涨落大,信噪比会很低。我自己的经验阈值是:如果对比度图的标准差超过平均值的 10%,说明光线数不够,要继续增加追迹量。
6. 实操心得与个人建议
再分享一点我从几个项目里总结出来的经验。偏振敏感散射确实是个“没有银弹”的领域,不要一上来就追求最完整的 Muller-BSDF 建模。正确的打法是先用分偏振 BSDF 通道的工程近似,把整体杂散光和偏振敏感区域快速锁定,筛出主要贡献路径后,只针对那几条路径做或者补测数据,再用自定义 DLL 做精细确认。
我在当前这个项目里,90% 的决策其实是靠前两步完成的,自定义 DLL 主要用于验证几个极端情况下的物理一致性。原因很简单,完整 4x4 Muller-BSDF 的测量和拟合成本太高,工程上没有必要对所有表面都用。等你真正遇到一条关键路径,它的偏振散射贡献占系统误差的主导地位,这时投入资源做完整建模才是划算的。
最后提醒一句:Zemax 版本升级后,偏振追迹和散射 DLL 的接口有过几次变化,旧项目文件在新版本里打开,偏振相关设置可能被重置。做系列仿真时,尽量固定软件版本,或者在每次版本切换后跑一遍最简单的偏振验证样例(一束线偏振光打平面反射),确保基础行为一致,再继续手头的工作。