☰
为什么LUT做不了真胶片?SpektraLab用81波长光谱还原暗房冲印的完整指南
2026/11/4 11:53:21 网站建设 项目流程

【免费下载链接】SpektraLab

SpektraLab — a macOS film and print simulator. SwiftUI app + a Metal/C++ render engine, linked in, no Python at run time. A fork of andreavolpato/spektrafilm.

项目地址:https://gitcode.com/gh_mirrors/sp/SpektraLab
点击查看免费下载

SpektraLab 是一款运行在 macOS 上的胶片与冲印模拟器:它不靠 LUT 滤镜,而是让光线穿过 81 个波长的光谱,还原胶片染料层与相纸的真实暗房过程——颗粒、光晕、成色剂、放大机滤色,一样都不少。

如果你用过 LUT,大概都有过同一个困惑:同一个 LUT,套在天空一片绿、套在皮肤一片黄,怎么都"不对味"。这不是你调得不好,而是 LUT 从原理上就装不下"胶片"。SpektraLab 换了一条路:不去"调出"胶片的样子,而是按物理过程把一张底片冲印出来。这篇文章讲清楚 LUT 到底差在哪,以及 SpektraLab 的光谱管线是怎么把暗房搬进电脑的。

先说结论:LUT 是"滤镜",不是"胶片"

LUT(Look-Up Table,查找表)做的事,用一句话就能说完:给每个像素的 RGB 三元组,查出另一个 RGB 三元组。它是一个"逐像素查表"的函数,没有波长、没有位置、没有过程。

而真正的胶片,有三样东西 LUT 从结构上就装不下:

LUT 装不下的东西为什么 LUT 做不到SpektraLab 怎么做
光谱(波长)3 通道 RGB 已把 81 个波长压缩成 3 个数,信息丢了全程携带 81 个波长做积分
空间效应查表只看"这一像素的值",看不到邻居颗粒、光晕、成色剂扩散都是空间过程
冲印过程结果被"烤死"进一张固定表放大机、相纸、显影都是可算的物理阶段

这三点里,最关键的是第一点和第二点。光谱丢了,就没法区分"胶片和相纸各自在不同波长上吸了多少光";空间丢了,就没法做出高亮边缘旁边那条标志性的红色光晕。

81 波长:SpektraLab 的光谱管线长什么样

在 ARCHITECTURE.md 里,SpektraLab 把整条管线画成一句链式反应:

场景光 → 相机+胶片 → 潜影 → 显影 → 负片染料密度 → 放大机+相纸 → 潜印 → 显影 → 相纸染料密度 → 扫描/观看 → 输出 RGB

颜色不是按"RGB"算的,而是380–780 nm、每 5 nm 一个采样点,共 81 个波长。一个像素在引擎内部不是一组 RGB,而是"三条染料层在 81 个波长上的密度"。整条管线分三步:

第 1 步 · 曝光:RGB → 胶片"看到的光谱"引擎用 Hanatos 2025 的光谱上采样,把输入 RGB 还原成"如果这个颜色照到胶片上,胶片会看到什么样的 81 波长光谱",再乘以胶片自己的光谱敏感度。这一步在 engine/src/core/hanatos.cpp 里完成,192×192×81 的大表在初始化时就折算成一张 192×192×3 的查找表,所以逐像素时几乎零开销。

第 2 步 · 显影:曝光 → 染料密度曝光量查胶片的特性曲线,得到青/品红/黄三条染料层的密度;接着是DIR 成色剂耦合——染料之间互相抑制并做空间扩散;最后叠上颗粒。这一整段是胶片"化学"的核心。

第 3 步 · 冲印:密度 → 光 → 响应(两次光谱积分)把负片的染料密度放进放大机。引擎把同一个积分跑两遍:第一遍用"放大机灯光 + 相纸敏感度",得到相纸上的潜影;显影成相纸染料密度后,第二遍用"CIE 标准观察者 + 观看光源",得到最终 RGB。公式写出来是这样:

$$\text{out}m = \sum{\lambda} I(\lambda),S_m(\lambda);10^{-\left(\sum_k c_k D_k(\lambda) + D_{\text{base}}(\lambda)\right)}$$

关键在于:引擎内部根本没有 RGB 这个中间空间,ProPhoto RGB 只是入口和出口的"量色标准"。这就是它和 LUT 最本质的区别——LUT 是"RGB 进、RGB 出",SpektraLab 是"光谱进、光谱出"。整条节点序列在 engine/src/pipeline/pipeline.cpp。

物理尺度:颗粒与光晕为什么"真"

LUT 的第二个死穴是空间。SpektraLab 把颗粒和光晕做成真正的空间 + 随机过程,而且按画幅以微米为单位缩放:你告诉它这张底片是 120 还是 135,颗粒、光晕、成色剂扩散就会自动按画幅变粗变细。同一张照片叫成 35 mm,颗粒就正确地更粗。

光晕(halation)是光谱模拟最"炫"也最"难"的例子。对于没有防光晕层的胶片(比如 CineStill 800T),强光会穿过乳剂层、在片基底面发生菲涅尔反射再折返回来重新曝光。引擎用一组受物理约束的高斯混合去拟合这个"后反射光晕",甚至还原出"红色光晕比绿色远 1.5–2.7 倍"的现象——这就是为什么真实扫描里,一盏日光灯旁边总有一道橙红色的边。这套拟合见 engine/src/core/halation_psf.hpp。

这种效果,任何逐像素查表的 LUT 都做不出来——它需要"这一像素的强光,去影响它周围的像素"。

放大机与相纸:真正的"暗房"

