-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Compositor Effects (Post-Processing) demo (#1058)
Co-authored-by: Hugo Locurcio <[email protected]>
- Loading branch information
1 parent
785e321
commit 909331a
Showing
14 changed files
with
596 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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. |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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", | ||
] | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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" |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} |
Oops, something went wrong.