无法从 Godot 的着色器中正确读取 FORMAT_R8 uniform sampler2D 纹理

Can't read a FORMAT_R8 uniform sampler2D texture correctly from within a shader in Godot

我需要将整数数组作为统一传递给着色器。由于尚不支持统一数组,我使用 FORMAT_R8 texture/isampler2D 来实现此目的。

如果 我从纹理中正确读取字节,它们应该在 [0..7] 范围内然后通过标准化该值,我应该得到不同的红色阴影,但这并没有发生。

在截图中你可以看到,当读入值为0时,它正确显示为黑色,但当它不是0时,它是全红色,没有阴影。我尝试比较读取的值,它总是大于我可以尝试的任何值,最大为 32 位有符号整数的最大值,所以不知道我从纹理中读回的值是什么。

有什么想法吗?

完整项目在这里:https://github.com/Drean64/clash-of-the-colors/tree/simple

GDScript:

extends ViewportContainer

func _ready():
    var attr_ink = PoolByteArray([])
    attr_ink.resize(32*24)
    for i in range(0, 32*24):
        attr_ink[i] = i % 8 # fill array with [0..7]
    
    var image = Image.new()
    image.create_from_data(32, 24, false, Image.FORMAT_R8, attr_ink)

    var ink = ImageTexture.new()
    ink.create_from_image(image, 0)
    
    (self.material as ShaderMaterial).set_shader_param("ink", ink)

着色器:

shader_type canvas_item;

uniform isampler2D ink;

void fragment() {
    COLOR = vec4(0., 0., 0., 1.); // Black
    ivec2 cell = ivec2(UV / TEXTURE_PIXEL_SIZE) / 8; // 8x8 pixel cell coordinate
    int ink_color = texelFetch(ink, cell, 0).r; // Should be [0..7]
    COLOR.r = float(ink_color) / 7.; // Should normalize the red value to [0..1]
    // Line above is a kind of debug to get a hint at what the ink_color value is
}

这对我有用:

  1. 使用sampler2D代替isampler2D(这似乎是罪魁祸首)。
  2. 通过乘以 32.0(即 256.0 / 8.0)进行归一化。

即:

COLOR.r = texelFetch(ink, cell, 0).r * 32.0;

更新:显然使用isampler2D时值在那里,但编码错误。

我首先确认纹理生成正确。问题是在为着色器绑定纹理时。

我查看了 Godot 的源代码,但没有任何关于为 isampler2D 投标纹理的具体信息。这让我觉得它的处理方式与 sampler2D 相同(或者我不太擅长观察,在任何一种情况下我都应该尝试弄清楚着色器代码的问题)。

首先要尝试弄清楚值有多大。他们来到了 1000000000。所以,看起来我们正在获得 32 位值。在尝试了每一个之后,这就非常接近解决方案了:

int x = texelFetch(ink, cell, 0).r;
COLOR.r = mod(float(x / (256 * 256)), 256.0) / 256.0;

差不多可以了。由于我不清楚的原因,它应该有第二种红色阴影,但它是黑色的。这让我觉得也许这些值被编码为浮点数。

根据这个想法,我决定将值除以 2097152 (2²¹ = 256 * 256 * 32)。这将消除大部分小数部分。

float                    binary                                integer
5.877471754111438e-39 -> 0 00000000 01000000000000000000000 -> 2097152
                    1 -> 0 01111111 00000000000000000000000 -> 1065353216
                    2 -> 0 10000000 00000000000000000000000 -> 1073741824
                    3 -> 0 10000000 10000000000000000000000 -> 1077936128
                    4 -> 0 10000001 00000000000000000000000 -> 1082130432
                    5 -> 0 10000001 01000000000000000000000 -> 1084227584
                    6 -> 0 10000001 10000000000000000000000 -> 1086324736
                    7 -> 0 10000001 11000000000000000000000 -> 1088421888
                                  X XX

我使用 https://baseconvert.com/ieee-754-floating-point

得到了这些浮点值的二进制表示

我的想法是采用我在上面标记为 X 的位。在除以 2097152 后,它们将移动到三个最低有效位。这给我们留下了这段代码:

int x = texelFetch(ink, cell, 0).r;
COLOR.r = mod(float(x / 2097152), 8.0) / 8.0;

这有用吗?再次,几乎。这与之前的结果相同。除了这个根据上面的 table 附带对结果的(部分)解释,因为值 2 在我上面标记为 X 的位上为 0。事实上,0 和 1 的值也需要修补。

现在,我只能修补值,不知何故,我们会有工作代码,对吧?我只需要弄清楚这些值最终是什么。

如果我尝试将 2 的值 1073741824 除以 2097152,我会得到 512。哪个……没用(我会责怪 baseconvert.com)。但作为搜索实际值的良好起点。

我以下面的代码结束,它将给出预期的结果 isampler2D:

int x = texelFetch(ink, cell, 0).r;
int y = x / 2097152;
COLOR.r = mod(float(y), 8.0) / 8.0;
if (y == 476)
{
   COLOR.r = 1.0 / 8.0;
}
if (y == 480)
{
   COLOR.r = 2.0 / 8.0;
}
if (y == 482)
{
    COLOR.r = 3.0 / 8.0;
}

所以,我的解释是,它将值作为浮点数传递,但将它们作为整数读取。