ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

OptiScaler神经渲染色彩管线解析:OkLab色相校正与AP1色域压缩数学全解

OptiScaler神经渲染色彩管线解析:OkLab色相校正与AP1色域压缩数学全解

【免费下载链接】OptiScaler_DLSSNR

OptiScaler bridges upscaling/frame gen across GPUs. Supports DLSS2+/XeSS/FSR2+ inputs, replaces native upscalers, enables FSR-FG/XeFG on non-FG titles. Supports Nukem mod for DLSSG-to-FSR3 FG. This fork adds support for neural rendering on dx12 games, more to come.

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

OptiScaler 的神经渲染(DLSS Neural Rendering)分支让 DLSS 5 的神经渲染模型可以跑在 DX12 游戏的超采样输出之上,而它画面"稳、不偏色"的秘密,就藏在一条精心设计的色彩管线里。这篇文章带你完整拆解这条管线的数学基础:白点归一化、软膝编码、双分支亮度比合成,以及 OkLab 色相校正与 AP1 色域压缩是如何联手保住画面色彩的。

一、为什么神经渲染需要一条色彩管线

神经渲染模型(nvngx_dlssnr.dll,NGX 特性 18)是在成品 sRGB 编码帧上训练出来的,它只认识 0..1 范围内、亮度有界的"成片"。但 OptiScaler 超采样器的输出是线性、开口无界的线性光缓冲——高光可以轻松超过 1,暗部数值可以接近千分之一。

直接把这样的帧喂给模型,等于让它看一张不存在的图片。所以整条管线做的事情可以概括为一句话:把线性帧"翻译"成模型认识的代理图像(proxy),模型回答后再"翻译"回原始帧的线性空间,并且翻译全程不能歪色。

所有相关参数都集中在菜单的 "DLSS Neural Rendering" 分组里(DlssNr_Menu.cpp),配置项则对应 Config.h 中标记为removable as one block的[DlssNr]区块。

二、管线全景:Encode → 模型 → Resolve

整条管线实现在一个预编译的 compute shader 里:dlssnr.hlsl,主机侧的常量缓冲定义在 DlssNr_Common.h(WhitePoint、TransferStrength、ColourStrength等)。运行模式枚举(DlssNr_Common.h):

模式作用
Encode帧 → 代理图(归一化 + 编码)
Resolve代理 + 模型答案 + 原始帧 → 合成最终帧
Downsample模型按更低分辨率工作时的代理降采样
Meter/Calibrate读取游戏曝光、标定缓冲尺度

合成阶段(resolve)的核心链路是:

  1. 白点归一化:整帧除以一个"白点"数值,把图片拉进 [0,1];
  2. 双分支亮度比合成:把模型答案按亮度比缩放回原始帧的亮度位置;
  3. OkLab 色相校正:防止缩放过程改写色相;
  4. 亮度比护栏 + 色彩强度混合:限制单帧波动、控制模型色彩介入程度;
  5. AP1 色域压缩:把越界颜色安全地拉回可显示范围。

三、白点归一化:给开口缓冲找一个"分母"

归一化的分母不是拍脑袋的常量。菜单里 "White point from" 提供三个来源(DlssNr_Menu.cpp):

  • Paper white only:手动滑杆。不同游戏需要的数值差异巨大——Nioh 3 的线性缓冲峰值在两百左右,需要约 240 的分母,而 GTA V 的曝光算出来只要 2.7;
  • The game's own exposure:直接读游戏自己维护的 1×1 曝光纹理(D3D12 下甚至在 shader 里实时读取,消除 3~4 帧的 CPU 读回延迟);
  • A buffer the scan found:针对算出曝光却不传出来的游戏,由曝光扫描(DlssNr_ExposureScan.cpp)按形状匹配候选缓冲并锚定比例。

分母选错的后果很直观:偏小则帧永远归一化不掉,亮度比算出几百,OkLab 的立方根变换拿到远超设计范围的值——屏幕上就是一片绿偏。这也是为什么白点是整条管线的地基。

