No way to make Draw functions to not have overlaping transparency/opacity

this also goes for RenderingServer as well by proxy

Hi! I’ve been going mad trying to find a way to make a way for canvas items to share a uniform alpha value like how the CanvasGroups does. but without the CanvasGroup Node so it can be used on multiple things like bullets without tanking the performance on
everything.

i was wanting to achieve a similar effect to the fading animation used in arras.io where muliple moving parts fade as one whole part
Screenshot 2025-09-12 170021

i’ve tried Rendering server & draw() to only realize there really just isn’t a way to make alpha not blend & overlap…

so I’ve finally came here for some final shreads of hope that maybe im wrong or where else to look.

Code & screenshots

RenderServer attempt
Screenshot 2025-09-11 200848

extends Node2D

var sprite_thingy : RID

@export var debug_tex : Texture2D

func _ready() -> void:
	var rs = RenderingServer
	sprite_thingy = rs.canvas_item_create()
	rs.canvas_item_set_parent(sprite_thingy, get_canvas_item())
	rs.canvas_item_add_circle(sprite_thingy, Vector2.ZERO, 200, Color.BLACK)
	rs.canvas_item_add_texture_rect(sprite_thingy, Rect2(Vector2(100, 0), Vector2(200, 200)), debug_tex)
	rs.canvas_item_set_self_modulate(sprite_thingy, Color(0, 0, 0, 0.5))

Draw() attempt
Screenshot 2025-09-13 170418

extends Node2D

var sprite_thingy : RID

@export var debug_tex : Texture2D

func _draw() -> void:
	draw_circle(position, 200, Color.BLACK)
	draw_texture_rect(debug_tex, Rect2(Vector2(100, 0), Vector2(200, 200)), false)


It’s not possible to do it without a CanvasGroup node or using the same mechanism the CanvasGroup node uses internally.

Here’s your RenderingServer example fixed doing that:

@tool
extends Node2D

var sprite_thingy : RID

@export var debug_tex : Texture2D:
	set(value):
		debug_tex = value
		queue_redraw()

func _ready() -> void:
	var rs = RenderingServer
	rs.canvas_item_set_canvas_group_mode(get_canvas_item(), RenderingServer.CANVAS_GROUP_MODE_TRANSPARENT, 10, 10, false)
	sprite_thingy = rs.canvas_item_create()
	rs.canvas_item_set_parent(sprite_thingy, get_canvas_item())
	self_modulate.a = 0.5


func _draw() -> void:
	var rs = RenderingServer
	rs.canvas_item_clear(sprite_thingy)
	rs.canvas_item_add_circle(sprite_thingy, Vector2.ZERO, 200, Color.BLACK)
	rs.canvas_item_add_texture_rect(sprite_thingy, Rect2(Vector2(100, 0), Vector2(200, 200)), debug_tex.get_rid())


func _notification(what: int) -> void:
	if what == NOTIFICATION_PREDELETE:
		RenderingServer.free_rid(sprite_thingy)

Result:

2 Likes

Thank you very much!

i’ve been wasting my week scrambling for solutions to my oddly specific issue. although it still requires a Node2d per thing to work. i made a re-post on github and got recommended an interesting idea to make a GDShader that draws the extra layers in the shader then does the transparency.

anyway thank you very much for taking time out of your day for my issue!

You don’t need a Node2D per thing for this to work. You can create them in the same script. Example:

extends Node2D

var rids = []
var sprite_data = []

func _ready() -> void:
	var rs = RenderingServer
	var noise = FastNoiseLite.new()
	noise.seed = randi()
	var texture = preload("res://icon.svg")
	for x in 100:
		for y in 100:
			var parent = rs.canvas_item_create()
			var child = rs.canvas_item_create()
			rs.canvas_item_set_parent(parent, get_canvas_item())
			rs.canvas_item_set_z_index(parent, 0)
			rs.canvas_item_set_parent(child, parent)
			rs.canvas_item_set_canvas_group_mode(parent, RenderingServer.CANVAS_GROUP_MODE_TRANSPARENT, 5, true)
			rs.canvas_item_set_self_modulate(parent, Color(1, 1, 1, remap(noise.get_noise_2d(x, y), -1, 1, 0.1, 0.9)))
			var transform = Transform2D(randf_range(0, PI), Vector2.ONE, 0, Vector2(x * 200, y * 200))
			rs.canvas_item_set_transform(parent, transform)
			var r = randi_range(100, 200)
			rs.canvas_item_add_circle(child, Vector2.ZERO, r, Color(randf(), randf(), randf(), 1.0))
			rs.canvas_item_set_transform(child, Transform2D(0, Vector2(0.5, 0.5), 0, Vector2.ZERO))
			rs.canvas_item_add_texture_rect(child, Rect2(randi_range(-r, r) / 2, randi_range(-r, r) / 2, texture.get_width(), texture.get_height()), texture.get_rid())

			sprite_data.push_back({sprite = parent, transform = transform})
			rids.push_back(parent)
			rids.push_back(child)


func _process(delta: float) -> void:
	for data in sprite_data:
		var t = Transform2D(data.transform)
		t.origin += Vector2(randf_range(-10, 10), randf_range(-10, 10))
		RenderingServer.canvas_item_set_transform(data.sprite, t)


func _notification(what: int) -> void:
	if what == NOTIFICATION_PREDELETE:
		for rid in rids:
			RenderingServer.free_rid(rid)

Result:

Canvas items outside of the viewport will be automatically culled but performance will still be bad with a lot of them on screen though.

A shader will work and will probably be faster but it will be harder to write.

2 Likes

sorry for the long reply but i can’t say this enough but thank you for all of this!

I very slightly edited (literally just changing color and making modulate alpha random) your example code and got these results which clearly show more unique alpha values while still acting as whole.

101% gonna be crediting if i end up doing something with this.

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.