后期处理:传播非数字值或无穷大值
着色器运算产生不确定的结果时,会出现非数字 (NaN) 值和无穷大 (Inf) 值。在视觉上,它们表现为纯黑或纯白像素。
可能导致 NaN/Inf 的示例运算为:
- 对任何负数执行平方根 (sqrt) 或对数 (log/log2) 运算将产生 NaN。
- 执行模运算 A % B(其中 A 为无穷大值或 B 为 0)将产生 NaN。
- 将任何数字除以 0 将产生 Inf(例如,将长度为 0 的向量进行归一化)。
除了着色器运算外,未初始化的内存也可能包含/产生 NaN 或 Inf。在某些平台上,不会将新建的渲染纹理上的像素值初始化为 0。这意味着,如果在写入或清除新的渲染纹理之前访问渲染纹理,它们可能会产生 NaN/Inf。一般情况下,应该始终在使用渲染纹理之前将渲染纹理清除,或者在读取渲染纹理之前写入想要从中读取的每个值。
传播 NaN/Inf
将 NaN 或 Inf 作为操作数的任何运算也将产生 NaN/Inf 结果。这在高清渲染管线 (HDRP) 中很重要,因为 HDRP 执行的涉及 NaN/Inf 值的任何过滤或模糊操作都会进一步传播无效值。
HDRP 中的一个常见问题是泛光会产生黑屏。泛光效果本身不会产生 NaN/Inf,但会在屏幕上的其他位置传播生成的 NaN/Inf。发生这种情况的原因是,为了计算泛光,HDRP 会对场景颜色进行下采样/过滤,然后再上采样至所需的分辨率。如果屏幕上只有一个 NaN/Inf,则下采样过程将传播无效值,直到覆盖整个纹理为止,因此后续的上采样仅包含 NaN 值,从而导致整个屏幕变黑。
例如,HDRP 计算泛光时,由材质问题引起的 NaN 会扩散到整个场景:
HDRP 生成由诸如屏幕空间反射、屏幕空间折射和失真等功能使用的颜色棱锥时,会发生类似问题。
如果禁用泛光并且屏幕停止变黑,导致黑屏的原因可能是存在单个 NaN/Inf 像素,但不是真正可见,而泛光将此像素传播到整个屏幕上。原因并非泛光产生了无效值。
解决 NaN 和 Inf 问题
阻止泛光或其他 HDRP 功能传播 NaN/Inf 值的最佳方法是解决 NaN/Inf 值的根源问题。如需了解如何执行此操作,请参阅查找 NaN 和 Inf。
如果无法解决 NaN/Inf 值的根源问题,可采用 HDRP 摄像机提供的一项功能,这项功能会用黑色像素来替换 NaN 和 Inf 值。这样可以阻止泛光之类的效果传播 NaN/Inf 值,但是这是一个相当耗费资源的过程。要启用此功能,请选择一个摄像机,然后在 Inspector 中启用 Stop NaNs 复选框。请注意,只有在无法解决 NaN/Inf 值的根源问题时,才应启用此功能。
查找 NaN 和 Inf
为了帮助找到 NaN/Inf 的根本原因,HDRP 提供了一种调试模式,以易于识别的颜色显示包含 NaN/Inf 的像素。要使用此调试模式,请执行以下操作:
- 打开 Render Pipeline Debug 窗口(菜单:Window > Render Pipeline > Render Pipeline Debug)。
- 找到 Rendering,然后将 Fullscreen Debug Mode 设置为 NanTracker。
这可以帮助查看屏幕上是否确实存在 NaN/Inf 以及是哪种材质所致。但是,如果需要了解更多信息(例如具体是哪个绘制调用导致了这个问题),可以使用帧调试工具,例如 RenderDoc。如需了解如何在 Unity 中使用 RenderDoc 来捕获帧,请参阅 RenderDoc 集成。
一种导致出现 NaN 的常见情况是使用定义不当的法线(例如等于零矢量的法线)来导入网格。 可以使用 Render Pipeline Debug 窗口的Material 面板中的一种法线可视化模式来查找这些法线。
RenderDoc
捕获帧后,RenderDoc 可以将具有 NaN/Inf 值的像素显示为纯红色,这有助于查找 NaN/Inf 值,因为与 HDRP 为无效值渲染的标准白色/黑色像素相比,纯红色会显眼得多。为此,请在 Texture Viewer 中打开 Overlay 下拉选单,然后单击 NaN/Inf/-ve Display 选项。
了解如何更轻松地发现 NaN/Inf 值后,就可以开始寻找它们。如果它们仍然不明显,可以查看泛光分发情况以了解泛光从何处传播 NaN/Inf 像素,然后查明相关的确切像素。以传播 NaN/Inf 部分中的示例图像为例,通过泛光的 NaN/Inf 值扩散情况可以发现根源位于屏幕中心附近的球体材质上。
找到哪些材质/着色器产生 NaN/Inf 之后,可以对它们进行调试以找出实际导致无效值的运算。