四、Encode 编码:软膝、Neutwo 与 Hybrid 三条曲线

归一化之后还要把帧编码进 [0,1] 的 sRGB 空间。这是"给模型看什么"的关键决策,gReversibleMode对应三个选项(dlssnr.hlsl):

🔹 软膝 SoftKnee(默认,Off)

亮度超过 0.75 后不再硬切,而是按0.75 + 0.25 × (1 − e^-(luma−0.75)/0.25)指数回卷,避免模型看到一片逐帧闪烁的平白;另外用"峰值通道整体缩放"替代逐通道截断,防止蓝色被截成青色(dlssnr.hlsl)。缺点是回卷把高光细节压进极窄的区间,模型根本分辨不了。

🔹 Neutwo 可逆代理

Neutwo(x) = x·rsqrt(x² + 1)把 [0, ∞) 无死区地映射到 [0, 1),高光层次完整保留且可精确反解。技巧同样是一个标量作用于峰值通道,三通道比例不变、色相不弯(dlssnr.hlsl)。代价是中间调也被压缩,柔光场景反而可能更差。

🔹 Hybrid(折中方案,推荐)

HybridCurve:0.75 以下恒等(中间调与 Off 完全一致),以上只对超出部分施加 Neutwo,拐点处一阶连续。一条在处处 ≥ Off 的曲线:既不留中间调损失,又拿回高光细节(dlssnr.hlsl)。

五、双分支亮度比:合成而不是相加

模型返回的不是一帧"差异",而是一张完整的图。早期版本把它当差值加到帧上,结果没有任何东西约束总和的落点——暖色主体可能带绿回来。现在的设计(dlssnr.hlsl)是把它当一张图,用亮度比拉回原始帧的亮度位置:

  • 原始亮度 < 代理亮度(帧比代理暗):ratio = originalLuma / proxyLuma,以帧自己的亮度为目标;
  • 原始亮度 > 代理亮度(帧有代理装不下的头room):ratio = (modelLuma + (originalLuma − proxyLuma)) / modelLuma,把这部分真实亮度叠在模型答案之上而不是缩放掉,高光因此不会被压暗。

在此基础上还有两道保险:

  1. 暗部底限kRatioFloor = 1/512:暗像素上微小的模型改动会产生巨大比值,底限让比值随亮度趋零平滑收敛到 1,消除"沸腾"噪块;
  2. 最大比值护栏boundedRatio = clamp(amplified, 1/guard, guard):强度大于 1 时通过对比值取幂来放大(而不是外推混合),保证结果始终被护栏约束,双向限幅避免暗部塌陷。

色彩强度(Colour strength)则做两件不同的事:0..1 时,在"仅亮度结果(游戏自己的色相 + 模型的明度)"与"完整模型色彩"之间插值——两个端点都是合法图片,中间点也是;大于 1 时进入过饱和模式,只在 OkLab 空间放大 a/b 分量(L 与色相保持不变),越界部分交给下一步 AP1 压缩滚出。

上图是色彩处理不当的典型症状(色调偏移、辉光丢失)。神经渲染管线的每一步设计,本质上都是在回答"如何避免这类偏色"。

六、OkLab 色相校正:色相从哪来,就往哪放

问题的根源:对一个 RGB 三元组做整体比例缩放不会改变色相,但把"差异"加上去会。缩放后的颜色再回混合时,色相可能漂移。OkLab 校正(HueOkLab,dlssnr.hlsl)的做法:

  1. 进 OkLab:线性 RGB → LMS(Bjorn Ottosson 公布的矩阵)→ 逐分量有符号立方根 → Lab(ToOkLab,dlssnr.hlsl)。L 是亮度,a 轴是绿↔品红,b 轴是蓝↔黄;
  2. 只借方向,不借幅度:从模型自己的颜色(correct)取色度方向(a,b)/|chroma|,从缩放后的颜色(incorrect)只取色度模长|chroma|;
  3. 回 RGB 并压色域:ClampAp1(FromOkLab(...))。

