引擎问题报告:3D模式下所有材质输出颜色发暗

06/1950 浏览BUG反馈
这问题已经有一个月了,导致3d的项目画面很难蚌。
horizontal linehorizontal line
horizontal linehorizontal line
horizontal linehorizontal line
提交对象:UrhoX 引擎团队 报告类型:渲染管线行为问题 / 颜色不可控 严重程度:中(影响所有依赖精确颜色的 Unlit 用法:标记色、卡通平涂、UI 风格色、数据可视化)
horizontal linehorizontal line

1. 问题概述

自上个月一次更新后开始,所有颜色会变的灰蒙蒙的,即使使用 Techniques/NoTextureUnlit.xml(无光照材质)渲染的 3D 物体,其 MatDiffColor 设置的颜色无法所见即所得地输出到屏幕。颜色在渲染管线的输出阶段被 ACES / filmic 色调映射处理,表现为:
  • 高饱和色被去饱和、并发生色相偏移(例:纯红的绿/蓝通道凭空升高)
  • 整体亮度被高光肩部曲线压缩(输入 1.0 的通道达不到屏幕 255)
直觉上,"Unlit / 无光照"应当意味着"不参与任何光照与后期着色,颜色直接输出"。但实测表明 Unlit 仅跳过了光照计算,仍然经过输出端的 tonemap pass,导致开发者无法精确控制其最终显示颜色。
horizontal linehorizontal line

2. 复现环境

项目配置
渲染模式renderer.hdrRendering = true
光照预设LightGroup/Daytime.xml
材质技法Techniques/NoTextureUnlit.xml
颜色设置mat:SetShaderParameter("MatDiffColor", Variant(Color(r, g, b, 1.0)))
测试模型Models/Sphere.mdl(StaticModel)
吸色误差截图取色经多软件交叉验证,误差 ±2 以内(已排除截图色彩管理因素)
horizontal linehorizontal line

3. 最小复现步骤

lua
-- 1) 加载官方光照预设 local lightGroup = scene_:CreateChild("LightGroup") lightGroup:LoadXML(cache:GetResource("XMLFile", "LightGroup/Daytime.xml"):GetRoot()) -- 2) 创建一个 Unlit 球 local mat = Material:new() mat:SetTechnique(0, cache:GetResource("Technique", "Techniques/NoTextureUnlit.xml")) mat:SetShaderParameter("MatDiffColor", Variant(Color(1.0, 0.0, 0.0, 1.0))) -- 纯红 local node = scene_:CreateChild("Ball") local m = node:CreateComponent("StaticModel") m:SetModel(cache:GetResource("Model", "Models/Sphere.mdl")) m:SetMaterial(mat) -- 3) 屏幕吸色球中心,对比期望值 #FF0000
horizontal linehorizontal line

4. 实测数据矩阵

所有"期望屏幕值"按"sRGB 感知颜色直出"计算(即 输入 × 255)。
#tonemapLUTHDR输入 (linear)期望屏幕实测屏幕关键观察
1默认(0.80, 0.95, 0.20)#CCF233#D6DB98 (214,219,152)去饱和、发灰
2默认(1, 0, 0) 纯红#FF0000#C6372E (198,55,46)R 压低、G/B 凭空串色
3默认(1, 1, 1) 纯白#FFFFFF#DDDDDD (221,221,221)中性无偏色,但被压暗
4默认(1, 0, 0)#FF0000#C6372E关 HDR 无变化
5推远(1, 0, 0)#FF0000#C6372E推远雾 无变化
6默认(1, 0, 0)#FF0000(232,2,0)G/B 串色消失,R 仍压低
7默认(1, 0, 0)#FF0000(232, 2, 0)再次确认 HDR 无关
8默认(2, 0, 0) 超量clamp #FF0000(245, 1, 0)R 随输入升高但渐近饱和
关键 sRGB→linear 换算(佐证"曲线"性质):
屏幕 RsRGBlinear
221(纯白输入1.0)0.8670.722
232(纯红输入1.0,tonemap关)0.9100.807
245(纯红输入2.0,tonemap关)0.9610.913
horizontal linehorizontal line

