using_viewport_as_texture.rst 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299
  1. .. _doc_viewport_as_texture:
  2. Using a Viewport as a texture
  3. =============================
  4. Introduction
  5. ------------
  6. This tutorial will introduce you to using the :ref:`Viewport <class_Viewport>` as a
  7. texture that can be applied to 3D objects. In order to do so it will walk you through the process
  8. of making a procedural planet like the one below:
  9. .. image:: img/planet_example.png
  10. .. note:: This tutorial does not cover how to code a dynamic atmosphere like the one this planet has.
  11. This tutorial assumes you are familiar with how to set up a basic scene including:
  12. a :ref:`Camera <class_Camera>`, a :ref:`light source <class_OmniLight>`, a
  13. :ref:`Mesh Instance <class_MeshInstance>` with a :ref:`Primitive Mesh <class_PrimitiveMesh>`,
  14. and applying a :ref:`Spatial Material <class_SpatialMaterial>` to the mesh. The focus will be on using
  15. the :ref:`Viewport <class_Viewport>` to dynamically create textures that can be applied to the mesh.
  16. During the course of this tutorial will cover the following topics:
  17. - How to use a :ref:`Viewport <class_Viewport>` as a render texture
  18. - Mapping a texture to a sphere with equirectangular mapping
  19. - Fragment shader techniques for procedural planets
  20. - Setting a Roughness map from a :ref:`Viewport Texture <class_ViewportTexture>`
  21. Setting up the Viewport
  22. -----------------------
  23. First, add a :ref:`Viewport <class_Viewport>` to the scene.
  24. Next, set the size of the :ref:`Viewport <class_Viewport>` to ``(1024, 512)``. The
  25. :ref:`Viewport <class_Viewport>` can actually be any size so long as the width is double the height.
  26. The width needs to be double the height so that the image will accurately map onto the
  27. sphere as we will be using equirectangular projection, but more on that later.
  28. .. image:: img/planet_new_viewport.png
  29. Next, disable HDR and disable 3D. We don't need HDR because our planets surface will not be especially
  30. bright so values between ``0`` and ``1`` will be fine. And we will be using a :ref:`ColorRect <class_ColorRect>`
  31. to render the surface, so we don't need 3D either.
  32. Select the Viewport and add a :ref:`ColorRect <class_ColorRect>` as a child.
  33. Set the anchors "Right" and "Bottom" to ``1``, then make sure all the margins are set to ``0``. This
  34. will ensure that the :ref:`ColorRect <class_ColorRect>` takes up the entire :ref:`Viewport <class_Viewport>`.
  35. .. image:: img/planet_new_colorrect.png
  36. Next, we add a :ref:`Shader Material <class_ShaderMaterial>` to the :ref:`ColorRect <class_ColorRect>`.
  37. .. note:: Basic familiarity with shading is recommended for this tutorial. However, even if you are new
  38. to shaders, all the code will be provided so you should have no problem following along.
  39. .. code-block:: glsl
  40. shader_type canvas_item
  41. void fragment() {
  42. COLOR = vec4(UV.x, UV.y, 0.5, 1.0);
  43. }
  44. The above code renders a gradient like the one below.
  45. .. image:: img/planet_gradient.png
  46. Now we have the basics of a :ref:`Viewport <class_Viewport>` that we render to and we have a unique image that we can
  47. apply to the sphere.
  48. Applying the texture
  49. --------------------
  50. Now we go into the :ref:`Mesh Instance <class_MeshInstance>` and add a :ref:`Spatial Material <class_SpatialMaterial>`
  51. to it. No need for a special :ref:`Shader Material <class_ShaderMaterial>` (although that would be a good idea
  52. for more advanced effects, like the atmosphere in the example above).
  53. Open the newly created :ref:`Spatial Material <class_SpatialMaterial>` and scroll down to the "Albedo" section
  54. and click beside the "Texture" property to add an Albedo Texture. Here we will apply the texture we made.
  55. Choose "New ViewportTexture"
  56. .. image:: img/planet_new_viewport_texture.png
  57. Then from the menu that pops up select the Viewport that we rendered to earlier.
  58. .. image:: img/planet_pick_viewport_texture.png
  59. Your sphere should now be colored in with the colors we rendered to the Viewport
  60. .. image:: img/planet_seam.png
  61. Notice the ugly seam that forms where the texture wraps around? This is because we are picking
  62. a color based on UV coordinates and UV coordinates do not wrap around the texture. This is a classic
  63. problem in 2D map projection. Game developers often have a 2-dimensional map they want to project
  64. onto a sphere but when it wraps around it has large seams. There is an elegant work around for this
  65. problem that we will illustrate in the next section.
  66. Making the planet texture
  67. -------------------------
  68. So now when we render to our :ref:`Viewport <class_Viewport>` it appears magically on the sphere. But there is an ugly
  69. seam created by our texture coordinates. So how do we get a range of coordinates that wrap around
  70. the sphere in a nice way? One solution is to use a function that repeats on the domain of our texture.
  71. ``sin`` and ``cos`` are two such functions. Lets apply them to the texture and see what happens
  72. .. code-block:: glsl
  73. COLOR.xyz = vec3(sin(UV.x * 3.14159 * 4.0) * cos(UV.y * 3.14159 * 4.0) * 0.5 + 0.5);
  74. .. image:: img/planet_sincos.png
  75. Not too bad. If you look around you can see that the seam has now disappeared, but in its place we
  76. have pinching at the poles. This pinching is due to the way Godot maps textures to spheres in its
  77. :ref:`Spatial Material <class_SpatialMaterial>`. It uses a projection technique called equirectangular
  78. projection. Which translates a spherical map onto a 2D plane.
  79. .. note:: If you are interested in a little extra information on the technique, we will be converting from
  80. spherical coordinates into Cartesian coordinates. Spherical coordinates map the longitude and
  81. latitude of the sphere, while Cartesian coordinates are for all intents and purposes a
  82. vector from the center of the sphere to the point.
  83. For each pixel we will calculate its 3D position on the sphere. From that we will use
  84. 3D noise to determine a color value. By calculating the noise in 3D we solve the problem
  85. of the pinching at the poles. To understand why, picture the noise being calculated across the
  86. surface of the sphere instead of across the 2D plane. When you calculate across the
  87. surface of the sphere you never hit an edge, and hence you never create a seam or
  88. a pinch point on the pole. The following code converts the ``UVs`` into Cartesion
  89. coordinates.
  90. .. code-block:: glsl
  91. float theta = UV.y * 3.14159;
  92. float phi = UV.x * 3.14159 * 2.0;
  93. vec3 unit = vec3(0.0, 0.0, 0.0);
  94. unit.x = sin(phi) * sin(theta);
  95. unit.y = cos(theta) * -1.0;
  96. unit.z = cos(phi) * sin(theta);
  97. unit = normalize(unit);
  98. And if we use ``unit`` as an output ``COLOR`` value we get.
  99. .. image:: img/planet_normals.png
  100. Now that we can calculate the 3D position of the surface of the sphere we can use 3D noise
  101. to make the planet. We will be using this noise function directly from a `Shadertoy <https://www.shadertoy.com/view/Xsl3Dl>`_:
  102. .. code-block:: glsl
  103. vec3 hash(vec3 p) {
  104. p = vec3(dot(p, vec3(127.1, 311.7, 74.7)),
  105. dot(p, vec3(269.5, 183.3, 246.1)),
  106. dot(p, vec3(113.5, 271.9, 124.6)));
  107. return -1.0 + 2.0 * fract(sin(p) * 43758.5453123);
  108. }
  109. float noise(vec3 p) {
  110. vec3 i = floor(p);
  111. vec3 f = fract(p);
  112. vec3 u = f * f * (3.0 - 2.0 * f);
  113. return mix(mix(mix(dot(hash(i + vec3(0.0, 0.0, 0.0)), f - vec3(0.0, 0.0, 0.0)),
  114. dot(hash(i + vec3(1.0, 0.0, 0.0)), f - vec3(1.0, 0.0, 0.0)), u.x),
  115. mix(dot(hash(i + vec3(0.0, 1.0, 0.0)), f - vec3(0.0, 1.0, 0.0)),
  116. dot(hash(i + vec3(1.0, 1.0, 0.0)), f - vec3(1.0, 1.0, 0.0)), u.x), u.y),
  117. mix(mix(dot(hash(i + vec3(0.0, 0.0, 1.0)), f - vec3(0.0, 0.0, 1.0)),
  118. dot(hash(i + vec3(1.0, 0.0, 1.0)), f - vec3(1.0, 0.0, 1.0)), u.x),
  119. mix(dot(hash(i + vec3(0.0, 1.0, 1.0)), f - vec3(0.0, 1.0, 1.0)),
  120. dot(hash(i + vec3(1.0, 1.0, 1.0)), f - vec3(1.0, 1.0, 1.0)), u.x), u.y), u.z );
  121. }
  122. .. note:: All credit goes to the author, Inigo Quilez. It is published with the ``MIT`` licence.
  123. Now to use ``noise``, add the following to the ``fragment`` function:
  124. .. code-block:: glsl
  125. float n = noise(unit * 5.0);
  126. COLOR.xyz = vec3(n * 0.5 + 0.5);
  127. .. image:: img/planet_noise.png
  128. .. note:: In order to highlight the texture, we set the material to unshaded.
  129. You can see now that the noise indeed wraps seamlessly around the sphere. Although this
  130. looks nothing like the planet you were promised. So lets move onto something more colorful.
  131. Coloring the planet
  132. -------------------
  133. Now to make the planet colors. While, there are many ways to do this, for now we will stick
  134. with a gradient between water and land.
  135. To make a gradient in GLSL we use the ``mix`` function. ``mix`` takes two values to interpolate
  136. between and a third parameter to choose how much to interpolate between them, in essence
  137. it *mixes* the two values together. In other APIs this function is often called ``lerp``.
  138. Although, ``lerp`` is typically reserved for mixing two floats together, ``mix`` can take any
  139. values whether it be floats or vector types.
  140. .. code-block:: glsl
  141. COLOR.xyz = mix(vec3(0.05, 0.3, 0.5), vec3(0.9, 0.4, 0.1), n.x * 0.5 + 0.5);
  142. The first color is blue for the ocean. The second color is a kind of reddish color (because
  143. all alien planets need red terrain). And finally they are mixed together by ``n.x * 0.5 + 0.5``.
  144. ``n.x`` smoothly varies between ``-1`` and ``1``. So we map it into the ``0-1`` range that ``mix`` expects.
  145. Now you can see that the colors change between blue and red.
  146. .. image:: img/planet_noise_color.png
  147. That is a little more blurry than we want. Planets typically have a relatively clear separation between
  148. land and sea. In order to do that we will change the last term to ``smoothstep(-0.1, 0.0, n.x)``.
  149. And thus the whole line becomes:
  150. .. code-block:: glsl
  151. COLOR.xyz = mix(vec3(0.05, 0.3, 0.5), vec3(0.9, 0.4, 0.1), smoothstep(-0.1, 0.0, n.x));
  152. What ``smoothstep`` does is return ``0`` if the third parameter is below the first and return 1 if the
  153. third parameter is larger than the second and smoothly blends between ``0`` and ``1`` if the third number
  154. is between the first and the second. So in this line ``smoothstep`` returns ``0`` whenever ``n.x`` is less than ``-0.1``
  155. and it returns ``1`` whenever ``n.x`` is above ``0``.
  156. .. image:: img/planet_noise_smooth.png
  157. One more thing to make this a little more planet-y. The land shouldn't be so blobby lets make the edges
  158. a little rougher. A trick that is often used in shaders to make rough looking terrain with noise is
  159. to layer levels of noise over one another at various frequencies. We use one layer to make the
  160. overall blobby structure of the continents. Then another layer breaks up the edges a bit, and then
  161. another, and so on. What we will do is calculate ``n`` with four lines of shader code
  162. instead of just one. ``n`` becomes:
  163. .. code-block:: glsl
  164. float n = noise(unit * 5.0) * 0.5;
  165. n += noise(unit * 10.0) * 0.25;
  166. n += noise(unit * 20.0) * 0.125;
  167. n += noise(unit * 40.0) * 0.0625;
  168. And now the planet looks like:
  169. .. image:: img/planet_noise_fbm.png
  170. And with shading turned back on it looks like:
  171. .. image:: img/planet_noise_fbm_shaded.png
  172. Making an ocean
  173. ---------------
  174. One final thing to make this look more like a planet. The ocean and the land reflect light differently.
  175. So we want the ocean to shine a little more than the land. We can do this by passing a fourth value
  176. into the ``alpha`` channel of our output ``COLOR`` and using it as a Roughness map.
  177. .. code-block:: glsl
  178. COLOR.a = 0.3 + 0.7 * smoothstep(-0.1, 0.0, n);
  179. This line returns ``0.3`` for water and ``1.0`` for land. This means that the land is going to be quite
  180. rough while the water will be quite smooth.
  181. And then in the material under the "Metallic" section make sure ``Metallic`` is set to ``0`` and
  182. ``Specular`` is set to ``1``. The reason for this is the water reflects light really well, but
  183. isn't metallic. These values are not physically accurate, but they are good enough for this demo.
  184. Next under the "Roughness" section set ``Roughness`` to ``1`` and set the roughness texture to a
  185. :ref:`Viewport Texture <class_ViewportTexture>` pointing to our planet texture :ref:`Viewport <class_Viewport>`.
  186. Finally set the ``Texture Channel`` to ``Alpha``. This instructs the renderer to use the ``alpha``
  187. channel of our output ``COLOR`` as the ``Roughness`` value.
  188. .. image:: img/planet_ocean.png
  189. You'll notice that very little changes except that the planet is no longer reflecting the sky.
  190. This is happening because by default when something is rendered with an
  191. alpha value it gets drawn as a transparent object over the background. And since the default background
  192. of the :ref:`Viewport <class_Viewport>` is opaque, the ``alpha`` channel of the
  193. :ref:`Viewport Texture <class_ViewportTexture>` is ``1`` resulting in the planet texture being
  194. drawn with slightly fainter colors and a ``Roughness`` value of ``1`` everywhere. To correct this we
  195. go into the :ref:`Viewport <class_Viewport>` and set "Transparent Bg" to on. Since we are now
  196. rendering one transparent object on top of another we want to enable ``blend_premul_alpha``:
  197. .. code-block:: glsl
  198. render_mode blend_premul_alpha;
  199. This pre-multiplies the colors by the ``alpha`` value and then blends them correctly together. Typically
  200. when blending one transparent color on top of another, even if the background has an ``alpha`` of ``0`` (as it
  201. does in this case), you end up with weird color bleed issues. Setting ``blend_premul_alpha`` fixes that.
  202. Now the planet should look like it is reflecting light on the ocean but not the land. If you haven't done
  203. so already, add an :ref:`OmniLight <class_OmniLight>` to the scene so you can move it around and see the
  204. effect of the reflections on the ocean.
  205. .. image:: img/planet_ocean_reflect.png
  206. And there you have it. A procedural planet generated using a :ref:`Viewport <class_Viewport>`.