I need to pass an array of integers to a shader as a uniform. As uniform arrays are not yet supported, I'm using a FORMAT_R8
texture/isampler2D to achieve this.
If I'm correctly reading the bytes from the texture, they should be in the range [0..7] and then by normalizing that value, I should get different shades of red, but that's not happening.
In the screenshot you can see that when the value is read as 0, it's correctly displaying it black, but when it's other than 0, it's full red, no shades. I tried comparing the value read and it's always greater than any value I can try, up to the maximum value of a 32 bit signed integer, so no idea what value I'm reading back from the texture.
Any ideas?
The full project is here: 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:
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
}
This is what worked for me:
sampler2D
instead ofisampler2D
(this seems to be the culprit).32.0
(that is256.0 / 8.0
).That is:
Update: Apparently the values are there when using
isampler2D
, but encoded wrong.I began by confirming that the texture was being generated correctly. The problem is when binding the texture for the shader.
I had a look at Godot source, but didn't anything specific to biding the texture for
isampler2D
. Which made me think that it was handled the same assampler2D
(or I'm not very good at looking, in either case I should try to figure things messing with the shader code).First thing to try was to figure out how large the values where. And they came around
1000000000
. So, it appears we are getting 32 bits values. After trying each one, this gets pretty close to a solution:That almost works. For reasons unclear to me, where this should had the second shade of red, it is black. That made me think that perhaps the values are encoded as float.
Under that idea, I decided to divide the values by
2097152
(2²¹ = 256 * 256 * 32). This would get rid of most of the fractional part.I got these binary representation of floating point values using https://baseconvert.com/ieee-754-floating-point
My idea was to take the bits I marked an X above. After dividing by
2097152
, they would have moved to the three least significant bits. That leaves us this code:Does that work? Again, almost. It is the same result as earlier. Except this one comes with a (partial) explanation of the result by virtue of the table above, since the value 2 is 0 on the bits I marked an X above. In fact, the values for 0 and 1 would also need patching.
Now, I could only patch the values, somehow, we would have working code, right? I just need to figure out what the values ended up being.
If I try dividing the value for 2 which is
1073741824
by2097152
I get512
. Which… Didn't work (And I'll blame baseconvert.com). But served as good starting place to search the actual values.I ended with following code, which will give the expected result with
isampler2D
:So there, my explanation is that it is passing the values as floating point numbers, but reading them as integers.