compositor.rst 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377
  1. .. _doc_compositor:
  2. The Compositor
  3. ==============
  4. The compositor is a new feature in Godot 4 that allows control over
  5. the rendering pipeline when rendering the contents of a :ref:`Viewport <class_Viewport>`.
  6. It can be configured on a :ref:`WorldEnvironment <class_WorldEnvironment>`
  7. node where it applies to all Viewports, or it can be configured on
  8. a :ref:`Camera3D <class_Camera3D>` and apply only to
  9. the Viewport using that camera.
  10. The :ref:`Compositor <class_Compositor>` resource is used to configure
  11. the compositor. To get started, create a new compositor on the appropriate node:
  12. .. image:: img/new_compositor.webp
  13. .. note::
  14. The compositor is currently a feature that is only supported by
  15. the Mobile and Forward+ renderers.
  16. Compositor effects
  17. ------------------
  18. Compositor effects allow you to insert additional logic into the rendering
  19. pipeline at various stages. This is an advanced feature that requires
  20. a high level of understanding of the rendering pipeline to use to
  21. its best advantage.
  22. As the core logic of the compositor effect is called from the rendering
  23. pipeline it is important to note that this logic will thus run within
  24. the thread on which rendering takes place.
  25. Care needs to be taken to ensure we don't run into threading issues.
  26. To illustrate how to use compositor effects we'll create a simple
  27. post processing effect that allows you to write your own shader code
  28. and apply this full screen through a compute shader.
  29. You can find the finished demo project `here <https://github.com/godotengine/godot-demo-projects/tree/master/compute/post_shader>`_.
  30. We start by creating a new script called ``post_process_shader.gd``.
  31. We'll make this a tool script so we can see the compositor effect work in the editor.
  32. We need to extend our node from :ref:`CompositorEffect <class_CompositorEffect>`.
  33. We must also give our script a class name.
  34. .. code-block:: gdscript
  35. :caption: post_process_shader.gd
  36. @tool
  37. extends CompositorEffect
  38. class_name PostProcessShader
  39. Next we're going to define a constant for our shader template code.
  40. This is the boilerplate code that makes our compute shader work.
  41. .. code-block:: gdscript
  42. const template_shader: String = """
  43. #version 450
  44. // Invocations in the (x, y, z) dimension
  45. layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
  46. layout(rgba16f, set = 0, binding = 0) uniform image2D color_image;
  47. // Our push constant
  48. layout(push_constant, std430) uniform Params {
  49. vec2 raster_size;
  50. vec2 reserved;
  51. } params;
  52. // The code we want to execute in each invocation
  53. void main() {
  54. ivec2 uv = ivec2(gl_GlobalInvocationID.xy);
  55. ivec2 size = ivec2(params.raster_size);
  56. if (uv.x >= size.x || uv.y >= size.y) {
  57. return;
  58. }
  59. vec4 color = imageLoad(color_image, uv);
  60. #COMPUTE_CODE
  61. imageStore(color_image, uv, color);
  62. }
  63. """
  64. For more information on how compute shaders work,
  65. please check :ref:`Using compute shaders <doc_compute_shaders>`.
  66. The important bit here is that for every pixel on our screen,
  67. our ``main`` function is executed and inside of this we load
  68. the current color value of our pixel, execute our user code,
  69. and write our modified color back to our color image.
  70. ``#COMPUTE_CODE`` gets replaced by our user code.
  71. In order to set our user code, we need an export variable.
  72. We'll also define a few script variables we'll be using:
  73. .. code-block:: gdscript
  74. @export_multiline var shader_code: String = "":
  75. set(value):
  76. mutex.lock()
  77. shader_code = value
  78. shader_is_dirty = true
  79. mutex.unlock()
  80. var rd: RenderingDevice
  81. var shader: RID
  82. var pipeline: RID
  83. var mutex: Mutex = Mutex.new()
  84. var shader_is_dirty: bool = true
  85. Note the use of a :ref:`Mutex <class_Mutex>` in our code.
  86. Most of our implementation gets called from the rendering engine
  87. and thus runs within our rendering thread.
  88. We need to ensure that we set our new shader code, and mark our
  89. shader code as dirty, without our render thread accessing this
  90. data at the same time.
  91. Next we initialize our effect.
  92. .. code-block:: gdscript
  93. # Called when this resource is constructed.
  94. func _init():
  95. effect_callback_type = EFFECT_CALLBACK_TYPE_POST_TRANSPARENT
  96. rd = RenderingServer.get_rendering_device()
  97. The main thing here is setting our ``effect_callback_type`` which tells
  98. the rendering engine at what stage of the render pipeline to call our code.
  99. .. note::
  100. Currently we only have access to the stages of the 3D rendering pipeline!
  101. We also get a reference to our rendering device, which will come in very handy.
  102. We also need to clean up after ourselves, for this we react to the
  103. ``NOTIFICATION_PREDELETE`` notification:
  104. .. code-block:: gdscript
  105. # System notifications, we want to react on the notification that
  106. # alerts us we are about to be destroyed.
  107. func _notification(what):
  108. if what == NOTIFICATION_PREDELETE:
  109. if shader.is_valid():
  110. # Freeing our shader will also free any dependents such as the pipeline!
  111. rd.free_rid(shader)
  112. Note that we do not use our mutex here even though we create our shader inside
  113. of our render thread.
  114. The methods on our rendering server are thread safe and ``free_rid`` will
  115. be postponed cleaning up the shader until after any frames currently being
  116. rendered are finished.
  117. Also note that we are not freeing our pipeline. The rendering device does
  118. dependency tracking and as the pipeline is dependent on the shader, it will
  119. be automatically freed when the shader is destructed.
  120. From this point onwards our code will run on the rendering thread.
  121. Our next step is a helper function that will recompile the shader if the user
  122. code was changed.
  123. .. code-block:: gdscript
  124. # Check if our shader has changed and needs to be recompiled.
  125. func _check_shader() -> bool:
  126. if not rd:
  127. return false
  128. var new_shader_code: String = ""
  129. # Check if our shader is dirty.
  130. mutex.lock()
  131. if shader_is_dirty:
  132. new_shader_code = shader_code
  133. shader_is_dirty = false
  134. mutex.unlock()
  135. # We don't have a (new) shader?
  136. if new_shader_code.is_empty():
  137. return pipeline.is_valid()
  138. # Apply template.
  139. new_shader_code = template_shader.replace("#COMPUTE_CODE", new_shader_code);
  140. # Out with the old.
  141. if shader.is_valid():
  142. rd.free_rid(shader)
  143. shader = RID()
  144. pipeline = RID()
  145. # In with the new.
  146. var shader_source: RDShaderSource = RDShaderSource.new()
  147. shader_source.language = RenderingDevice.SHADER_LANGUAGE_GLSL
  148. shader_source.source_compute = new_shader_code
  149. var shader_spirv: RDShaderSPIRV = rd.shader_compile_spirv_from_source(shader_source)
  150. if shader_spirv.compile_error_compute != "":
  151. push_error(shader_spirv.compile_error_compute)
  152. push_error("In: " + new_shader_code)
  153. return false
  154. shader = rd.shader_create_from_spirv(shader_spirv)
  155. if not shader.is_valid():
  156. return false
  157. pipeline = rd.compute_pipeline_create(shader)
  158. return pipeline.is_valid()
  159. At the top of this method we again use our mutex to protect accessing our
  160. user shader code and our is dirty flag.
  161. We make a local copy of the user shader code if our user shader code is dirty.
  162. If we don't have a new code fragment, we return true if we already have a
  163. valid pipeline.
  164. If we do have a new code fragment we embed it in our template code and then
  165. compile it.
  166. .. warning::
  167. The code shown here compiles our new code in runtime.
  168. This is great for prototyping as we can immediately see the effect
  169. of the changed shader.
  170. This prevents precompiling and caching this shader which may be an issues
  171. on some platforms such as consoles.
  172. Note that the demo project comes with an alternative example where
  173. a ``glsl`` file contains the entire compute shader and this is used.
  174. Godot is able to precompile and cache the shader with this approach.
  175. Finally we need to implement our effect callback, the rendering engine will call
  176. this at the right stage of rendering.
  177. .. code-block:: gdscript
  178. # Called by the rendering thread every frame.
  179. func _render_callback(p_effect_callback_type, p_render_data):
  180. if rd and p_effect_callback_type == EFFECT_CALLBACK_TYPE_POST_TRANSPARENT and _check_shader():
  181. # Get our render scene buffers object, this gives us access to our render buffers.
  182. # Note that implementation differs per renderer hence the need for the cast.
  183. var render_scene_buffers: RenderSceneBuffersRD = p_render_data.get_render_scene_buffers()
  184. if render_scene_buffers:
  185. # Get our render size, this is the 3D render resolution!
  186. var size = render_scene_buffers.get_internal_size()
  187. if size.x == 0 and size.y == 0:
  188. return
  189. # We can use a compute shader here.
  190. var x_groups = (size.x - 1) / 8 + 1
  191. var y_groups = (size.y - 1) / 8 + 1
  192. var z_groups = 1
  193. # Push constant.
  194. var push_constant: PackedFloat32Array = PackedFloat32Array()
  195. push_constant.push_back(size.x)
  196. push_constant.push_back(size.y)
  197. push_constant.push_back(0.0)
  198. push_constant.push_back(0.0)
  199. # Loop through views just in case we're doing stereo rendering. No extra cost if this is mono.
  200. var view_count = render_scene_buffers.get_view_count()
  201. for view in range(view_count):
  202. # Get the RID for our color image, we will be reading from and writing to it.
  203. var input_image = render_scene_buffers.get_color_layer(view)
  204. # Create a uniform set.
  205. # This will be cached; the cache will be cleared if our viewport's configuration is changed.
  206. var uniform: RDUniform = RDUniform.new()
  207. uniform.uniform_type = RenderingDevice.UNIFORM_TYPE_IMAGE
  208. uniform.binding = 0
  209. uniform.add_id(input_image)
  210. var uniform_set = UniformSetCacheRD.get_cache(shader, 0, [ uniform ])
  211. # Run our compute shader.
  212. var compute_list:= rd.compute_list_begin()
  213. rd.compute_list_bind_compute_pipeline(compute_list, pipeline)
  214. rd.compute_list_bind_uniform_set(compute_list, uniform_set, 0)
  215. rd.compute_list_set_push_constant(compute_list, push_constant.to_byte_array(), push_constant.size() * 4)
  216. rd.compute_list_dispatch(compute_list, x_groups, y_groups, z_groups)
  217. rd.compute_list_end()
  218. At the start of this method we check if we have a rendering device,
  219. if our callback type is the correct one, and check if we have our shader.
  220. .. note::
  221. The check for the effect type is only a safety mechanism.
  222. We've set this in our ``_init`` function, however it is possible
  223. for the user to change this in the UI.
  224. Our ``p_render_data`` parameter gives us access to an object that holds
  225. data specific to the frame we're currently rendering. We're currently only
  226. interested in our render scene buffers, which provide us access to all the
  227. internal buffers used by the rendering engine.
  228. Note that we cast this to :ref:`RenderSceneBuffersRD <class_RenderSceneBuffersRD>`
  229. to expose the full API to this data.
  230. Next we obtain our ``internal size`` which is the resolution of our 3D render
  231. buffers before they are upscaled (if applicable), upscaling happens after our
  232. post processes have run.
  233. From our internal size we calculate our group size, see our local size in our
  234. template shader.
  235. .. UPDATE: Not supported yet. When structs are supported here, update this
  236. .. paragraph.
  237. We also populate our push constant so our shader knows our size.
  238. Godot does not support structs here **yet** so we use a
  239. ``PackedFloat32Array`` to store this data into. Note that we have
  240. to pad this array with a 16 byte alignment. In other words, the
  241. length of our array needs to be a multiple of 4.
  242. Now we loop through our views, this is in case we're using multiview rendering
  243. which is applicable for stereo rendering (XR). In most cases we will only have
  244. one view.
  245. .. note::
  246. There is no performance benefit to use multiview for post processing
  247. here, handling the views separately like this will still enable the GPU
  248. to use parallelism if beneficial.
  249. Next we obtain the color buffer for this view. This is the buffer into which
  250. our 3D scene has been rendered.
  251. We then prepare a uniform set so we can communicate the color buffer to our
  252. shader.
  253. Note the use of our :ref:`UniformSetCacheRD <class_UniformSetCacheRD>` cache
  254. which ensures we can check for our uniform set each frame.
  255. As our color buffer can change from frame to frame and our uniform cache
  256. will automatically clean up uniform sets when buffers are freed, this is
  257. the safe way to ensure we do not leak memory or use an outdated set.
  258. Finally we build our compute list by binding our pipeline,
  259. binding our uniform set, pushing our push constant data,
  260. and calling dispatch for our groups.
  261. With our compositor effect completed, we now need to add it to our compositor.
  262. On our compositor we expand the compositor effects property
  263. and press ``Add Element``.
  264. Now we can add our compositor effect:
  265. .. image:: img/add_compositor_effect.webp
  266. After selecting our ``PostProcessShader`` we need to set our user shader code:
  267. .. code-block:: glsl
  268. float gray = color.r * 0.2125 + color.g * 0.7154 + color.b * 0.0721;
  269. color.rgb = vec3(gray);
  270. With that all done, our output is in grayscale.
  271. .. image:: img/post_process_shader.webp
  272. .. note::
  273. For a more advanced example of post effects, check out the
  274. `Radial blur based sky rays <https://github.com/BastiaanOlij/RERadialSunRays>`_
  275. example project created by Bastiaan Olij.