How to optimize getting pixel color using raycasting for Compatible renderer

Godot Version

4.4.1

Question

Hi everyone,
I followed a tutorial and make a function to get color of a pixel using ray-casting method. The result came very quick in Forward+ renderer, but in Compatible, it runs very slow , around 3~4 seconds.

My plan is making a webgame , the map is a big bmp file, and this is not very good. Is there any solution?

func _on_player_province_selected(coord) -> void:
	var province_color = province_map.get_image().get_pixel(coord.x*10, coord.y*10)
	var selected_prov = $Provinces.color2province[province_color]
	print(selected_prov)
	pass

func shoot_ray():
	var mouse_pos = get_viewport().get_mouse_position()
	var ray_len = 2000
	var from = camera.project_ray_origin(mouse_pos)
	var to = from + camera.project_ray_normal(mouse_pos)*ray_len
	var space = get_world_3d().direct_space_state
	var ray_query = PhysicsRayQueryParameters3D.new()
	ray_query.from = from
	ray_query.to = to
	var raycast_res = space.intersect_ray(ray_query)
	
	if !(raycast_res.is_empty()):
		var rx = raycast_res.position.x
		#var ry = raycast_res.position.y
		var rz = raycast_res.position.z
		print("FOUND")
		province_selected.emit(Vector2(rx,rz))

(these two functions are separated in each files)

How often are you making this raycast? Though I don’t think it should have a noticeable impact on performance even if you are making it every frame. Maybe it is because of the get_pixel call?

I called the raycast every time i clicked on the screen

func _unhandled_input(event: InputEvent) -> void:
	if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT and event.is_pressed():
		shoot_ray()

about get_pixel, yeah i tested it out and it is the problem

Would it be faster if you fetched the image data at the start and just accessed it that way instead of using get_pixel?

I don’t understand what you mean. Can you tell me more about it?

If you use get_data() you can a PackedByteArray of the map data. Checking that may be faster than calling get_pixel().

So if I am not wrong, I’m going to extract colors from PackedByteArray? If so, how can I do that? I checked out the docs and there are no color properties kek

PackedByteArray is a list of the colors. It’s just they are in byte form instead of Color form.

I made a array :

@onready var packed_data
# Called when the node enters the scene tree for the first time.
func _ready() -> void:
	packed_data = PackedByteArray(province_map.get_image().get_data())
	print(packed_data)
	pass

and got a PackedByteArray like this:
[0, 29, 38, 0, 29, 38, 0, 29, 38, 0, 29, 38, 0, 29, 38, 0, 29, 38, 0, 29, 38, 0, 29, 38, 0, 29, 38, 0, 29, 38, 0, 29, 38, 0, 29, 38, 0, 29, 38, 0, 29, 38, 0, 29, 38, 0, 29, 38, 0, 29, 38, 0, 29, 38, 0, 29, 38, 0, 29, 38, 0, 29, 38, 0, 29, 38, 0, 29, 38, 0, 29, 38, 0, 29, 38, 0, 29, 38, 0, 29, 38, 0, 29, 38, 0, 29, 38, 0, 29, 38, 0, 29, 38, 0, 29, 38, 0, 29, 38, 0, 29, 38, 0, 29, 38, 0, 29, 38, 0, 29, 38, 0, 29, 38, 0, 29, 38, 0, 29, 38, 0, 29, 38, 0, 29, 38, 0, 29, 38, 0, 29, 38, 0, 29, 38, 0, 29, 38, 0, 29, 38, 0, 29, 38, 0, 29, 38, 0, 29, 38, 0, 29, 38, 0, 29, 38, 0, 29, 38, 0, 29, 38, 0, 29, 38, 0, 29, 38, 0, 29, 38, 0, 29, 38, 0, 29, 38, 0, 29, 38, 0, 29, 38, 0, 29, 38, 0, 29, 38, 0, 29, 38, 0, 29, 38, 0, 29, 38, 0, 29, 38, 0, 29, 38, 0, 29, 38, 0, 29, 38, 0, 29, 38, 0, 29, 38, 0, 29, 38, 0, 29, 38, 0, 29, 38, 0, 29, 38, 0, 29, 38, 0, 29, 38, 0, 29, 38, 0, 29, 38, 0, 29, 38, 0, 29, 38, 0, 29, 38, 0, 29, 38, 0, 29, 38, 0, 29, 38, 19, 34, 38, 19, 34, 38, 19, 34, 38, 19, 34, 38, 19, 34, 38, 19, 34, 38, 19, 34, 38, 19, 34, 38, 19, 34, 38, 19, 34, 38, 19, 34, 38, 19, 34, 38, 19, 34, 38, 19, 34, 38, 19, 34, 38, 19, 34, 38, 19, 34, 38, 19, 34, 38, 19, 34, 38, 19, 34, 38, 19, 34, 38, 19, 34, 38, 19, 34, 38, 19, 34, 38, 19, 34, 38, 19, 34, 38, 19, 34, 38, 19, 34, 38, 19, 34, 38, 19, 34, 38, 19, 34, 38, 19, 34, 38, 19, 34, 38, 19, 34, 38, 19, 34, 38, 19, 34, 38, 19, 34, 38, 19, 34, 38, 19, 34, 38, 19, 34, 38, 19, 34, 38, 19, 34, 38, 19, 34, 38, 19, 34, 38, 19, 34, 38, 19, 34, 38, 19, 34, 38, 19, 34, 38, 19, 34, 38, 19, 34, 38, 19, 34, 38, 19, 34, 38, 19, 34, 38, 19, 34, 38, 19, 34, 38, 19, 34, 38,[...]

But how can I access the byte to get the color? These are my attempts but nothing worked

var province_color = Color(packed_data[coord.x*10][coord.y*10])

and

var n = (coord.y *10 * province_map.get_width()) + coord.x *10
var province_color = Color(packed_data[n][n+1][n+2])

But I always got an error
image

I dont know how to correctly get the color

Me neither, but moving get_image() to @onready allready gives a nice boost, without fiddling with bytes:

extends TextureRect
@onready var map_img = texture.get_image()
@onready var mat = get_material()

func _on_gui_input(event):
	if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT and event.is_pressed():
		var mouse_pos = get_local_mouse_position()
		var color = map_img.get_pixel(mouse_pos.x, mouse_pos.y)
		mat.set_shader_parameter("highlight_color", color);

This is a test I made with a TextureRect. I am also sending the pixel color to a custom shader for highlighting the map region.

1 Like

Yeah maybe tassup’s solution works better, I guess getting the image is what takes longer than the bytes.

But if you want to know how to access the color, I think it’s something like this: the pixels are all listed in left to right, top to bottom order. Each number (byte) is a component of the color, and it goes in RGBA order. So you need to check four bytes at once to get the color.

2 Likes

I came up using tassup’s method, and it does very well! I thought it was get_pixel problem, but that is on get_image instead.

thanks you two for helping me!

Here are the way i boosted the process, in case someone needs:

Change from this:
var province_color = province_map.get_image().get_pixel(coord.x*10, coord.y*10) to

@onready var province_image = province_map.get_image()
var province_color = province_image.get_pixel(coord.x*10, coord.y*10)
2 Likes