HDR 模式下所有 PBR 模型颜色严重失真
修改于前天 11:4971 浏览BUG反馈
使用模型都出现颜色严重失真情况,都是14号突然出现,13号之前都是没有问题的。经过2天排查发现是系统问题,个人无法修复。
使用两次对话确认问题,花费2W以上积分
第二次对话连接:TapTap 制造
第一次对话连接:TapTap 制造
问题描述
我在开发一款 DND 风格的回合制策略 RPG。项目中有 4 个 3D 场景:角色创建界面(展示 3D 角色模型,背后有 CustomGeometry 做的渐变天空穹顶和地板)、酒馆招募界面(结构与角色创建相同)、六边形网格战斗场景(3D 角色站在六边形地块上,有方向光和 Zone 雾)、以及大地图场景(六边形地形瓦片、POI 标记、迷雾系统)。
这些场景的灯光配置分两种方式:角色创建和酒馆场景通过加载 LightGroup/Daytime.xml 获取灯光(其中方向光 brightness=3.0, temperature=6500, castShadows=true);战斗和大地图场景则手动创建 DirectionalLight,brightness 同样为 3.0。
角色模型共 6 个(战士男/女、法师男/女、盗贼男/女),全部通过 GLB 导入工具转换为引擎 MDL 格式。材质统一使用 PBRMetallicRoughDiffNormalSpec.xml Technique,三张贴图(法线/漫反射/金属度粗糙度),MatDiffColor=(1,1,1,1),Metallic=1.0,Roughness=1.0,颜色完全由 Albedo 贴图提供。
遇到的问题是:所有 6 个角色模型在所有 4 个 3D 场景中颜色都显示异常。整体像蒙了一层白雾,饱和度严重降低。原本深紫色的法师袍变成了亮紫/蓝紫色,棕色的皮肤变成了粉白色,模型表面的暗部和亮部之间的过渡几乎消失了,变成平坦的亮色块。这个问题不是个别模型的现象——6 个不同来源、不同颜色的模型全部表现一致。
关键发现:把 renderer.hdrRendering 设为 false 之后,同样的模型、同样的材质、同样的灯光,颜色就基本正常了。这直接说明问题不在模型、材质或灯光上,而是出在 HDR 开启后引擎自动注入的后处理管线里。
排查过程
为了确认不是自己代码或资产的问题,我花了 10 多轮对话逐一排除了所有可能性。
首先检查了材质参数,所有材质 XML 的 MatDiffColor、Metallic、Roughness 都符合标准 PBR 工作流,没有异常值。然后确认了贴图本身没有问题——关闭 HDR 后同样的贴图显示颜色正常。GLB 导入流程也严格按照导入手册操作,没有遗漏步骤。
接着检查了代码,确认全部场景中没有任何 rp:Append() 调用——也就是说我没有手动添加任何后处理效果,所有后处理都是引擎默认注入的。又尝试了降低灯光亮度,从 brightness=3.0 降到 1.0,发现颜色失真的特征不变,只是画面整体变暗了,排除了"灯光太亮"的可能。
还检查了 PBR Technique XML 的 shader define 参数,没有发现异常配置。也检查了贴图的 sRGB 标志,但由于关闭 HDR 后颜色就正常,sRGB 不太可能是主因。
所有自身原因排除后,我编写了一个叫 RenderDiag v2.1 的诊断脚本。这个脚本在运行时直接 dump 引擎的 Graphics 和 Renderer 全局状态、RenderPath 的完整命令列表和参数值。通过这个诊断工具,我终于拿到了引擎实际运行时的管线数据,发现了问题的根源。
诊断数据与发现
运行时环境状态
诊断脚本采集到的引擎状态:apiName = GL3,graphics.sRGB = false(但 sRGBSupport = true 且 sRGBWriteSupport = true,说明硬件支持但引擎没有开启),renderer.hdrRendering = true,multiSample = 2。
RenderPath 完整命令列表
诊断脚本 dump 出了当前 RenderPath 的全部 49 条渲染命令。其中前 20 条是场景渲染(CLEAR、depth pass、SSAO、base pass、前向光照等),从第 21 条开始是后处理。我没有手动添加任何后处理,以下全部是引擎在 HDR 模式下自动注入的:
命令 [21] DOF(景深)
命令 [22] FXAA2(抗锯齿)
命令 [23] BloomHDR tag=BRIGHT2
命令 [24] BloomHDR tag=BRIGHT4
命令 [25] BloomHDR tag=BRIGHT8
命令 [26] BloomHDR tag=BRIGHT16
命令 [27] BloomHDR tag=BRIGHT32
命令 [28] BloomHDR tag=COMBINE32
命令 [29] BloomHDR tag=COMBINE16
命令 [30] BloomHDR tag=COMBINE8
命令 [31] BloomHDR tag=COMBINE2
命令 [32] BloomHDRPlus tag=BRIGHT
命令 [33] BloomHDRPlus tag=DUALBLUR_DOWN4
命令 [34] BloomHDRPlus tag=DUALBLUR_DOWN8
命令 [35] BloomHDRPlus tag=DUALBLUR_DOWN16
命令 [36] BloomHDRPlus tag=DUALBLUR_DOWN32
命令 [37] BloomHDRPlus tag=DUALBLUR_UP16
命令 [38] BloomHDRPlus tag=DUALBLUR_UP8
命令 [39] BloomHDRPlus tag=DUALBLUR_UP4
命令 [40] BloomHDRPlus tag=DUALBLUR_COMBINE
命令 [41] ColorGrading
命令 [42] GaussianBlur tag=BLURH
命令 [43] GaussianBlur tag=BLURV
命令 [44] GaussianBlur tag=BLURH
命令 [45] GaussianBlur tag=BLURV
命令 [46] GaussianBlur tag=BLURH
命令 [47] GaussianBlur tag=BLURV
命令 [48] GaussianBlur tag=COMBINE
一共 28 个后处理 pass,其中 19 个是泛光(Bloom)相关的。
发现 1:两套 Bloom 系统同时激活
从命令列表可以看到,引擎同时运行了 BloomHDR(命令 [23] 到 [31],共 10 个 pass)和 BloomHDRPlus(命令 [32] 到 [40],共 9 个 pass)两套完全独立的泛光系统。BloomHDR 用的是传统 5 级降采样 + 4 级升采样的金字塔方式,BloomHDRPlus 用的是 DualBlur(双重模糊)方式。两套系统是串联执行的,意味着场景画面先被 BloomHDR 做了一轮泛光处理,然后结果又被 BloomHDRPlus 做了第二轮泛光。图像被泛光了两遍。
发现 2:BloomHDR 的默认合成参数非常极端
诊断脚本采集到的 BloomHDR 默认参数是:
BloomHDRThreshold = 0.4
BloomHDRMix = 1 0.2(即 Vector2(1.0, 0.2))
Threshold=0.4 意味着画面中亮度超过 40% 的像素全部参与泛光计算。在正常场景中,大部分像素的亮度都会超过 0.4,所以几乎整个画面都会被泛光覆盖。
BloomHDRMix=(1.0, 0.2) 的含义是合成时的权重——泛光结果乘以 1.0,原始画面乘以 0.2,然后相加。换算一下:最终画面中泛光贡献占 1.0/(1.0+0.2)=83%,原始画面仅占 0.2/(1.0+0.2)=17%。也就是说,PBR 着色器精心计算出来的固有色、金属质感、粗糙度表现等,在最终画面中只剩下 17% 的权重,剩下 83% 都是模糊化的泛光。这就是为什么所有模型看起来都是"白蒙蒙"的——不是模型颜色有问题,而是原始颜色被泛光淹没了。
发现 3:管线中没有找到显式的 Tonemapping 通道
49 条命令中没有找到名为 Tonemap 或 ToneMapping 的独立通道。方向光 brightness=3.0 配合 PBR 着色器会产生大量 HDR 值(亮度 > 1.0),这些值在没有 tonemapping 压缩的情况下,高光区域可能直接被 clamp 到 1.0(纯白),导致高光处完全过曝丢失细节。
命令 [41] 的 ColorGrading 有可能在内部包含了 tonemapping 处理,但从 Lua 层我无法确认 ColorGrading 的 shader 内部做了什么,也不知道它有哪些可调参数。
发现 4:7 个 GaussianBlur pass 默认启用
命令 [42] 到 [48] 是 7 个高斯模糊 pass(3 轮水平+垂直模糊 + 最终合成)。不清楚这组模糊的设计用途是什么,以及它对最终画面会产生什么影响。但 7 个全屏 pass 的额外开销是客观存在的。
我尝试过的修复
通过 rp:SetShaderParameter() 在 Lua 层调整 BloomHDR 的参数:
local rp = viewport:GetRenderPath()
rp:SetShaderParameter("BloomHDRThreshold", Variant(0.95))
rp:SetShaderParameter("BloomHDRMix", Variant(Vector2(0.12, 0.88)))
把阈值从 0.4 提高到 0.95(只有很亮的区域才泛光),把合成比例从 (1.0, 0.2) 改成 (0.12, 0.88)(原图权重从 17% 提高到 88%)。这对 BloomHDR 有效,画面有一定改善。
但问题是:这只调整了第一套 BloomHDR 的参数,第二套 BloomHDRPlus 仍然在以默认参数全力运行。我不知道 BloomHDRPlus 的参数名叫什么,SetShaderParameter 无法控制它。我也尝试了 rp:SetEnabled("BloomHDRPlus", false),但不确定这个 API 对 pass 组(tag-based)是否真的有效,没有找到相关文档说明。
同样,我也无法确认 ColorGrading 有哪些可调参数,无法确定 GaussianBlur 是否可以安全禁用,也无法在 Lua 层修改 graphics.sRGB 的值。
这个问题的更广泛影响
这不仅仅是"我的项目颜色不对"这么简单。从诊断数据来看,这是一个系统性的管线配置问题,会影响所有使用 HDR + PBR 的项目:
对画面质量的影响:BloomHDRMix 默认 (1.0, 0.2) 意味着 PBR 材质系统计算出的颜色在最终画面中只占 17%。美术无论怎么调整 Albedo 贴图、Metallic、Roughness 参数,最终效果都会被泛光覆盖。暗色系的风格(哥特、暗黑、写实中世纪)几乎不可能实现,因为所有深色都会被洗白。场景的明暗对比和立体感也会丧失,因为泛光会把暗部也填亮。
对性能的影响:19 个 Bloom pass(每个都是全屏采样 + 渲染目标切换)在移动端是不小的开销。而且两套 Bloom 系统功能重复——都是做泛光效果——同时运行除了让画面更白、浪费更多 GPU 算力之外,没有明显的视觉收益。加上 7 个 GaussianBlur pass,仅泛光和模糊就占了 26 个后处理 pass。
对开发者的影响:HDR 模式变得实质上不可用——开启 HDR 就必然颜色失真,不开就没有 HDR 光照效果,开发者被卡在两难中。这个问题的排查成本非常高,我花了 10 多轮对话、编写了专用的运行时诊断工具才定位到根本原因,一般开发者可能直接放弃 HDR 或者以为是自己材质的问题反复调整。
诊断脚本说明
以上所有数据由自制的 RenderDiag v2.1 脚本在运行时采集,脚本位于 scripts/tools/RenderDiag.lua。它在运行时读取 graphics 和 renderer 的全局属性,遍历 RenderPath 的所有命令并打印名称/tag/参数,同时 dump 材质 shader 参数和纹理 sRGB 标志。如果需要复现,将游戏启动场景切换到 renderDiag 即可在控制台看到完整输出。



