123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320 |
- .. _doc_your_second_spatial_shader:
- Your first Spatial shader: part 2
- =================================
- From a high-level, what Godot does is give the user a bunch of parameters that can be optionally set
- (``AO``, ``SSS_Strength``, ``RIM``, etc.). These parameters correspond to different complex effects
- (Ambient Occlusion, SubSurface Scattering, Rim Lighting, etc.). When not written to, the code is thrown
- out before it is compiled and so the shader does not incur the cost of the extra feature. This makes it
- easy for users to have complex PBR-correct shading, without writing complex shaders. Of course, Godot
- also allows you to ignore all these parameters and write a fully customized shader.
- For a full list of these parameters see the :ref:`spatial shader <doc_spatial_shader>` reference doc.
- A difference between the vertex function and a fragment function is that the vertex function
- runs per vertex and sets properties such as ``VERTEX`` (position) and ``NORMAL``, while
- the fragment shader runs per pixel and, most importantly, sets the ``ALBEDO`` color of the :ref:`Mesh<class_MeshInstance>`.
- Your first spatial fragment function
- ------------------------------------
- As mentioned in the previous part of this tutorial. The standard use of the fragment function
- in Godot is to set up different material properties and let Godot handle the rest. In order
- to provide even more flexibility, Godot also provides things called render modes. Render
- modes are set at the top of the shader, directly below ``shader_type``, and they specify
- what sort of functionality you want the built-in aspects of the shader to have.
- For example, if you do not want to have lights affect an object, set the render mode to
- ``unshaded``:
- .. code-block:: glsl
- render_mode unshaded;
- You can also stack multiple render modes together. For example, if you want to use toon
- shading instead of more-realistic PBR shading, set the diffuse mode and specular mode to toon:
- .. code-block:: glsl
- render_mode diffuse_toon, specular_toon;
- This model of built-in functionality allows you to write complex custom shaders by changing
- only a few parameters.
- For a full list of render modes see the :ref:`Spatial shader reference <doc_spatial_shader>`.
- In this part of the tutorial, we will walk through how to take the bumpy terrain from the
- previous part and turn it into an ocean.
- First let's set the color of the water. We do that by setting ``ALBEDO``.
- ``ALBEDO`` is a ``vec3`` that contains the color of the object.
- Let's set it to a nice shade of blue.
- .. code-block:: glsl
- void fragment() {
- ALBEDO = vec3(0.1, 0.3, 0.5);
- }
- .. image:: img/albedo.png
- We set it to a very dark shade of blue because most of the blueness of the water will
- come from reflections from the sky.
- The PBR model that Godot uses relies on two main parameters: ``METALLIC`` and ``ROUGHNESS``.
- ``ROUGHNESS`` specifies how smooth/rough the surface of a material is. A low ``ROUGHNESS`` will
- make a material appear like a shiny plastic, while a high roughness makes the material appear
- more solid in color.
- ``METALLIC`` specifies how much like a metal the object is. It is better set close to ``0`` or ``1``.
- Think of ``METALLIC`` as changing the balance between the reflection and the ``ALBEDO`` color. A
- high ``METALLIC`` almost ignores ``ALBEDO`` altogether, and looks like a mirror of the sky. While
- a low ``METALLIC`` has a more equal representation of sky color and ``ALBEDO`` color.
- ``ROUGHNESS`` increases from ``0`` to ``1`` from left to right while ``METALLIC`` increase from
- ``0`` to ``1`` from top to bottom.
- .. image:: img/PBR.png
- .. note:: ``METALLIC`` should be close to ``0`` or ``1`` for proper PBR shading. Only set it between
- them for blending between materials.
- Water is not a metal, so we will set its ``METALLIC`` property to ``0.0``. Water is also highly
- reflective, so we will set its ``ROUGHNESS`` property to be quite low as well.
- .. code-block:: glsl
- void fragment() {
- METALLIC = 0.0;
- ROUGHNESS = 0.01;
- ALBEDO = vec3(0.1, 0.3, 0.5);
- }
- .. image:: img/plastic.png
- Now we have a smooth plastic looking surface. It is time to think about some particular properties of
- water that we want to emulate. There are two main ones that will take this from a weird plastic surface
- to nice stylized water. The first is specular reflections. Specular reflections are those bright spots
- you see from where the sun reflects directly into your eye. The second is fresnel reflectance.
- Fresnel reflectance is the property of objects to become more reflective at shallow angles. It is the
- reason why you can see into water below you, but farther away it reflects the sky.
- In order to increase the specular reflections, we will do two things. First, we will change the render
- mode for specular to toon because the toon render mode has larger specular highlights.
- .. code-block:: glsl
- render_mode specular_toon;
- .. image:: img/specular-toon.png
- Second we will
- add rim lighting. Rim lighting increases the effect of light at glancing angles. Usually it is used
- to emulate the way light passes through fabric on the edges of an object, but we will use it here to
- help achieve a nice watery effect.
- .. code-block:: glsl
- void fragment() {
- RIM = 0.2;
- METALLIC = 0.0;
- ROUGHNESS = 0.01;
- ALBEDO = vec3(0.1, 0.3, 0.5);
- }
- .. image:: img/rim.png
- In order to add fresnal reflectance we will compute a fresnel term in our fragment shader.
- We are not going to use a real fresnel term, instead we will approximate it using the dot
- product of the ``NORMAL`` and ``VIEW`` vectors. The ``NORMAL`` vector points away from a
- surface of the, while the ``VIEW`` vector is the direction between your eye and that point
- on the surface. The dot product between them is a handy way to tell when you are looking
- at the surface head-on your at a glancing angle.
- .. code-block:: glsl
- float fresnel = sqrt(1.0 - dot(NORMAL, VIEW));
- And mix it into both ``ROUGHNESS`` and ``ALBEDO``. This is the benefit of ShaderMaterials over
- SpatialMaterials. With SpatialMaterials we could set these properties with a texture, or to a flat
- number. But with shaders we can set them based on any mathematical function that we can dream up.
- .. code-block:: glsl
- void fragment() {
- float fresnel = sqrt(1.0 - dot(NORMAL, VIEW));
- RIM = 0.2;
- METALLIC = 0.0;
- ROUGHNESS = 0.01 * (1.0 - fresnel);
- ALBEDO = vec3(0.1, 0.3, 0.5) + (0.1 * fresnel);
- }
- .. image:: img/fresnel.png
- And now, with only 5 lines of code, you can have complex looking water. Now that we have
- lighting, this water is looking too bright. Let's darken it. This is done easily by
- decreasing the values of the ``vec3`` we pass into ``ALBEDO``. Let's set them to
- ``vec3(0.01, 0.03, 0.05)``.
- .. image:: img/dark-water.png
- Animating with ``TIME``
- -----------------------
- Going back to the vertex function, we can animated the waves using the built-in variable ``TIME``.
- ``TIME`` is a built-in variable that is accessible from the vertex and fragment functions.
- In the last tutorial we calculated height by reading from a heightmap. For this tutorial,
- we will do the same. Put the heightmap code in a function called ``height()``.
- .. code-block:: glsl
- float height(vec2 position) {
- return texture(noise, position / 10.0).x; //scaling factor is based on mesh size (This PlanMesh is 10x10)
- }
- In order to use ``TIME`` in the ``height()`` function we need to pass it in.
- .. code-block:: glsl
- float height(vec2 position, float time) {
- }
- And make sure to correctly pass it in inside the vertex function.
- .. code-block:: glsl
- void vertex() {
- vec2 pos = VERTEX.xz;
- float k = height(pos, TIME);
- VERTEX.y = k;
- }
- Instead of using a normalmap to calculate normals. We are going to compute them manually in the
- ``vertex()`` function. To do so use the following line of code.
- .. code-block:: glsl
- NORMAL = normalize(vec3(k - height(pos + vec2(0.1, 0.0), TIME), 0.1, k - height(pos + vec2(0.0, 0.1), TIME)));
- We need to compute ``NORMAL`` manually because in the next section we will be using math to create
- complex-looking waves.
- Now, we are going to make the ``height()`` function a little more complicated by offsetting ``position``
- by the cosine of ``TIME``.
- .. code-block:: glsl
- float height(vec2 position, float time) {
- vec2 offset = 0.01 * cos(position + time);
- return texture(noise, (position / 10.0) - offset).x;
- }
- This results in waves that move slowly, but not in a very natural way. The next section will dig deeper
- into using shaders to create more complex effects, in this case realistic waves, by adding a few
- more mathematical functions.
- Advanced effects: waves
- -----------------------
- What makes shaders so powerful is that you can achieve complex effects by using math. To illustrate
- this, we are going to take our waves to the next level by modifying the ``height()`` function and
- by introducing a new function called ``wave()``.
- ``wave()`` has one parameter, ``position``, which is the same as it is in ``height()``.
- We are going to call ``wave()`` multiple times in ``height()`` in order to fake the way waves look.
- .. code-block:: glsl
- float wave(vec2 position){
- position += texture(noise, position / 10.0).x * 2.0 - 1.0;
- vec2 wv = 1.0 - abs(sin(position));
- return pow(1.0 - pow(wv.x * wv.y, 0.65), 4.0);
- }
- At first this looks complicated. So let's go through it line-by-line.
- .. code-block:: glsl
- position += texture(noise, position / 10.0).x * 2.0 - 1.0;
- Offset the position by the ``noise`` texture. This will make the waves curve so they are not straight lines
- completely aligned with the grid.
- .. code-block:: glsl
- vec2 wv = 1.0 - abs(sin(position));
- Define a wave-like function using ``sin()`` and ``position``. Normally ``sin()`` waves are very round.
- We use ``abs()`` to absolute to give them a sharp ridge and constrain them to the 0-1 range. And then we
- subtract it from ``1.0`` to put the peak on top.
- .. code-block:: glsl
- return pow(1.0 - pow(wv.x * wv.y, 0.65), 4.0);
- Multiply the x-directional wave by the y-directional wave and raise it to a power to sharpen the peaks.
- Then subtract that from ``1.0`` so that the ridges become peaks and raise that to a power to sharpen the
- ridges.
- We can now replace the contents of our ``height()`` function with ``wave()``.
- .. code-block:: glsl
- float height(vec2 position, float time) {
- float h = wave(position);
- }
- Using this you get:
- .. image:: img/wave1.png
- The shape of the sin wave is too obvious. So let's spread the waves out a bit. We do this by scaling ``position``.
- .. code-block:: glsl
- float height(vec2 position, float time) {
- float h = wave(position*0.4);
- }
-
- Now it looks much better.
- .. image:: img/wave2.png
- We can do even better if we layer multiple waves on top of each other at varying
- frequencies and amplitudes. What this means is that we are going to scale position for each one to make
- the waves thinner or wider (frequency). And we are going to multiply the output of the wave to make them shorter
- or taller (amplitude).
- Here is an example for how you could layer the four waves to achieve nicer looking waves.
- .. code-block:: glsl
- float height(vec2 position, float time) {
- float d = wave((position + time) * 0.4, 8.0) * 0.3;
- d += wave((position - time) * 0.3, 8.0) * 0.3;
- d += wave((position + time) * 0.5, 4.0) * 0.2;
- d += wave((position - time) * 0.6, 4.0) * 0.2;
- return d;
- }
- Note that we add time to two and subtract it from the other two. This makes the waves move in different directions
- creating a complex effect. Also note that the amplitudes (the number the result is multiplied by) all
- add up to ``1.0``. This keeps the wave in the 0-1 range.
- With this code you should end up with more complex looking waves and all you had to do was add a little bit
- of math!
- .. image:: img/wave3.png
- For more information about Spatial shaders read the :ref:`Shading Language <doc_shading_language>`
- doc and the :ref:`Spatial Shaders <doc_spatial_shader>` doc. Also look at more advanced tutorials
- in the :ref:`Shading section <toc-learn-features-shading>` and the :ref:`3D <toc-learn-features-3d>` sections.
|