vertex_displacement_with_shaders.rst 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368
  1. .. _doc_vertex_displacement_with_shaders:
  2. Vertex displacement with shaders
  3. ================================
  4. Introduction
  5. ------------
  6. This tutorial will teach you how to displace the vertices of
  7. a :ref:`Plane Mesh<class_PlaneMesh>` inside a shader. Vertex displacement can be used
  8. for a wide variety of effects, but most commonly it is used
  9. as a quick way to turn a flat plane into a simple terrain. Typically
  10. this is done using a heightmap, but in order to keep everything self
  11. contained, in this tutorial we will use noise in a shader. At the end
  12. of this tutorial we will have a deformed plane that looks like a
  13. miniature terrain complete with dynamic lighting.
  14. By reading this tutorial you should gain a basic understanding of:
  15. * How to create and subdivide a :ref:`Plane Mesh<class_PlaneMesh>`
  16. * How to create and assign a material to a :ref:`Mesh<class_MeshInstance>`
  17. * How to write a :ref:`Shader<class_Shader>` that displaces the vertices of a :ref:`Mesh<class_MeshInstance>`
  18. * How to pass values (Uniforms) into a :ref:`Shader<class_Shader>` to update the :ref:`Mesh<class_MeshInstance>` in realtime
  19. * How to approximate normals from a height function
  20. * How to use a light with a custom material
  21. The plane mesh
  22. --------------
  23. First, add a :ref:`Spatial<class_Spatial>` node to the scene to act as the root. Next, add a :ref:`MeshInstance<class_MeshInstance>`
  24. as a child.
  25. .. image:: img/vertex_displacement_new_mesh.png
  26. Select the newly created :ref:`MeshInstance<class_MeshInstance>`. Then click on the button that says "null"
  27. next to the :ref:`Mesh<class_MeshInstance>` in the Inspector. This will bring up a list of :ref:`PrimitiveMeshes<class_PrimitiveMesh>`.
  28. Select "New PlaneMesh".
  29. .. image:: img/vertex_displacement_planemesh.png
  30. The button will change into a small image of a plane. Click on it to enter into
  31. the Inspector for the :ref:`Plane Mesh<class_MeshInstance>`.
  32. Then, in the viewport, click in the upper left corner where it says [Perspective].
  33. A menu will appear. In the middle of the menu are options for how to display the scene.
  34. Select 'Display Wireframe'.
  35. .. image:: img/vertex_displacement_viewport_settings.png
  36. This will allow you to see the triangles making up the plane.
  37. .. image:: img/vertex_displacement_wireframe1.png
  38. Now set the ``Subdivide Width`` and ``Subdivide Height`` to ``32``.
  39. .. image:: img/vertex_displacement_subdivided_mesh.png
  40. You can see that there are now way more triangles in the :ref:`Mesh<class_MeshInstance>`. This will give
  41. us more vertices to work with and thus allow us to add more detail.
  42. .. image:: img/vertex_displacement_wireframe2.png
  43. Shader magic
  44. ------------
  45. Now that we have a :ref:`Plane Mesh<class_MeshInstance>` to draw lets setup the material that will deform the :ref:`Mesh<class_MeshInstance>`.
  46. Click beside material in the :ref:`Plane Mesh<class_MeshInstance>` Menu and create a new :ref:`ShaderMaterial<class_ShaderMaterial>`.
  47. .. image:: img/vertex_displacement_new_shader_material.png
  48. Then click on the created :ref:`ShaderMaterial<class_ShaderMaterial>`.
  49. Then click beside 'shader' and create a new :ref:`Shader<class_Shader>`.
  50. .. image:: img/vertex_displacement_new_shader.png
  51. Click into the newly created :ref:`Shader<class_Shader>`. You should now see Godot's Shader editor.
  52. .. image:: img/vertex_displacement_shader_editor.png
  53. Notice how it is throwing an error? This is because the shader editor reloads shaders on
  54. the fly automatically. The first thing Godot shaders need is a declaration of what type of
  55. shader they are. Accordingly, we set the variable ``shader_type`` to ``spatial``. One more
  56. thing we will add is the ``render_mode``, we will set it to ``unshaded``. This means that
  57. Godot won't run the light shader on this object.
  58. ::
  59. shader_type spatial;
  60. render_mode unshaded;
  61. This should remove the errors and your :ref:`Mesh<class_MeshInstance>` should turn white. If you were to comment out
  62. the ``render_mode`` the plane would appear blue because it would pick up the sky colors.
  63. Next we will define a vertex shader. The vertex shader determines where the vertices of your
  64. :ref:`Mesh<class_MeshInstance>` appear in the final scene. We will be using it to offset the height of each vertex and
  65. make our flat plane appear like a little terrain.
  66. We define the vertex shader like so:
  67. ::
  68. void vertex() {
  69. }
  70. With nothing in the ``vertex`` function Godot will use its default vertex shader. We can easily
  71. start to make changes by adding a single line:
  72. ::
  73. void vertex() {
  74. VERTEX.y += cos(VERTEX.x) * sin(VERTEX.z);
  75. }
  76. Adding this line you should get an image like the one below.
  77. .. image:: img/vertex_displacement_cos.png
  78. Okay, lets unpack this. The ``y`` value of the ``VERTEX`` is being increased. And we are passing
  79. the ``x`` and ``z`` components of the ``VERTEX`` as arguments to ``cos`` and ``sin`` this gives us
  80. a wave like appearance across the ``x`` and ``z`` axis.
  81. What we want to achieve is the look of little hills, after all ``cos`` and ``sin`` already look kind of like
  82. hills. We do so by scaling the inputs to the ``cos`` and ``sin`` functions.
  83. ::
  84. void vertex() {
  85. VERTEX.y += cos(VERTEX.x * 4.0) * sin(VERTEX.z * 4.0);
  86. }
  87. .. image:: img/vertex_displacement_cos_scaled.png
  88. This looks better, but it is still too spiky. This is because ``cos`` and ``sin`` output values between ``-1`` and ``1``,
  89. so the range of the output is much too high. We correct this by multiplying the result by ``0.5`` to reduce the size.
  90. ::
  91. void vertex() {
  92. VERTEX.y += cos(VERTEX.x * 4.0) * sin(VERTEX.z * 4.0) * 0.5;
  93. }
  94. .. image:: img/vertex_displacement_cos_amplitude.png
  95. Looks much more hilly now. But ``cos`` and ``sin`` are boring. Lets move onto something more interesting.
  96. Noise
  97. -----
  98. Noise is a very popular tool for procedural generation. Think of it as similar to the cosine function
  99. where you have repeating hills except with noise each hill has a different height. Understanding
  100. noise is not necessary for this tutorial. There is nothing wrong with simply copying and pasting
  101. the code below.
  102. The first function we use to generate the noise is the ``hash`` function. It gives the random height
  103. for each of the hill tops.
  104. ::
  105. float hash(vec2 p) {
  106. return fract(sin(dot(p * 17.17, vec2(14.91, 67.31))) * 4791.9511);
  107. }
  108. You will find similar functions to this all over the internet. It is lovingly referred to as the
  109. 'one-liner hash function'. It works well for simple noise, but there are many better alternatives
  110. floating around as well. For this tutorial it will work fine.
  111. Next we define the ``noise`` function. It smoothly interpolates between the random heights.
  112. Again, if this code seems daunting, do not worry, just copy paste and move on with the tutorial.
  113. ::
  114. float noise(vec2 x) {
  115. vec2 p = floor(x);
  116. vec2 f = fract(x);
  117. f = f * f * (3.0 - 2.0 * f);
  118. vec2 a = vec2(1.0, 0.0);
  119. return mix(mix(hash(p + a.yy), hash(p + a.xy), f.x),
  120. mix(hash(p + a.yx), hash(p + a.xx), f.x), f.y);
  121. }
  122. Lastly, to add detail we combine successive layers of noise using something called fractal
  123. brownian motion or FBM. Scary name aside FBM noise just adds together layers of noise with
  124. increase frequency and decreasing amplitude. To implement it we run over a for loop where
  125. we increase the frequency each level, decrease the amplitude, and calculate a new layer of noise.
  126. ::
  127. float fbm(vec2 x) {
  128. float height = 0.0;
  129. float amplitude = 0.5;
  130. float frequency = 3.0;
  131. for (int i = 0; i < 6; i++){
  132. height += noise(x * frequency) * amplitude;
  133. amplitude *= 0.5;
  134. frequency *= 2.0;
  135. }
  136. return height;
  137. }
  138. We can now use this noise function in place of ``cos`` and ``sin`` in the previous section.
  139. ::
  140. float height = fbm(VERTEX.xz * 4.0);
  141. VERTEX.y += height * 0.5;
  142. .. image:: img/vertex_displacement_noise1.png
  143. With the noise function in place we already have something that looks kind of cool.
  144. There is a lot of detail, it kind of looks hilly or mountainous.
  145. Fragment Shader
  146. ---------------
  147. The difference between a vertex shader and a fragment shader is that the vertex shader
  148. runs per vertex and sets properties such as ``VERTEX`` (position) and ``NORMAL``, while
  149. the fragment shader runs per pixel and, most importantly, sets the ``ALBEDO`` color of the :ref:`Mesh<class_MeshInstance>`.
  150. Now lets look at the :ref:`Mesh<class_MeshInstance>` with a regular shader instead of the wireframe. Set the
  151. viewport back to 'Display Normal'.
  152. .. image:: img/vertex_displacement_noise2.png
  153. The :ref:`Mesh<class_MeshInstance>` appears completely white because the fragment shader is coloring each pixel white,
  154. but if every pixel is white we lose detail on the :ref:`Mesh<class_MeshInstance>`. So lets color each pixel based
  155. on the height calculated in the vertex shader. We do so by setting the ``COLOR`` variable
  156. in the vertex shader. And by setting the ``ALBEDO`` in the fragment shader to the calculated
  157. ``COLOR`` variable.
  158. ::
  159. void vertex() {
  160. ...
  161. COLOR.xyz = vec3(height);
  162. }
  163. void fragment(){
  164. ALBEDO = COLOR.xyz;
  165. }
  166. With this change we can see the detail of the :ref:`Mesh<class_MeshInstance>`, even without displaying the :ref:`Mesh<class_MeshInstance>`'s wireframe.
  167. .. image:: img/vertex_displacement_noise3.png
  168. Uniforms
  169. --------
  170. Uniform variables allow you to pass data from the game into the shader. They can
  171. be very useful for controlling shader effects. Uniforms can be almost any
  172. datatype that can be used in the shader. To use a uniform you declare it in
  173. your :ref:`Shader<class_Shader>` using the keyword ``uniform``.
  174. Lets make a uniform that changes the height of the terrain.
  175. ::
  176. uniform float height_scale = 0.5;
  177. Godot lets you initialize a uniform with a value, here ``height_scale`` is set to
  178. ``0.5``. You can set uniforms from gdscript by calling the function ``set_shader_param``
  179. on the material corresponding to the shader. The value passed from gdscript takes
  180. precedence over the value used to initialize it in the shader.
  181. ::
  182. material.set_shader_param("height_scale", 0.5)
  183. Remember that the string passed into ``set_shader_param`` must match the name
  184. of the uniform variable in the :ref:`Shader<class_Shader>`. You can use the uniform variable anywhere
  185. inside your :ref:`Shader<class_Shader>`. Here, we will use it to set the height value instead
  186. of arbitrarily multiplying by ``0.5``.
  187. ::
  188. VERTEX.y += height * height_scale;
  189. The terrain should look exactly the same, but now we have control over the height easily.
  190. Here is the same terrain with ``height_scale`` set to ``1``:
  191. .. image:: img/vertex_displacement_uniform1.png
  192. And here it is with ``height_scale`` set to ``0.2``:
  193. .. image:: img/vertex_displacement_uniform2.png
  194. Using uniforms we can even change the value every frame to animate the height of the terrain.
  195. Combined with :ref:`Tweens<class_Tween>` this can be especially useful for simple animations.
  196. Interacting with light
  197. ----------------------
  198. As a final part of this tutorial lets try to set up the terrain to interact with light.
  199. First, we will add an :ref:`OmniLight<class_OmniLight>` to the scene.
  200. .. image:: img/vertex_displacement_light1.png
  201. You should notice that nothing changes, this is because we set the ``render_mode`` to ``unshaded``
  202. at the beginning of this tutorial, lets remove that.
  203. ::
  204. shader_type spatial;
  205. //render_mode unshaded;
  206. .. image:: img/vertex_displacement_light2.png
  207. It looks slightly better now, you can see the light affecting the terrain, and it has
  208. turned blue as a result of the sky. The problem is the light is affecting the terrain
  209. as if it were a flat plane. This is because the light shader uses the normals of the
  210. :ref:`Mesh<class_MeshInstance>` to calculate light. The normals are stored in the :ref:`Mesh<class_MeshInstance>`, but we are changing
  211. the shape of the :ref:`Mesh<class_MeshInstance>` in the shader so the normals are no longer correct. To fix this
  212. we need to recalculate the normals in the shader. Godot makes this easy for us, all we
  213. have to do is calculate the new normal and set ``NORMAL`` to that value in the vertex shader.
  214. With ``NORMAL`` set Godot will do all the difficult lighting calculations for us.
  215. To calculate the normal from noise we are going to use a technique called 'central differences'.
  216. This is used a lot, especially in places like shadertoy, to calculate normals in shaders.
  217. What we will do is calculate the noise at four points surrounding the vertex in the ``x`` and ``z`` directions and then calculate
  218. the slope at the vertex from that. After all a normal is just an indicator of the slope of the
  219. noise.
  220. We calculate the normal with one line in the vertex shader.
  221. ::
  222. vec2 e = vec2(0.01, 0.0);
  223. vec3 normal = normalize(vec3(fbm(VERTEX.xz - e) - fbm(VERTEX.xz + e), 2.0 * e.x, fbm(VERTEX.xz - e.yx) - fbm(VERTEX.xz + e.yx)));
  224. NORMAL = normal;
  225. The variable ``e`` just makes it easier to add and subtract the right value from the ``VERTEX``.
  226. Setting ``e`` to a lower number will increase the level of detail of the normal.
  227. With ``NORMAL`` calculated the terrain now looks like:
  228. .. image:: img/vertex_displacement_normal.png
  229. This still does not look how we want it to. The issue here is that the noise changes
  230. faster than the vertices do. So when we calculate the normal at the point of the
  231. ``VERTEX`` it does not align with what we see in the final :ref:`Mesh<class_MeshInstance>`. In order to fix
  232. this we add more vertices. The below image is made with a :ref:`Mesh<class_MeshInstance>` with ``subdivision`` set
  233. to ``100``.
  234. .. image:: img/vertex_displacement_normal_detailed1.png
  235. Now we can drag the light around and the lighting will update automatically.
  236. .. image:: img/vertex_displacement_normal_detailed2.png
  237. .. image:: img/vertex_displacement_normal_detailed3.png
  238. If you zoom the camera out you can see that the :ref:`Mesh<class_MeshInstance>` now looks like a small terrain.
  239. .. image:: img/vertex_displacement_terrain.png
  240. That is everything for this tutorial. Hopefully you understand the basics of vertex
  241. shaders in Godot. As a further exercise try changing the ``height_scale`` from gdscript,
  242. try using different :ref:`Primitive Meshes<class_PrimitiveMesh>`, and try making your
  243. own functions to calculate ``height``.
  244. For further information on how to use shaders in Godot
  245. you should check out the :ref:`doc_shading_language` page.