| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308 | .. _doc_viewport_as_texture:Using a SubViewport as a texture================================Introduction------------This tutorial will introduce you to using the :ref:`SubViewport <class_SubViewport>` as atexture that can be applied to 3D objects. In order to do so, it will walk you through the processof making a procedural planet like the one below:.. image:: img/planet_example.png.. note:: This tutorial does not cover how to code a dynamic atmosphere like the one this planet has.This tutorial assumes you are familiar with how to set up a basic scene including:a :ref:`Camera3D <class_Camera3D>`, a :ref:`light source <class_OmniLight3D>`, a:ref:`MeshInstance3D <class_MeshInstance3D>` with a :ref:`Primitive Mesh <class_PrimitiveMesh>`,and applying a :ref:`StandardMaterial3D <class_StandardMaterial3D>` to the mesh. The focus will be on usingthe :ref:`SubViewport <class_SubViewport>` to dynamically create textures that can be applied to the mesh.In this tutorial, we'll cover the following topics:- How to use a :ref:`SubViewport <class_SubViewport>` as a render texture- Mapping a texture to a sphere with equirectangular mapping- Fragment shader techniques for procedural planets- Setting a Roughness map from a :ref:`Viewport Texture <class_ViewportTexture>`Setting up the scene--------------------Create a new scene and add the following nodes exactly as shown below... image:: img/viewport_texture_node_tree.webpGo into the the MeshInstance3D and make the mesh a SphereMeshSetting up the SubViewport--------------------------Click on the :ref:`SubViewport <class_SubViewport>` node and set its size to ``(1024, 512)``. The:ref:`SubViewport <class_SubViewport>` can actually be any size so long as the width is double theheight. The width needs to be double the height so that the image will accurately map onto thesphere, as we will be using equirectangular projection, but more on that later.Next disable 3D. We will be using a :ref:`ColorRect <class_ColorRect>` to render the surface, sowe don't need 3D either... image:: img/planet_new_viewport.webpSelect the :ref:`ColorRect <class_ColorRect>` and in the inspector set the anchors preset to ``Full Rect``.This will ensure that the :ref:`ColorRect <class_ColorRect>` takes up the entire :ref:`SubViewport <class_SubViewport>`... image:: img/planet_new_colorrect.webpNext, we add a :ref:`Shader Material <class_ShaderMaterial>` to the :ref:`ColorRect <class_ColorRect>` (ColorRect > CanvasItem > Material > Material > ``New ShaderMaterial``)... note:: Basic familiarity with shading is recommended for this tutorial. However, even if you are new          to shaders, all the code will be provided, so you should have no problem following along.Click the dropdown menu button for the shader material and click / Edit. From here go to Shader > ``New Shader``.give it a name and click "Create". click the shader in the inspector to open the shader editor. Delete the default codeand add the following:.. code-block:: glsl    shader_type canvas_item;    void fragment() {        COLOR = vec4(UV.x, UV.y, 0.5, 1.0);    }save the shader code, you'll see in the inspector that the above code renders a gradient like the one below... image:: img/planet_gradient.pngNow we have the basics of a :ref:`SubViewport <class_SubViewport>` that we render to and we have a unique image that we canapply to the sphere.Applying the texture--------------------Now go into the :ref:`MeshInstance3D <class_MeshInstance3D>` and add a :ref:`StandardMaterial3D <class_StandardMaterial3D>`to it. No need for a special :ref:`Shader Material <class_ShaderMaterial>` (although that would be a good ideafor more advanced effects, like the atmosphere in the example above).MeshInstance3D > GeometryInstance > Geometry > Material Override > ``New StandardMaterial3D``Then click the dropdown for the StandardMaterial3D and click "Edit"Go to the "Resource" section and check the ``Local to scene`` box. Then, go to the "Albedo" sectionand click beside the "Texture" property to add an Albedo Texture. Here we will apply the texture we made.Choose "New ViewportTexture".. image:: img/planet_new_viewport_texture.webpClick on the ViewportTexture you just created in the inspector, then click "Assign".Then, from the menu that pops up, select the Viewport that we rendered to earlier... image:: img/planet_pick_viewport_texture.webpYour sphere should now be colored in with the colors we rendered to the Viewport... image:: img/planet_seam.webpNotice the ugly seam that forms where the texture wraps around? This is because we are pickinga color based on UV coordinates and UV coordinates do not wrap around the texture. This is a classicproblem in 2D map projection. Game developers often have a 2-dimensional map they want to projectonto a sphere, but when it wraps around, it has large seams. There is an elegant workaround for thisproblem that we will illustrate in the next section.Making the planet texture-------------------------So now, when we render to our :ref:`SubViewport <class_SubViewport>`, it appears magically on the sphere. But there is an uglyseam created by our texture coordinates. So how do we get a range of coordinates that wrap aroundthe sphere in a nice way? One solution is to use a function that repeats on the domain of our texture.``sin`` and ``cos`` are two such functions. Let's apply them to the texture and see what happens. Replace theexisting color code in the shader with the following:.. code-block:: glsl    COLOR.xyz = vec3(sin(UV.x * 3.14159 * 4.0) * cos(UV.y * 3.14159 * 4.0) * 0.5 + 0.5);.. image:: img/planet_sincos.webpNot too bad. If you look around, you can see that the seam has now disappeared, but in its place, wehave pinching at the poles. This pinching is due to the way Godot maps textures to spheres in its:ref:`StandardMaterial3D <class_StandardMaterial3D>`. It uses a projection technique called equirectangularprojection, which translates a spherical map onto a 2D plane... note:: If you are interested in a little extra information on the technique, we will be converting from          spherical coordinates into Cartesian coordinates. Spherical coordinates map the longitude and          latitude of the sphere, while Cartesian coordinates are, for all intents and purposes, a          vector from the center of the sphere to the point.For each pixel, we will calculate its 3D position on the sphere. From that, we will use3D noise to determine a color value. By calculating the noise in 3D, we solve the problemof the pinching at the poles. To understand why, picture the noise being calculated across thesurface of the sphere instead of across the 2D plane. When you calculate across thesurface of the sphere, you never hit an edge, and hence you never create a seam ora pinch point on the pole. The following code converts the ``UVs`` into Cartesiancoordinates... code-block:: glsl    float theta = UV.y * 3.14159;    float phi = UV.x * 3.14159 * 2.0;    vec3 unit = vec3(0.0, 0.0, 0.0);    unit.x = sin(phi) * sin(theta);    unit.y = cos(theta) * -1.0;    unit.z = cos(phi) * sin(theta);    unit = normalize(unit);And if we use ``unit`` as an output ``COLOR`` value, we get:.. image:: img/planet_normals.webpNow that we can calculate the 3D position of the surface of the sphere, we can use 3D noiseto make the planet. We will be using this noise function directly from a `Shadertoy <https://www.shadertoy.com/view/Xsl3Dl>`_:.. code-block:: glsl    vec3 hash(vec3 p) {        p = vec3(dot(p, vec3(127.1, 311.7, 74.7)),                 dot(p, vec3(269.5, 183.3, 246.1)),                 dot(p, vec3(113.5, 271.9, 124.6)));        return -1.0 + 2.0 * fract(sin(p) * 43758.5453123);    }    float noise(vec3 p) {      vec3 i = floor(p);      vec3 f = fract(p);      vec3 u = f * f * (3.0 - 2.0 * f);      return mix(mix(mix(dot(hash(i + vec3(0.0, 0.0, 0.0)), f - vec3(0.0, 0.0, 0.0)),                         dot(hash(i + vec3(1.0, 0.0, 0.0)), f - vec3(1.0, 0.0, 0.0)), u.x),                     mix(dot(hash(i + vec3(0.0, 1.0, 0.0)), f - vec3(0.0, 1.0, 0.0)),                         dot(hash(i + vec3(1.0, 1.0, 0.0)), f - vec3(1.0, 1.0, 0.0)), u.x), u.y),                 mix(mix(dot(hash(i + vec3(0.0, 0.0, 1.0)), f - vec3(0.0, 0.0, 1.0)),                         dot(hash(i + vec3(1.0, 0.0, 1.0)), f - vec3(1.0, 0.0, 1.0)), u.x),                     mix(dot(hash(i + vec3(0.0, 1.0, 1.0)), f - vec3(0.0, 1.0, 1.0)),                         dot(hash(i + vec3(1.0, 1.0, 1.0)), f - vec3(1.0, 1.0, 1.0)), u.x), u.y), u.z );    }.. note:: All credit goes to the author, Inigo Quilez. It is published under the ``MIT`` licence.Now to use ``noise``, add the following to the    ``fragment`` function:.. code-block:: glsl    float n = noise(unit * 5.0);    COLOR.xyz = vec3(n * 0.5 + 0.5);.. image:: img/planet_noise.webp.. note:: In order to highlight the texture, we set the material to unshaded.You can see now that the noise indeed wraps seamlessly around the sphere. Although thislooks nothing like the planet you were promised. So let's move onto something more colorful.Coloring the planet-------------------Now to make the planet colors. While there are many ways to do this, for now, we will stickwith a gradient between water and land.To make a gradient in GLSL, we use the ``mix`` function. ``mix`` takes two values to interpolatebetween and a third argument to choose how much to interpolate between them; in essence,it *mixes* the two values together. In other APIs, this function is often called ``lerp``.However, ``lerp`` is typically reserved for mixing two floats together; ``mix`` can take anyvalues whether it be floats or vector types... code-block:: glsl    COLOR.xyz = mix(vec3(0.05, 0.3, 0.5), vec3(0.9, 0.4, 0.1), n * 0.5 + 0.5);The first color is blue for the ocean. The second color is a kind of reddish color (becauseall alien planets need red terrain). And finally, they are mixed together by ``n * 0.5 + 0.5``.``n`` smoothly varies between ``-1`` and ``1``. So we map it into the ``0-1`` range that ``mix`` expects.Now you can see that the colors change between blue and red... image:: img/planet_noise_color.webpThat is a little more blurry than we want. Planets typically have a relatively clear separation betweenland and sea. In order to do that, we will change the last term to ``smoothstep(-0.1, 0.0, n)``.And thus the whole line becomes:.. code-block:: glsl    COLOR.xyz = mix(vec3(0.05, 0.3, 0.5), vec3(0.9, 0.4, 0.1), smoothstep(-0.1, 0.0, n));What ``smoothstep`` does is return ``0`` if the third argument is below the first and ``1`` if thethird argument is larger than the second and smoothly blends between ``0`` and ``1`` if the third numberis between the first and the second. So in this line, ``smoothstep`` returns ``0`` whenever ``n`` is less than ``-0.1``and it returns ``1`` whenever ``n`` is above ``0``... image:: img/planet_noise_smooth.webpOne more thing to make this a little more planet-y. The land shouldn't be so blobby; let's make the edgesa little rougher. A trick that is often used in shaders to make rough looking terrain with noise isto layer levels of noise over one another at various frequencies. We use one layer to make theoverall blobby structure of the continents. Then another layer breaks up the edges a bit, and thenanother, and so on. What we will do is calculate ``n`` with four lines of shader codeinstead of just one. ``n`` becomes:.. code-block:: glsl    float n = noise(unit * 5.0) * 0.5;    n += noise(unit * 10.0) * 0.25;    n += noise(unit * 20.0) * 0.125;    n += noise(unit * 40.0) * 0.0625;And now the planet looks like:.. image:: img/planet_noise_fbm.webpMaking an ocean---------------One final thing to make this look more like a planet. The ocean and the land reflect light differently.So we want the ocean to shine a little more than the land. We can do this by passing a fourth valueinto the ``alpha`` channel of our output ``COLOR`` and using it as a Roughness map... code-block:: glsl    COLOR.a = 0.3 + 0.7 * smoothstep(-0.1, 0.0, n);This line returns ``0.3`` for water and ``1.0`` for land. This means that the land is going to be quiterough, while the water will be quite smooth.And then, in the material, under the "Metallic" section, make sure ``Metallic`` is set to ``0`` and``Specular`` is set to ``1``. The reason for this is the water reflects light really well, butisn't metallic. These values are not physically accurate, but they are good enough for this demo.Next, under the "Roughness" section set the roughness texture to a:ref:`Viewport Texture <class_ViewportTexture>` pointing to our planet texture :ref:`SubViewport <class_SubViewport>`.Finally, set the ``Texture Channel`` to ``Alpha``. This instructs the renderer to use the ``alpha``channel of our output ``COLOR`` as the ``Roughness`` value... image:: img/planet_ocean.webpYou'll notice that very little changes except that the planet is no longer reflecting the sky.This is happening because, by default, when something is rendered with analpha value, it gets drawn as a transparent object over the background. And since the default backgroundof the :ref:`SubViewport <class_SubViewport>` is opaque, the ``alpha`` channel of the:ref:`Viewport Texture <class_ViewportTexture>` is ``1``, resulting in the planet texture beingdrawn with slightly fainter colors and a ``Roughness`` value of ``1`` everywhere. To correct this, wego into the :ref:`SubViewport <class_SubViewport>` and enable the "Transparent Bg" property. Since we are nowrendering one transparent object on top of another, we want to enable ``blend_premul_alpha``:.. code-block:: glsl    render_mode blend_premul_alpha;This pre-multiplies the colors by the ``alpha`` value and then blends them correctly together. Typically,when blending one transparent color on top of another, even if the background has an ``alpha`` of ``0`` (as itdoes in this case), you end up with weird color bleed issues. Setting ``blend_premul_alpha`` fixes that.Now the planet should look like it is reflecting light on the ocean but not the land. move around the :ref:`OmniLight3D <class_OmniLight3D>`in the scene so you can see the effect of the reflections on the ocean... image:: img/planet_ocean_reflect.webpAnd there you have it. A procedural planet generated using a :ref:`SubViewport <class_SubViewport>`.
 |