How to get a single pixel color value from a Texture in SHADER

Godot Version

4.2.1

Question

shader_type spatial;
render_mode unshaded;

uniform sampler2D t1_albedo : source_color,repeat_enable;
void fragment() {
	ivec2 cell = ivec2( 3, 3 );
	float ink_color = texelFetch(t1_albedo, cell, 0).r;
	ALBEDO = vec3 (0.0,0.0,0.5);
	if( ink_color >= 0.6 )
		ALBEDO = vec3 (1.0,0.0,0.0);
}

give the code above, I am getting the pixel at position 3,3 on my texture. if it have 60% or more red, it will output full red.
My issue is that, the texture I am using is all black except 3,3 has 204/255 or 80/100 (80%) red. this code works fine as is, but if I put:
if( ink_color == 0.8 ) instead of if( ink_color >= 0.6 )
or even
if( ink_color >= 0.65 ) instead of if( ink_color >= 0.6 )
if no longer works!!
how can this be? I can assure you, in my image editing software, the pixel 3,3 is 80% red. why is there a discrepancy between the amount of red? It gets that there is red here, but not 80/100… only works at about 62/100… why is this?!

shader_type spatial;
render_mode unshaded;

uniform sampler2D t1_albedo: source_color, repeat_enable;

float linearToSrgbF(float x)
{
	if(x <= 0.0)
		return 0.0;
	else if(x >= 1.0)
		return 1.0;
	else if(x < 0.0031308)
		return x * 12.92;
	else
		return pow(x, 1.0 / 2.4) * 1.055 - 0.055;
}

void fragment() {
	ivec2 cell = ivec2( 3, 3 );
	float ink_color = texelFetch(t1_albedo, cell, 0).r;
	
	ink_color = linearToSrgbF(ink_color);
	
	ALBEDO = vec3 (0.0,0.0,0.5);
	if( ink_color >= 0.6 )
		ALBEDO = vec3 (1.0,0.0,0.0);
}

Also, check if you specified the texel coordinates correctly, because the counting apparently starts from (0, 0)

1 Like

what you set isnt working… I see red, which seems good but…
when I put ivec2 cell = ivec2( 0, 3 ); i still see red and I know that pixel 3,3 is the only get (80%) pixel.
in fact… not matter WHAT I set cell = to I get red.
so something is wrong.

imageMap

here is the image texture I am using if it helps…

here is the image texture I am using if it helps…

Try switching the compress mode to lossless.

Check your texture channels in the editor. There should be red, green, blue.

I also noticed that in compatibility mode the linearToSrgbF() does not seem to be needed.

what is compatibility mode?

Have you solved your problem? If not, can you zip your project and upload it for me to check?

yes it sort of works, but it’s still not 100% precise. I think it is because the RGB values are in 255 and not 100.
therefore, in paint program, setting a pixel to 2/100 = 5.1/255.
but we cannot have fraction, so the files is saved to a PNG/JPG or wtv as 5/255 (2 digit hex)
then, when read by the shader, 5/255 = 1.9/100. if we are expecting 2, there will be issues of precision.

To solve this I simply rounded a bit and skipped some values. so I rounded to the nearest 4/100 basically, giving a margin of error. It’s not ideal but it works. I need precise values because I will be using the image as a reference. so each pixel will be replaced with another color, or texture, mapping out a system to swap parts of my mesh. Sort of an atlas system.

Im also not sure why I need the linearToSrgbF(float x) function and what is compatibility mode as I never use this…
I am not very experienced with shaders, but have written code in the past using 2D imaging software, to project 3D meshes onto a 2D canvas and never had this issue. I could load textures and get each pixel and make the manipulations I wanted.
Im a bit worried about perfomance issues, with these extra steps using linearToSrgbF and other calculations. It just seems like a relatively simple thing I am trying to do. make a mesh, cut it into sections and slap on varying textures on different regions, like an atlas…

