From a847f0cfda4be8672f97ad9abaf7eeca6bdb6901 Mon Sep 17 00:00:00 2001 From: Kasper Date: Fri, 1 Aug 2025 00:20:03 +0300 Subject: [PATCH] Do it from ground up like normal by copying a tutorial/demo --- project.godot | 4 + root.gd | 30 ------ root.gdshader | 19 ---- root.gdshader.uid | 1 - root.tscn | 33 ------ shaders/decay_cs.glsl | 46 ++++++++ shaders/decay_cs.glsl.import | 14 +++ show_simulation.gdshader | 7 ++ show_simulation.gdshader.uid | 1 + slime_simulation.gd | 144 +++++++++++++++++++++++++ root.gd.uid => slime_simulation.gd.uid | 0 slime_simulation.tscn | 21 ++++ 12 files changed, 237 insertions(+), 83 deletions(-) delete mode 100644 root.gd delete mode 100644 root.gdshader delete mode 100644 root.gdshader.uid delete mode 100644 root.tscn create mode 100644 shaders/decay_cs.glsl create mode 100644 shaders/decay_cs.glsl.import create mode 100644 show_simulation.gdshader create mode 100644 show_simulation.gdshader.uid create mode 100644 slime_simulation.gd rename root.gd.uid => slime_simulation.gd.uid (100%) create mode 100644 slime_simulation.tscn diff --git a/project.godot b/project.godot index 92d1ead..235c1e5 100644 --- a/project.godot +++ b/project.godot @@ -14,3 +14,7 @@ config/name="Slime-Moldies" run/main_scene="uid://bf4ohdg0sfl8l" config/features=PackedStringArray("4.4", "Forward Plus") config/icon="res://icon.svg" + +[rendering] + +environment/defaults/default_clear_color=Color(0.0605303, 0.0605304, 0.0605303, 1) diff --git a/root.gd b/root.gd deleted file mode 100644 index 017468d..0000000 --- a/root.gd +++ /dev/null @@ -1,30 +0,0 @@ -extends Node2D - -@onready var sim_viewport := $SubViewport -@onready var display_sprite := $Sprite2D - -var tex_a: Texture2D -var tex_b: Texture2D -var swap = false - -func _ready(): - # Create textures - tex_a = ImageTexture.create_from_image( - Image.create(sim_viewport.size.x, sim_viewport.size.y, false, Image.FORMAT_RGBAF) - ) - tex_b = tex_a.duplicate() - - # Assign one texture to the simulation shader as input - $SubViewport/ColorRect.material.set("shader_parameter/trail_tex", tex_a) - display_sprite.texture = tex_a - -func _process(_delta): - # Swap textures each frame - swap = !swap - var src = tex_a if swap else tex_b - var dst = tex_b if swap else tex_a - - # Assign src as input and dst as render target - $SubViewport/ColorRect.material.set("shader_parameter/trail_tex", src) - sim_viewport.render_target_update_mode = SubViewport.UPDATE_ALWAYS - display_sprite.texture = dst diff --git a/root.gdshader b/root.gdshader deleted file mode 100644 index 4d78157..0000000 --- a/root.gdshader +++ /dev/null @@ -1,19 +0,0 @@ -shader_type canvas_item; - -uniform sampler2D trail_tex; - -uniform float sensor_distance = 2.0; -uniform float sensor_angle = 15.0; -uniform float rotation_angle = 23.0; -uniform float move_distance = 2.67; -uniform float decay_factor = 0.75; - -void fragment() { - vec2 uv = UV; - // Read current trail value - vec4 trail = texture(trail_tex, uv); - // Apply decay - trail *= decay_factor; - - COLOR = trail; -} \ No newline at end of file diff --git a/root.gdshader.uid b/root.gdshader.uid deleted file mode 100644 index f8660d4..0000000 --- a/root.gdshader.uid +++ /dev/null @@ -1 +0,0 @@ -uid://b3wu436nky225 diff --git a/root.tscn b/root.tscn deleted file mode 100644 index e04c0c7..0000000 --- a/root.tscn +++ /dev/null @@ -1,33 +0,0 @@ -[gd_scene load_steps=5 format=3 uid="uid://bf4ohdg0sfl8l"] - -[ext_resource type="Shader" uid="uid://b3wu436nky225" path="res://root.gdshader" id="1_pq8q7"] -[ext_resource type="Script" uid="uid://d1bbllvcal2uw" path="res://root.gd" id="1_pyidc"] - -[sub_resource type="ShaderMaterial" id="ShaderMaterial_pq8q7"] -shader = ExtResource("1_pq8q7") -shader_parameter/sensor_distance = 2.0 -shader_parameter/sensor_angle = 15.0 -shader_parameter/rotation_angle = 23.0 -shader_parameter/move_distance = 2.67 -shader_parameter/decay_factor = 0.75 - -[sub_resource type="ShaderMaterial" id="ShaderMaterial_pyidc"] -shader = ExtResource("1_pq8q7") -shader_parameter/sensor_distance = 2.0 -shader_parameter/sensor_angle = 15.0 -shader_parameter/rotation_angle = 23.0 -shader_parameter/move_distance = 2.67 -shader_parameter/decay_factor = 0.75 - -[node name="Node2D" type="Node2D"] -script = ExtResource("1_pyidc") - -[node name="Sprite2D" type="Sprite2D" parent="."] -material = SubResource("ShaderMaterial_pq8q7") - -[node name="SubViewport" type="SubViewport" parent="."] - -[node name="ColorRect" type="ColorRect" parent="SubViewport"] -material = SubResource("ShaderMaterial_pyidc") -offset_right = 40.0 -offset_bottom = 40.0 diff --git a/shaders/decay_cs.glsl b/shaders/decay_cs.glsl new file mode 100644 index 0000000..1b716af --- /dev/null +++ b/shaders/decay_cs.glsl @@ -0,0 +1,46 @@ +#[compute] +#version 450 + +// Invocations in the (x, y, z) dimension +layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; + +// Our textures. +layout(r32f, set = 0, binding = 0) uniform restrict readonly image2D current_image; +layout(r32f, set = 1, binding = 0) uniform restrict readonly image2D previous_image; +layout(r32f, set = 2, binding = 0) uniform restrict writeonly image2D output_image; + +// PushConstants +layout(push_constant, std430) uniform Params { + vec2 texture_size; + float decay_factor; + float dunno_lol; +} params; + + +// The code we want to execute in each invocation +void main() { + + ivec2 size = ivec2(params.texture_size.x - 1, params.texture_size.y - 1); + ivec2 uv = ivec2(gl_GlobalInvocationID.xy); + + // Just in case the texture size is not divisable by 8. + if ((uv.x > size.x) || (uv.y > size.y)) { + return; + } + + ivec2 tl = ivec2(0, 0); + float current_v = imageLoad(current_image, uv).r; + float up_v = imageLoad(current_image, clamp(uv - ivec2(0, 1), tl, size)).r; + float down_v = imageLoad(current_image, clamp(uv + ivec2(0, 1), tl, size)).r; + float left_v = imageLoad(current_image, clamp(uv - ivec2(1, 0), tl, size)).r; + float right_v = imageLoad(current_image, clamp(uv + ivec2(1, 0), tl, size)).r; + + float new_v = current_v * params.decay_factor; + + if (new_v < 0.0) { + new_v = 0.0; + } + vec4 result = vec4(new_v, new_v, new_v, 1.0); + + imageStore(output_image, uv, result); +} diff --git a/shaders/decay_cs.glsl.import b/shaders/decay_cs.glsl.import new file mode 100644 index 0000000..5d051df --- /dev/null +++ b/shaders/decay_cs.glsl.import @@ -0,0 +1,14 @@ +[remap] + +importer="glsl" +type="RDShaderFile" +uid="uid://vn5yxao0oxx0" +path="res://.godot/imported/decay_cs.glsl-2b5bb02442e13b8af8a2cd634f7d3362.res" + +[deps] + +source_file="res://shaders/decay_cs.glsl" +dest_files=["res://.godot/imported/decay_cs.glsl-2b5bb02442e13b8af8a2cd634f7d3362.res"] + +[params] + diff --git a/show_simulation.gdshader b/show_simulation.gdshader new file mode 100644 index 0000000..4012661 --- /dev/null +++ b/show_simulation.gdshader @@ -0,0 +1,7 @@ +shader_type canvas_item; +uniform sampler2D trail_tex; + +void fragment() { + float v = texture(trail_tex, UV).r; + COLOR = vec4(vec3(v), 1.0); +} \ No newline at end of file diff --git a/show_simulation.gdshader.uid b/show_simulation.gdshader.uid new file mode 100644 index 0000000..06f34fe --- /dev/null +++ b/show_simulation.gdshader.uid @@ -0,0 +1 @@ +uid://bh4h5oqyhr5bx diff --git a/slime_simulation.gd b/slime_simulation.gd new file mode 100644 index 0000000..eeeefe7 --- /dev/null +++ b/slime_simulation.gd @@ -0,0 +1,144 @@ +extends Node2D + +@export var texture_size: Vector2i = Vector2i(512, 512) +@export var decay_factor: float = 0.98 + +@onready var mesh := $MeshInstance2D + +var texture: Texture2DRD +var next_texture: int = 0 + + +func _ready(): + RenderingServer.call_on_render_thread(_initialize_compute_code.bind(texture_size)) + var mat: ShaderMaterial = mesh.material + texture = mat.get_shader_parameter("trail_tex") + + +func _process(_delta: float) -> void: + next_texture = (next_texture + 1) % 3 + + if texture: + texture.texture_rd_rid = texture_rds[next_texture] + + RenderingServer.call_on_render_thread( + _render_process.bind(next_texture, texture_size, decay_factor) + ) + + +func _exit_tree() -> void: + if texture: + texture.texture_rd_id = RID() + + RenderingServer.call_on_render_thread(_free_compute_resources) + + +# Everything after this point is designed to run on our rendering thread. + +var rd: RenderingDevice + +var shader: RID +var pipeline: RID + +# We use 3 textures: +# - One to render into +# - One that contains the last frame rendered +# - One for the frame before that +var texture_rds: Array[RID] = [RID(), RID(), RID()] +var texture_sets: Array[RID] = [RID(), RID(), RID()] + + +func _load_shader(rd_: RenderingDevice, path: String) -> RID: + var shader_file := load(path) + var shader_spirv: RDShaderSPIRV = shader_file.get_spirv() + return rd_.shader_create_from_spirv(shader_spirv) + + +func _create_uniform_set(texture_rd: RID) -> RID: + var uniform := RDUniform.new() + uniform.uniform_type = RenderingDevice.UNIFORM_TYPE_IMAGE + uniform.binding = 0 + uniform.add_id(texture_rd) + # Even though we're using 3 sets, they are identical, so we're kinda cheating. + return rd.uniform_set_create([uniform], shader, 0) + + +func _initialize_compute_code(init_with_texture_size: Vector2i) -> void: + # As this becomes part of our normal frame rendering, + # we use our main rendering device here. + rd = RenderingServer.get_rendering_device() + + # Create shader + shader = _load_shader(rd, "res://shaders/decay_cs.glsl") + pipeline = rd.compute_pipeline_create(shader) + + # Create trail texture + var tf: RDTextureFormat = RDTextureFormat.new() + tf.format = RenderingDevice.DATA_FORMAT_R32_SFLOAT + tf.texture_type = RenderingDevice.TEXTURE_TYPE_2D + tf.width = init_with_texture_size.x + tf.height = init_with_texture_size.y + tf.depth = 1 + tf.array_layers = 1 + tf.mipmaps = 1 + tf.usage_bits = ( + RenderingDevice.TEXTURE_USAGE_SAMPLING_BIT + | RenderingDevice.TEXTURE_USAGE_COLOR_ATTACHMENT_BIT + | RenderingDevice.TEXTURE_USAGE_STORAGE_BIT + | RenderingDevice.TEXTURE_USAGE_CAN_UPDATE_BIT + | RenderingDevice.TEXTURE_USAGE_CAN_COPY_TO_BIT + ) + + for i in 3: + texture_rds[i] = rd.texture_create(tf, RDTextureView.new(), []) + + # Make sure our textures are cleared. + rd.texture_clear(texture_rds[i], Color.WHITE, 0, 1, 0, 1) + + # Now create our uniform set so we can use these textures in our shader. + texture_sets[i] = _create_uniform_set(texture_rds[i]) + + +func _render_process(with_next_texture: int, tex_size: Vector2i, decay_fctr: float) -> void: + var push_constant := PackedFloat32Array() + push_constant.push_back(tex_size.x) + push_constant.push_back(tex_size.y) + push_constant.push_back(decay_fctr) + push_constant.push_back(0.0) # not sure if this is needed + + @warning_ignore("integer_division") + var x_groups := (tex_size.x - 1) / 8 + 1 + @warning_ignore("integer_division") + var y_groups := (tex_size.y - 1) / 8 + 1 + + var next_set := texture_sets[with_next_texture] + var current_set := texture_sets[(with_next_texture - 1) % 3] + var previous_set := texture_sets[(with_next_texture - 2) % 3] + + # Run the compute shader + var compute_list := rd.compute_list_begin() + rd.compute_list_bind_compute_pipeline(compute_list, pipeline) + rd.compute_list_bind_uniform_set(compute_list, current_set, 0) + rd.compute_list_bind_uniform_set(compute_list, previous_set, 1) + rd.compute_list_bind_uniform_set(compute_list, next_set, 2) + rd.compute_list_set_push_constant( + compute_list, push_constant.to_byte_array(), push_constant.size() * 4 + ) + rd.compute_list_dispatch(compute_list, x_groups, y_groups, 1) + rd.compute_list_end() + + # We don't need to sync up here, Godots default barriers will do the trick. + # If you want the output of a compute shader to be used as input of + # another computer shader you'll need to add a barrier: + #rd.barrier(RenderingDevice.BARRIER_MASK_COMPUTE) + + +func _free_compute_resources() -> void: + # Note that our sets and pipeline are cleaned up automatically + # as they are dependencies :P + for i in 3: + if texture_rds[i]: + rd.free_rid(texture_rds[i]) + + if shader: + rd.free_rid(shader) diff --git a/root.gd.uid b/slime_simulation.gd.uid similarity index 100% rename from root.gd.uid rename to slime_simulation.gd.uid diff --git a/slime_simulation.tscn b/slime_simulation.tscn new file mode 100644 index 0000000..93127b2 --- /dev/null +++ b/slime_simulation.tscn @@ -0,0 +1,21 @@ +[gd_scene load_steps=6 format=3 uid="uid://bf4ohdg0sfl8l"] + +[ext_resource type="Script" uid="uid://d1bbllvcal2uw" path="res://slime_simulation.gd" id="1_pxe3a"] +[ext_resource type="Shader" uid="uid://bh4h5oqyhr5bx" path="res://show_simulation.gdshader" id="2_pxe3a"] + +[sub_resource type="Texture2DRD" id="Texture2DRD_pxe3a"] + +[sub_resource type="ShaderMaterial" id="ShaderMaterial_pxe3a"] +shader = ExtResource("2_pxe3a") +shader_parameter/trail_tex = SubResource("Texture2DRD_pxe3a") + +[sub_resource type="QuadMesh" id="QuadMesh_pxe3a"] + +[node name="SlimeSimulation" type="Node2D"] +script = ExtResource("1_pxe3a") + +[node name="MeshInstance2D" type="MeshInstance2D" parent="."] +material = SubResource("ShaderMaterial_pxe3a") +position = Vector2(256, 256) +scale = Vector2(512, 512) +mesh = SubResource("QuadMesh_pxe3a")