- i’ll paste the relevant code below
- I wanted a lighting system that works per quarter of tiles (my tiles are 16x16) that also included the ability to have partial shadows from shadowcasters. The system is composed of “light nodes” extended from the 2D node class and of a single lighting module extended from a Sprite2D. The lightnodes sends their data (position, intensity, light color, …) when needed to the module. The module is just Sprite2D overlayed on the screen and blended with the image under. each frame the module proceeds as i described in my original message.
- sorry, that was unclear. I mean that i have a previous iteration of coding that system in the same version of godot, it worked similarly but it wasn’t paralellized well so sluggish. I’ll send it in another message
```
class_name CustomLight
extends Node2D
@onready var player : Player = self.get_tree().current_scene.get_node(“player”) #to check distance to rendering windo@onready
@onready var Light_module : LightModule = self.get_tree().current_scene.get_node(“LightModule”)
var screen_resolution : Vector2i = GlobalValues.screen_resolution
var screen_tile_width: int = GlobalValues.screen_tile_width
var screen_tile_height: int = GlobalValues.screen_tile_height
var work_group_width : int = GlobalValues.work_group_width
var work_group_height : int = GlobalValues.work_group_height
#—variables@onready
@onready var player_coordinates : Vector2i = floor(player.global_position/8) #tile player_coordinates
var light_module_index : int
#—light parame@exporters—
@export var light_direction : Ve@export_rangetor2
@export_range(0, 3.64, 0.01) var light_angle : float = 3.64 #angle of the con@export of light
@export var light_tint : Color = Color(1.0, 1.0, 1.0, 1.0) #color picker, third v@export_rangelue is ignored
@export_range(0, 50, 0.1) var light_intens@export_rangety : float = 1
@export_range(0, 1, 0.01) var light_constant_falloff : float = 1@export_range#falloff of the light
@export_range(0, 0.04, 0.0001) var light_linear_falloff : float @export_range 0.005 #falloff of the light
@export_range(0, 0.001, 0.000001) var light_quadratic_falloff : fl@onreadyat = 0.00005 #falloff of the light
@onready var light_color : Vector4 = Vector4(light_tint.r, light_tint.g, light_tint.b, light_intensity) #actual vector passed to the shaders
func _ready() → void:
Light_module.add_light_node(self)
#func _physics_process(delta: float) → void:
#player_coordinates = floor(player.global_position/8)
#func _process(delta: float) → void:
#if (self.global_position - player.global_position).x > screen_resolution.x/2 || (self.global_position - player.global_position).y > screen_resolution.y/2:
#light_angle
#light_direction = 2*(1+sin(Engine.get_frames_drawn()/70.))
#light_color.w = light_intensity
#if = 2*(1+sin(Engine.get_frames_drawn()/70.))
#light_direction = light_direction.rotated(0.01)
#light_direction = light_direction.normalized()
#if Engine.get_frames_drawn() %100 ==0:
#light_color = Vector4(randf(), randf(), randf(), light_intensity)
#l@onreadyght_module.colors[self] = light_color
func _exit_tree() → void:
Light_module.remove_light_node(light_module_index)
```
and
```
´@onready´
class_name LightModule
extends Sprite2D
@onready var player @onready Player = self.get_tree().current_scene.get_node(“player”) #access to the player (to check distance to rendering window)
@onready var player_coordinates : Vector2i #tile player_coordinates
@onready var occluder_grabber : Area2D = self.get_node(“occluder_grabber”)
#—screen parameters—
var image_data : PackedByteArray
var screen_resolution : Vector2i = GlobalValues.screen_resolution
var screen_tile_width: int = GlobalValues.screen_tile_width
var screen_tile_height: int = GlobalValues.screen_tile_height
var work_group_width : int = GlobalValues.work_group_width
var work_group_height : int = GlobalValues.work_group_height
var half_screen : Vector2 = -Vector2(screen_tile_width, screen_tile_height)*4
var image_pass : Image @export Image.create_empty(screen_tile_width,screen_tile@export_rangeheight,false,Image.FORMAT_RGBAF)
var image_texture: Ima@onreadyeTexture = ImageTexture.create_from_image(image_pass)
#—shader parameters—
@export var ambient_light_tint : Color = Color(1,1,1,1)
@export_range(0, 50, 0.1) var ambient_light_intensity : float = 0.0
@onready var ambient_light_color : Vector4 = Vector4(ambient_light_tint.r, ambient_light_tint.g, ambient_light_tint.b, ambient_light_intensity) #actual vector passed to the shaders
var light_array : Array[CustomLight] #useless i think
var direction_array : PackedFloat32Array #stores Vector2
var angle_array : PackedFloat32Array
var color_array : PackedFloat32Array #stores Vector4
var falloff_factor_array : PackedFloat32Array
var position_array : PackedFloat32Array
var occluder_opacity_array : PackedFloat32Array
var occluder_size_array : PackedInt32Array
var triangularized_occluder_array : PackedFloat32Array
var occluder_nb : int
var occluders_total_size : int
#—shaders, pipelines, uniform sets—
var rd : RenderingDevice = GlobalValues.rd
var cast_shadow_shader_file := load(“res://shaders/CustomLight_V3_cast_shadow_shader.glsl”) #load GLSL shader
var cast_shadow_shader : RID = rd.shader_create_from_spirv(cast_shadow_shader_file.get_spirv()) #creates the shader instance from the spirV, thanks to the rendering device
var cast_shadow_pipeline : RID = rd.compute_pipeline_create(cast_shadow_shader) # Create a compute pipeline
var cast_shadow_uniform_set : RID
var shader_file := load(“res://shaders/CustomLight_V3_compute_shader.glsl”) #load GLSL shader
var shader : RID = rd.shader_create_from_spirv(shader_file.get_spirv()) #creates the shader instance from the spirV, thanks to the rendering device
var pipeline : RID = rd.compute_pipeline_create(shader) # Create a compute pipeline
var compute_list : int
var uniform_set : RID
var output_file := load(“res://shaders/CustomLight_V4_output_shader.glsl”) #load GLSL shader
var output_shader : RID = rd.shader_create_from_spirv(output_file.get_spirv()) #creates the shader instance from the spirV, thanks to the rendering device
var output_pipeline : RID = rd.compute_pipeline_create(output_shader) # Create a compute pipeline
var output_uniform_set : RID
#—bytes, buffers & uniforms—
var input_bytes : PackedByteArray #stores a collection of input data
var input_buffer : RID
var input_uniform : RDUniform
var direction_bytes : PackedByteArray #stores the light_directions
var direction_buffer : RID
var direction_uniform : RDUniform
var angle_bytes : PackedByteArray #stores the light_angles
var angle_buffer : RID
var angle_uniform : RDUniform
var color_bytes : PackedByteArray #stores the light_colors
var color_buffer : RID
var color_uniform : RDUniform
var falloff_factor_bytes : PackedByteArray #stores the light_falloff factors
var falloff_factor_buffer : RID
var falloff_factor_uniform : RDUniform
var position_bytes : PackedByteArray #stores the light position
var position_buffer : RID
var position_uniform : RDUniform
var triangularized_occluder_bytes : PackedByteArray #stores the triangularized occluders
var triangularized_occluder_buffer : RID
var triangularized_occluder_uniform : RDUniform
var cast_shadow_bytes : PackedByteArray #stores the cast shadows
var cast_shadow_buffer : RID
var cast_shadow_uniform : RDUniform
var occluder_bytes : PackedByteArray #stores the occluder polygon data as spawned by the chunks
var occluder_buffer : RID
var occluder_uniform : RDUniform
var occluder_size_bytes : PackedByteArray #stores the sizes of successive occluders
var occluder_size_buffer : RID
var occluder_size_uniform : RDUniform
var occluder_opacity_bytes : PackedByteArray #stores the opacity of the occluders
var occluder_opacity_buffer : RID
var occluder_opacity_uniform : RDUniform
var output_bytes : PackedByteArray #stores the output
var output_buffer : RID
var output_uniform : RDUniform
var ambient_bytes : PackedByteArray
var ambient_buffer : RID
var ambient_uniform : RDUniform
func _ready()->void:
self.scale = Vector2(8,8)
self.texture = image_texture
self.visible = false
self.get_child(0).position = Vector2(screen_tile_width/2, screen_tile_height/2)
output_bytes.resize(4screen_tile_widthscreen_tile_height*4)
output_buffer = rd.storage_buffer_create(output_bytes.size(),output_bytes)
global_position = half_screen
new_global_position = half_screen
print(global_position)
print(new_global_position)
func _physics_process(delta: float) → void:
pass
var new_global_position : Vector2
var int_pos : Vector2i
##input: /
##function: processes all the light nodes with shaders. Cast shadows are first calculated then the intensity per pixel
func _process(delta: float) → void:
if (Engine.get_frames_drawn()%120 == 0):
print(“\nnew change incoming:”)
print("coordinates: ", global_position)
if (Engine.get_frames_drawn()%120 == 1):
global_position = new_global_position
new_global_position += Vector2(8,8)
print("coordinates: ", global_position)
if (Engine.get_frames_drawn()%120 == 2):
print("coordinates: ", global_position)
int_pos = global_position.snapped(Vector2i(1,1))
print(“int_post:”, int_pos )
#global_position = half_screen + snapped(player.global_position,Vector2(8,8))
#var time_ms0 = Time.get_ticks_usec()
#var time_ms = Time.get_ticks_usec()
if(player.global_position.y>0):
ambient_light_color.w = ambient_light_intensity*max(0,1-player.global_position.y/600.0)
for i in range(light_array.size()):
position_array[2*i] = light_array[i].global_position.x
position_array[2*i+1] = light_array[i].global_position.y
input_bytes = PackedInt32Array([int_pos.x, int_pos.y, screen_tile_width, screen_tile_height]).to_byte_array()
direction_bytes = direction_array.to_byte_array()
angle_bytes = angle_array.to_byte_array()
color_bytes = color_array.to_byte_array()
falloff_factor_bytes = falloff_factor_array.to_byte_array()
position_bytes = position_array.to_byte_array()
triangularized_occluder_bytes = PackedInt32Array([triangularized_occluder_array.size()/2, occluders_total_size*2*4]).to_byte_array() #t_o_vertex_nb, cast_shadow_vertex_nb
triangularized_occluder_bytes.append_array(triangularized_occluder_array.to_byte_array())
occluder_bytes = get_occluders().to_byte_array()
cast_shadow_bytes.resize(light_array.size() * occluders_total_size*4*2*4*4 ) #4 bytes per float32, 2 triangles per side, 4 vertex per triangle (counting opacity), 4 float32 per vertex (vec3 take vec4 size)
#print("OccluderGet + some stuff: ", float(Time.get_ticks_usec() -time_ms)/1000)
#time_ms = Time.get_ticks_usec()
occluder_size_bytes = PackedInt32Array([light_array.size(), occluders_total_size*2*4, occluder_nb, 0]).to_byte_array() #light nb, cast_shadow_vertex_nb, occluder_nb, padding
occluder_size_bytes.append_array(occluder_size_array.to_byte_array())
occluder_opacity_bytes = occluder_opacity_array.to_byte_array()
ambient_bytes = PackedFloat32Array([ambient_light_color.x, ambient_light_color.y, ambient_light_color.z, ambient_light_color.w]).to_byte_array()
input_buffer = rd.storage_buffer_create(input_bytes.size(), input_bytes)
direction_buffer = rd.storage_buffer_create(direction_bytes.size(), direction_bytes)
angle_buffer = rd.storage_buffer_create(angle_bytes.size(), angle_bytes)
color_buffer = rd.storage_buffer_create(color_bytes.size(), color_bytes)
falloff_factor_buffer = rd.storage_buffer_create(falloff_factor_bytes.size(), falloff_factor_bytes)
position_buffer = rd.storage_buffer_create(position_bytes.size(), position_bytes)
triangularized_occluder_buffer = rd.storage_buffer_create(triangularized_occluder_bytes.size(), triangularized_occluder_bytes)
cast_shadow_buffer = rd.storage_buffer_create(cast_shadow_bytes.size(), cast_shadow_bytes)
occluder_buffer = rd.storage_buffer_create(occluder_bytes.size(), occluder_bytes)
occluder_size_buffer = rd.storage_buffer_create(occluder_size_bytes.size(), occluder_size_bytes)
occluder_opacity_buffer = rd.storage_buffer_create(occluder_opacity_bytes.size(), occluder_opacity_bytes)
ambient_buffer = rd.storage_buffer_create(ambient_bytes.size(),ambient_bytes)
input_uniform = RDUniform.new()
input_uniform.uniform_type = RenderingDevice.UNIFORM_TYPE_STORAGE_BUFFER
input_uniform.binding = 0
input_uniform.add_id(input_buffer)
direction_uniform = RDUniform.new()
direction_uniform.uniform_type = RenderingDevice.UNIFORM_TYPE_STORAGE_BUFFER
direction_uniform.binding = 1
direction_uniform.add_id(direction_buffer)
angle_uniform = RDUniform.new()
angle_uniform.uniform_type = RenderingDevice.UNIFORM_TYPE_STORAGE_BUFFER
angle_uniform.binding = 2
angle_uniform.add_id(angle_buffer)
color_uniform = RDUniform.new()
color_uniform.uniform_type = RenderingDevice.UNIFORM_TYPE_STORAGE_BUFFER
color_uniform.binding = 3
color_uniform.add_id(color_buffer)
falloff_factor_uniform = RDUniform.new()
falloff_factor_uniform.uniform_type = RenderingDevice.UNIFORM_TYPE_STORAGE_BUFFER
falloff_factor_uniform.binding = 4
falloff_factor_uniform.add_id(falloff_factor_buffer)
position_uniform = RDUniform.new()
position_uniform.uniform_type = RenderingDevice.UNIFORM_TYPE_STORAGE_BUFFER
position_uniform.binding = 5
position_uniform.add_id(position_buffer)
triangularized_occluder_uniform = RDUniform.new()
triangularized_occluder_uniform.uniform_type = RenderingDevice.UNIFORM_TYPE_STORAGE_BUFFER
triangularized_occluder_uniform.binding = 6
triangularized_occluder_uniform.add_id(triangularized_occluder_buffer)
cast_shadow_uniform = RDUniform.new()
cast_shadow_uniform.uniform_type = RenderingDevice.UNIFORM_TYPE_STORAGE_BUFFER
cast_shadow_uniform.binding = 7
cast_shadow_uniform.add_id(cast_shadow_buffer)
output_uniform = RDUniform.new()
output_uniform.uniform_type = RenderingDevice.UNIFORM_TYPE_STORAGE_BUFFER
output_uniform.binding = 8
output_uniform.add_id(output_buffer)
occluder_uniform = RDUniform.new()
occluder_uniform.uniform_type = RenderingDevice.UNIFORM_TYPE_STORAGE_BUFFER
occluder_uniform.binding = 9
occluder_uniform.add_id(occluder_buffer)
occluder_size_uniform = RDUniform.new()
occluder_size_uniform.uniform_type = RenderingDevice.UNIFORM_TYPE_STORAGE_BUFFER
occluder_size_uniform.binding = 10
occluder_size_uniform.add_id(occluder_size_buffer)
occluder_opacity_uniform = RDUniform.new()
occluder_opacity_uniform.uniform_type = RenderingDevice.UNIFORM_TYPE_STORAGE_BUFFER
occluder_opacity_uniform.binding = 11
occluder_opacity_uniform.add_id(occluder_opacity_buffer)
ambient_uniform = RDUniform.new()
ambient_uniform.uniform_type = RenderingDevice.UNIFORM_TYPE_STORAGE_BUFFER
ambient_uniform.binding = 12
ambient_uniform.add_id(ambient_buffer)
uniform_set = rd.uniform_set_create([input_uniform, direction_uniform, angle_uniform, color_uniform, falloff_factor_uniform,
position_uniform, triangularized_occluder_uniform, cast_shadow_uniform, output_uniform], shader, 0)
cast_shadow_uniform_set = rd.uniform_set_create([position_uniform, cast_shadow_uniform, occluder_uniform, occluder_size_uniform,
occluder_opacity_uniform], cast_shadow_shader, 1)
output_uniform_set = rd.uniform_set_create([ambient_uniform, output_uniform], output_shader, 2)
#print("Uniforms + some buffers set up: ", float(Time.get_ticks_usec() -time_ms)/1000)
#time_ms = Time.get_ticks_usec()
var res : int = occluders_total_size/64 +1
compute_list = rd.compute_list_begin()
rd.compute_list_bind_compute_pipeline(compute_list, cast_shadow_pipeline)
rd.compute_list_bind_uniform_set(compute_list, cast_shadow_uniform_set, 1)
rd.compute_list_dispatch(compute_list, res, light_array.size(), 1)
rd.compute_list_add_barrier(compute_list)
rd.compute_list_bind_compute_pipeline(compute_list, pipeline)
rd.compute_list_bind_uniform_set(compute_list, uniform_set, 0)
rd.compute_list_dispatch(compute_list, work_group_width*work_group_height/4, light_array.size(), 1) #TODO back to light light_array.size()
rd.compute_list_add_barrier(compute_list)
rd.compute_list_bind_compute_pipeline(compute_list, output_pipeline)
rd.compute_list_bind_uniform_set(compute_list, output_uniform_set, 2)
rd.compute_list_dispatch(compute_list, work_group_width*work_group_height, 1, 1)
rd.compute_list_end()
rd.submit()
rd.sync()
#print("Shader done: ", float(Time.get_ticks_usec() -time_ms)/1000)
#time_ms = Time.get_ticks_usec()
output_bytes = rd.buffer_get_data(output_buffer)
image_pass.set_data(screen_tile_width, screen_tile_height, false,Image.FORMAT_RGBAF, output_bytes)
image_texture.update(image_pass)
if (Engine.get_frames_drawn()%120 == 0 || Engine.get_frames_drawn()%120 == 1|| Engine.get_frames_drawn()%120 == 2):
print("passed coordinates: (", output_bytes.to_float32_array()[0], "," ,output_bytes.to_float32_array()[1], ")")
print("--synced--")
#print("grabbing the data: ", float(Time.get_ticks_usec() -time_ms)/1000)
#print("Total time taken in ms: ", float(Time.get_ticks_usec() -time_ms0)/1000, "\n")
#note: for the rasterizer see:
and for the cast shadows: https://www.gamedev.net/tutorials/_/technical/graphics-programming-and-theory/dynamic-2d-soft-shadows-r3065/
##input: /
##function: outputs polygons of occluders within range of the light source in the form [vertex_1.x, vertex_1.y, vertex_2.x, … ]. Also update related variables
func get_occluders()-> PackedFloat32Array:
var acc : int = 0
var occluders : PackedFloat32Array =
var overlapping_nodes := occluder_grabber.get_overlapping_bodies()
occluder_opacity_array.clear()
occluder_size_array.clear()
triangularized_occluder_array.clear()
for node in overlapping_nodes:
if node.get_script() != null && node.get_script().get_global_name() == “SemiTranspBody2D”:
occluder_opacity_array.append(node.transparency)
map_add_position(occluders, node.get_child(0)) #should that work? on some i do get_node(“hitbox”) but somehow here it’s not needed?
acc += node.get_child(0).polygon.size()
occluder_size_array.append(acc)
triangularize_polygon(node.global_position, node.get_child(0).polygon, node.transparency)
elif node.get_class() == “StaticBody2D”:
occluder_opacity_array.append(0)
map_add_position(occluders, node.get_child(0))
acc += node.get_child(0).polygon.size()
occluder_size_array.append(acc)
elif node.get_class() == “CharacterBody2D” && !(node == get_parent()): #TODO repair that, somehow it bugs out - smth to do with going too far in the array of triangles i think?
occluder_opacity_array.append(0)
map_add_position(occluders, node.get_node(“hitbox”))
acc += node.get_node(“hitbox”).polygon.size()
occluder_size_array.append(acc)
occluders_total_size = occluders.size()/2 # counts the total size of occluders to resize the array properly. equal to #input:
occluder_nb = overlapping_nodes.size()
return occluders
#input: /
##input: a node with a polygon data in PackedFloat32Array form and an array
##function: appends the polygons slid by the position of the node to the array
func map_add_position(input_array : PackedFloat32Array, node : Node2D) → void:
var return_array : PackedFloat32Array
return_array.resize(2node.polygon.size())
for i in range(0, node.polygon.size()):
return_array[2i] = node.polygon[i].x + node.global_position.x
return_array[2*i+1] = node.polygon[i].y + node.global_position.y
input_array.append_array(return_array)
#function: /
##input: a node with a polygon data
##function: outputs a triangularized polygon in an array in which triangles take the form [vertex1.x, vertex1.y, … vertex3.y, opacity, 0]
func triangularize_polygon(obj_position : Vector2, poly : PackedVector2Array, opacity : float) → void:
var triangulated_index = Geometry2D.triangulate_polygon(poly)
for i in range(triangulated_index.size()/3):
triangularized_occluder_array.append_array(PackedFloat32Array(
[obj_position.x + poly[triangulated_index[3i]].x, obj_position.y + poly[triangulated_index[3i]].y,
obj_position.x + poly[triangulated_index[3i+2]].x, obj_position.y + poly[triangulated_index[3i+2]].y,
obj_position.x + poly[triangulated_index[3i+1]].x, obj_position.y + poly[triangulated_index[3i+1]].y,
opacity, 0]
))
return
#note: /
##input: a light node to add to the list
##function: adds it then rebuild the light property arrays
func add_light_node(node : CustomLight) → void:
node.light_module_index = light_array.size()
light_array.append(node)
rebuild_light_arrays(light_array.size()-1)
#function: /
##input: the index of a light to remove from the list
##function: removes it then rebuild the light property arrays
func remove_light_node(index : int) → void:
light_array.pop_at(index)
for i in range(index, light_array.size()):
light_array[i].light_module_index = i
rebuild_light_arrays(index)
#input: /
##input: an index from which to start rebuilding the property arrays
##function: properly resize and fills the arrays
func rebuild_light_arrays(start_index : int = 0) → void:
position_array.resize(2light_array.size())
direction_array.resize(2light_array.size())
angle_array.resize(light_array.size())
color_array.resize(4light_array.size())
falloff_factor_array.resize(3light_array.size())
for i in range(start_index, light_array.size()):
position_array[2i] = light_array[i].global_position.x
position_array[2i+1] = light_array[i].global_position.y
direction_array[2i] = light_array[i].light_direction.x
direction_array[2i+1] = light_array[i].light_direction.y
angle_array[i] = light_array[i].light_angle
color_array[4i] = light_array[i].light_color.x
color_array[4i+1] = light_array[i].light_color.y
color_array[4i+2] = light_array[i].light_color.z
color_array[4i+3] = light_array[i].light_color.w
falloff_factor_array[3i] = light_array[i].light_constant_falloff
falloff_factor_array[3i+1] = light_array[i].light_linear_falloff
falloff_factor_array[3*i+2] = light_array[i].light_quadratic_falloff
#note: /
##function: the index of a light node and an integer representing a value to update
##function: updates the value of that light
func update_light_value(i : int, value : int = -1) → void :
match value:
-1:
position_array[2i] = light_array[i].global_position.x
position_array[2i+1] = light_array[i].global_position.y
direction_array[2i] = light_array[i].light_direction.x
direction_array[2i+1] = light_array[i].light_direction.y
angle_array[i] = light_array[i].light_angle
color_array[4i] = light_array[i].light_color.x
color_array[4i+1] = light_array[i].light_color.y
color_array[4i+2] = light_array[i].light_color.z
color_array[4i+3] = light_array[i].light_color.w
falloff_factor_array[3i] = light_array[i].light_constant_falloff
falloff_factor_array[3i+1] = light_array[i].light_linear_falloff
falloff_factor_array[3i+2] = light_array[i].light_quadratic_falloff
0:
position_array[2i] = light_array[i].global_position.x
position_array[2i+1] = light_array[i].global_position.y
1:
direction_array[2i] = light_array[i].light_direction.x
direction_array[2i+1] = light_array[i].light_direction.y
2:
angle_array[i] = light_array[i].light_angle
3:
color_array[4i] = light_array[i].light_color.x
color_array[4i+1] = light_array[i].light_color.y
color_array[4i+2] = light_array[i].light_color.z
color_array[4i+3] = light_array[i].light_color.w
4:
falloff_factor_array[3i] = light_array[i].light_constant_falloff
falloff_factor_array[3i+1] = light_array[i].light_linear_falloff
falloff_factor_array[3i+2] = light_array[i].light_quadratic_falloff
#input: /
##input: /
##function: free rids before exiting the tree
func _exit_tree() → void:
rd.free_rid(cast_shadow_shader)
rd.free_rid(cast_shadow_pipeline)
rd.free_rid(cast_shadow_uniform_set)
rd.free_rid(shader)
rd.free_rid(pipeline)
rd.free_rid(uniform_set)
rd.free_rid(output_shader)
rd.free_rid(output_pipeline)
rd.free_rid(output_uniform_set)
rd.free_rid(input_buffer)
rd.free_rid(direction_buffer)
rd.free_rid(angle_buffer)
rd.free_rid(color_buffer)
rd.free_rid(falloff_factor_buffer)
rd.free_rid(position_buffer)
rd.free_rid(triangularized_occluder_buffer)
rd.free_rid(cast_shadow_buffer)
rd.free_rid(occluder_buffer)
rd.free_rid(occluder_size_buffer)
rd.free_rid(occluder_opacity_buffer)
rd.free_rid(output_buffer)
rd.free_rid(ambient_buffer)
#note: /
```