一个容易被忽略的细节:方向必须先归一化再乘以模长,而不是用两个模长之比去缩放。后者要除以correctChroma且只能用"精确等于 0"来防护——一个接近灰的像素色度约 1e-7,会轻松绕开防护,把数值噪声放大成四千倍的饱和色。《仁王3》夜景大量近无彩色像素,正是这个 bug 的现场,表现为被误报为"绿色噪点"。

💡 归一化写法下,方向向量构造即单位长度,输出不可能超过incorrectChroma——数值上天然有界。

七、AP1 色域压缩:把"不可能的颜色"拉回"可能的颜色"

合成可能产生负分量——那不是任何显示器能显示的颜色。早期实现是硬 clamp:转 AP1 → 逐通道max(0)→ 转回来。但对饱和像素做逐通道操作恰恰就是色相扭曲器:最先触墙的那个通道决定了其余通道的颜色。

现在的ClampAp1(dlssnr.hlsl)改用中性轴压缩:

  1. 用 Hunt-Pointer-Estevez LMS 基(基于 BT.709)表达颜色,并携带固定的 D65 适应状态(18% 灰);
  2. 计算同亮度的 D65 中性色——所有颜色被拉向的锚点;
  3. 求最大标量 s,使neutral + (color − neutral) · s的每个通道都非负(dlssnr.hlsl);
  4. 整个颜色乘这一个 s:色相原样保留,只有饱和度让步。

关键性质:所有分量非负时 s = 1,输出与输入逐位相同——安全到可以对每个像素无条件执行。色彩强度 >1 的过饱和路径同样依赖它:越界颜色在色域边界"滚落"为最大限度鲜艳但仍是真实颜色的结果,而不是拍平一片死白。

八、参数速查:这些数学对应菜单里的什么

菜单参数对应管线步骤建议
White point from白点归一化优先"游戏自己的曝光";没有就用扫描锚定
Detail strength双分支合成的混合端点0 = 超采样原样输出,1 = 模型完整图片
Colour strengthOkLab 混合 + 过饱和0 = 只拿模型的明度;>1 过饱和,边界处滚落
Reversible proxySoftKnee / Neutwo / Hybrid 编码Hybrid + composed 兼顾中间调与高光
Enlargement低分辨率模型答案的回放大方式模型低于 100% 时选 Matched residual

这些旋钮的完整说明(含每个参数的悬停帮助文本)都在 DlssNr_Menu.cpp。

九、小结

OptiScaler 神经渲染色彩管线的精髓可以浓缩成三条设计原则:

  1. 合成是比例,不是差值——模型答案是一张完整的图,用亮度比拉回正确位置,插值端点都是合法图片;
  2. 色相只借方向不借幅度——OkLab 空间里,缩放只改饱和度,色相永远来自模型自己的颜色;
  3. 越界靠中性轴压缩,不靠逐通道截断——AP1 压缩对合规像素逐位无损,只对越界像素让步饱和度。

顺带说明:这套色彩合成(双分支比值、OkLab 校正、中性轴压缩)源自 RenoDX 的 DLSS 5 插件(clshortfuse),OkLab 矩阵为 Bjorn Ottosson 公布常数,AP1/sRGB/PQ 变换为标准色彩科学,归属详见 RenoDX_ATTRIBUTION.txt,模块整体设计说明见 dlssnr/README.md。理解了这些数学,你就能明白为什么"强度 0 时画面逐位不变""色彩强度 0 时色相分毫不移"——它们不是巧合,而是管线的构造保证。

【免费下载链接】OptiScaler_DLSSNR

OptiScaler bridges upscaling/frame gen across GPUs. Supports DLSS2+/XeSS/FSR2+ inputs, replaces native upscalers, enables FSR-FG/XeFG on non-FG titles. Supports Nukem mod for DLSSG-to-FSR3 FG. This fork adds support for neural rendering on dx12 games, more to come.

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

相关推荐

上一篇:如何使用Python脚本批量控制Easy Effects音频效果:完整API调用指南
下一篇:Obsidian插件汉化终极指南:三步打造全中文笔记工作空间

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

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进