胶片模拟里最容易被忽略的,是放大机和相纸。SpektraLab 把它们当成一等公民:

  • 放大机滤色:用一套可调的双折射滤色片,按 CC 色深单位去"减"放大机灯光,相当于暗房里的色温/品红/黄滤片。引擎在 engine/src/core/spectral.cpp 里把滤色片建成 81 波长上的函数,再乘到灯光上。
  • 相纸自己的染料层:和胶片一样,相纸也带 81 波长的敏感度与染料密度,参与第二次光谱积分。
  • 物理档位:内置 34 款实测胶片/相纸档案(25 款负片 + 9 款相纸),全部来自真实测量数据,而非"调出来的近似"。

引擎里唯一用 LUT 的地方,恰好证明它的边界

有意思的是,SpektraLab 并非完全不用 LUT。它确实用了一张 LUT,但只用在一个很窄的场景:在不同相纸之间快速切换预览。

这张表是 33×33×33、约 431 kB 的浮点表,把"负片密度 → 某个相纸的成品"直接烘焙成查表,4500 万像素下 2 毫秒就能翻一张相纸,比重新冲印(约 9–167 毫秒)快得多。但代码里特意写了一句话,恰好点破了 LUT 的天花板:

"Glare is a spatial, stochastic veiling field and cannot live in a pointwise table." (眩光是空间性的、随机的遮蔽场,装不进逐点的表里。)

也就是说,连 SpektraLab 用 LUT 的那张表,都主动丢掉了眩光——因为那是空间 + 随机的东西,点表装不下。这张 LUT 的设计边界见 engine/src/core/print_lut.hpp。

不到一秒的"重印":性能与导出

很多人担心"物理模拟 = 很慢"。SpektraLab 用 C++ 把引擎直接编译进 App(没有运行时 Python),在 M3 Max 上测得:

  • 预览尺寸(长边 2560)重印约0.01 秒;
  • 全分辨率(7800×5800)渲染约0.87 秒;
  • 整个安装包 32 MB,其中代码约 3 MB。

导出页会先在你的目标文件原始分辨率下软打样,确认和画布逐像素一致后才落盘;导出走的是 ImageIO,色彩只做一次映射。

数字中间片(DI):给后期留空间

除了直接出成品,SpektraLab 还能导出数字中间片:按 Cineon 风格的对数编码(代码 95 = 胶片底基黑,685 = 参考白,每十度 300 码,负 gamma 0.6),逐波长去掉胶片底基。这让文件"打印密度"化、灰度精确中性,适合后续调色——而成品冲印本身不做任何印后编辑,"修图"的活儿留给别的软件。详见 engine/src/core/digital_intermediate.cpp。

快速上手:三步打开你的第一张胶片

  1. 打开 RAW:把文件拖进窗口即可。引擎先把 RAW 解码成线性 ProPhoto 浮点。
  2. 选胶片 + 选相纸:左侧 Film 列表挑负片,Print 列表挑相纸(默认已配好一对)。
  3. 冲印并导出:右侧 Pre-Dev / Post-Dev 调曝光、白平衡、曲线;满意后进导出页选格式与色彩空间落盘。

💡 小贴士:Post-Dev(印后:曝光/曲线/色彩平衡)是"Layer 2",只走一次绘制、几乎无延迟;Film/Print 与 Pre-Dev 则会触发一次引擎渲染。把控制项放对"层",是滑块跟不跟手的关键。

小结

  • LUT 是逐像素查表:没有波长、没有空间、没有过程,所以它只能"滤镜",做不出真胶片。
  • SpektraLab 按物理过程算:81 波长光谱 × 胶片/相纸染料层 × 两次光谱积分,颗粒、光晕、成色剂全部是真实的物理与空间过程。
  • 快且诚实:C++ Metal 引擎编译进 App,预览重印约 0.01 秒;连它自己用的那张 LUT,都诚实地丢掉了点表装不下的眩光。

如果你想要的是"调出一个胶片味",LUT 够用;如果你想要的是"把这张底片真的冲印出来",那是 SpektraLab 这条光谱管线在做的事。

源码与文档导航

想了解内部实现,可以从这几处入手:

  • 总览与色彩链:ARCHITECTURE.md、README.zh-CN.md
  • 引擎 C 接口(整条管线入口):engine/include/spektrafilm/spk_engine.h
  • 光谱上采样(RGB → 曝光):engine/src/core/hanatos.cpp
  • 光谱常量与放大机滤色:engine/src/core/spectral.cpp
  • 光晕后反射拟合:engine/src/core/halation_psf.hpp
  • 胶片/相纸档案结构:engine/src/core/profile.hpp
  • 冲印管线节点序列:engine/src/pipeline/pipeline.cpp
  • 数字中间片(Cineon log):engine/src/core/digital_intermediate.cpp
  • 实测胶片/相纸档案示例:engine/resources_product/profiles/
  • 前端参数模型:modern_UI/Spektrafilm/Spektrafilm/Model/Params.swift

【免费下载链接】SpektraLab

SpektraLab — a macOS film and print simulator. SwiftUI app + a Metal/C++ render engine, linked in, no Python at run time. A fork of andreavolpato/spektrafilm.

项目地址:https://gitcode.com/gh_mirrors/sp/SpektraLab
点击查看免费下载

相关推荐

上一篇:OptiScaler Client 组件全解:Fakenvapi、NukemFG、FSR4 INT8 与 OptiPatcher 一次讲透
下一篇:GDAL 的 ERS 栅格驱动深度解析:读写 ERMapper .ERS 头文件格式

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询