mirror of
https://github.com/godotengine/godot-demo-projects.git
synced 2026-01-05 07:20:07 +01:00
Add Compositor Effects (Post-Processing) demo (#1058)
Co-authored-by: Hugo Locurcio <hugo.locurcio@hugo.pro>
This commit is contained in:
39
compute/post_shader/README.md
Normal file
39
compute/post_shader/README.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# Compositor Effects (Post-Processing)
|
||||
|
||||
This demo shows how to use compositor effects to create a post process.
|
||||
This functionality only works in render device based renderers such as the Forward+ renderer.
|
||||
|
||||
Language: GDScript
|
||||
|
||||
Renderer: Forward+
|
||||
|
||||
> Note: this demo requires Godot 4.3 or later
|
||||
|
||||
## Screenshots
|
||||
|
||||

|
||||
|
||||
## Technical description
|
||||
|
||||
This demo shows the use of the new compositor effect system to add a compute shader based post process.
|
||||
A compositor effect needs to first be implemented as a subclass of the `CompositorEffect` resource.
|
||||
An instance of this resource can then be added to the `Compositor`
|
||||
either as part of a `WorldEnvironment` node or as part of a `Camera3D` node.
|
||||
|
||||
During rendering of a viewport the `_render_callback` on this resource will be called
|
||||
at the configured stage and additional rendering commands can be submitted.
|
||||
|
||||
The two examples in this project both add a compute call to apply a full screen effect.
|
||||
Both are designed as tool scripts so they work both in editor and in runtime.
|
||||
|
||||
`post_process_shader.gd` shows an example where a template shader is used into which user code
|
||||
is injected. The user code is stored in a property of the compositor effect.
|
||||
This approach is able to recompile the shader as the property changes in runtime.
|
||||
This approach is not able to make efficient use of shader caching and may not be supported on certain
|
||||
platforms, such as certain consoles, that require precompiling of shaders.
|
||||
|
||||
`post_process_grayscale.gd` show an example where the shader code is stored in a file,
|
||||
namely `post_process_grayscale.glsl` and is compiled on initialisation.
|
||||
For editing a project this means that the shader is compiled once when the effect is loaded.
|
||||
Making changes to the `glsl` file will require reloading the scene.
|
||||
The advantage of this approach is that Godot can precompile the `glsl` file.
|
||||
1
compute/post_shader/icon.svg
Normal file
1
compute/post_shader/icon.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg height="128" width="128" xmlns="http://www.w3.org/2000/svg"><rect x="2" y="2" width="124" height="124" rx="14" fill="#363d52" stroke="#212532" stroke-width="4"/><g transform="scale(.101) translate(122 122)"><g fill="#fff"><path d="M105 673v33q407 354 814 0v-33z"/><path d="m105 673 152 14q12 1 15 14l4 67 132 10 8-61q2-11 15-15h162q13 4 15 15l8 61 132-10 4-67q3-13 15-14l152-14V427q30-39 56-81-35-59-83-108-43 20-82 47-40-37-88-64 7-51 8-102-59-28-123-42-26 43-46 89-49-7-98 0-20-46-46-89-64 14-123 42 1 51 8 102-48 27-88 64-39-27-82-47-48 49-83 108 26 42 56 81zm0 33v39c0 276 813 276 814 0v-39l-134 12-5 69q-2 10-14 13l-162 11q-12 0-16-11l-10-65H446l-10 65q-4 11-16 11l-162-11q-12-3-14-13l-5-69z" fill="#478cbf"/><path d="M483 600c0 34 58 34 58 0v-86c0-34-58-34-58 0z"/><circle cx="725" cy="526" r="90"/><circle cx="299" cy="526" r="90"/></g><g fill="#414042"><circle cx="307" cy="532" r="60"/><circle cx="717" cy="532" r="60"/></g></g></svg>
|
||||
|
After Width: | Height: | Size: 949 B |
37
compute/post_shader/icon.svg.import
Normal file
37
compute/post_shader/icon.svg.import
Normal file
@@ -0,0 +1,37 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://ckgggpfd707sy"
|
||||
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
|
||||
23
compute/post_shader/main.gd
Normal file
23
compute/post_shader/main.gd
Normal file
@@ -0,0 +1,23 @@
|
||||
extends Node3D
|
||||
|
||||
@onready var compositor: Compositor = $WorldEnvironment.compositor
|
||||
|
||||
|
||||
func _input(event: InputEvent) -> void:
|
||||
if event.is_action_pressed(&"toggle_grayscale_effect"):
|
||||
compositor.compositor_effects[0].enabled = not compositor.compositor_effects[0].enabled
|
||||
update_info_text()
|
||||
|
||||
if event.is_action_pressed(&"toggle_shader_effect"):
|
||||
compositor.compositor_effects[1].enabled = not compositor.compositor_effects[1].enabled
|
||||
update_info_text()
|
||||
|
||||
|
||||
func update_info_text() -> void:
|
||||
$Info.text = """Grayscale effect: %s
|
||||
Shader effect: %s
|
||||
""" % [
|
||||
"Enabled" if compositor.compositor_effects[0].enabled else "Disabled",
|
||||
"Enabled" if compositor.compositor_effects[1].enabled else "Disabled",
|
||||
]
|
||||
|
||||
121
compute/post_shader/main.tscn
Normal file
121
compute/post_shader/main.tscn
Normal file
@@ -0,0 +1,121 @@
|
||||
[gd_scene load_steps=17 format=3 uid="uid://bpfg1l8j4i08u"]
|
||||
|
||||
[ext_resource type="Script" path="res://main.gd" id="1_o0pyp"]
|
||||
[ext_resource type="Texture2D" uid="uid://br4k6sn2rvgj" path="res://pattern.png" id="1_r22bv"]
|
||||
[ext_resource type="Script" path="res://post_process_shader.gd" id="1_rkpno"]
|
||||
[ext_resource type="Script" path="res://post_process_grayscale.gd" id="2_pwabc"]
|
||||
|
||||
[sub_resource type="ProceduralSkyMaterial" id="ProceduralSkyMaterial_lnmx8"]
|
||||
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_guc0r"]
|
||||
sky_material = SubResource("ProceduralSkyMaterial_lnmx8")
|
||||
|
||||
[sub_resource type="Environment" id="Environment_fjaix"]
|
||||
background_mode = 2
|
||||
sky = SubResource("Sky_guc0r")
|
||||
tonemap_mode = 2
|
||||
glow_enabled = true
|
||||
|
||||
[sub_resource type="CompositorEffect" id="CompositorEffect_d6jju"]
|
||||
resource_local_to_scene = false
|
||||
resource_name = ""
|
||||
enabled = true
|
||||
effect_callback_type = 4
|
||||
needs_motion_vectors = false
|
||||
needs_normal_roughness = false
|
||||
script = ExtResource("2_pwabc")
|
||||
|
||||
[sub_resource type="CompositorEffect" id="CompositorEffect_ek4c3"]
|
||||
resource_local_to_scene = false
|
||||
resource_name = ""
|
||||
enabled = false
|
||||
effect_callback_type = 4
|
||||
needs_motion_vectors = false
|
||||
needs_normal_roughness = false
|
||||
script = ExtResource("1_rkpno")
|
||||
shader_code = " // Invert color.
|
||||
color.rgb = vec3(1.0 - color.r, 1.0 - color.g, 1.0 - color.b);
|
||||
"
|
||||
|
||||
[sub_resource type="Compositor" id="Compositor_xxhi4"]
|
||||
compositor_effects = Array[CompositorEffect]([SubResource("CompositorEffect_d6jju"), SubResource("CompositorEffect_ek4c3")])
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_xlpoj"]
|
||||
albedo_color = Color(0, 0.684707, 0.148281, 1)
|
||||
albedo_texture = ExtResource("1_r22bv")
|
||||
texture_filter = 5
|
||||
|
||||
[sub_resource type="PlaneMesh" id="PlaneMesh_82vj7"]
|
||||
material = SubResource("StandardMaterial3D_xlpoj")
|
||||
size = Vector2(10, 10)
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_aqyxc"]
|
||||
albedo_color = Color(0.946837, 0.315651, 0.66999, 1)
|
||||
albedo_texture = ExtResource("1_r22bv")
|
||||
texture_filter = 5
|
||||
|
||||
[sub_resource type="SphereMesh" id="SphereMesh_iuyuf"]
|
||||
material = SubResource("StandardMaterial3D_aqyxc")
|
||||
|
||||
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_188mc"]
|
||||
albedo_color = Color(0.436357, 0.305476, 0.999959, 1)
|
||||
albedo_texture = ExtResource("1_r22bv")
|
||||
texture_filter = 5
|
||||
|
||||
[sub_resource type="BoxMesh" id="BoxMesh_h605a"]
|
||||
material = SubResource("StandardMaterial3D_188mc")
|
||||
|
||||
[node name="Main" type="Node3D"]
|
||||
script = ExtResource("1_o0pyp")
|
||||
|
||||
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."]
|
||||
transform = Transform3D(-0.866025, -0.433013, 0.25, 0, 0.5, 0.866025, -0.5, 0.75, -0.433013, 0, 0, 0)
|
||||
shadow_enabled = true
|
||||
shadow_bias = 0.04
|
||||
directional_shadow_mode = 0
|
||||
directional_shadow_fade_start = 1.0
|
||||
directional_shadow_max_distance = 15.0
|
||||
|
||||
[node name="WorldEnvironment" type="WorldEnvironment" parent="."]
|
||||
environment = SubResource("Environment_fjaix")
|
||||
compositor = SubResource("Compositor_xxhi4")
|
||||
|
||||
[node name="Camera3D" type="Camera3D" parent="."]
|
||||
transform = Transform3D(0.866025, -0.129409, 0.482963, -1.54268e-08, 0.965926, 0.258819, -0.5, -0.224144, 0.836516, 1, 1.2, 2)
|
||||
fov = 60.0
|
||||
|
||||
[node name="Ground" type="MeshInstance3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, -3.01202)
|
||||
mesh = SubResource("PlaneMesh_82vj7")
|
||||
|
||||
[node name="Sphere" type="MeshInstance3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, -0.796)
|
||||
mesh = SubResource("SphereMesh_iuyuf")
|
||||
|
||||
[node name="Box" type="MeshInstance3D" parent="."]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.333, 0.5, -0.392)
|
||||
mesh = SubResource("BoxMesh_h605a")
|
||||
|
||||
[node name="Info" type="Label" parent="."]
|
||||
offset_left = 24.0
|
||||
offset_top = 24.0
|
||||
offset_right = 64.0
|
||||
offset_bottom = 47.0
|
||||
theme_override_constants/outline_size = 4
|
||||
text = "Grayscale effect: Enabled
|
||||
Shader effect: Disabled"
|
||||
|
||||
[node name="Help" type="Label" parent="."]
|
||||
anchors_preset = 2
|
||||
anchor_top = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_left = 24.0
|
||||
offset_top = -47.0
|
||||
offset_right = 175.0
|
||||
offset_bottom = -24.0
|
||||
grow_vertical = 0
|
||||
theme_override_constants/outline_size = 4
|
||||
text = "G: Toggle grayscale effect
|
||||
S: Toggle shader effect"
|
||||
BIN
compute/post_shader/pattern.png
Normal file
BIN
compute/post_shader/pattern.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.8 KiB |
34
compute/post_shader/pattern.png.import
Normal file
34
compute/post_shader/pattern.png.import
Normal file
@@ -0,0 +1,34 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://br4k6sn2rvgj"
|
||||
path="res://.godot/imported/pattern.png-888ea151ee9fa7a079d3252596260765.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://pattern.png"
|
||||
dest_files=["res://.godot/imported/pattern.png-888ea151ee9fa7a079d3252596260765.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=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
|
||||
89
compute/post_shader/post_process_grayscale.gd
Normal file
89
compute/post_shader/post_process_grayscale.gd
Normal file
@@ -0,0 +1,89 @@
|
||||
@tool
|
||||
extends CompositorEffect
|
||||
class_name PostProcessGrayScale
|
||||
|
||||
var rd: RenderingDevice
|
||||
var shader: RID
|
||||
var pipeline: RID
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
effect_callback_type = EFFECT_CALLBACK_TYPE_POST_TRANSPARENT
|
||||
rd = RenderingServer.get_rendering_device()
|
||||
RenderingServer.call_on_render_thread(_initialize_compute)
|
||||
|
||||
|
||||
# System notifications, we want to react on the notification that
|
||||
# alerts us we are about to be destroyed.
|
||||
func _notification(what: int) -> void:
|
||||
if what == NOTIFICATION_PREDELETE:
|
||||
if shader.is_valid():
|
||||
# Freeing our shader will also free any dependents such as the pipeline!
|
||||
RenderingServer.free_rid(shader)
|
||||
|
||||
|
||||
#region Code in this region runs on the rendering thread.
|
||||
# Compile our shader at initialization.
|
||||
func _initialize_compute() -> void:
|
||||
rd = RenderingServer.get_rendering_device()
|
||||
if not rd:
|
||||
return
|
||||
|
||||
# Compile our shader.
|
||||
var shader_file := load("res://post_process_grayscale.glsl")
|
||||
var shader_spirv: RDShaderSPIRV = shader_file.get_spirv()
|
||||
|
||||
shader = rd.shader_create_from_spirv(shader_spirv)
|
||||
if shader.is_valid():
|
||||
pipeline = rd.compute_pipeline_create(shader)
|
||||
|
||||
|
||||
# Called by the rendering thread every frame.
|
||||
func _render_callback(p_effect_callback_type: EffectCallbackType, p_render_data: RenderData) -> void:
|
||||
if rd and p_effect_callback_type == EFFECT_CALLBACK_TYPE_POST_TRANSPARENT and pipeline.is_valid():
|
||||
# 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 := p_render_data.get_render_scene_buffers()
|
||||
if render_scene_buffers:
|
||||
# Get our render size, this is the 3D render resolution!
|
||||
var size: Vector2i = render_scene_buffers.get_internal_size()
|
||||
if size.x == 0 and size.y == 0:
|
||||
return
|
||||
|
||||
# We can use a compute shader here.
|
||||
@warning_ignore("integer_division")
|
||||
var x_groups := (size.x - 1) / 8 + 1
|
||||
@warning_ignore("integer_division")
|
||||
var y_groups := (size.y - 1) / 8 + 1
|
||||
var z_groups := 1
|
||||
|
||||
# Create push constant.
|
||||
# Must be aligned to 16 bytes and be in the same order as defined in the shader.
|
||||
var push_constant := PackedFloat32Array([
|
||||
size.x,
|
||||
size.y,
|
||||
0.0,
|
||||
0.0,
|
||||
])
|
||||
|
||||
# Loop through views just in case we're doing stereo rendering. No extra cost if this is mono.
|
||||
var view_count: int = render_scene_buffers.get_view_count()
|
||||
for view in view_count:
|
||||
# Get the RID for our color image, we will be reading from and writing to it.
|
||||
var input_image: RID = 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.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_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()
|
||||
#endregion
|
||||
34
compute/post_shader/post_process_grayscale.glsl
Normal file
34
compute/post_shader/post_process_grayscale.glsl
Normal file
@@ -0,0 +1,34 @@
|
||||
#[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;
|
||||
|
||||
// Our push constant
|
||||
layout(push_constant, std430) uniform Params {
|
||||
vec2 raster_size;
|
||||
vec2 reserved;
|
||||
} params;
|
||||
|
||||
// The code we want to execute in each invocation
|
||||
void main() {
|
||||
ivec2 uv = ivec2(gl_GlobalInvocationID.xy);
|
||||
ivec2 size = ivec2(params.raster_size);
|
||||
|
||||
// Prevent reading/writing out of bounds.
|
||||
if (uv.x >= size.x || uv.y >= size.y) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Read from our color buffer.
|
||||
vec4 color = imageLoad(color_image, uv);
|
||||
|
||||
// Apply our changes.
|
||||
float gray = color.r * 0.2125 + color.g * 0.7154 + color.b * 0.0721;
|
||||
color.rgb = vec3(gray);
|
||||
|
||||
// Write back to our color buffer.
|
||||
imageStore(color_image, uv, color);
|
||||
}
|
||||
14
compute/post_shader/post_process_grayscale.glsl.import
Normal file
14
compute/post_shader/post_process_grayscale.glsl.import
Normal file
@@ -0,0 +1,14 @@
|
||||
[remap]
|
||||
|
||||
importer="glsl"
|
||||
type="RDShaderFile"
|
||||
uid="uid://y08qi2a3t16m"
|
||||
path="res://.godot/imported/post_process_grayscale.glsl-74f080e1b01e56b39260b8709956a520.res"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://post_process_grayscale.glsl"
|
||||
dest_files=["res://.godot/imported/post_process_grayscale.glsl-74f080e1b01e56b39260b8709956a520.res"]
|
||||
|
||||
[params]
|
||||
|
||||
160
compute/post_shader/post_process_shader.gd
Normal file
160
compute/post_shader/post_process_shader.gd
Normal file
@@ -0,0 +1,160 @@
|
||||
@tool
|
||||
extends CompositorEffect
|
||||
class_name PostProcessShader
|
||||
|
||||
const template_shader := """#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;
|
||||
|
||||
// Our push constant.
|
||||
// Must be aligned to 16 bytes, just like the push constant we passed from the script.
|
||||
layout(push_constant, std430) uniform Params {
|
||||
vec2 raster_size;
|
||||
vec2 pad;
|
||||
} params;
|
||||
|
||||
// The code we want to execute in each invocation.
|
||||
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 := "":
|
||||
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.new()
|
||||
var shader_is_dirty := true
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
effect_callback_type = EFFECT_CALLBACK_TYPE_POST_TRANSPARENT
|
||||
rd = RenderingServer.get_rendering_device()
|
||||
|
||||
|
||||
# System notifications, we want to react on the notification that
|
||||
# alerts us we are about to be destroyed.
|
||||
func _notification(what: int) -> void:
|
||||
if what == NOTIFICATION_PREDELETE:
|
||||
if shader.is_valid():
|
||||
# Freeing our shader will also free any dependents such as the pipeline!
|
||||
RenderingServer.free_rid(shader)
|
||||
|
||||
|
||||
#region Code in this region runs on the rendering thread.
|
||||
# Check if our shader has changed and needs to be recompiled.
|
||||
func _check_shader() -> bool:
|
||||
if not rd:
|
||||
return false
|
||||
|
||||
var new_shader_code := ""
|
||||
|
||||
# Check if our shader is dirty.
|
||||
mutex.lock()
|
||||
if shader_is_dirty:
|
||||
new_shader_code = shader_code
|
||||
shader_is_dirty = false
|
||||
mutex.unlock()
|
||||
|
||||
# We don't have a (new) shader?
|
||||
if new_shader_code.is_empty():
|
||||
return pipeline.is_valid()
|
||||
|
||||
# Apply template.
|
||||
new_shader_code = template_shader.replace("#COMPUTE_CODE", new_shader_code);
|
||||
|
||||
# Out with the old.
|
||||
if shader.is_valid():
|
||||
rd.free_rid(shader)
|
||||
shader = RID()
|
||||
pipeline = RID()
|
||||
|
||||
# In with the new.
|
||||
var shader_source := 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()
|
||||
|
||||
|
||||
# Called by the rendering thread every frame.
|
||||
func _render_callback(p_effect_callback_type: EffectCallbackType, p_render_data: RenderData) -> void:
|
||||
if rd and p_effect_callback_type == EFFECT_CALLBACK_TYPE_POST_TRANSPARENT and _check_shader():
|
||||
# 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 := p_render_data.get_render_scene_buffers()
|
||||
if render_scene_buffers:
|
||||
# Get our render size, this is the 3D render resolution!
|
||||
var size: Vector2i = render_scene_buffers.get_internal_size()
|
||||
if size.x == 0 and size.y == 0:
|
||||
return
|
||||
|
||||
# We can use a compute shader here.
|
||||
@warning_ignore("integer_division")
|
||||
var x_groups := (size.x - 1) / 8 + 1
|
||||
@warning_ignore("integer_division")
|
||||
var y_groups := (size.y - 1) / 8 + 1
|
||||
var z_groups := 1
|
||||
|
||||
# Create push constant.
|
||||
# Must be aligned to 16 bytes and be in the same order as defined in the shader.
|
||||
var push_constant := PackedFloat32Array([
|
||||
size.x,
|
||||
size.y,
|
||||
0.0,
|
||||
0.0,
|
||||
])
|
||||
|
||||
# Loop through views just in case we're doing stereo rendering. No extra cost if this is mono.
|
||||
var view_count: int = render_scene_buffers.get_view_count()
|
||||
for view in view_count:
|
||||
# Get the RID for our color image, we will be reading from and writing to it.
|
||||
var input_image: RID = 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.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_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()
|
||||
#endregion
|
||||
44
compute/post_shader/project.godot
Normal file
44
compute/post_shader/project.godot
Normal file
@@ -0,0 +1,44 @@
|
||||
; 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="Compositor Effects (Post-Processing)"
|
||||
run/main_scene="res://main.tscn"
|
||||
config/features=PackedStringArray("4.3", "Forward Plus")
|
||||
config/icon="res://icon.svg"
|
||||
|
||||
[debug]
|
||||
|
||||
gdscript/warnings/untyped_declaration=1
|
||||
|
||||
[display]
|
||||
|
||||
window/stretch/mode="canvas_items"
|
||||
window/stretch/aspect="expand"
|
||||
|
||||
[input]
|
||||
|
||||
toggle_grayscale_effect={
|
||||
"deadzone": 0.5,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":71,"physical_keycode":0,"key_label":0,"unicode":103,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
toggle_shader_effect={
|
||||
"deadzone": 0.5,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":83,"physical_keycode":0,"key_label":0,"unicode":115,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
|
||||
[rendering]
|
||||
|
||||
lights_and_shadows/directional_shadow/soft_shadow_filter_quality=3
|
||||
textures/default_filters/anisotropic_filtering_level=4
|
||||
anti_aliasing/quality/msaa_3d=2
|
||||
0
compute/post_shader/screenshots/.gdignore
Normal file
0
compute/post_shader/screenshots/.gdignore
Normal file
BIN
compute/post_shader/screenshots/post_process_shader.webp
Normal file
BIN
compute/post_shader/screenshots/post_process_shader.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 163 KiB |
Reference in New Issue
Block a user