Browse Source

Merge pull request #5946 from RolandMarchand/NewTutorialCustomPostProcessing

Replace the custom post-processing shader tutorial
Max Hilbrunner 3 years ago
parent
commit
3524748f27

+ 102 - 127
tutorials/shaders/custom_postprocessing.rst

@@ -6,167 +6,142 @@ Custom post-processing
 Introduction
 ------------
 
-Godot provides many post-processing effects out of the box, including Bloom, DOF, and SSAO. Sometimes you
-want to write your own custom effect. Here's how you can do so.
+Godot provides many post-processing effects out of the box, including Bloom,
+DOF, and SSAO. However, advanced use cases may require custom effects. This
+article explains how to write your own custom effects.
 
-Post-processing effects are shaders applied to a frame after Godot rendered it. You first want to render
-your scene into a :ref:`Viewport <class_Viewport>`, then render the ``Viewport``
-inside a :ref:`ViewportTexture <class_ViewportTexture>` and show it on the screen.
-
-The easiest way to implement a custom post-processing shader is to use Godot's built-in ability to read from
-the screen texture. If you're not familiar with this, you should read the :ref:`Screen Reading Shaders
-Tutorial <doc_screen-reading_shaders>` first.
-
-.. note::
-
-    As of the time of writing, Godot does not support rendering to multiple buffers at the same time. Your
-    post-processing shader will not have access to normals or other render passes. You only have
-    access to the rendered frame.
+The easiest way to implement a custom post-processing shader is to use Godot's
+built-in ability to read from the screen texture. If you're not familiar with
+this, you should read the
+:ref:`Screen Reading Shaders Tutorial <doc_screen-reading_shaders>` first.
 
 Single pass post-processing
 ---------------------------
 
-You will need a ``Viewport`` to render your scene to, and a scene to render your
-``Viewport`` on the screen. You can use a :ref:`ViewportContainer
-<class_ViewportContainer>` to display your ``Viewport`` on the entire screen or inside
-another :ref:`Control <class_Control>` node.
-
-.. note::
+Post-processing effects are shaders applied to a frame after Godot has rendered
+it. To apply a shader to a frame, create a :ref:`CanvasLayer
+<class_CanvasLayer>`, and give it a :ref:`ColorRect <class_ColorRect>`. Assign a
+new :ref:`ShaderMaterial <class_ShaderMaterial>` to the newly created
+``ColorRect``, and set the ``ColorRect``'s layout to "Full Rect".
 
-    Rendering using a ``Viewport`` gives you control over
-    how the scene render, including the framerate, and you can use the
-    ``ViewportContainer`` to render 3D objects in a 2D scene.
+Your scene tree will look something like this:
 
-For this demo, we will use a :ref:`Node2D <class_Node2D>` with a ``ViewportContainer`` and finally a
-``Viewport``. Your **Scene** tab should look like this:
+.. image:: img/post_tree1.png
 
-.. image:: img/post_hierarchy1.png
-
-Inside the ``Viewport``, you can have whatever you want. This will contain
-your main scene. For this tutorial, we will use a field of random boxes:
-
-.. image:: img/post_boxes.png
+.. note::
 
-Add a new :ref:`ShaderMaterial <class_ShaderMaterial>` to the ``ViewportContainer``, and assign a new
-shader resource to it. You can access your rendered ``Viewport`` with the built-in ``TEXTURE`` uniform.
+   Another more efficient method is to use a :ref:`BackBufferCopy
+   <class_BackBufferCopy>` to copy a region of the screen to a buffer and to
+   access it in a shader script through ``texture(SCREEN_TEXTURE, ...)``.
 
 .. note::
 
-    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
-    manually, like so:
+    As of the time of writing, Godot does not support rendering to multiple
+    buffers at the same time. Your post-processing shader will not have access
+    to normals or other render passes. You only have access to the rendered
+    frame.
 
-    .. code-block:: glsl
+For this demo, we will use this :ref:`Sprite <class_Sprite2D>` of a sheep.
 
-      // Inside the Shader.
-      uniform sampler2D ViewportTexture;
+.. image:: img/post_example1.png
 
-    And you can pass the texture into the shader from GDScript like so:
+Assign a new :ref:`Shader <class_Shader>` to the ``ColorRect``'s
+``ShaderMaterial``. You can access the frame's texture and UV with the built in
+``SCREEN_TEXTURE`` and ``SCREEN_UV`` uniforms.
 
-    ::
+Copy the following code to your shader. The code below is a hex pixelization
+shader by `arlez80 <https://bitbucket.org/arlez80/hex-mosaic/src/master/>`_,
 
