diff --git a/compute/post_effects/.gitattributes b/compute/post_effects/.gitattributes new file mode 100644 index 00000000000..8ad74f78d9c --- /dev/null +++ b/compute/post_effects/.gitattributes @@ -0,0 +1,2 @@ +# Normalize EOL for all files that Git considers text files. +* text=auto eol=lf diff --git a/compute/post_effects/.gitignore b/compute/post_effects/.gitignore new file mode 100644 index 00000000000..4709183670a --- /dev/null +++ b/compute/post_effects/.gitignore @@ -0,0 +1,2 @@ +# Godot 4+ specific ignores +.godot/ diff --git a/compute/post_effects/README.md b/compute/post_effects/README.md new file mode 100644 index 00000000000..073433021b8 --- /dev/null +++ b/compute/post_effects/README.md @@ -0,0 +1,11 @@ +# Post effects + +This demo shows how to implement post effects using the new RenderingEffect class. + +Renderer: Forward Plus + +> Note: this demo requires Godot 4.2 or later + +## Screenshots + +![Screenshot](screenshots/compute_texture.webp) diff --git a/compute/post_effects/gray_scale/gray_scale.gd b/compute/post_effects/gray_scale/gray_scale.gd new file mode 100644 index 00000000000..722434169fb --- /dev/null +++ b/compute/post_effects/gray_scale/gray_scale.gd @@ -0,0 +1,74 @@ +@tool +extends RenderingEffect +class_name RenderingEffectGrayScale + +# This is a very simple effects demo that takes our color values and writes +# back gray scale values. + +func _init(): + effect_callback_type = RenderingEffect.EFFECT_CALLBACK_TYPE_POST_TRANSPARENT + RenderingServer.call_on_render_thread(_initialize_compute) + +func _notification(what): + if what == NOTIFICATION_PREDELETE: + # When this is called it should be safe to clean up our shader. + # If not we'll crash anyway because we can no longer call our _render_callback. + if shader.is_valid(): + rd.free_rid(shader) + +############################################################################### +# Everything after this point is designed to run on our rendering thread + +var rd : RenderingDevice + +var shader : RID +var pipeline : RID + +func _initialize_compute(): + rd = RenderingServer.get_rendering_device() + if !rd: + return + + # Create our shader + var shader_file = load("res://gray_scale/gray_scale.glsl") + var shader_spirv: RDShaderSPIRV = shader_file.get_spirv() + shader = rd.shader_create_from_spirv(shader_spirv) + pipeline = rd.compute_pipeline_create(shader) + +func _render_callback(p_effect_callback_type, p_render_data): + if rd and p_effect_callback_type == RenderingEffect.EFFECT_CALLBACK_TYPE_POST_TRANSPARENT: + # Get our render scene buffers object, this gives us access to our render buffers. + # Note that implementation differs per renderer hence the need for the cast. + var render_scene_buffers : RenderSceneBuffersRD = p_render_data.get_render_scene_buffers() + if render_scene_buffers: + # Get our render size, this is the 3D render resolution! + var size = render_scene_buffers.get_internal_size() + if size.x == 0 and size.y == 0: + return + + # We can use a compute shader here + var x_groups = (size.x - 1) / 8 + 1 + var y_groups = (size.y - 1) / 8 + 1 + + # Barrier + rd.barrier(RenderingDevice.BARRIER_MASK_ALL_BARRIERS, RenderingDevice.BARRIER_MASK_COMPUTE) + + # Loop through views just in case we're doing stereo rendering. No extra cost if this is mono. + var view_count = render_scene_buffers.get_view_count() + for view in range(view_count): + # Get the RID for our color image, we will be reading from and writing to it. + var input_image = render_scene_buffers.get_color_layer(view) + + # Create a uniform set, this will be cached, the cache will be cleared if our viewports configuration is changed + 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 ]) + + # Run our 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, uniform_set, 0) + rd.compute_list_dispatch(compute_list, x_groups, y_groups, 1) + rd.compute_list_end() diff --git a/compute/post_effects/gray_scale/gray_scale.glsl b/compute/post_effects/gray_scale/gray_scale.glsl new file mode 100644 index 00000000000..22ae75c59fe --- /dev/null +++ b/compute/post_effects/gray_scale/gray_scale.glsl @@ -0,0 +1,18 @@ +#[compute] +#version 450 + +// Invocations in the (x, y, z) dimension +layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; + +layout(rgba16f, set = 0, binding = 0) uniform image2D color_image; + +// The code we want to execute in each invocation +void main() { + ivec2 uv = ivec2(gl_GlobalInvocationID.xy); + + vec4 color = imageLoad(color_image, uv); + float gray = max(max(color.r, color.b), color.g); + color.rgb = vec3(gray); + + imageStore(color_image, uv, color); +} diff --git a/compute/post_effects/gray_scale/gray_scale.glsl.import b/compute/post_effects/gray_scale/gray_scale.glsl.import new file mode 100644 index 00000000000..4348cdddd4a --- /dev/null +++ b/compute/post_effects/gray_scale/gray_scale.glsl.import @@ -0,0 +1,14 @@ +[remap] + +importer="glsl" +type="RDShaderFile" +uid="uid://cycdb7vyg3b6g" +path="res://.godot/imported/gray_scale.glsl-cdec27ad421b3dcac596053515285dc6.res" + +[deps] + +source_file="res://gray_scale/gray_scale.glsl" +dest_files=["res://.godot/imported/gray_scale.glsl-cdec27ad421b3dcac596053515285dc6.res"] + +[params] + diff --git a/compute/post_effects/icon.svg b/compute/post_effects/icon.svg new file mode 100644 index 00000000000..b370ceb7274 --- /dev/null +++ b/compute/post_effects/icon.svg @@ -0,0 +1 @@ + diff --git a/compute/post_effects/icon.svg.import b/compute/post_effects/icon.svg.import new file mode 100644 index 00000000000..b657c0d9eb9 --- /dev/null +++ b/compute/post_effects/icon.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cm88hfhyutq2d" +path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://icon.svg" +dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/compute/post_effects/main.gd b/compute/post_effects/main.gd new file mode 100644 index 00000000000..79f25d09910 --- /dev/null +++ b/compute/post_effects/main.gd @@ -0,0 +1,52 @@ +extends Node3D + +@onready var sssr_button = $UI/MarginContainer/VBoxContainer/SSSR +@onready var gray_scale_button = $UI/MarginContainer/VBoxContainer/GrayScaleBtn + +var create_depth_mips_effect : RenderingEffectCreateDepthMips +var apply_sssr_effect : RenderingEffectApplySSSR +var gray_scale_effect : RenderingEffectGrayScale + +# Called when the node enters the scene tree for the first time. +func _ready(): + var environment : Environment = $WorldEnvironment.environment + for effect in environment.rendering_effects: + if effect.get_script() == RenderingEffectCreateDepthMips: + create_depth_mips_effect = effect + elif effect.get_script() == RenderingEffectApplySSSR: + apply_sssr_effect = effect + elif effect.get_script() == RenderingEffectGrayScale: + gray_scale_effect = effect + + if create_depth_mips_effect and apply_sssr_effect: + sssr_button.button_pressed = create_depth_mips_effect.enabled + + if gray_scale_effect: + gray_scale_button.button_pressed = gray_scale_effect.enabled + +var cam_x = 0.0 +var cam_y = 0.0 + +func _input(event): + if event is InputEventMouseMotion: + var mouse_event : InputEventMouseMotion = event + + if mouse_event.button_mask & MOUSE_BUTTON_MASK_LEFT: + cam_x = clamp(cam_x + mouse_event.relative.y * 0.01, -PI * 0.1, PI * 0.25) + cam_y -= mouse_event.relative.x * 0.01 + + var b1 : Basis = Basis(Vector3.UP, cam_y) + var b2 : Basis = Basis(Vector3.LEFT, cam_x) + + $Pivot.transform.basis = b1 * b2 + +func _on_simple_ssr_toggled(toggled_on): + if create_depth_mips_effect and apply_sssr_effect: + create_depth_mips_effect.enabled = toggled_on + apply_sssr_effect.enabled = toggled_on + +func _on_gray_scale_btn_toggled(toggled_on): + if gray_scale_effect: + gray_scale_effect.enabled = toggled_on + + diff --git a/compute/post_effects/main.tscn b/compute/post_effects/main.tscn new file mode 100644 index 00000000000..cae4016226d --- /dev/null +++ b/compute/post_effects/main.tscn @@ -0,0 +1,161 @@ +[gd_scene load_steps=23 format=3 uid="uid://hay7hbv1cp62"] + +[ext_resource type="Script" path="res://gray_scale/gray_scale.gd" id="1_di38g"] +[ext_resource type="Script" path="res://main.gd" id="1_ekxfu"] +[ext_resource type="Texture2D" uid="uid://bshdn70nudsun" path="res://pattern.png" id="2_d1qxb"] +[ext_resource type="Script" path="res://simple_ssr/create_depth_mips.gd" id="2_lv16x"] +[ext_resource type="Script" path="res://simple_ssr/apply_sssr.gd" id="3_ygheb"] +[ext_resource type="Texture2D" uid="uid://d0e66wb8yplkw" path="res://uv.jpg" id="5_ybioc"] + +[sub_resource type="RenderingEffect" id="RenderingEffect_rprpc"] +resource_local_to_scene = false +resource_name = "" +enabled = true +effect_callback_type = 0 +access_resolved_color = false +access_resolved_depth = true +needs_motion_vectors = false +needs_normal_roughness = false +script = ExtResource("2_lv16x") + +[sub_resource type="RenderingEffect" id="RenderingEffect_sm2nk"] +resource_local_to_scene = false +resource_name = "" +enabled = true +effect_callback_type = 2 +access_resolved_color = false +access_resolved_depth = false +needs_motion_vectors = false +needs_normal_roughness = true +needs_separate_specular = true +script = ExtResource("3_ygheb") +max_distance = 1.0 +max_steps = 32 + +[sub_resource type="RenderingEffect" id="RenderingEffect_jcxua"] +resource_local_to_scene = false +resource_name = "" +enabled = false +effect_callback_type = 4 +needs_motion_vectors = false +needs_normal_roughness = false +script = ExtResource("1_di38g") + +[sub_resource type="ProceduralSkyMaterial" id="ProceduralSkyMaterial_ptioq"] +sky_horizon_color = Color(0.64625, 0.65575, 0.67075, 1) +ground_horizon_color = Color(0.64625, 0.65575, 0.67075, 1) + +[sub_resource type="Sky" id="Sky_gkygq"] +sky_material = SubResource("ProceduralSkyMaterial_ptioq") + +[sub_resource type="Environment" id="Environment_kx13e"] +background_mode = 2 +sky = SubResource("Sky_gkygq") +tonemap_mode = 2 +ssr_depth_tolerance = 3.41 +glow_enabled = true +rendering_effects = Array[RenderingEffect]([SubResource("RenderingEffect_rprpc"), SubResource("RenderingEffect_sm2nk"), SubResource("RenderingEffect_jcxua")]) + +[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_tvupy"] +albedo_texture = ExtResource("5_ybioc") +metallic = 0.5 +roughness = 0.75 +uv1_scale = Vector3(5, 5, 5) + +[sub_resource type="PlaneMesh" id="PlaneMesh_fbrjs"] +size = Vector2(20, 20) + +[sub_resource type="SphereMesh" id="SphereMesh_nqw7b"] + +[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_mwci7"] +albedo_texture = ExtResource("2_d1qxb") +metallic = 0.78 +roughness = 0.24 + +[sub_resource type="SphereMesh" id="SphereMesh_lau6s"] + +[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_qdjdj"] +albedo_color = Color(1, 0.298039, 0.247059, 1) +albedo_texture = ExtResource("2_d1qxb") +metallic = 0.26 +roughness = 0.59 + +[sub_resource type="CylinderMesh" id="CylinderMesh_jepk0"] + +[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_xkdqw"] +albedo_color = Color(0.580392, 0.658824, 1, 1) +albedo_texture = ExtResource("2_d1qxb") +metallic = 0.86 +roughness = 0.29 + +[sub_resource type="BoxMesh" id="BoxMesh_syokj"] + +[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_chobt"] +albedo_color = Color(0.588235, 0.282353, 0.443137, 1) +albedo_texture = ExtResource("2_d1qxb") +metallic = 0.52 +roughness = 0.42 + +[node name="Main" type="Node3D"] +script = ExtResource("1_ekxfu") + +[node name="WorldEnvironment" type="WorldEnvironment" parent="."] +environment = SubResource("Environment_kx13e") + +[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."] +transform = Transform3D(0.959131, 0.245052, -0.141481, -0.0849703, 0.726354, 0.682048, 0.269902, -0.642152, 0.717491, 0, 1.94732, 0) +shadow_enabled = true + +[node name="Pivot" type="Node3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.553607, 0.779114, 0) + +[node name="Camera3D" type="Camera3D" parent="Pivot"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 3) + +[node name="Floor" type="MeshInstance3D" parent="."] +material_override = SubResource("StandardMaterial3D_tvupy") +mesh = SubResource("PlaneMesh_fbrjs") + +[node name="MetalSphere" type="MeshInstance3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.721009, 0) +mesh = SubResource("SphereMesh_nqw7b") +surface_material_override/0 = SubResource("StandardMaterial3D_mwci7") + +[node name="RedSphere" type="MeshInstance3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.17882, 0.628256, -0.532507) +mesh = SubResource("SphereMesh_lau6s") +surface_material_override/0 = SubResource("StandardMaterial3D_qdjdj") + +[node name="Cylinder" type="MeshInstance3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.29466, 0, 0.409778) +mesh = SubResource("CylinderMesh_jepk0") +surface_material_override/0 = SubResource("StandardMaterial3D_xkdqw") + +[node name="Box" type="MeshInstance3D" parent="."] +transform = Transform3D(0.682049, -0.297997, -0.667837, -1.49012e-08, 0.913212, -0.407486, 0.731306, 0.277925, 0.622855, 2.41454, 0.500351, 0.0996274) +mesh = SubResource("BoxMesh_syokj") +surface_material_override/0 = SubResource("StandardMaterial3D_chobt") + +[node name="UI" type="CanvasLayer" parent="."] + +[node name="MarginContainer" type="MarginContainer" parent="UI"] +offset_right = 40.0 +offset_bottom = 40.0 +theme_override_constants/margin_left = 5 +theme_override_constants/margin_top = 5 +theme_override_constants/margin_right = 5 +theme_override_constants/margin_bottom = 5 + +[node name="VBoxContainer" type="VBoxContainer" parent="UI/MarginContainer"] +layout_mode = 2 + +[node name="SSSR" type="CheckBox" parent="UI/MarginContainer/VBoxContainer"] +layout_mode = 2 +text = "SSSR" + +[node name="GrayScaleBtn" type="CheckBox" parent="UI/MarginContainer/VBoxContainer"] +layout_mode = 2 +text = "Gray scale" + +[connection signal="toggled" from="UI/MarginContainer/VBoxContainer/SSSR" to="." method="_on_simple_ssr_toggled"] +[connection signal="toggled" from="UI/MarginContainer/VBoxContainer/GrayScaleBtn" to="." method="_on_gray_scale_btn_toggled"] diff --git a/compute/post_effects/pattern.png b/compute/post_effects/pattern.png new file mode 100644 index 00000000000..8bf420b0d55 Binary files /dev/null and b/compute/post_effects/pattern.png differ diff --git a/compute/post_effects/pattern.png.import b/compute/post_effects/pattern.png.import new file mode 100644 index 00000000000..9843f6e98cb --- /dev/null +++ b/compute/post_effects/pattern.png.import @@ -0,0 +1,35 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bshdn70nudsun" +path.s3tc="res://.godot/imported/pattern.png-888ea151ee9fa7a079d3252596260765.s3tc.ctex" +metadata={ +"imported_formats": ["s3tc_bptc"], +"vram_texture": true +} + +[deps] + +source_file="res://pattern.png" +dest_files=["res://.godot/imported/pattern.png-888ea151ee9fa7a079d3252596260765.s3tc.ctex"] + +[params] + +compress/mode=2 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=true +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=0 diff --git a/compute/post_effects/project.godot b/compute/post_effects/project.godot new file mode 100644 index 00000000000..a92c9ae3fae --- /dev/null +++ b/compute/post_effects/project.godot @@ -0,0 +1,20 @@ +; 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="TestRenderingEffects" +run/main_scene="res://main.tscn" +config/features=PackedStringArray("4.2", "Forward Plus") +config/icon="res://icon.svg" + +[rendering] + +anti_aliasing/quality/msaa_3d=2 diff --git a/compute/post_effects/screenshot.webp b/compute/post_effects/screenshot.webp new file mode 100644 index 00000000000..2c3a51ba711 Binary files /dev/null and b/compute/post_effects/screenshot.webp differ diff --git a/compute/post_effects/screenshot.webp.import b/compute/post_effects/screenshot.webp.import new file mode 100644 index 00000000000..e7f0c20d1bb --- /dev/null +++ b/compute/post_effects/screenshot.webp.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bohtjmc6w0xhy" +path="res://.godot/imported/screenshot.webp-cd0032f76ed32b0f5daefad458fed035.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://screenshot.webp" +dest_files=["res://.godot/imported/screenshot.webp-cd0032f76ed32b0f5daefad458fed035.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/compute/post_effects/simple_ssr/apply_sssr.gd b/compute/post_effects/simple_ssr/apply_sssr.gd new file mode 100644 index 00000000000..a0d96b19874 --- /dev/null +++ b/compute/post_effects/simple_ssr/apply_sssr.gd @@ -0,0 +1,247 @@ +@tool +extends RenderingEffect +class_name RenderingEffectApplySSSR + +# This is the second effect that make up a very basic screen space +# reflection implementation. +# +# This is a multipart process +# Step 1: Create a reflection texture +# Step 2: Create mipmaps of our reflection texture +# Step 3: Apply our reflection texture to our color buffer +# +# Note that we perform everything at half resolution. +# This is designed to only work on the Forward+ renderer. + +@export var max_distance : float = 1.0 +@export_range(0, 64, 1) var max_steps : int = 32 + +func _init(): + effect_callback_type = RenderingEffect.EFFECT_CALLBACK_TYPE_POST_SKY + needs_normal_roughness = true + needs_separate_specular = true + RenderingServer.call_on_render_thread(_initialize_compute) + +func _notification(what): + if what == NOTIFICATION_PREDELETE: + # When this is called it should be safe to clean up our shader. + # If not we'll crash anyway because we can no longer call our _render_callback. + if nearest_sampler.is_valid(): + rd.free_rid(nearest_sampler) + if linear_sampler.is_valid(): + rd.free_rid(linear_sampler) + if reflection_shader.is_valid(): + rd.free_rid(reflection_shader) + if overlay_shader.is_valid(): + rd.free_rid(overlay_shader) + +############################################################################### +# Everything after this point is designed to run on our rendering thread + +var rd : RenderingDevice + +var nearest_sampler : RID +var linear_sampler : RID + +var reflection_shader : RID +var reflection_pipeline : RID + +var overlay_shader : RID +var overlay_pipeline : RID + +func _initialize_compute(): + rd = RenderingServer.get_rendering_device() + if !rd: + return + + # Create our samplers + var sampler_state : RDSamplerState = RDSamplerState.new() + sampler_state.min_filter = RenderingDevice.SAMPLER_FILTER_NEAREST + sampler_state.mag_filter = RenderingDevice.SAMPLER_FILTER_NEAREST + nearest_sampler = rd.sampler_create(sampler_state) + + sampler_state = RDSamplerState.new() + sampler_state.min_filter = RenderingDevice.SAMPLER_FILTER_LINEAR + sampler_state.mag_filter = RenderingDevice.SAMPLER_FILTER_LINEAR + linear_sampler = rd.sampler_create(sampler_state) + + # Create our reflection shader and pipeline + var shader_file = load("res://simple_ssr/reflect_sssr.glsl") + var shader_spirv: RDShaderSPIRV = shader_file.get_spirv() + reflection_shader = rd.shader_create_from_spirv(shader_spirv) + reflection_pipeline = rd.compute_pipeline_create(reflection_shader) + + # Create our overlay shader + shader_file = load("res://simple_ssr/overlay_sssr.glsl") + shader_spirv = shader_file.get_spirv() + overlay_shader = rd.shader_create_from_spirv(shader_spirv) + +func _render_callback(p_effect_callback_type, p_render_data : RenderData): + if rd and p_effect_callback_type == RenderingEffect.EFFECT_CALLBACK_TYPE_POST_SKY: + # Get our render scene buffers object, this gives us access to our render buffers. + # Note that implementation differs per renderer hence the need for the cast. + var render_scene_buffers : RenderSceneBuffersRD = p_render_data.get_render_scene_buffers() + var render_scene_data : RenderSceneDataRD = p_render_data.get_render_scene_data() + if render_scene_buffers and render_scene_data: + # Get our render size, this is the 3D render resolution! + var render_size = render_scene_buffers.get_internal_size() + if render_size.x <= 1 and render_size.y <= 1: + return + + var half_size = Vector2i((render_size.x + 1) / 2, (render_size.y + 1) / 2) + + # We can use a compute shader here + var x_groups = (half_size.x - 1) / 8 + 1 + var y_groups = (half_size.y - 1) / 8 + 1 + + # Barrier + rd.barrier(RenderingDevice.BARRIER_MASK_ALL_BARRIERS, RenderingDevice.BARRIER_MASK_COMPUTE) + + # Loop through views just in case we're doing stereo rendering. No extra cost if this is mono. + var view_count = render_scene_buffers.get_view_count() + for view in range(view_count): + # Get the RID for our color image, we will be reading from and writing to it. + var color_image = render_scene_buffers.get_color_layer(view) + var normal_image = render_scene_buffers.get_texture_slice("forward_clustered", "normal_roughness", view, 0, 1, 1) + + if !render_scene_buffers.has_texture("SSSR", "reflection"): + var usage_bits : int = RenderingDevice.TEXTURE_USAGE_SAMPLING_BIT | RenderingDevice.TEXTURE_USAGE_STORAGE_BIT + var mipmaps : int = 1 ## May up this if we want blurred reflections + render_scene_buffers.create_texture("SSSR", "reflection", RenderingDevice.DATA_FORMAT_R8G8B8A8_UNORM, usage_bits, RenderingDevice.TEXTURE_SAMPLES_1, half_size, 1, mipmaps, true) + + var depth_mips : RID = render_scene_buffers.get_texture_slice("SSSR", "depth_mips", 0, 0, 1, 4) + var reflection : RID = render_scene_buffers.get_texture_slice("SSSR", "reflection", 0, 0, 1, 1) + + var projection : Projection = render_scene_data.get_view_projection(view) + var eye_offset = render_scene_data.get_view_eye_offset(view) + + ################################################################################### + # Step 2, generate reflection map + + # We don't have structures (yet) so we need to build our push constant + # "the hard way"... + var push_constant : PackedFloat32Array = PackedFloat32Array() + var ipx = projection.x + var ipy = projection.y + var ipz = projection.z + var ipw = projection.w + push_constant.push_back(ipx.x) + push_constant.push_back(ipx.y) + push_constant.push_back(ipx.z) + push_constant.push_back(ipx.w) + push_constant.push_back(ipy.x) + push_constant.push_back(ipy.y) + push_constant.push_back(ipy.z) + push_constant.push_back(ipy.w) + push_constant.push_back(ipz.x) + push_constant.push_back(ipz.y) + push_constant.push_back(ipz.z) + push_constant.push_back(ipz.w) + push_constant.push_back(ipw.x) + push_constant.push_back(ipw.y) + push_constant.push_back(ipw.z) + push_constant.push_back(ipw.w) + push_constant.push_back(eye_offset.x) + push_constant.push_back(eye_offset.y) + push_constant.push_back(eye_offset.z) + push_constant.push_back(0.0) + push_constant.push_back(half_size.x) + push_constant.push_back(half_size.y) + push_constant.push_back(max_distance) + push_constant.push_back(max_steps) + + # Create a uniform sets, this will be cached, the cache will be cleared if our viewports configuration is changed + + var uniform : RDUniform = RDUniform.new() + uniform.uniform_type = RenderingDevice.UNIFORM_TYPE_SAMPLER_WITH_TEXTURE + uniform.binding = 0 + uniform.add_id(nearest_sampler) + uniform.add_id(depth_mips) + + var depth_set = UniformSetCacheRD.get_cache(reflection_shader, 0, [ uniform ]) + + uniform = RDUniform.new() + uniform.uniform_type = RenderingDevice.UNIFORM_TYPE_SAMPLER_WITH_TEXTURE + uniform.binding = 0 + uniform.add_id(nearest_sampler) + uniform.add_id(normal_image) + var normal_set = UniformSetCacheRD.get_cache(reflection_shader, 1, [ uniform ]) + + uniform = RDUniform.new() + uniform.uniform_type = RenderingDevice.UNIFORM_TYPE_SAMPLER_WITH_TEXTURE + uniform.binding = 0 + uniform.add_id(linear_sampler) + uniform.add_id(color_image) + var color_set = UniformSetCacheRD.get_cache(reflection_shader, 2, [ uniform ]) + + uniform = RDUniform.new() + uniform.uniform_type = RenderingDevice.UNIFORM_TYPE_IMAGE + uniform.binding = 0 + uniform.add_id(reflection) + var reflect_set = UniformSetCacheRD.get_cache(reflection_shader, 3, [ uniform ]) + + rd.draw_command_begin_label("Stochastic SSR", Color(1.0, 1.0, 1.0, 1.0)) + + # Run our compute shader + var compute_list := rd.compute_list_begin() + rd.compute_list_bind_compute_pipeline(compute_list, reflection_pipeline) + rd.compute_list_bind_uniform_set(compute_list, depth_set, 0) + rd.compute_list_bind_uniform_set(compute_list, normal_set, 1) + rd.compute_list_bind_uniform_set(compute_list, color_set, 2) + rd.compute_list_bind_uniform_set(compute_list, reflect_set, 3) + 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() + + ################################################################################### + # Step 3, generate reflection mips + + # TODO + + ################################################################################### + # Step 4, overlay reflections + + # Barrier + rd.barrier(RenderingDevice.BARRIER_MASK_COMPUTE, RenderingDevice.BARRIER_MASK_RASTER) + + # Get our framebuffer + var fb : RID = FramebufferCacheRD.get_cache_multipass([ color_image ], [ ], 1) + var fb_format = rd.framebuffer_get_format(fb) + + # Now need access to our reflection image but with a sampler + uniform = RDUniform.new() + uniform.uniform_type = RenderingDevice.UNIFORM_TYPE_SAMPLER_WITH_TEXTURE + uniform.binding = 0 + uniform.add_id(linear_sampler) + uniform.add_id(reflection) + reflect_set = UniformSetCacheRD.get_cache(overlay_shader, 0, [ uniform ]) + + # Check our pipeline + if !overlay_pipeline.is_valid(): + var prs = RDPipelineRasterizationState.new() + var pms = RDPipelineMultisampleState.new() + var pdss = RDPipelineDepthStencilState.new() + var pcbs = RDPipelineColorBlendState.new() + var attachment = RDPipelineColorBlendStateAttachment.new() + attachment.enable_blend = true + attachment.alpha_blend_op = RenderingDevice.BLEND_OP_ADD + attachment.color_blend_op = RenderingDevice.BLEND_OP_ADD + attachment.src_color_blend_factor = RenderingDevice.BLEND_FACTOR_SRC_ALPHA + attachment.dst_color_blend_factor = RenderingDevice.BLEND_FACTOR_ONE + attachment.src_alpha_blend_factor = RenderingDevice.BLEND_FACTOR_SRC_ALPHA + attachment.dst_alpha_blend_factor = RenderingDevice.BLEND_FACTOR_ONE + pcbs.attachments = [ attachment ] + + overlay_pipeline = rd.render_pipeline_create(overlay_shader, fb_format, RenderingDevice.INVALID_FORMAT_ID, RenderingDevice.RENDER_PRIMITIVE_TRIANGLES, prs, pms, pdss, pcbs) + + var clear_colors = PackedColorArray() + var draw_list = rd.draw_list_begin(fb, RenderingDevice.INITIAL_ACTION_KEEP, RenderingDevice.FINAL_ACTION_READ, RenderingDevice.INITIAL_ACTION_KEEP, RenderingDevice.FINAL_ACTION_READ, clear_colors); + + rd.draw_list_bind_render_pipeline(draw_list, overlay_pipeline) + rd.draw_list_bind_uniform_set(draw_list, reflect_set, 0) # we can reuse reflect_set here.. + # rd.draw_list_set_push_constant(draw_list, raster_push_constant, raster_push_constant.size()) + rd.draw_list_draw(draw_list, false, 1, 3) + + rd.draw_list_end() + + rd.draw_command_end_label() diff --git a/compute/post_effects/simple_ssr/create_depth_mips.gd b/compute/post_effects/simple_ssr/create_depth_mips.gd new file mode 100644 index 00000000000..c0a5347485a --- /dev/null +++ b/compute/post_effects/simple_ssr/create_depth_mips.gd @@ -0,0 +1,126 @@ +@tool +extends RenderingEffect +class_name RenderingEffectCreateDepthMips + +# This is one of our two effects that make up a very basic screen space +# reflection implementation. +# +# This first stage runs right after creating our pre-depth pass and +# generates mip levels of our depth data using a minimum value filter. +# +# Note that we perform everything at half resolution. +# This is designed to only work on the Forward+ renderer. + +func _init(): + effect_callback_type = RenderingEffect.EFFECT_CALLBACK_TYPE_PRE_OPAQUE + access_resolved_depth = true + RenderingServer.call_on_render_thread(_initialize_compute) + +func _notification(what): + if what == NOTIFICATION_PREDELETE: + # When this is called it should be safe to clean up our shader. + # If not we'll crash anyway because we can no longer call our _render_callback. + if shader.is_valid(): + rd.free_rid(shader) + +############################################################################### +# Everything after this point is designed to run on our rendering thread + +var rd : RenderingDevice + +var shader : RID +var pipeline : RID + +func _initialize_compute(): + rd = RenderingServer.get_rendering_device() + if !rd: + return + + # Create our shader + var shader_file = load("res://simple_ssr/create_depth_mips.glsl") + var shader_spirv: RDShaderSPIRV = shader_file.get_spirv() + shader = rd.shader_create_from_spirv(shader_spirv) + pipeline = rd.compute_pipeline_create(shader) + +func _render_callback(p_effect_callback_type, p_render_data): + if rd and p_effect_callback_type == RenderingEffect.EFFECT_CALLBACK_TYPE_PRE_OPAQUE: + # Get our render scene buffers object, this gives us access to our render buffers. + # Note that implementation differs per renderer hence the need for the cast. + var render_scene_buffers : RenderSceneBuffersRD = p_render_data.get_render_scene_buffers() + if render_scene_buffers: + # Get our render size, this is the 3D render resolution! + var render_size = render_scene_buffers.get_internal_size() + if render_size.x <= 1 and render_size.y <= 1: + return + + var half_size = Vector2i((render_size.x + 1) / 2, (render_size.y + 1) / 2) + + # We can use a compute shader here + var x_groups = (half_size.x - 1) / 8 + 1 + var y_groups = (half_size.y - 1) / 8 + 1 + + # Barrier + rd.barrier(RenderingDevice.BARRIER_MASK_ALL_BARRIERS, RenderingDevice.BARRIER_MASK_COMPUTE) + + # Loop through views just in case we're doing stereo rendering. No extra cost if this is mono. + var view_count = render_scene_buffers.get_view_count() + for view in range(view_count): + # Get the RID for our depth image, we will be reading from it. + var input_image = render_scene_buffers.get_texture_slice("render_buffers", "depth", 0, 0, 1, 1) + + # Make sure our output image exists + if !render_scene_buffers.has_texture("SSSR", "depth_mips"): + var usage_bits : int = RenderingDevice.TEXTURE_USAGE_SAMPLING_BIT | RenderingDevice.TEXTURE_USAGE_STORAGE_BIT + render_scene_buffers.create_texture("SSSR", "depth_mips", RenderingDevice.DATA_FORMAT_R32_SFLOAT, usage_bits, RenderingDevice.TEXTURE_SAMPLES_1, half_size, 1, 4, true) + + # We don't have structures (yet) so we need to build our push constant + # "the hard way"... + var push_constant : PackedFloat32Array = PackedFloat32Array() + push_constant.push_back(render_size.x) + push_constant.push_back(render_size.y) + push_constant.push_back(half_size.x) + push_constant.push_back(half_size.y) + + # Create a uniform set, this will be cached, the cache will be cleared if our viewports configuration is changed + var depth_image_uniform : RDUniform = RDUniform.new() + depth_image_uniform.uniform_type = RenderingDevice.UNIFORM_TYPE_IMAGE + depth_image_uniform.binding = 0 + depth_image_uniform.add_id(input_image) + + var uniform_set = UniformSetCacheRD.get_cache(shader, 0, [ depth_image_uniform ]) + + var depth_mip1_uniform : RDUniform = RDUniform.new() + depth_mip1_uniform.uniform_type = RenderingDevice.UNIFORM_TYPE_IMAGE + depth_mip1_uniform.binding = 0 + depth_mip1_uniform.add_id(render_scene_buffers.get_texture_slice("SSSR", "depth_mips", 0, 0, 1, 1)) + + var depth_mip2_uniform : RDUniform = RDUniform.new() + depth_mip2_uniform.uniform_type = RenderingDevice.UNIFORM_TYPE_IMAGE + depth_mip2_uniform.binding = 1 + depth_mip2_uniform.add_id(render_scene_buffers.get_texture_slice("SSSR", "depth_mips", 0, 1, 1, 1)) + + var depth_mip3_uniform : RDUniform = RDUniform.new() + depth_mip3_uniform.uniform_type = RenderingDevice.UNIFORM_TYPE_IMAGE + depth_mip3_uniform.binding = 2 + depth_mip3_uniform.add_id(render_scene_buffers.get_texture_slice("SSSR", "depth_mips", 0, 2, 1, 1)) + + var depth_mip4_uniform : RDUniform = RDUniform.new() + depth_mip4_uniform.uniform_type = RenderingDevice.UNIFORM_TYPE_IMAGE + depth_mip4_uniform.binding = 3 + depth_mip4_uniform.add_id(render_scene_buffers.get_texture_slice("SSSR", "depth_mips", 0, 3, 1, 1)) + + var depth_mips_set = UniformSetCacheRD.get_cache(shader, 1, [ depth_mip1_uniform, depth_mip2_uniform, depth_mip3_uniform, depth_mip4_uniform ]) + + rd.draw_command_begin_label("Create depth mips", Color(1.0, 1.0, 1.0, 1.0)) + + # Run our 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, uniform_set, 0) + rd.compute_list_bind_uniform_set(compute_list, depth_mips_set, 1) + 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() + + rd.draw_command_end_label() + diff --git a/compute/post_effects/simple_ssr/create_depth_mips.glsl b/compute/post_effects/simple_ssr/create_depth_mips.glsl new file mode 100644 index 00000000000..c5b4123fbed --- /dev/null +++ b/compute/post_effects/simple_ssr/create_depth_mips.glsl @@ -0,0 +1,118 @@ +#[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 push PushConstant +layout(push_constant, std430) uniform Params { + vec2 render_size; + vec2 half_size; +} params; + +// layout(set = 0, binding = 0) uniform sampler2D depth_image; +layout(r32f, set = 0, binding = 0) uniform restrict readonly image2D depth_image; + +layout(r32f, set = 1, binding = 0) uniform restrict writeonly image2D depth_mip1; +layout(r32f, set = 1, binding = 1) uniform restrict writeonly image2D depth_mip2; +layout(r32f, set = 1, binding = 2) uniform restrict writeonly image2D depth_mip3; +layout(r32f, set = 1, binding = 3) uniform restrict writeonly image2D depth_mip4; + +// Share our results +shared float min_buffer[8][8]; + +// The code we want to execute in each invocation +void main() { + ivec2 dest_uv = ivec2(gl_GlobalInvocationID.xy); + uvec2 grp_uv = gl_LocalInvocationID.xy; + + ivec2 render_size = ivec2(params.render_size.xy); + // ivec2 half_size = ivec2(params.half_size.xy); + + ivec2 source_uv = dest_uv * 2; + + { + float d1 = 1.0; + float d2 = 1.0; + float d3 = 1.0; + float d4 = 1.0; + + if (source_uv.x < render_size.x && source_uv.y < render_size.y) { + d1 = imageLoad(depth_image, source_uv).r; + } + if (source_uv.x + 1 < render_size.x && source_uv.y < render_size.y) { + d2 = imageLoad(depth_image, source_uv + ivec2(1, 0)).r; + } + if (source_uv.x < render_size.x && source_uv.y + 1 < render_size.y) { + d3 = imageLoad(depth_image, source_uv + ivec2(0, 1)).r; + } + if (source_uv.x < render_size.x + 1 && source_uv.y + 1 < render_size.y) { + d4 = imageLoad(depth_image, source_uv + ivec2(1, 1)).r; + } + + float d = min(min(d1, d2), min(d3, d4)); + min_buffer[grp_uv.x][grp_uv.y] = d; + + imageStore(depth_mip1, dest_uv, vec4(d, d, d, d)); + } + + // Prepare next.. + bool continue_mips = grp_uv.x % 2 == 0 && grp_uv.y % 2 == 0; + dest_uv /= 2; + + // Wait for our group + groupMemoryBarrier(); + barrier(); + + if (continue_mips) { + float d1 = min_buffer[grp_uv.x + 0][grp_uv.y + 0]; + float d2 = min_buffer[grp_uv.x + 1][grp_uv.y + 0]; + float d3 = min_buffer[grp_uv.x + 0][grp_uv.y + 1]; + float d4 = min_buffer[grp_uv.x + 1][grp_uv.y + 1]; + + float d = min(min(d1, d2), min(d3, d4)); + min_buffer[grp_uv.x][grp_uv.y] = d; + + imageStore(depth_mip2, dest_uv, vec4(d, d, d, d)); + } + + // Prepare next.. + continue_mips = grp_uv.x % 4 == 0 && grp_uv.y % 4 == 0; + dest_uv /= 2; + + // Wait for our group + groupMemoryBarrier(); + barrier(); + + if (continue_mips) { + float d1 = min_buffer[grp_uv.x + 0][grp_uv.y + 0]; + float d2 = min_buffer[grp_uv.x + 2][grp_uv.y + 0]; + float d3 = min_buffer[grp_uv.x + 0][grp_uv.y + 2]; + float d4 = min_buffer[grp_uv.x + 2][grp_uv.y + 2]; + + float d = min(min(d1, d2), min(d3, d4)); + min_buffer[grp_uv.x][grp_uv.y] = d; + + imageStore(depth_mip3, dest_uv, vec4(d, d, d, d)); + } + + // Prepare next.. + continue_mips = grp_uv.x % 8 == 0 && grp_uv.y % 8 == 0; + dest_uv /= 2; + + // Wait for our group + groupMemoryBarrier(); + barrier(); + + if (continue_mips) { + float d1 = min_buffer[grp_uv.x + 0][grp_uv.y + 0]; + float d2 = min_buffer[grp_uv.x + 4][grp_uv.y + 0]; + float d3 = min_buffer[grp_uv.x + 0][grp_uv.y + 4]; + float d4 = min_buffer[grp_uv.x + 4][grp_uv.y + 4]; + + float d = min(min(d1, d2), min(d3, d4)); + min_buffer[grp_uv.x][grp_uv.y] = d; + + imageStore(depth_mip4, dest_uv, vec4(d, d, d, d)); + } +} diff --git a/compute/post_effects/simple_ssr/create_depth_mips.glsl.import b/compute/post_effects/simple_ssr/create_depth_mips.glsl.import new file mode 100644 index 00000000000..1258b718e01 --- /dev/null +++ b/compute/post_effects/simple_ssr/create_depth_mips.glsl.import @@ -0,0 +1,14 @@ +[remap] + +importer="glsl" +type="RDShaderFile" +uid="uid://bxlhxnqa6c374" +path="res://.godot/imported/create_depth_mips.glsl-754c739c1db40dfc7ce2cc3538bedc98.res" + +[deps] + +source_file="res://simple_ssr/create_depth_mips.glsl" +dest_files=["res://.godot/imported/create_depth_mips.glsl-754c739c1db40dfc7ce2cc3538bedc98.res"] + +[params] + diff --git a/compute/post_effects/simple_ssr/overlay_sssr.glsl b/compute/post_effects/simple_ssr/overlay_sssr.glsl new file mode 100644 index 00000000000..33d684a1164 --- /dev/null +++ b/compute/post_effects/simple_ssr/overlay_sssr.glsl @@ -0,0 +1,28 @@ +#[vertex] + +#version 450 + +layout(location = 0) out vec2 uv_interp; + +void main() { + vec2 base_arr[3] = vec2[](vec2(-1.0, -1.0), vec2(-1.0, 3.0), vec2(3.0, -1.0)); + gl_Position = vec4(base_arr[gl_VertexIndex], 0.0, 1.0); + uv_interp = clamp(gl_Position.xy, vec2(0.0, 0.0), vec2(1.0, 1.0)) * 2.0; // saturate(x) * 2.0 +} + +#[fragment] + +#version 450 + +layout(location = 0) in vec2 uv_interp; + +layout(set = 0, binding = 0) uniform sampler2D reflection_color; + +layout(location = 0) out vec4 frag_color; + +void main() { + vec4 color = textureLod(reflection_color, uv_interp, 0); + color.a = 1.0; + + frag_color = color; +} diff --git a/compute/post_effects/simple_ssr/overlay_sssr.glsl.import b/compute/post_effects/simple_ssr/overlay_sssr.glsl.import new file mode 100644 index 00000000000..8c6e266da4e --- /dev/null +++ b/compute/post_effects/simple_ssr/overlay_sssr.glsl.import @@ -0,0 +1,14 @@ +[remap] + +importer="glsl" +type="RDShaderFile" +uid="uid://bipnoay7i12je" +path="res://.godot/imported/overlay_sssr.glsl-6d90da1757b956f07329873339297d27.res" + +[deps] + +source_file="res://simple_ssr/overlay_sssr.glsl" +dest_files=["res://.godot/imported/overlay_sssr.glsl-6d90da1757b956f07329873339297d27.res"] + +[params] + diff --git a/compute/post_effects/simple_ssr/reflect_sssr.glsl b/compute/post_effects/simple_ssr/reflect_sssr.glsl new file mode 100644 index 00000000000..ab45d1b81eb --- /dev/null +++ b/compute/post_effects/simple_ssr/reflect_sssr.glsl @@ -0,0 +1,132 @@ +#[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 push PushConstant +layout(push_constant, std430) uniform Params { + mat4 projection; + vec4 eye_offset; + vec2 half_size; + float max_distance; + float max_steps; +} params; + +layout(set = 0, binding = 0) uniform sampler2D depth_image; + +layout(set = 1, binding = 0) uniform sampler2D normal_image; + +layout(set = 2, binding = 0) uniform sampler2D color_image; + +layout(rgba8, set = 3, binding = 0) uniform restrict writeonly image2D reflect_image; + +// The code we want to execute in each invocation +void main() { + float epsilon = 1e-6f; + + ivec2 depth_uv = ivec2(gl_GlobalInvocationID.xy); + ivec2 dest_uv = depth_uv; + vec2 uv = vec2(depth_uv) / params.half_size; + + vec4 reflection = vec4(0.0, 0.0, 0.0, 0.0); + + // ivec2 render_size = ivec2(params.render_size.xy); + ivec2 half_size = ivec2(params.half_size.xy); + + if (depth_uv.x >= half_size.x || depth_uv.y >= half_size.y) { + return; + } + + // Calculate normal and roughness + vec4 normal_roughness = textureLod(normal_image, uv, 0); + vec3 normal = normalize(normal_roughness.rgb * 2.0 - 1.0); + float roughness = normal_roughness.a; + + if (roughness >= 0.6) { + imageStore(reflect_image, dest_uv, vec4(0.0)); + return; + } + + // Calculate position + float d = textureLod(depth_image, uv, 0).r; + if (d + epsilon >= 1.0) { + imageStore(reflect_image, dest_uv, vec4(0.0)); + return; + } + + vec4 unproject = vec4(uv.x * 2.0 - 1.0, (1.0 - uv.y) * 2.0 - 1.0, d, 1.0); + vec4 unprojected = inverse(params.projection) * unproject; + vec3 vertex = unprojected.xyz / unprojected.w; + + vec3 view_dir = normalize(vertex + params.eye_offset.xyz); + + // Calculate reflection + vec3 ray_dir = normalize(reflect(view_dir, normal)); + if (dot(ray_dir, normal) <= epsilon) { + imageStore(reflect_image, dest_uv, vec4(0.0)); + return; + } + vec3 ray_end = vertex + (ray_dir * params.max_distance); + + vec4 pos = params.projection * vec4(vertex, 1.0); + vec4 end_pos = params.projection * vec4(ray_end, 1.0); // TODO limit end to between near and far + vec4 step = (end_pos - pos) / params.max_steps; // TODO change step size to actually follow pixels in depth map + float delta = params.max_distance / params.max_steps; + + int level = 0; + int was_level = 0; + int max_steps = int(params.max_steps); + vec2 curr_cell = depth_uv; + float divider = 1; + float dist = 0.0; + + while (level > -1) { + pos += step; + dist += delta; + vec3 projected = pos.xyz / pos.w; + + uv = vec2(projected.x, -projected.y) * 0.5 + 0.5; + + vec2 new_cell = vec2(floor(uv.x * params.half_size.x / divider), floor(uv.y * params.half_size.y / divider)); + if (new_cell != curr_cell) { + d = textureLod(depth_image, uv, level).r; + /* + if (level < 3 && projected.z <= d) { + level += 1; + divider *= 2.0; + d = textureLod(depth_image, uv, level).r; + } + */ + if (projected.z > d) { + level -= 1; + divider /= 2.0; + } + + if (level > -1) { + curr_cell = vec2(floor(uv.x * params.half_size.x / divider), floor(uv.y * params.half_size.y / divider)); + } + } + + max_steps -= 1; + if (max_steps == 0) { + imageStore(reflect_image, dest_uv, vec4(0.0)); + return; + } + } + + // Determine reflection + normal_roughness = textureLod(normal_image, uv, 0); + normal = normalize(normal_roughness.rgb * 2.0 - 1.0); + + if (dot(-ray_dir, normal) <= epsilon) { + imageStore(reflect_image, dest_uv, vec4(0.0)); + return; + } + + // Distance fadeout + float fade = (params.max_distance - dist) / params.max_distance; + + vec4 color = textureLod(color_image, uv, 0); + imageStore(reflect_image, dest_uv, vec4(color.rgb * fade, roughness)); +} diff --git a/compute/post_effects/simple_ssr/reflect_sssr.glsl.import b/compute/post_effects/simple_ssr/reflect_sssr.glsl.import new file mode 100644 index 00000000000..0c5f27dcde7 --- /dev/null +++ b/compute/post_effects/simple_ssr/reflect_sssr.glsl.import @@ -0,0 +1,14 @@ +[remap] + +importer="glsl" +type="RDShaderFile" +uid="uid://bq4w8kwnfa5sp" +path="res://.godot/imported/reflect_sssr.glsl-3cec6f0b283c021071e77c69a716c074.res" + +[deps] + +source_file="res://simple_ssr/reflect_sssr.glsl" +dest_files=["res://.godot/imported/reflect_sssr.glsl-3cec6f0b283c021071e77c69a716c074.res"] + +[params] + diff --git a/compute/post_effects/uv.jpg b/compute/post_effects/uv.jpg new file mode 100644 index 00000000000..0c0f3cda83b Binary files /dev/null and b/compute/post_effects/uv.jpg differ diff --git a/compute/post_effects/uv.jpg.import b/compute/post_effects/uv.jpg.import new file mode 100644 index 00000000000..d4940237f10 --- /dev/null +++ b/compute/post_effects/uv.jpg.import @@ -0,0 +1,35 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://d0e66wb8yplkw" +path.s3tc="res://.godot/imported/uv.jpg-a4017ea0cc1e9ffba43864a0979cf9fc.s3tc.ctex" +metadata={ +"imported_formats": ["s3tc_bptc"], +"vram_texture": true +} + +[deps] + +source_file="res://uv.jpg" +dest_files=["res://.godot/imported/uv.jpg-a4017ea0cc1e9ffba43864a0979cf9fc.s3tc.ctex"] + +[params] + +compress/mode=2 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=true +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=0