Vulkan - 浮点着色器颜色值到读取缓冲区中的 uchar 值的奇怪映射

Vulkan - strange mapping of float shader color values to uchar values in a read buffer

我知道着色器 [0..1] 中的浮点颜色值范围映射到 UCHAR 缓冲区中的 [0..255] 范围。

据此,对于 UCHAR 缓冲区中的每次更改,我期望着色器颜色值的步长为 1/255。

但结果却大相径庭。这是前两个步骤:

着色器中的红色浮点值 -> 读取缓冲区中的 UCHAR 值

0.000000 -> 0

0.002197 -> 0

0.002198 -> 1

0.006102 -> 1

0.006105 -> 2

前两个步骤在 0.002197 和 0.006102 左右,与预期步骤不同:0.00392 和 0.00784。

那么映射公式是什么?

Unsigned integer normalization基于公式f = i/INT_MAX,其中f是浮点值(钳位到[0, 1]后),i是整数值,INT_MAX 是本例中整数位深度 (255) 的最大整数值。

因此,如果您有一个浮点数,并且想要它的无符号标准化整数值,您可以使用 i = f * INT_MAX。当然......整数与浮点数的精度不同。那么如果f * INT_MAX的结果是0.5,那么它的整数值是多少呢?它可以是 0,也可以是 1,具体取决于四舍五入的方式。

允许实现以他们喜欢的任何方式舍入整数值。鼓励他们使用最接近的舍入(post-转换 0.49 将变为 0,而 0.5 将变为 1),但这不是要求。唯一的要求是它必须选择两个最接近的值之一(它不能将 0.5 变成 3)并且 0.0 和 1.0 的确切浮点值(包括任何限制在它们上的值)必须精确表示为整数 0 和 INT_MAX.


如果您明确需要直接舍入,您随时可以自己进行归一化。事实上,GLSL 有特定的功能可以帮助你。以下假设您正在尝试写入具有 Vulkan 格式 R8G8B8A8_UNORM 的纹理,并且我们假设您正在写入存储图像,而不是通过片段着色器的输出(您也可以这样做,但你失去了融合)。

因此,第 1 步是将 layout 格式更改为 r32ui。也就是说,您现在正在写入一个无符号的 32 位值,而不是 4 个无符号的 8 位规范化值。这完全正确。

第 2 步是使用 packUNorm4x8 function。此函数执行浮点到整数规范化,但规范 显式 正确执行舍入。在 imageStore 函数中使用该函数的 return 值,就可以了。

如果你想写入片段着色器输出,那就有点复杂了。在那里,您将需要使用不同的图像视图,一个使用 R32_UINT 格式的图像视图。因此,您正在创建 4x8 位规范化纹理的 32 位无符号整数视图。它必须成为渲染目标,因此您将不得不进行子通道手术。从那里,只需写下 packUNorm4x8.

的结果

当然,您会立即丢失混合和类似操作,因为您正在写入整数值。并且由于您必须执行该子通道手术,因此写入它的任何着色器都可能也需要执行此操作。

另外请注意,在这两种情况下,您可能都需要调整所写值的组件顺序。 packUNorm4x8 被明确定义为小端,而(我相信?)R8G8B8A8 被指定为该顺序,从最重要到最不重要。因此,您可能需要基本上使用 packUNorm4x8(value.abgr).

进行字节序交换