Finding a color in a texture.

I’m doing the PirateSoftware Game Jam 16 in Godot. It became convenient to have a function for my game that:

Detects if a color (within some tolerance) exists in a texture.

I don’t care where it is, or how much of it is in there, just a boolean.

It just seems like one of those things computers would be good at. However, I ran into a few limitations.

  1. The texture is a ViewportTexture, and has a size that scales with the size of the main window. So an exhaustive search is too slow (yes, I tried). Actually, the main reason this is slow is because of how much it costs to copy the texture from the GPU to the CPU for the exhaustive search.
  2. Rule 5 of the submission says “Your submission must run in the browser.” That means we’re working with the Compatibility renderer, which does not support compute shaders.
  3. Textures don’t behave like I thought they did.

So, and I don’t know why I thought this, but I thought that when a texture shrinks, the filtering would have an additive effect on the alpha channel. So, if I first masked the texture through a second viewport, I could use a shader to filter for the target color using the alpha channel, then shrink the texture to a single pixel and read the resulting alpha. This does not work.

I’m able to get pretty good performance using this approach: instead shrinking the texture to a more manageable amount, followed by an exhaustive search. This mostly fixes the load on the GPU to CPU memory transfer and works pretty well. But, since texture shrinking doesn’t work like I thought, the quality of the detection also suffers from the lower resolution.

This is my first time posting here, do let me know if I’m doing it wrong.

Can you give more detail on what is the final effect you’re trying to achieve?

1 Like

I just want to tell if a texture has a certain color in it. Sleeping on it, I see the problem with what I was doing.

I was decreasing the resolution of the texture by feeding it to a TextureRect beneath a low resolution SubViewport. Naturally, this has a downsampling effect, rather than a “shrinking” of any kind.

To counteract this, I added a box-blur pass to the rect under the downsamping viewport. The box blur has a natural additive effect like what I was looking for. It’s a little clunky, but the result is a script that tells me if a texture contains a certain color with a much lower than expected performance impact.

Overall, it only adds a couple kilobytes per frame tops of GPU transfer because the downsampling is so aggressive. It could use a lot more tuning (like lining up the box blur radius to match the downsampling ratio to get a more precise measurement), but I quit the game jam to prioritize my health :sweat_smile: and the basic system works. Overall, it seems like a good approach to achieve a “shrinking” effect live. I can add a minimal project file later if that sounds helpful, but my hands are pretty full right now.

I shouldn’t mark this as a solution because I don’t have that project file yet and it’s a pretty janky approach.

well one way I know is iterate over all pixels of image in:

func test_if_color_in_threshold(image:Image)->bool:
	var min_thd:=0.5
	var max_thd:=0.6
	for x in image.get_width(): for y in image.get_height():
		var color:=image.get_pixel(x,y)
		if color.r >= min_thd and color.r <= max_thd: return true
	return false

this code for red channel

1 Like

I tried this, but the bottleneck is copying the texture from vram to to regular memory.

Since I’m working eith textures, I have to convert them to an image in this way to work with them from cpu, which is really slow.

Of course, the iterations are also a bottleneck.

I did try this approach. It dropped my fps to about 15 on my laptop when working on a full-screen viewport texture. I’m running this every frame, and had a web target, so the latency is not acceptable. Though, in general, this is a surprisingly effective approach, especially if it could maybe run as a coroutine at a reduced framerate.

its possible to use multithreading if make image division /4 and make use of WorkerThreadPool class to check only 1/4 image for one worker. It should work as it doesn’t a Node that tightly tied to main thread.

you may just check only pixel group for instance make steps that checks 1 pixel every 4 pixels in range(0, image.get_width(), 4) if precision isn’t required