From cc2c13b90214455b0c8975c75abb30eceacced10 Mon Sep 17 00:00:00 2001 From: Garrett Gunnell Date: Mon, 2 Dec 2024 21:05:48 -0800 Subject: [PATCH] Implement godot compositor tutorial --- .gitattributes | 2 + .gitignore | 7 +++ post_process.tscn | 40 +++++++++++++ post_process_shader.gd | 129 +++++++++++++++++++++++++++++++++++++++++ project.godot | 15 +++++ 5 files changed, 193 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 post_process.tscn create mode 100644 post_process_shader.gd create mode 100644 project.godot diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..8ad74f7 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Normalize EOL for all files that Git considers text files. +* text=auto eol=lf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..39506e5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +# Godot 4+ specific ignores +.godot/ +/android/ + +/Ignored + +icon.svg* \ No newline at end of file diff --git a/post_process.tscn b/post_process.tscn new file mode 100644 index 0000000..44561fb --- /dev/null +++ b/post_process.tscn @@ -0,0 +1,40 @@ +[gd_scene load_steps=8 format=3 uid="uid://ccgywstxl3rj6"] + +[ext_resource type="Script" path="res://post_process_shader.gd" id="1_g22wf"] +[ext_resource type="Texture2D" uid="uid://c6jccoq62id4d" path="res://Ignored/tumblr_280d93eb9dac421ba08746f2d5362045_8c4f7d77_1280.jpg" id="2_lxnnh"] + +[sub_resource type="Environment" id="Environment_msfv3"] + +[sub_resource type="CompositorEffect" id="CompositorEffect_5gbdv"] +resource_local_to_scene = false +resource_name = "" +enabled = true +effect_callback_type = 4 +needs_motion_vectors = false +needs_normal_roughness = false +script = ExtResource("1_g22wf") +shader_code = "float gray = color.r * 0.2125 + color.g * 0.7154 + color.b * 0.0721; +color.rgb = vec3(gray);" + +[sub_resource type="Compositor" id="Compositor_4ipny"] +compositor_effects = Array[CompositorEffect]([SubResource("CompositorEffect_5gbdv")]) + +[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_in1jo"] +shading_mode = 0 +albedo_texture = ExtResource("2_lxnnh") +billboard_mode = 1 + +[sub_resource type="QuadMesh" id="QuadMesh_yds3v"] +material = SubResource("StandardMaterial3D_in1jo") + +[node name="Node3D" type="Node3D"] + +[node name="WorldEnvironment" type="WorldEnvironment" parent="."] +environment = SubResource("Environment_msfv3") +compositor = SubResource("Compositor_4ipny") + +[node name="Camera3D" type="Camera3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1.01111) + +[node name="MeshInstance3D" type="MeshInstance3D" parent="."] +mesh = SubResource("QuadMesh_yds3v") diff --git a/post_process_shader.gd b/post_process_shader.gd new file mode 100644 index 0000000..c4c88c4 --- /dev/null +++ b/post_process_shader.gd @@ -0,0 +1,129 @@ +@tool +extends CompositorEffect +class_name PostProcessShader + +const template_shader : String = "#version 450 + +// Invocations in the (x, y, z) dimension (thread group sizes) +layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; + +layout(rgba16f, set = 0, binding = 0) uniform image2D color_image; + +// Our push constant (cbuffer??) +layout(push_constant, std430) uniform Params { + vec2 raster_size; + vec2 reserved; +} params; + +// main function/kernel? +void main() { + ivec2 uv = ivec2(gl_GlobalInvocationID.xy); + ivec2 size = ivec2(params.raster_size); + + if (uv.x >= size.x || uv.y >= size.y) return; + + vec4 color = imageLoad(color_image, uv); + + #COMPUTE_CODE + + imageStore(color_image, uv, color); +} +" + +@export_multiline var shader_code : String = "": + set(value): + mutex.lock() + shader_code = value + shader_is_dirty = true + mutex.unlock() + +var rd : RenderingDevice +var shader : RID +var pipeline : RID + +var mutex : Mutex = Mutex.new() +var shader_is_dirty : bool = true + +func _init(): + effect_callback_type = EFFECT_CALLBACK_TYPE_POST_TRANSPARENT + rd = RenderingServer.get_rendering_device() + +func _notification(what): + if what == NOTIFICATION_PREDELETE: + if shader.is_valid(): + rd.free_rid(shader) + + +func _check_shader() -> bool: + if not rd: + return false + + var new_shader_code : String = "" + + mutex.lock() + if shader_is_dirty: + new_shader_code = shader_code + shader_is_dirty = false + mutex.unlock() + + if new_shader_code.is_empty(): + return pipeline.is_valid() + + new_shader_code = template_shader.replace("#COMPUTE_CODE", new_shader_code); + + if shader.is_valid(): + rd.free_rid(shader) + shader = RID() + pipeline = RID() + + var shader_source : RDShaderSource = RDShaderSource.new() + shader_source.language = RenderingDevice.SHADER_LANGUAGE_GLSL + shader_source.source_compute = new_shader_code + var shader_spirv : RDShaderSPIRV = rd.shader_compile_spirv_from_source(shader_source) + + if shader_spirv.compile_error_compute != "": + push_error(shader_spirv.compile_error_compute) + push_error("In: " + new_shader_code) + return false + + shader = rd.shader_create_from_spirv(shader_spirv) + if not shader.is_valid(): + return false + + pipeline = rd.compute_pipeline_create(shader) + return pipeline.is_valid() + +func _render_callback(p_effect_callback_type, p_render_data): + if rd and p_effect_callback_type == EFFECT_CALLBACK_TYPE_POST_TRANSPARENT and _check_shader(): + var render_scene_buffers : RenderSceneBuffersRD = p_render_data.get_render_scene_buffers() + if render_scene_buffers: + var size = render_scene_buffers.get_internal_size() + if size.x == 0 and size.y == 0: + return + + var x_groups = (size.x - 1) / 8 + 1 + var y_groups = (size.y - 1) / 8 + 1 + var z_groups = 1 + + var push_constant : PackedFloat32Array = PackedFloat32Array() + push_constant.push_back(size.x) + push_constant.push_back(size.y) + push_constant.push_back(0.0) + push_constant.push_back(0.0) + + var view_count = render_scene_buffers.get_view_count() + for view in range(view_count): + var input_image = render_scene_buffers.get_color_layer(view) + + var uniform : RDUniform = RDUniform.new() + uniform.uniform_type = RenderingDevice.UNIFORM_TYPE_IMAGE + uniform.binding = 0 + uniform.add_id(input_image) + var uniform_set = UniformSetCacheRD.get_cache(shader, 0, [uniform]) + + var compute_list := rd.compute_list_begin() + rd.compute_list_bind_compute_pipeline(compute_list, pipeline) + rd.compute_list_bind_uniform_set(compute_list, uniform_set, 0) + 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, z_groups) + rd.compute_list_end() \ No newline at end of file diff --git a/project.godot b/project.godot new file mode 100644 index 0000000..e764964 --- /dev/null +++ b/project.godot @@ -0,0 +1,15 @@ +; Engine configuration file. +; It's best edited using the editor UI and not directly, +; since the parameters that go here are not all obvious. +; +; Format: +; [section] ; section goes between [] +; param=value ; assign values to parameters + +config_version=5 + +[application] + +config/name="Post Processing" +config/features=PackedStringArray("4.3", "Forward Plus") +config/icon="res://icon.svg"