custom_postprocessing.rst 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. .. _doc_custom_postprocessing:
  2. Custom post-processing
  3. ======================
  4. Introduction
  5. ------------
  6. Godot provides many post-processing effects out of the box, including Bloom,
  7. DOF, and SSAO, which are described in :ref:`doc_environment_and_post_processing`.
  8. However, advanced use cases may require custom effects. This article explains how
  9. to write your own custom effects.
  10. The easiest way to implement a custom post-processing shader is to use Godot's
  11. built-in ability to read from the screen texture. If you're not familiar with
  12. this, you should read the
  13. :ref:`Screen Reading Shaders Tutorial <doc_screen-reading_shaders>` first.
  14. Single pass post-processing
  15. ---------------------------
  16. Post-processing effects are shaders applied to a frame after Godot has rendered
  17. it. To apply a shader to a frame, create a :ref:`CanvasLayer
  18. <class_CanvasLayer>`, and give it a :ref:`ColorRect <class_ColorRect>`. Assign a
  19. new :ref:`ShaderMaterial <class_ShaderMaterial>` to the newly created
  20. ``ColorRect``, and set the ``ColorRect``'s anchor preset to Full Rect:
  21. .. figure:: img/custom_postprocessing_anchors_preset_full_rect.webp
  22. :align: center
  23. :alt: Setting the anchor preset to Full Rect on the ColorRect node
  24. Setting the anchor preset to Full Rect on the ColorRect node
  25. Your scene tree will look something like this:
  26. .. image:: img/post_tree1.png
  27. .. note::
  28. Another more efficient method is to use a :ref:`BackBufferCopy
  29. <class_BackBufferCopy>` to copy a region of the screen to a buffer and to
  30. access it in a shader script through a ``sampler2D`` using
  31. ``hint_screen_texture``.
  32. .. note::
  33. As of the time of writing, Godot does not support rendering to multiple
  34. buffers at the same time. Your post-processing shader will not have access
  35. to other render passes and buffers not exposed by Godot (such as depth or
  36. normal/roughness). You only have access to the rendered frame and buffers
  37. exposed by Godot as samplers.
  38. For this demo, we will use this :ref:`Sprite <class_Sprite2D>` of a sheep.
  39. .. image:: img/post_example1.png
  40. Assign a new :ref:`Shader <class_Shader>` to the ``ColorRect``'s
  41. ``ShaderMaterial``. You can access the frame's texture and UV with a
  42. ``sampler2D`` using ``hint_screen_texture`` and the built-in ``SCREEN_UV``
  43. uniforms.
  44. Copy the following code to your shader. The code below is a hex pixelization
  45. shader by `arlez80 <https://bitbucket.org/arlez80/hex-mosaic/src/master/>`_,
  46. .. code-block:: glsl
  47. shader_type canvas_item;
  48. uniform vec2 size = vec2(32.0, 28.0);
  49. // If you intend to read from mipmaps with `textureLod()` LOD values greater than `0.0`,
  50. // use `filter_nearest_mipmap` instead. This shader doesn't require it.
  51. uniform sampler2D screen_texture : hint_screen_texture, repeat_disable, filter_nearest;
  52. void fragment() {
  53. vec2 norm_size = size * SCREEN_PIXEL_SIZE;
  54. bool less_than_half = mod(SCREEN_UV.y / 2.0, norm_size.y) / norm_size.y < 0.5;
  55. vec2 uv = SCREEN_UV + vec2(norm_size.x * 0.5 * float(less_than_half), 0.0);
  56. vec2 center_uv = floor(uv / norm_size) * norm_size;
  57. vec2 norm_uv = mod(uv, norm_size) / norm_size;
  58. center_uv += mix(vec2(0.0, 0.0),
  59. mix(mix(vec2(norm_size.x, -norm_size.y),
  60. vec2(0.0, -norm_size.y),
  61. float(norm_uv.x < 0.5)),
  62. mix(vec2(0.0, -norm_size.y),
  63. vec2(-norm_size.x, -norm_size.y),
  64. float(norm_uv.x < 0.5)),
  65. float(less_than_half)),
  66. float(norm_uv.y < 0.3333333) * float(norm_uv.y / 0.3333333 < (abs(norm_uv.x - 0.5) * 2.0)));
  67. COLOR = textureLod(screen_texture, center_uv, 0.0);
  68. }
  69. The sheep will look something like this:
  70. .. image:: img/post_example2.png
  71. Multi-pass post-processing
  72. --------------------------
  73. Some post-processing effects like blurs are resource intensive. You can make
  74. them run a lot faster if you break them down in multiple passes. In a multipass
  75. material, each pass takes the result from the previous pass as an input and
  76. processes it.
  77. To produce a multi-pass post-processing shader, you stack ``CanvasLayer`` and
  78. ``ColorRect`` nodes. In the example above, you use a ``CanvasLayer`` object to
  79. render a shader using the frame on the layer below. Apart from the node
  80. structure, the steps are the same as with the single-pass post-processing
  81. shader.
  82. Your scene tree will look something like this:
  83. .. image:: img/post_tree2.png
  84. As an example, you could write a full screen Gaussian blur effect by attaching
  85. the following pieces of code to each of the ``ColorRect`` nodes. The order in
  86. which you apply the shaders depends on the position of the ``CanvasLayer`` in
  87. the scene tree, higher means sooner. For this blur shader, the order does not
  88. matter.
  89. .. code-block:: glsl
  90. shader_type canvas_item;
  91. uniform sampler2D screen_texture : hint_screen_texture, repeat_disable, filter_nearest;
  92. // Blurs the screen in the X-direction.
  93. void fragment() {
  94. vec3 col = texture(screen_texture, SCREEN_UV).xyz * 0.16;
  95. col += texture(screen_texture, SCREEN_UV + vec2(SCREEN_PIXEL_SIZE.x, 0.0)).xyz * 0.15;
  96. col += texture(screen_texture, SCREEN_UV + vec2(-SCREEN_PIXEL_SIZE.x, 0.0)).xyz * 0.15;
  97. col += texture(screen_texture, SCREEN_UV + vec2(2.0 * SCREEN_PIXEL_SIZE.x, 0.0)).xyz * 0.12;
  98. col += texture(screen_texture, SCREEN_UV + vec2(2.0 * -SCREEN_PIXEL_SIZE.x, 0.0)).xyz * 0.12;
  99. col += texture(screen_texture, SCREEN_UV + vec2(3.0 * SCREEN_PIXEL_SIZE.x, 0.0)).xyz * 0.09;
  100. col += texture(screen_texture, SCREEN_UV + vec2(3.0 * -SCREEN_PIXEL_SIZE.x, 0.0)).xyz * 0.09;
  101. col += texture(screen_texture, SCREEN_UV + vec2(4.0 * SCREEN_PIXEL_SIZE.x, 0.0)).xyz * 0.05;
  102. col += texture(screen_texture, SCREEN_UV + vec2(4.0 * -SCREEN_PIXEL_SIZE.x, 0.0)).xyz * 0.05;
  103. COLOR.xyz = col;
  104. }
  105. .. code-block:: glsl
  106. shader_type canvas_item;
  107. uniform sampler2D screen_texture : hint_screen_texture, repeat_disable, filter_nearest;
  108. // Blurs the screen in the Y-direction.
  109. void fragment() {
  110. vec3 col = texture(screen_texture, SCREEN_UV).xyz * 0.16;
  111. col += texture(screen_texture, SCREEN_UV + vec2(0.0, SCREEN_PIXEL_SIZE.y)).xyz * 0.15;
  112. col += texture(screen_texture, SCREEN_UV + vec2(0.0, -SCREEN_PIXEL_SIZE.y)).xyz * 0.15;
  113. col += texture(screen_texture, SCREEN_UV + vec2(0.0, 2.0 * SCREEN_PIXEL_SIZE.y)).xyz * 0.12;
  114. col += texture(screen_texture, SCREEN_UV + vec2(0.0, 2.0 * -SCREEN_PIXEL_SIZE.y)).xyz * 0.12;
  115. col += texture(screen_texture, SCREEN_UV + vec2(0.0, 3.0 * SCREEN_PIXEL_SIZE.y)).xyz * 0.09;
  116. col += texture(screen_texture, SCREEN_UV + vec2(0.0, 3.0 * -SCREEN_PIXEL_SIZE.y)).xyz * 0.09;
  117. col += texture(screen_texture, SCREEN_UV + vec2(0.0, 4.0 * SCREEN_PIXEL_SIZE.y)).xyz * 0.05;
  118. col += texture(screen_texture, SCREEN_UV + vec2(0.0, 4.0 * -SCREEN_PIXEL_SIZE.y)).xyz * 0.05;
  119. COLOR.xyz = col;
  120. }
  121. Using the above code, you should end up with a full screen blur effect like
  122. below.
  123. .. image:: img/post_example3.png