-      # In GDScript.
-      func _ready():
-        $Sprite.material.set_shader_param("ViewportTexture", $Viewport.get_texture())
+.. code-block:: glsl
 
-Copy the following code to your shader. The above code is a single pass edge detection filter, a
-`Sobel filter <https://en.wikipedia.org/wiki/Sobel_operator>`_.
+    shader_type canvas_item;
 
-.. code-block:: glsl
+    uniform vec2 size = vec2(32.0, 28.0);
 
-  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;
-  }
+    void fragment() {
+            vec2 norm_size = size * SCREEN_PIXEL_SIZE;
+            bool half = mod(SCREEN_UV.y / 2.0, norm_size.y) / norm_size.y < 0.5;
+            vec2 uv = SCREEN_UV + vec2(norm_size.x * 0.5 * float(half), 0.0);
+            vec2 center_uv = floor(uv / norm_size) * norm_size;
+            vec2 norm_uv = mod(uv, norm_size) / norm_size;
+            center_uv += mix(vec2(0.0, 0.0),
+                             mix(mix(vec2(norm_size.x, -norm_size.y),
+                                     vec2(0.0, -norm_size.y),
+                                     float(norm_uv.x < 0.5)),
+                                 mix(vec2(0.0, -norm_size.y),
+                                     vec2(-norm_size.x, -norm_size.y),
+                                     float(norm_uv.x < 0.5)),
+                                 float(half)),
+                             float(norm_uv.y < 0.3333333) * float(norm_uv.y / 0.3333333 < (abs(norm_uv.x - 0.5) * 2.0)));
 
-.. note::
+            COLOR = textureLod(SCREEN_TEXTURE, center_uv, 0.0);
+    }
 
-    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.
+The sheep will look something like this:
 
-    .. image:: img/post_outline.png
+.. image:: img/post_example2.png
 
 Multi-pass post-processing
 --------------------------
 
-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.
-
-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``.
-
-Your scene hierarchy will look something like this:
-
-.. image:: img/post_hierarchy2.png
-
-Godot will render the bottom ``Viewport`` node first. So if the order of the passes matters for your
-shaders, make sure that you assign the shader you want to apply first to the lowest ``ViewportContainer`` in
-the tree.
+Some post-processing effects like blurs are resource intensive. You can make
+them run a lot faster if you break them down in multiple passes. In a multipass
+material, each pass takes the result from the previous pass as an input and
+processes it.
 
-.. note::
+To produce a multi-pass post-processing shader, you stack ``CanvasLayer`` and
+``ColorRect`` nodes. In the example above, you use a ``CanvasLayer`` object to
+render a shader using the frame on the layer below. Apart from the node
+structure, the steps are the same as with the single-pass post-processing
+shader.
 
-    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.
+Your scene tree will look something like this:
 
-Apart from the node structure, the steps are the same as with the single-pass post-processing shader.
+.. image:: img/post_tree2.png
 
-As an example, you could write a full screen Gaussian blur effect by attaching the following pieces of code
-to each of the :ref:`ViewportContainers <class_ViewportContainer>`. The order in which you apply the shaders
-does not matter:
+As an example, you could write a full screen Gaussian blur effect by attaching
+the following pieces of code to each of the ``ColorRect`` nodes. The order in
+which you apply the shaders depends on the position of the ``CanvasLayer`` in
+the scene tree, higher means sooner. For this blur shader, the order does not
+matter.
 
 .. code-block:: glsl
 
