Selaa lähdekoodia

added advanced postprocessing tutorial (#1897)

Clay John 6 vuotta sitten
vanhempi
commit
c26b17b377
2 muutettua tiedostoa jossa 196 lisäystä ja 0 poistoa
  1. 195 0
      tutorials/shading/advanced_postprocessing.rst
  2. 1 0
      tutorials/shading/index.rst

+ 195 - 0
tutorials/shading/advanced_postprocessing.rst

@@ -0,0 +1,195 @@
+.. _doc_advanced_postprocessing:
+
+Advanced post-processing
+========================
+
+Introduction
+------------
+
+This tutorial describes an advanced method for post-processing in Godot. 
+In particular, it will explain how to write a post-processing shader that 
+uses the depth buffer. You should already be familiar with post-processing 
+generally and, in particular, with the methods outlined in the :ref:`custom post-processing tutorial <doc_custom_postprocessing>`.
+
+In the previous post-processing tutorial we rendered the scene to a :ref:`Viewport <class_Viewport>` 
+and then rendered the Viewport in a :ref:`ViewportContainer <class_ViewportContainer>` 
+to the main scene. One limitation of this method is that we could not access the 
+depth buffer because the depth buffer is only available in spatial shaders and 
+Viewports do not maintain depth information.
+
+Full screen quad
+----------------
+
+In the :ref:`custom post-processing tutorial <doc_custom_postprocessing>` we 
+covered how to use a Viewport to make custom post-processing effects. There are 
+two main drawbacks of using a Viewport:
+
+1. The depth buffer cannot be accessed
+2. The effect of the post-processing shader is not visible in the editor
+
+To get around the limitation on using the depth buffer, use a :ref:`MeshInstance <class_MeshInstance>` 
+with a :ref:`QuadMesh <class_QuadMesh>` primitive. This allows us to use a spatial 
+shader and to access the depth texture of the scene. Next, use a vertex shader 
+to make the quad cover the screen at all times so that the post-processing 
+effect will be applied at all times, including in the editor.
+
+First, create a new MeshInstance and set its mesh to a QuadMesh. This creates a quad 
+centered at position ``(0, 0, 0)`` with a width and height of ``1``. Set the width
+and height to ``2``. Right now the quad occupies a position in world space at the 
+origin, however, we want it to move with the camera so that it always covers the 
+entire screen. To do this, we will bypass the coordinate transforms that translate 
+the vertex positions through the difference coordinate spaces and treat the vertices 
+as if they were already in clip space. 
+
+The vertex shader expects coordinates to be output in clip space, which are coordinates 
+ranging from ``-1`` at the left and bottom of the screen to ``1`` at the top and right 
+of the screen. This is why the QuadMesh needs to have height and width of ``2``. 
+Godot handles the transform from model to view space to clip space behind the scenes, 
+so we need to nullify the effects of Godot's transformations.
+
+First, set ``render_mode`` to ``skip_vertex_transform``, which removes the transformation
+from model space to view space. Godot handles the transformation from view space to clip space
+behind the scenes with the ``PROJECTION_MATRIX`` even when ``skip_vertex_transform`` is set. 
+Nullify the projection matrix by setting it to the `identity matrix <https://en.wikipedia.org/wiki/Identity_matrix>`_.
+In Godot this is done by passing a `1` to a ``mat4``.
+
+.. code-block:: glsl
+
+  shader_type spatial;
+  render_mode skip_vertex_transform, unshaded;
+
+  void vertex() {
+    PROJECTION_MATRIX = mat4(1.0);
+  }
+
+Even with this vertex shader the quad keeps disappearing. This is due to frustum 
+culling which is done on the CPU. Frustum culling uses the camera matrix and the
+AABBs of Meshes to determine if the Mesh will be visible *before* passing it to the GPU.
+The CPU has no knowledge of what we are doing with the vertices so it assumes the 
+coordinates specified refer to world positions, not clip space positions, which results
+in Godot culling the quad when we turn away from the center of the scene. In 
+order to keep the quad from being culled there are a few options:
+
+1. Add the QuadMesh as a child to the camera, so the camera is always pointed at it
+2. Make the AABB as large as possible so it can always be seen
+
+The second option ensures that the quad is visible in the editor. While the first
+option guarantees that it will still be visible even if the camera moves outside the AABB.
+You can also use both options.
+
+Depth texture
+-------------
+
+To read from the depth texture, perform a texture lookup using ``texture()`` and
+the uniform variable ``DEPTH_TEXTURE``.
+
+.. code-block:: glsl
+
+  float depth = texture(DEPTH_TEXTURE, SCREEN_UV).x;
+
+.. note:: Similar to accessing the screen texture, accessing the depth texture is only
+          possible when reading from the current viewport. The depth texture cannot be 
+          accessed from another viewport you have rendered to.
+
+The values returned by ``DEPTH_TEXTURE`` are between ``0`` and ``1`` and are nonlinear. 
+When displaying depth directly from the ``DEPTH_TEXTURE`` everything will look almost 
+white unless it is very close. This is because the depth buffer stores objects closer
+to the camera using more bits than those further, so most of the detail in depth
+buffer is found close to the camera. In order to make the depth value align with world or 
+model coordinates we need to linearise the value. When we apply the projection matrix to the 
+vertex position the z value is made nonlinear, so to linearise it we multiply it by the 
+inverse of the projection matrix which in Godot is accessible with the variable 
+``INV_PROJECTION_MATRIX``
+
+First take the screen space coordinates and transform them into normalized device 
+coordinates (NDC). NDC run from ``-1`` to ``1``, similar to clip space coordinates. 
+Reconstruct the NDC using ``SCREEN_UV`` for the ``x`` and ``y`` axis, and 
+the depth value for ``z``.
+
+.. code-block:: glsl
+
+  void fragment() {
+    float depth = texture(DEPTH_TEXTURE, SCREEN_UV).x;
+    vec3 ndc = vec3(SCREEN_UV, depth) * 2.0 - 1.0;
+  }
+
+Convert NDC to view space by multiplying the NDC by ``INV_PROJECTION_MATRIX``.
+Recall that view space gives positions relative to the camera so the ``z`` value will give us
+the distance to the point.
+
+.. code-block:: glsl
+
+  void fragment() {
+    ...
+    vec4 view = INV_PROJECTION_MATRIX * vec4(ndc, 1.0);
+    view.xyz /= view.w;
+    float linear_depth = -view.z;
+  }
+
+Because the camera is facing the negative ``z`` direction the position will have a negative ``z`` value.
+In order to get a usable depth value we have to negate ``view.z``.
+
+The world position can be constructed from the depth buffer using the following code. Note
+that the ``CAMERA_MATRIX`` is needed to transform the position from view space into world space so 
+it needs to be passed to the fragment shader with a varying.
+
+.. code-block:: glsl
+
+  varying mat4 CAMERA;
+
+  void vertex() {
+    CAMERA = CAMERA_MATRIX;
+  }
+
+  void fragment() {
+    ...
+    vec4 world = CAMERA * INV_PROJECTION_MATRIX * vec4(ndc, 1.0);
+    vec3 world_position = world.xyz / world.w;
+  }
+
+An optimization
+---------------
+
+You can benefit from using a single large triangle rather than using a full 
+screen quad. The reason for this is explained `here <https://michaldrobot.com/2014/04/01/gcn-execution-patterns-in-full-screen-passes>`_. 
+However, the benefit is quite small and only beneficial when running especially 
+complex fragment shaders. 
+
+Set the Mesh in the MeshInstance to an :ref:`ArrayMesh <class_ArrayMesh>`. An 
+ArrayMesh is a tool that allows you to easily construct a Mesh from Arrays for
+vertices, normals, colors, etc.
+
+Now, attach a script to the MeshInstance and use the following code:
+
+::
+
+  extends MeshInstance
+
+  func _ready():
+    # Create a single triangle out of vertices
+    var verts = PoolVector3Array()
+    verts.append(Vector3(-1.0, -1.0, 0.0))
+    verts.append(Vector3(-1.0, 3.0, 0.0))
+    verts.append(Vector3(3.0, -1.0, 0.0))
+    
+    # Create an array of arrays
+    # This could contain normals, colors, uvs, etc.
+    var mesh_array = []
+    mesh_array.resize(Mesh.ARRAY_MAX) #required size for ArrayMesh Array
+    mesh_array[Mesh.ARRAY_VERTEX] = verts #position of vertex array in ArrayMesh Array
+    
+    # Create mesh from mesh_array
+    mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, mesh_array)
+
+.. note:: The triangle is specified in normalized device coordinates. Recall, NDC run 
+          from ``-1`` to ``1`` in both the ``x`` and ``y`` directions. This makes the screen
+          ``2`` units wide and ``2`` units tall. In order to cover the entire screen with 
+          a single triangle, use a triangle that is ``4`` units wide and ``4`` 
+          units tall, double its height and width.
+
+Assign the same vertex shader from above and everything should look exactly the same.
+
+The one drawback to using an ArrayMesh over using a QuadMesh is that the ArrayMesh 
+is not visible in the editor because the triangle is not constructed until the scene 
+is run. To get around that, construct a single triangle Mesh in a modelling program 
+and use that in the MeshInstance instead.

+ 1 - 0
tutorials/shading/index.rst

@@ -11,3 +11,4 @@ Shading
    screen-reading_shaders
    migrating_to_godot_shader_language
    vertex_displacement_with_shaders
+   advanced_postprocessing