2023-01-12 20:49:14 +01:00
|
|
|
|
2024-04-30 20:17:58 +02:00
|
|
|
# Custom post-processing
|
2022-03-18 17:46:08 +01:00
|
|
|
|
2024-04-30 20:17:58 +02:00
|
|
|
## Introduction
|
2022-03-18 17:46:08 +01:00
|
|
|
|
2024-03-16 20:56:52 +01:00
|
|
|
Pandemonium provides many post-processing effects out of the box, including Bloom, DOF, and SSAO. Sometimes you
|
2022-03-18 17:46:08 +01:00
|
|
|
want to write your own custom effect. Here's how you can do so.
|
|
|
|
|
2024-03-16 20:56:52 +01:00
|
|
|
Post-processing effects are shaders applied to a frame after Pandemonium rendered it. You first want to render
|
2023-01-12 19:43:03 +01:00
|
|
|
your scene into a `Viewport`, then render the `Viewport`
|
2023-01-12 19:30:47 +01:00
|
|
|
inside a `ViewportTexture` and show it on the screen.
|
2022-03-18 17:46:08 +01:00
|
|
|
|
2024-03-16 20:56:52 +01:00
|
|
|
The easiest way to implement a custom post-processing shader is to use Pandemonium's built-in ability to read from
|
2023-01-12 19:29:11 +01:00
|
|
|
the screen texture. If you're not familiar with this, you should read the `Screen Reading Shaders
|
2023-01-12 20:47:54 +01:00
|
|
|
Tutorial ( doc_screen-reading_shaders )` first.
|
2022-03-18 17:46:08 +01:00
|
|
|
|
2023-01-12 20:55:57 +01:00
|
|
|
Note:
|
|
|
|
|
2024-03-16 20:56:52 +01:00
|
|
|
As of the time of writing, Pandemonium does not support rendering to multiple buffers at the same time. Your
|
2022-03-18 17:46:08 +01:00
|
|
|
post-processing shader will not have access to normals or other render passes. You only have
|
|
|
|
access to the rendered frame.
|
|
|
|
|
2024-04-30 20:17:58 +02:00
|
|
|
## Single pass post-processing
|
2022-03-18 17:46:08 +01:00
|
|
|
|
2023-01-12 19:43:03 +01:00
|
|
|
You will need a `Viewport` to render your scene to, and a scene to render your
|
|
|
|
`Viewport` on the screen. You can use a `ViewportContainer
|
2023-01-12 20:47:54 +01:00
|
|
|
( ViewportContainer )` to display your `Viewport` on the entire screen or inside
|
2023-01-12 19:30:47 +01:00
|
|
|
another `Control` node.
|
2022-03-18 17:46:08 +01:00
|
|
|
|
2023-01-12 20:55:57 +01:00
|
|
|
Note:
|
|
|
|
|
2023-01-12 19:43:03 +01:00
|
|
|
Rendering using a `Viewport` gives you control over
|
2022-03-18 17:46:08 +01:00
|
|
|
how the scene render, including the framerate, and you can use the
|
2023-01-12 19:43:03 +01:00
|
|
|
`ViewportContainer` to render 3D objects in a 2D scene.
|
2022-03-18 17:46:08 +01:00
|
|
|
|
2023-01-12 19:43:03 +01:00
|
|
|
For this demo, we will use a `Node2D` with a `ViewportContainer` and finally a
|
|
|
|
`Viewport`. Your **Scene** tab should look like this:
|
2022-03-18 17:46:08 +01:00
|
|
|
|
2023-01-12 20:16:00 +01:00
|
|
|
![](img/post_hierarchy1.png)
|
2022-03-18 17:46:08 +01:00
|
|
|
|
2023-01-12 19:43:03 +01:00
|
|
|
Inside the `Viewport`, you can have whatever you want. This will contain
|
2022-03-18 17:46:08 +01:00
|
|
|
your main scene. For this tutorial, we will use a field of random boxes:
|
|
|
|
|
2023-01-12 20:16:00 +01:00
|
|
|
![](img/post_boxes.png)
|
2022-03-18 17:46:08 +01:00
|
|
|
|
2023-01-12 19:43:03 +01:00
|
|
|
Add a new `ShaderMaterial` to the `ViewportContainer`, and assign a new
|
|
|
|
shader resource to it. You can access your rendered `Viewport` with the built-in `TEXTURE` uniform.
|
2022-03-18 17:46:08 +01:00
|
|
|
|
2023-01-12 20:55:57 +01:00
|
|
|
Note:
|
|
|
|
|
2023-01-12 19:43:03 +01:00
|
|
|
You can choose not to use a `ViewportContainer`, but if you do so, you will
|
|
|
|
need to create your own uniform in the shader and pass the `Viewport` texture in
|
2022-03-18 17:46:08 +01:00
|
|
|
manually, like so:
|
|
|
|
|
2023-01-12 22:32:46 +01:00
|
|
|
```
|
2024-05-04 14:08:00 +02:00
|
|
|
// Inside the Shader.
|
|
|
|
uniform sampler2D ViewportTexture;
|
|
|
|
```
|
2022-03-18 17:46:08 +01:00
|
|
|
|
|
|
|
And you can pass the texture into the shader from GDScript like so:
|
|
|
|
|
2023-01-12 22:32:46 +01:00
|
|
|
```
|
2024-05-04 14:08:00 +02:00
|
|
|
# In GDScript.
|
|
|
|
func _ready():
|
|
|
|
$Sprite.material.set_shader_param("ViewportTexture", $Viewport.get_texture())
|
|
|
|
```
|
2022-03-18 17:46:08 +01:00
|
|
|
|
|
|
|
Copy the following code to your shader. The above code is a single pass edge detection filter, a
|
2023-01-12 20:57:31 +01:00
|
|
|
`Sobel filter ( https://en.wikipedia.org/wiki/Sobel_operator )`.
|
2022-03-18 17:46:08 +01:00
|
|
|
|
2023-01-12 22:32:46 +01:00
|
|
|
```
|
2024-05-04 14:08:00 +02:00
|
|
|
shader_type canvas_item;
|
|
|
|
|
|
|
|
void fragment() {
|
|
|
|
vec3 col = -8.0 * texture(TEXTURE, UV).xyz;
|
|
|
|
col += texture(TEXTURE, UV + vec2(0.0, SCREEN_PIXEL_SIZE.y)).xyz;
|
|
|
|
col += texture(TEXTURE, UV + vec2(0.0, -SCREEN_PIXEL_SIZE.y)).xyz;
|
|
|
|
col += texture(TEXTURE, UV + vec2(SCREEN_PIXEL_SIZE.x, 0.0)).xyz;
|
|
|
|
col += texture(TEXTURE, UV + vec2(-SCREEN_PIXEL_SIZE.x, 0.0)).xyz;
|
|
|
|
col += texture(TEXTURE, UV + SCREEN_PIXEL_SIZE.xy).xyz;
|
|
|
|
col += texture(TEXTURE, UV - SCREEN_PIXEL_SIZE.xy).xyz;
|
|
|
|
col += texture(TEXTURE, UV + vec2(-SCREEN_PIXEL_SIZE.x, SCREEN_PIXEL_SIZE.y)).xyz;
|
|
|
|
col += texture(TEXTURE, UV + vec2(SCREEN_PIXEL_SIZE.x, -SCREEN_PIXEL_SIZE.y)).xyz;
|
|
|
|
COLOR.xyz = col;
|
|
|
|
}
|
2023-01-12 22:32:46 +01:00
|
|
|
```
|
2022-03-18 17:46:08 +01:00
|
|
|
|
2023-01-12 20:55:57 +01:00
|
|
|
Note:
|
|
|
|
|
2022-03-18 17:46:08 +01:00
|
|
|
The Sobel filter reads pixels in a 9x9 grid around the current pixel and adds them together, using weight.
|
|
|
|
What makes it interesting is that it assigns weights to each pixel; +1 for each of the eight around the
|
|
|
|
center and -8 for the center pixel. The choice of weights is called a "kernel". You can use different
|
|
|
|
kernels to create edge detection filters, outlines, and all sorts of effects.
|
|
|
|
|
2023-01-12 20:16:00 +01:00
|
|
|
![](img/post_outline.png)
|
2022-03-18 17:46:08 +01:00
|
|
|
|
2024-04-30 20:17:58 +02:00
|
|
|
## Multi-pass post-processing
|
2022-03-18 17:46:08 +01:00
|
|
|
|
|
|
|
Some post-processing effects like blur are resource intensive. If you break them down in multiple passes
|
|
|
|
however, you can make them run a lot faster. In a multipass material, each pass takes the result from the
|
|
|
|
previous pass as an input and processes it.
|
|
|
|
|
2023-01-12 19:43:03 +01:00
|
|
|
To make a multi-pass post-processing shader, you stack `Viewport` nodes. In the example above, you
|
|
|
|
rendered the content of one `Viewport` object into the root `Viewport`, through a `ViewportContainer`
|
|
|
|
node. You can do the same thing for a multi-pass shader by rendering the content of one `Viewport` into
|
|
|
|
another and then rendering the last `Viewport` into the root `Viewport`.
|
2022-03-18 17:46:08 +01:00
|
|
|
|
|
|
|
Your scene hierarchy will look something like this:
|
|
|
|
|
2023-01-12 20:16:00 +01:00
|
|
|
![](img/post_hierarchy2.png)
|
2022-03-18 17:46:08 +01:00
|
|
|
|
2024-03-16 20:56:52 +01:00
|
|
|
Pandemonium will render the bottom `Viewport` node first. So if the order of the passes matters for your
|
2023-01-12 19:43:03 +01:00
|
|
|
shaders, make sure that you assign the shader you want to apply first to the lowest `ViewportContainer` in
|
2022-03-18 17:46:08 +01:00
|
|
|
the tree.
|
|
|
|
|
2023-01-12 20:55:57 +01:00
|
|
|
Note:
|
|
|
|
|
2022-03-18 17:46:08 +01:00
|
|
|
You can also render your Viewports separately without nesting them like this. You just
|
|
|
|
need to use two Viewports and to render them one after the other.
|
|
|
|
|
|
|
|
Apart from the node structure, the steps are the same as with the single-pass post-processing shader.
|
|
|
|
|
|
|
|
As an example, you could write a full screen Gaussian blur effect by attaching the following pieces of code
|
2023-01-12 19:30:47 +01:00
|
|
|
to each of the `ViewportContainers`. The order in which you apply the shaders
|
2022-03-18 17:46:08 +01:00
|
|
|
does not matter:
|
|
|
|
|
2023-01-12 22:32:46 +01:00
|
|
|
```
|
2024-05-04 14:08:00 +02:00
|
|
|
shader_type canvas_item;
|
|
|
|
|
|
|
|
// Blurs the screen in the X-direction.
|
|
|
|
void fragment() {
|
|
|
|
vec3 col = texture(TEXTURE, UV).xyz * 0.16;
|
|
|
|
col += texture(TEXTURE, UV + vec2(SCREEN_PIXEL_SIZE.x, 0.0)).xyz * 0.15;
|
|
|
|
col += texture(TEXTURE, UV + vec2(-SCREEN_PIXEL_SIZE.x, 0.0)).xyz * 0.15;
|
|
|
|
col += texture(TEXTURE, UV + vec2(2.0 * SCREEN_PIXEL_SIZE.x, 0.0)).xyz * 0.12;
|
|
|
|
col += texture(TEXTURE, UV + vec2(2.0 * -SCREEN_PIXEL_SIZE.x, 0.0)).xyz * 0.12;
|
|
|
|
col += texture(TEXTURE, UV + vec2(3.0 * SCREEN_PIXEL_SIZE.x, 0.0)).xyz * 0.09;
|
|
|
|
col += texture(TEXTURE, UV + vec2(3.0 * -SCREEN_PIXEL_SIZE.x, 0.0)).xyz * 0.09;
|
|
|
|
col += texture(TEXTURE, UV + vec2(4.0 * SCREEN_PIXEL_SIZE.x, 0.0)).xyz * 0.05;
|
|
|
|
col += texture(TEXTURE, UV + vec2(4.0 * -SCREEN_PIXEL_SIZE.x, 0.0)).xyz * 0.05;
|
|
|
|
COLOR.xyz = col;
|
|
|
|
}
|
2023-01-12 22:32:46 +01:00
|
|
|
```
|
2022-03-18 17:46:08 +01:00
|
|
|
|
2023-01-12 22:32:46 +01:00
|
|
|
```
|
2024-05-04 14:08:00 +02:00
|
|
|
shader_type canvas_item;
|
|
|
|
|
|
|
|
// Blurs the screen in the Y-direction.
|
|
|
|
void fragment() {
|
|
|
|
vec3 col = texture(TEXTURE, UV).xyz * 0.16;
|
|
|
|
col += texture(TEXTURE, UV + vec2(0.0, SCREEN_PIXEL_SIZE.y)).xyz * 0.15;
|
|
|
|
col += texture(TEXTURE, UV + vec2(0.0, -SCREEN_PIXEL_SIZE.y)).xyz * 0.15;
|
|
|
|
col += texture(TEXTURE, UV + vec2(0.0, 2.0 * SCREEN_PIXEL_SIZE.y)).xyz * 0.12;
|
|
|
|
col += texture(TEXTURE, UV + vec2(0.0, 2.0 * -SCREEN_PIXEL_SIZE.y)).xyz * 0.12;
|
|
|
|
col += texture(TEXTURE, UV + vec2(0.0, 3.0 * SCREEN_PIXEL_SIZE.y)).xyz * 0.09;
|
|
|
|
col += texture(TEXTURE, UV + vec2(0.0, 3.0 * -SCREEN_PIXEL_SIZE.y)).xyz * 0.09;
|
|
|
|
col += texture(TEXTURE, UV + vec2(0.0, 4.0 * SCREEN_PIXEL_SIZE.y)).xyz * 0.05;
|
|
|
|
col += texture(TEXTURE, UV + vec2(0.0, 4.0 * -SCREEN_PIXEL_SIZE.y)).xyz * 0.05;
|
|
|
|
COLOR.xyz = col;
|
|
|
|
}
|
2023-01-12 22:32:46 +01:00
|
|
|
```
|
2022-03-18 17:46:08 +01:00
|
|
|
|
|
|
|
Using the above code, you should end up with a full screen blur effect like below.
|
|
|
|
|
2023-01-12 20:16:00 +01:00
|
|
|
![](img/post_blur.png)
|
2022-03-18 17:46:08 +01:00
|
|
|
|
2023-01-12 20:47:54 +01:00
|
|
|
For more information on how `Viewport` nodes work, see the `Viewports Tutorial ( doc_viewports )`.
|