-  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;
-  }
+    shader_type canvas_item;
+
+    // Blurs the screen in the X-direction.
+    void fragment() {
+        vec3 col = texture(SCREEN_TEXTURE, SCREEN_UV).xyz * 0.16;
+        col += texture(SCREEN_TEXTURE, SCREEN_UV + vec2(SCREEN_PIXEL_SIZE.x, 0.0)).xyz * 0.15;
+        col += texture(SCREEN_TEXTURE, SCREEN_UV + vec2(-SCREEN_PIXEL_SIZE.x, 0.0)).xyz * 0.15;
+        col += texture(SCREEN_TEXTURE, SCREEN_UV + vec2(2.0 * SCREEN_PIXEL_SIZE.x, 0.0)).xyz * 0.12;
+        col += texture(SCREEN_TEXTURE, SCREEN_UV + vec2(2.0 * -SCREEN_PIXEL_SIZE.x, 0.0)).xyz * 0.12;
+        col += texture(SCREEN_TEXTURE, SCREEN_UV + vec2(3.0 * SCREEN_PIXEL_SIZE.x, 0.0)).xyz * 0.09;
+        col += texture(SCREEN_TEXTURE, SCREEN_UV + vec2(3.0 * -SCREEN_PIXEL_SIZE.x, 0.0)).xyz * 0.09;
+        col += texture(SCREEN_TEXTURE, SCREEN_UV + vec2(4.0 * SCREEN_PIXEL_SIZE.x, 0.0)).xyz * 0.05;
+        col += texture(SCREEN_TEXTURE, SCREEN_UV + vec2(4.0 * -SCREEN_PIXEL_SIZE.x, 0.0)).xyz * 0.05;
+        COLOR.xyz = col;
+    }
 
 .. code-block:: glsl
 
-  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;
-  }
-
-Using the above code, you should end up with a full screen blur effect like below.
-
-.. image:: img/post_blur.png
-
-For more information on how ``Viewport`` nodes work, see the :ref:`Viewports Tutorial <doc_viewports>`.
+    shader_type canvas_item;
+
+    // Blurs the screen in the Y-direction.
+    void fragment() {
+        vec3 col = texture(SCREEN_TEXTURE, SCREEN_UV).xyz * 0.16;
+        col += texture(SCREEN_TEXTURE, SCREEN_UV + vec2(0.0, SCREEN_PIXEL_SIZE.y)).xyz * 0.15;
+        col += texture(SCREEN_TEXTURE, SCREEN_UV + vec2(0.0, -SCREEN_PIXEL_SIZE.y)).xyz * 0.15;
+        col += texture(SCREEN_TEXTURE, SCREEN_UV + vec2(0.0, 2.0 * SCREEN_PIXEL_SIZE.y)).xyz * 0.12;
+        col += texture(SCREEN_TEXTURE, SCREEN_UV + vec2(0.0, 2.0 * -SCREEN_PIXEL_SIZE.y)).xyz * 0.12;
+        col += texture(SCREEN_TEXTURE, SCREEN_UV + vec2(0.0, 3.0 * SCREEN_PIXEL_SIZE.y)).xyz * 0.09;
+        col += texture(SCREEN_TEXTURE, SCREEN_UV + vec2(0.0, 3.0 * -SCREEN_PIXEL_SIZE.y)).xyz * 0.09;
+        col += texture(SCREEN_TEXTURE, SCREEN_UV + vec2(0.0, 4.0 * SCREEN_PIXEL_SIZE.y)).xyz * 0.05;
+        col += texture(SCREEN_TEXTURE, SCREEN_UV + vec2(0.0, 4.0 * -SCREEN_PIXEL_SIZE.y)).xyz * 0.05;
+        COLOR.xyz = col;
+    }
+
+Using the above code, you should end up with a full screen blur effect like
+below.
+
+.. image:: img/post_example3.png

BIN
tutorials/shaders/img/post_blur.png


BIN
tutorials/shaders/img/post_boxes.png


BIN
tutorials/shaders/img/post_color_rect.png


BIN
tutorials/shaders/img/post_example1.png


BIN
tutorials/shaders/img/post_example2.png


BIN
tutorials/shaders/img/post_example3.png


BIN
tutorials/shaders/img/post_hierarchy1.png


BIN
tutorials/shaders/img/post_hierarchy2.png


BIN
tutorials/shaders/img/post_outline.png


BIN
tutorials/shaders/img/post_tree1.png


BIN
tutorials/shaders/img/post_tree2.png