I tried also using an int array instead of a sample2D:
uniform int mapData[15];

But this did not work, because I want to use this system to create meshes and slap texture atlas on them. I want this to be flexible to be put on any mesh and the shader int array is not dynamically resizable so the same shader could not work for different meshes of varying sizes. So I chose to continue on with a texture 2D and this seems to work.

here is a picture of the result so far using this system I am making:

my code so far:


shader_type spatial;
render_mode unshaded;

uniform sampler2D mapData: source_color;
uniform sampler2D tileSet : source_color,repeat_disable,filter_nearest;
//not used for now, but evnetually can be used to cut tileSet into proper size
uniform ivec2 tileSize = ivec2 (16,16);

float linearToSrgbF(float x)
{
	if(x <= 0.0)
		return 0.0;
	else if(x >= 1.0)
		return 1.0;
	else if(x < 0.0031308)
		return x * 12.92;
	else
		return pow(x, 1.0 / 2.4) * 1.055 - 0.055;
}

void fragment() {
	
         //getting data from data image///////////////////////////////////////////////////////
        //map size: the size of our data image//
	ivec2 mSize = textureSize(mapData, 0);
        //the cell position from UV. We basically break UV coords down according to mapData size
	ivec2 pos = ivec2( int(UV.x*float(mSize.x)), int(UV.y*float(mSize.y)) );
        //now we can get the color saved in this cell, from our mapData image
	float data = linearToSrgbF(texelFetch(mapData, pos, 0).r);
	
	//getting texture from atlas (tileset Image) //////////////////////////////////////////////////
	
        //tileSet (atlas) size
	ivec2 tSize = textureSize(tileSet, 0);
	//we get the ratio (divides our atlas texture into 16x16 pieces, can be changed later for higher resolution atlas blocks) -> eventually use the tileSize variable instead
	vec2 ratio = vec2( 16.0/float(tSize.x), 16.0/float(tSize.y) );

	//we get value from mapData and round it (to adjust precision issue)
	float val = floor(10.0*(data) ); //-> round every 10/100 of r value
	val = floor(20.0*(data) ); //-> round every 5/100 of r value
        //in my case, the tileSet image I am using is 64 pixels wide, which is 4 blocks of 16x16 so I am iterating by 4 for x,y coords.  -> eventually calculate using the tileSize variable instead (tSize.x / tileSize.x )
	float x = float(int(val)%4);
	float y = floor(val/4.0);
	//
	offset = vec2( x, y);
	
	//we get map position -> using UV values, we compute the position on the atlas image we want to see, then offset by offset, which moves our atlas to the poper block, according to the data from mapPos image:
	vec2 mapPos = vec2( ratio.x*(UV.x*float(mSize.x)+offset.x)-ratio.x*floor(UV.x*float(mSize.x)),  ratio.y*(UV.y*float(mSize.y)+offset.y)-ratio.y*floor(UV.y*float(mSize.y)) );

	//we are done! get the rgb value of tileSet (atlas) image and apply it!
	vec4 albedo_tex = texture(tileSet,mapPos);
	ALBEDO = albedo_tex.rgb;
}

let me know what you think and if there is a more efficient way to do this! thanks!

here are the tileSet and mapData images I am using. TileSet is the atlas and mapData is whatever you want it to be. a black image with red pixels of varying r values. different r value = a different atlas image.

GrassSet
mapTest

also the mesh I am working on is a plane, made of 10x10 squares. the UVs are broken down so that the whole plane can put a full image on it. so each square is 1/10 of the UV texture.
You can easily make this in blender, but I just made it through code using the SurfaceTool function in Godot.

Perhaps you could try creating your ‘data’ texture in Godot using ‘Image.create_from_data()’. Setting the format to ’ FORMAT_R8’. At least this way you might be able to be more confident of the values in the texture, identify whether the problem is there or in the shader.

1 Like

thanks, I will try this