5. 根因分析

Unlit 颜色经过两层相互独立的处理,串成一条输出链:
MatDiffColor
   │
   ├─[A] 残余 filmic tone curve  →  高光肩部压缩亮度(输入1.0 → linear 0.81,且渐近 1.0 达不到)
   │
   ├─[B] ACES tonemapLUT         →  色域去饱和 + 色相偏移(纯红 G/B 串到 55/46)
   │
   └─ sRGB 输出 → 屏幕

层 A:ACES tonemapLUT(由 zone.tonemapLUTEnabled 控制)

  • 默认开启。负责去饱和与色相偏移
  • 证据:实验 2 → 6,关闭后纯红的 (G,B) 从 (55,46) 骤降到 (2,0),色相恢复纯净。
  • 这是"网球黄绿 #CCF233 变灰为 #D6DB98"、"纯红变暗橙红"的主因。

层 B:残余 tone curve(tonemapLUTEnabled 关不掉)

  • 即便关闭 tonemapLUTEnabled,纯红 R 仍被压到 232(linear 0.807),到不了 255。
  • 证据:实验 8,输入加倍 (2,0,0) 后 R 从 232 升到 245(linear 0.807→0.913)——随输入单调上升、增长递减、渐近上限。这是 filmic 高光肩部曲线的典型特征,而非硬钳(硬钳的话 R 不会随输入变化)。
  • 该曲线无法通过 Zone 级开关关闭,疑似位于更底层(RenderPath 的某个 tonemap command,或 shader 内置)。

重要补充:R=1.0 的压低不可能是 gamma

sRGB / gamma 变换的端点固定(1 的任何次方仍是 1),因此 R 从 1.0 被压到 0.81 不可能源于 gamma 编码,只能来自 tone curve。这进一步确认了层 B 的存在。
horizontal linehorizontal line

6. 已排除的因素(实验佐证)

假设因素排除依据
HDR 渲染管线(hdrRendering)实验 4、7:开关 HDR,结果完全不变
雾效(Zone fog)实验 5:雾推到极远,结果完全不变
环境光 / IBL / SH 漏入 Unlit关 tonemap 后 G/B≈(2,0),无加性环境项;且环境光只会加亮、不会压暗 R
gamma / sRGB 编码R=1.0 在任何 gamma 下恒为 1.0,无法解释 232
截图 / 显示色彩管理多软件交叉吸色,误差 ±2 以内
horizontal linehorizontal line

7. 期望行为与建议

期望

"Unlit / 无光照"材质应能精确所见即所得地输出 MatDiffColor,不被任何后期色调映射改写。这是 Unlit 在标记色、卡通平涂、UI/HUD 元素、数据可视化等场景的核心价值。

建议(任一即可缓解)

  1. 提供 Unlit 材质绕过 tonemap 的能力
  2. 例如新增一个 Techniques/NoTextureUnlitRaw.xml,或在材质上提供 shader 参数/flag(如 BypassTonemap),使其在最终合成阶段被排除出 tonemap pass。
  3. 打通 tone curve 的开关
  4. 当前 zone.tonemapLUTEnabled = false 只关掉了去饱和部分(层 A),残余 tone curve(层 B)仍生效。建议让该开关或新增开关能完整关闭整条曲线。
  5. 文档补充说明
  6. 在渲染文档中明确:"Unlit 材质仍会经过输出端 ACES tonemap",并给出在 ACES 下精确控制 Unlit 颜色的推荐做法(如预补偿公式 / HDR 输入值参考表)。

临时规避方案(开发者侧,供参考)

  • 保留 ACES:将目标颜色预补偿(提高亮度/饱和,高亮通道用 >1 的 HDR 输入)逼近期望屏幕值;缺点是高饱和纯色无法完美还原。
  • 全局关 ACESzone.tonemapLUTEnabled = false,使全场 Unlit 接近所见即所得;缺点是影响整个场景所有物体的观感,且残余 tone curve 仍留少量亮度压缩。
horizontal linehorizontal line
6
2