added your first spatial shader

This commit is contained in:
clayjohn
2019-04-13 14:18:33 -07:00
parent b0be2808d4
commit ef013f577c
63 changed files with 750 additions and 427 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

View File

@@ -11,5 +11,4 @@ Shading
intro_to_shaders_water_workshop
screen-reading_shaders
migrating_to_godot_shader_language
vertex_displacement_with_shaders
advanced_postprocessing

View File

@@ -1,368 +0,0 @@
.. _doc_vertex_displacement_with_shaders:
Vertex displacement with shaders
================================
Introduction
------------
This tutorial will teach you how to displace the vertices of
a :ref:`Plane Mesh<class_PlaneMesh>` inside a shader. Vertex displacement can be used
for a wide variety of effects, but most commonly it is used
as a quick way to turn a flat plane into a simple terrain. Typically
this is done using a heightmap, but in order to keep everything self
contained, in this tutorial, we will use noise in a shader. At the end
of this tutorial, we will have a deformed plane that looks like a
miniature terrain complete with dynamic lighting.
By reading this tutorial, you should gain a basic understanding of:
* How to create and subdivide a :ref:`Plane Mesh<class_PlaneMesh>`
* How to create and assign a material to a :ref:`Mesh<class_MeshInstance>`
* How to write a :ref:`Shader<class_Shader>` that displaces the vertices of a :ref:`Mesh<class_MeshInstance>`
* How to pass values (Uniforms) into a :ref:`Shader<class_Shader>` to update the :ref:`Mesh<class_MeshInstance>` in realtime
* How to approximate normals from a height function
* How to use a light with a custom material
The plane mesh
--------------
First, add a :ref:`Spatial<class_Spatial>` node to the scene to act as the root. Next, add a :ref:`MeshInstance<class_MeshInstance>`
as a child.
.. image:: img/vertex_displacement_new_mesh.png
Select the newly created :ref:`MeshInstance<class_MeshInstance>`. Then click on the button that says "null"
next to the :ref:`Mesh<class_MeshInstance>` in the Inspector. This will bring up a list of :ref:`PrimitiveMeshes<class_PrimitiveMesh>`.
Select "New PlaneMesh".
.. image:: img/vertex_displacement_planemesh.png
The button will change into a small image of a plane. Click on it to enter into
the Inspector for the :ref:`Plane Mesh<class_MeshInstance>`.
Then, in the viewport, click in the upper left corner where it says [Perspective].
A menu will appear. In the middle of the menu are options for how to display the scene.
Select 'Display Wireframe'.
.. image:: img/vertex_displacement_viewport_settings.png
This will allow you to see the triangles making up the plane.
.. image:: img/vertex_displacement_wireframe1.png
Now set the ``Subdivide Width`` and ``Subdivide Height`` to ``32``.
.. image:: img/vertex_displacement_subdivided_mesh.png
You can see that there are now many more triangles in the :ref:`Mesh<class_MeshInstance>`. This will give
us more vertices to work with and thus allow us to add more detail.
.. image:: img/vertex_displacement_wireframe2.png
Shader magic
------------
Now that we have a :ref:`Plane Mesh<class_MeshInstance>` to draw, let's set up the material that will deform the :ref:`Mesh<class_MeshInstance>`.
Click beside material in the :ref:`Plane Mesh<class_MeshInstance>` Menu and create a new :ref:`ShaderMaterial<class_ShaderMaterial>`.
.. image:: img/vertex_displacement_new_shader_material.png
Then click on the created :ref:`ShaderMaterial<class_ShaderMaterial>`.
Then click beside 'shader' and create a new :ref:`Shader<class_Shader>`.
.. image:: img/vertex_displacement_new_shader.png
Click into the newly created :ref:`Shader<class_Shader>`. You should now see Godot's Shader editor.
.. image:: img/vertex_displacement_shader_editor.png
Notice how it is throwing an error? This is because the shader editor reloads shaders on
the fly automatically. The first thing Godot shaders need is a declaration of what type of
shader they are. Accordingly, we set the variable ``shader_type`` to ``spatial``. One more
thing we will add is the ``render_mode``, we will set it to ``unshaded``. This means that
Godot won't run the light shader on this object.
::
shader_type spatial;
render_mode unshaded;
This should remove the errors and your :ref:`Mesh<class_MeshInstance>` should turn white. If you were to comment out
the ``render_mode``, the plane would appear blue because it would pick up the sky colors.
Next we will define a vertex shader. The vertex shader determines where the vertices of your
:ref:`Mesh<class_MeshInstance>` appear in the final scene. We will be using it to offset the height of each vertex and
make our flat plane appear like a little terrain.
We define the vertex shader like so:
::
void vertex() {
}
With nothing in the ``vertex`` function, Godot will use its default vertex shader. We can easily
start to make changes by adding a single line:
::
void vertex() {
VERTEX.y += cos(VERTEX.x) * sin(VERTEX.z);
}
Adding this line, you should get an image like the one below.
.. image:: img/vertex_displacement_cos.png
Okay, let's unpack this. The ``y`` value of the ``VERTEX`` is being increased. And we are passing
the ``x`` and ``z`` components of the ``VERTEX`` as arguments to ``cos`` and ``sin``; that gives us
a wave-like appearance across the ``x`` and ``z`` axes.
What we want to achieve is the look of little hills; after all. ``cos`` and ``sin`` already look kind of like
hills. We do so by scaling the inputs to the ``cos`` and ``sin`` functions.
::
void vertex() {
VERTEX.y += cos(VERTEX.x * 4.0) * sin(VERTEX.z * 4.0);
}
.. image:: img/vertex_displacement_cos_scaled.png
This looks better, but it is still too spiky. This is because ``cos`` and ``sin`` output values between ``-1`` and ``1``,
so the range of the output is much too high. We correct this by multiplying the result by ``0.5`` to reduce the size.
::
void vertex() {
VERTEX.y += cos(VERTEX.x * 4.0) * sin(VERTEX.z * 4.0) * 0.5;
}
.. image:: img/vertex_displacement_cos_amplitude.png
Looks much more hilly now. But ``cos`` and ``sin`` are boring. Let's move onto something more interesting.
Noise
-----
Noise is a very popular tool for procedural generation. Think of it as similar to the cosine function
where you have repeating hills except, with noise, each hill has a different height. Understanding
noise is not necessary for this tutorial. There is nothing wrong with simply copying and pasting
the code below.
The first function we use to generate the noise is the ``hash`` function. It gives the random height
for each of the hill tops.
::
float hash(vec2 p) {
return fract(sin(dot(p * 17.17, vec2(14.91, 67.31))) * 4791.9511);
}
You will find similar functions to this all over the internet. It is lovingly referred to as the
'one-liner hash function'. It works well for simple noise, but there are many better alternatives
floating around as well. For this tutorial, it will work fine.
Next we define the ``noise`` function. It smoothly interpolates between the random heights.
Again, if this code seems daunting, do not worry; just copy, paste and move on with the tutorial.
::
float noise(vec2 x) {
vec2 p = floor(x);
vec2 f = fract(x);
f = f * f * (3.0 - 2.0 * f);
vec2 a = vec2(1.0, 0.0);
return mix(mix(hash(p + a.yy), hash(p + a.xy), f.x),
mix(hash(p + a.yx), hash(p + a.xx), f.x), f.y);
}
Lastly, to add detail, we combine successive layers of noise using something called fractal
brownian motion or FBM. Scary name aside, FBM noise just adds together layers of noise with
increasing frequency and decreasing amplitude. To implement it, we run over a for loop where
we increase the frequency each level, decrease the amplitude, and calculate a new layer of noise.
::
float fbm(vec2 x) {
float height = 0.0;
float amplitude = 0.5;
float frequency = 3.0;
for (int i = 0; i < 6; i++){
height += noise(x * frequency) * amplitude;
amplitude *= 0.5;
frequency *= 2.0;
}
return height;
}
We can now use this noise function in place of ``cos`` and ``sin`` in the previous section.
::
float height = fbm(VERTEX.xz * 4.0);
VERTEX.y += height * 0.5;
.. image:: img/vertex_displacement_noise1.png
With the noise function in place, we already have something that looks kind of cool.
There is a lot of detail, it kind of looks hilly or mountainous.
Fragment shader
---------------
The difference between a vertex shader and a fragment shader is that the vertex shader
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>`.
Now let's look at the :ref:`Mesh<class_MeshInstance>` with a regular shader instead of the wireframe. Set the
viewport back to 'Display Normal'.
.. image:: img/vertex_displacement_noise2.png
The :ref:`Mesh<class_MeshInstance>` appears completely white because the fragment shader is coloring each pixel white;
but if every pixel is white, we lose detail on the :ref:`Mesh<class_MeshInstance>`. So let's color each pixel based
on the height calculated in the vertex shader. We do so by setting the ``COLOR`` variable
in the vertex shader. And by setting the ``ALBEDO`` in the fragment shader to the calculated
``COLOR`` variable.
::
void vertex() {
...
COLOR.xyz = vec3(height);
}
void fragment(){
ALBEDO = COLOR.xyz;
}
With this change, we can see the detail of the :ref:`Mesh<class_MeshInstance>`, even without displaying the :ref:`Mesh<class_MeshInstance>`'s wireframe.
.. image:: img/vertex_displacement_noise3.png
Uniforms
--------
Uniform variables allow you to pass data from the game into the shader. They can
be very useful for controlling shader effects. Uniforms can be almost any
datatype that can be used in the shader. To use a uniform, you declare it in
your :ref:`Shader<class_Shader>` using the keyword ``uniform``.
Let's make a uniform that changes the height of the terrain.
::
uniform float height_scale = 0.5;
Godot lets you initialize a uniform with a value; here, ``height_scale`` is set to
``0.5``. You can set uniforms from GDScript by calling the function ``set_shader_param``
on the material corresponding to the shader. The value passed from GDScript takes
precedence over the value used to initialize it in the shader.
::
material.set_shader_param("height_scale", 0.5)
Remember that the string passed into ``set_shader_param`` must match the name
of the uniform variable in the :ref:`Shader<class_Shader>`. You can use the uniform variable anywhere
inside your :ref:`Shader<class_Shader>`. Here, we will use it to set the height value instead
of arbitrarily multiplying by ``0.5``.
::
VERTEX.y += height * height_scale;
The terrain should look exactly the same, but now we have control over the height easily.
Here is the same terrain with ``height_scale`` set to ``1``:
.. image:: img/vertex_displacement_uniform1.png
And here it is with ``height_scale`` set to ``0.2``:
.. image:: img/vertex_displacement_uniform2.png
Using uniforms, we can even change the value every frame to animate the height of the terrain.
Combined with :ref:`Tweens<class_Tween>`, this can be especially useful for simple animations.
Interacting with light
----------------------
As a final part of this tutorial, let's try to set up the terrain to interact with light.
First, we will add an :ref:`OmniLight<class_OmniLight>` to the scene.
.. image:: img/vertex_displacement_light1.png
You should notice that nothing changes. That is because we set the ``render_mode`` to ``unshaded``
at the beginning of this tutorial; let's remove that.
::
shader_type spatial;
//render_mode unshaded;
.. image:: img/vertex_displacement_light2.png
It looks slightly better now; you can see the light affecting the terrain, and it has
turned blue as a result of the sky. The problem is the light is affecting the terrain
as if it were a flat plane. This is because the light shader uses the normals of the
:ref:`Mesh<class_MeshInstance>` to calculate light. The normals are stored in the :ref:`Mesh<class_MeshInstance>`, but we are changing
the shape of the :ref:`Mesh<class_MeshInstance>` in the shader, so the normals are no longer correct. To fix this,
we need to recalculate the normals in the shader. Godot makes this easy for us; all we
have to do is calculate the new normal and set ``NORMAL`` to that value in the vertex shader.
With ``NORMAL`` set, Godot will do all the difficult lighting calculations for us.
To calculate the normal from noise, we are going to use a technique called 'central differences'.
This is used a lot, especially in places like Shadertoy, to calculate normals in shaders.
What we will do is calculate the noise at four points surrounding the vertex in the ``x`` and ``z`` directions and then calculate
the slope at the vertex from that. After all, a normal is just an indicator of the slope of the
noise.
We calculate the normal with one line in the vertex shader.
::
vec2 e = vec2(0.01, 0.0);
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)));
NORMAL = normal;
The variable ``e`` just makes it easier to add and subtract the right value from the ``VERTEX``.
Setting ``e`` to a lower number will increase the level of detail of the normal.
With ``NORMAL`` calculated, the terrain now looks like:
.. image:: img/vertex_displacement_normal.png
This still does not look how we want it to. The issue here is that the noise changes
faster than the vertices do. So when we calculate the normal at the point of the
``VERTEX``, it does not align with what we see in the final :ref:`Mesh<class_MeshInstance>`. In order to fix
this, we add more vertices. The below image is made with a :ref:`Mesh<class_MeshInstance>` with ``subdivision`` set
to ``100``.
.. image:: img/vertex_displacement_normal_detailed1.png
Now, we can drag the light around and the lighting will update automatically.
.. image:: img/vertex_displacement_normal_detailed2.png
.. image:: img/vertex_displacement_normal_detailed3.png
If you zoom the camera out, you can see that the :ref:`Mesh<class_MeshInstance>` now looks like a small terrain.
.. image:: img/vertex_displacement_terrain.png
That is everything for this tutorial. Hopefully, you now understand the basics of vertex
shaders in Godot. As a further exercise, try changing the ``height_scale`` from gdscript,
try using different :ref:`Primitive Meshes<class_PrimitiveMesh>`, and try making your
own functions to calculate ``height``.
For further information on how to use shaders in Godot,
you should check out the :ref:`doc_shading_language` page.

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

View File

@@ -4,13 +4,13 @@ Your first shader
This tutorial series will walk you through writing your first shader. It is intended
for people who have very little prior experience with shaders and want to get started
with the basics. This tutorial will not cover advanced topics and it is not
comprehensive. For a comprehensive and detailed overview of shaders in godot see the
comprehensive. For a comprehensive and detailed overview of shaders in Godot see the
:ref:`Shading Reference Page <toc-shading-reference>`.
"What are shaders" gives a high-level overview of what shaders are and how they fit
"What are shaders" gives you a high-level overview of what shaders are and how they fit
into the rendering pipeline.
The "your first shader" tutorials walks you through the process of writing a shader
The "your first shader" tutorials walk you through the process of writing a shader
step-by-step.
For a more general introduction into shaders and the OpenGL Shading Language, use
@@ -23,5 +23,4 @@ For a more general introduction into shaders and the OpenGL Shading Language, us
what_are_shaders
your_first_canvasitem_shader
your_first_spatial_shader
your_first_particles_shader
your_first_visual_shader
your_second_spatial_shader

View File

@@ -1,7 +1,7 @@
.. _doc_what_are_shaders:
What are shaders
================
What are shaders?
=================
Introduction
------------
@@ -18,34 +18,86 @@ shaders a little easier by exposing many useful built-in features and handling s
lower-level initialization work for you. However, GLSL (the OpenGL Shading Language, which Godot uses)
is still unintuitive and restricting, especially for users who are used to GDScript.
But what are they
-----------------
But what are they?
------------------
Shaders are a special kind of program that runs on the GPU.
Shaders are a special kind of program that runs on Graphics Processing Units (GPUs). Most computers
have some sort of GPU, either one integrated into their CPU or discrete (meaning it is a separate
hardware component, for example, the typical graphics card). GPUs are especially useful for
rendering because they are optimized for running thousands of instructions in parallel.
Objects are drawn using an associated shader. The output of the shader is the pixel colors drawn
to the viewport. Shaders do not save their results, nor can they be used to draw outside their
corresponding mesh. The lack of flexibility allows shaders to be incredibly fast.
The output of the shader is typically the colored pixels of the object drawn to the viewport. But some
shaders allow for specialized outputs (this is especially true for APIs like Vulkan). Shaders operate
inside the shader pipeline. The standard process is the vertex -> fragment shader pipeline. The vertex
shader is used to decided where each vertex (point in a 3D model, or corner of a Sprite) goes and the
fragment shader decides what color individual pixels receive.
Suppose you want to update all the pixels in a texture to a given color, on the CPU you would write:
::
for x in range(width):
for y in range(height):
set_color(x, y, some_color)
In a shader you are given access only to the inside of the loop so what you write looks like this:
.. code-block:: glsl
// function called for each pixel
void fragment() {
COLOR = some_color;
}
You have no control over how this function is called. So you have to design your shaders
differently from how you would design programs on the CPU.
A consequence of the shader pipeline is that you cannot access the results from a previous
run of the shader, you cannot access other pixels from the pixel being drawn, and you cannot
write outside of the current pixel being drawn. This enables the GPU to execute the shader
for different pixels in parallel, as they do not depend on each other. This lack of
flexibility is designed to work with the GPU which allows shaders to be incredibly fast.
What can they do
----------------
^^^^^^^^^^^^^^^^
- position vertices very fast
- compute color very fast
- compute lighting very fast
- lots and lots of math
What can't they do
------------------
- cant draw outside mesh
- cant access other pixels from current pixel (or vertices)
- cant store previous iterations
^^^^^^^^^^^^^^^^^^
- draw outside mesh
- access other pixels from current pixel (or vertices)
- store previous iterations
- update on the fly (they can, but they need to be compiled)
Structure of a shader
---------------------
In Godot, shaders are made up of 3 main functions: the ``vertex()`` function, the ``fragment()``
function and the ``light()`` function.
The ``vertex()`` function runs over all the vertices in the mesh and sets their positions as well
as some other per-vertex variables.
The ``fragment()`` function runs for every pixel that is covered by the mesh. It uses the variables
from the ``vertex()`` function to run. The variables from the ``vertex()`` function are interpolated
between the vertices to provide the values for the ``fragment()`` function.
The ``light()`` function runs for every pixel and for every light. It takes variables from the
``fragment()`` function and from previous runs of itself.
For more information about how shaders operate specifically in Godot see the :ref:`Shaders <doc_shaders>` doc.
Technical overview
------------------
GPUs are able to render graphics much faster than CPUs for a few reasons, but most notably,
because they are able to run calculations massively in parallel. A CPU typically has 4 or 8 cores
while a GPU typically has hundreds. That means a GPU can do hundreds of tasks at once. GPU architects
while a GPU typically has thousands. That means a GPU can do hundreds of tasks at once. GPU architects
have exploited this in a way that allows for doing many calculations very quickly, but only when
many or all cores are doing the same calculation at once, but with different data.

View File

@@ -11,7 +11,7 @@ graphics. All modern rendering is done with shaders. For a more detailed descrip
of what shaders are please see :ref:`What are shaders <doc_what_are_shaders>`.
This tutorial will focus on the practical aspects of writing shader programs by walking
readers through the process of writing a shader with both vertex and fragment functions.
you through the process of writing a shader with both vertex and fragment functions.
This tutorial targets absolute beginners to shaders.
.. note:: If you have experience writing shaders and are just looking for
@@ -24,11 +24,12 @@ Setup
while :ref:`Spatial <doc_spatial_shader>` shaders are used to draw all 3D objects.
In order to use a shader it must be attached inside a :ref:`Material <class_material>`
which must be attached to an object. To draw multiple objects with the same material,
the material must be attached to each object.
which must be attached to an object. Materials are a type of :ref:`Resource <doc_resources>`.
To draw multiple objects with the same material, the material must be attached to each object.
All objects derived from a :ref:`CanvasItem <class_canvasitem>` have a material property.
This includes all gui elements, sprites, TileMaps, MeshInstance2Ds etc.
This includes all :ref:`GUI elements <class_Control>`, :ref:`Sprites <class_sprite>`, :ref:`TileMaps <class_tilemap>`,
:ref:`MeshInstance2Ds <class_meshinstance2d>` etc.
They also have an option to inherit their parent's material. This can be useful if you have
a large number of nodes that you want to use the same material.
@@ -39,11 +40,11 @@ In the Inspector, click beside "Texture" where it says "[empty]" and select "Loa
"Icon.png". For new projects, this is the Godot icon. You should now see the icon in the viewport.
Next, look down in the Inspector, under the CanvasItem section, click beside "Material" and select
"new ShaderMaterial". This creates a new Material. Click on the sphere that appears. Godot currently
"New ShaderMaterial". This creates a new Material resource. Click on the sphere that appears. Godot currently
doesn't know whether you are writing a CanvasItem Shader or a Spatial Shader and it previews the output
of spatial shaders. So what you are seeing is the output of the default Spatial Shader.
Click beside "Shader" and select "new Shader". Finally, click on the new shader resource and the shader
Click beside "Shader" and select "New Shader". Finally, click on the new shader resource and the shader
editor will open. You are now ready to begin writing your first shader.
Your first CanvasItem shader
@@ -54,7 +55,7 @@ the following format:
.. code-block:: glsl
shader_type canvas_item
shader_type canvas_item;
Becuase we are writing a CanvasItem shader, we specify ``canvas_item`` in the first line. All our code will
go beneath this declaration.
@@ -68,14 +69,17 @@ functions are significantly more complex than vertex and fragment functions and
Your first fragment function
----------------------------
The fragment function runs for every pixel covered by a triangle and determine what color that pixel should be
based on information passed in from the vertex function.
The fragment function runs for every pixel in a Sprite and determines what color that pixel should be.
They are restricted to the pixels covered by the triangle, that means you cannot use one to, for example,
create an outline around a sprite.
They are restricted to the pixels covered by the Sprite, that means you cannot use one to, for example,
create an outline around a Sprite.
The most basic fragment function does nothing except assign a single color to every pixel.
We do so by writing a ``vec4`` to the built-in variable ``COLOR``. ``vec4`` is shorthand for constructing
a vector with 4 numbers. For more information about vectors see the :ref:`Vector math tutorial <doc_vector_math>`
``COLOR`` is both an input variable to the fragment function and the final output from it.
.. code-block:: glsl
void fragment(){
@@ -89,12 +93,16 @@ Congratulations! You're done. You have successfully written your first shader in
Now let's make things more complex.
There are many inputs to the fragment function that you can use for calculating ``COLOR``.
``UV`` is one of them. Uv coordinates are specified in your mesh and they tell the shader
where to read from textures for each part of the mesh.
``UV`` is one of them. UV coordinates are specified in your Sprite (without you knowing it!)
and they tell the shader where to read from textures for each part of the mesh.
In the fragment function you can only read from ``UV``, but you can use it in other functions
or to assign values to ``COLOR`` directly.
``UV`` varies between 0-1 from left-right and from top-bottom.
.. image:: img/iconuv.png
.. code-block:: glsl
void fragment() {
@@ -106,7 +114,7 @@ or to assign values to ``COLOR`` directly.
Using ``TEXTURE`` built-in
^^^^^^^^^^^^^^^^^^^^^^^^^^
When you want to adjust a color in a sprite you cannot just adjust the color from the texture
When you want to adjust a color in a Sprite you cannot just adjust the color from the texture
manually like in the code below.
.. code-block:: glsl
@@ -119,7 +127,7 @@ manually like in the code below.
The default fragment function reads from a texture and displays it. When you overwrite the default fragment function,
you lose that functionality, so you have to implement it yourself. You read from textures using the
``texture`` function. Certain nodes, like Sprites, have a dedicated texture variable that can be accessed in the shader
using ``TEXTURE``. Use it together with ``UV`` and ``texture`` to draw the sprite.
using ``TEXTURE``. Use it together with ``UV`` and ``texture`` to draw the Sprite.
.. code-block:: glsl
@@ -143,7 +151,7 @@ You can use uniforms by defining them at the top of your shader like so:
For more information about usage see the :ref:`Shading Language doc <doc_shading_language>`.
Add a uniform to change the amount of blue in our sprite.
Add a uniform to change the amount of blue in our Sprite.
.. code-block:: glsl
@@ -154,7 +162,7 @@ Add a uniform to change the amount of blue in our sprite.
COLOR.b = blue;
}
Now you can change the amount of blue in the sprite from the editor. Look back at the Inspector
Now you can change the amount of blue in the Sprite from the editor. Look back at the Inspector
under where you created your shader. You should see a section called "Shader Param". Unfold that
section and you will see the uniform you just declared. If you change the value in the editor, it
will overwrite the default value you provided in the shader.
@@ -162,8 +170,8 @@ will overwrite the default value you provided in the shader.
Interacting with shaders from code
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
You can change uniforms from code using the function ``set_shader_param`` which is called on the nodes
material resource. With a sprite node, the following code can be used to set the ``blue`` uniform.
You can change uniforms from code using the function ``set_shader_param()`` which is called on the node's
material resource. With a Sprite node, the following code can be used to set the ``blue`` uniform.
::
@@ -198,7 +206,7 @@ Combined with the ``TIME`` built-in variable, this can be used for simple animat
.. code-block:: glsl
void vertex() {
// Animate sprite moving in big circle around its location
// Animate Sprite moving in big circle around its location
VERTEX += vec2(cos(TIME)*100.0, sin(TIME)*100.0);
}

View File

@@ -1,9 +0,0 @@
.. _doc_your_first_particles_shader:
Your first Particles shader
===========================
Introduction
------------

View File

@@ -3,7 +3,338 @@
Your first Spatial shader
============================
Introduction
You have decided to start writing your own custom Spatial shader. Maybe you saw a cool trick
online that was done with shaders, or you have found that the
:ref:`SpatialMaterial <class_SpatialMaterial>` isn't quite meeting your needs. Either way,
you have decided to write your own and now you need figure out where to start.
This tutorial will explain how to write a Spatial shader and will cover more topics than the
:ref:`CanvasItem <doc_your_first_canvasitem_shader>` tutorial.
Spatial shaders have more built-in functionality than CanvasItem shaders. The expectation with
spatial shaders is that Godot has already provided the functionality for common use cases and all
the user needs to do in the shader is set the proper parameters. This is especially true for a
PBR (physically based rendering) workflow.
This is a two-part tutorial. In this first part we are going to go through how to make a simple terrain
using vertex displacement from a heightmap in the vertex function. In the :ref:`second part <doc_your_second_spatial_shader>`
we are going to take the concepts from this tutorial and walk through how to set up custom materials
in a fragment shader by writing an ocean water shader.
.. note:: This tutorial assumes some basic shader knowledge such as types (``vec2``, ``float``,
``sampler2D``), and functions. If you are uncomfortable with these concepts it is
best to get a gentle introduction from `The Book of Shaders
<https://thebookofshaders.com>`_ before completing this tutorial.
Where to assign my material
---------------------------
In 3D, objects are drawn using :ref:`Meshes <class_Mesh>`. Meshes are a resource type that store geometry
(the shape of your object) and materials (the color and how the object reacts to light) in units called
"surfaces". A Mesh can have multiple surfaces, or just one. Typically, you would
import a mesh from another program (e.g. Blender). But Godot also has a few :ref:`PrimitiveMeshes <class_primitivemesh>`
that allow you to add basic geometry to a scene without importing Meshes.
There are multiple node types that you can use to draw a mesh. The main one is :ref:`MeshInstance <class_meshinstance>`,
but you can also use :ref:`Particles <class_particles>`, :ref:`MultiMeshes <class_MultiMesh>` (with a
:ref:`MultiMeshInstance <class_multimeshinstance>`), or others.
Typically, a material is associated with a given surface in a mesh, but some nodes, like MeshInstance, allow
you to override the material for a specific surface, or for all surfaces.
If you set a material on the surface or mesh itself, then all MeshInstances that share that mesh will share that material.
However, if you want to reuse the same mesh across multiple mesh instances, but have different materials for each
instance then you should set the material on the Meshinstance.
For this tutorial we will set our material on the mesh itself rather than taking advantage of the MeshInstance's
ability to override materials.
Setting up
----------
Add a new :ref:`MeshInstance <class_meshinstance>` node to your scene.
In the inspector tab beside "Mesh" click "[empty]" and select "New PlaneMesh".
Then click on the image of a plane that appears.
This adds a :ref:`PlaneMesh <class_planemesh>` to our scene.
Then, in the viewport, click in the upper left corner on the button that says "Perspective".
A menu will appear. In the middle of the menu are options for how to display the scene.
Select 'Display Wireframe'.
This will allow you to see the triangles making up the plane.
.. image:: img/plane.png
Now set ``Subdivide Width`` and ``Subdivide Depth`` to ``32``.
.. image:: img/plane-sub-set.png
You can see that there are now many more triangles in the :ref:`Mesh<class_MeshInstance>`. This will give
us more vertices to work with and thus allow us to add more detail.
.. image:: img/plane-sub.png
:ref:`PrimitiveMeshes <class_primitivemesh>`, like PlaneMesh, only have one surface, so instead of
an array of materials there is only one. Click beside "Material" where it says "[empty]" and
select "New ShaderMaterial". Then click the sphere that appears.
Now click beside "Shader" where it says "[empty]" and select "New Shader".
The shader editor should now pop up and you are ready to begin writing your first Spatial shader!
Shader magic
------------
.. image:: img/shader-error.png
Notice how there is already error? This is because the shader editor reloads shaders on
the fly. The first thing Godot shaders need is a declaration of what type of shader they are.
We set the variable ``shader_type`` to ``spatial`` becuase this is a spatial shader.
.. code-block:: glsl
shader_type spatial;
Next we will define the ``vertex()`` function. The ``vertex()`` function determines where
the vertices of your :ref:`Mesh<class_MeshInstance>` appear in the final scene. We will be
using it to offset the height of each vertex and make our flat plane appear like a little terrain.
We define the vertex shader like so:
.. code-block:: glsl
void vertex() {
}
With nothing in the ``vertex()`` function, Godot will use its default vertex shader. We can easily
start to make changes by adding a single line:
.. code-block:: glsl
void vertex() {
VERTEX.y += cos(VERTEX.x) * sin(VERTEX.z);
}
Adding this line, you should get an image like the one below.
.. image:: img/cos.png
Okay, let's unpack this. The ``y`` value of the ``VERTEX`` is being increased. And we are passing
the ``x`` and ``z`` components of the ``VERTEX`` as arguments to ``cos`` and ``sin``; that gives us
a wave-like appearance across the ``x`` and ``z`` axes.
What we want to achieve is the look of little hills; after all. ``cos`` and ``sin`` already look kind of like
hills. We do so by scaling the inputs to the ``cos`` and ``sin`` functions.
.. code-block:: glsl
void vertex() {
VERTEX.y += cos(VERTEX.x * 4.0) * sin(VERTEX.z * 4.0);
}
.. image:: img/cos4.png
This looks better, but it is still too spiky and repetitive, let's make it a little more interesting.
Noise heightmap
---------------
Noise is a very popular tool for faking the look of terrain. Think of it as similar to the cosine function
where you have repeating hills except, with noise, each hill has a different height.
Godot provides the :ref:`NoiseTexture <class_noisetexture>` resource for generating a noise texture
that can be accessed from a shader.
To access a texture in a shader add the following code near the top of your shader, outside the
``vertex()`` function.
.. code-block:: glsl
uniform sampler2D noise;
This will allow you to send a noise texture to the shader. Now look in the inspecter under your material.
You should see a section called "Shader Params". If you open it up you will see a section called "noise".
Click beside it where it says "[empty]" and select "New NoiseTexture". Then in your NoiseTexture click beside
where it says "Noise" and select "New OpenSimplexNoise".
:ref:`OpenSimplexNoise <class_opensimplexnoise>` is used by the NoiseTexture to generate a heightmap.
Once you set it up and should look like this.
.. image:: img/noise-set.png
Now, access the noise texture using the ``texture()`` function. ``texture()`` takes the texture as a first
argument and the position on the texture as the second. We use the ``x`` and ``z`` channels of ``VERTEX`` to
determine where on the texture to look up.
.. code-block:: glsl
float height = texture(noise, VERTEX.xz / 2.0 ); //divide by the size of the PlaneMesh
VERTEX.y += height;
Using this code you can see the texture creates random looking hills.
.. image:: img/noise.png
Right now it is too spiky, we want to soften the hills a bit. To do that we will use a uniform.
You already used a uniform above to pass in the noise texture, now let's learn how they work.
Uniforms
--------
Uniform variables allow you to pass data from the game into the shader. They are
very useful for controlling shader effects. Uniforms can be almost any
datatype that can be used in the shader. To use a uniform, you declare it in
your :ref:`Shader<class_Shader>` using the keyword ``uniform``.
Let's make a uniform that changes the height of the terrain.
.. code-block:: glsl
uniform float height_scale = 0.5;
Godot lets you initialize a uniform with a value; here, ``height_scale`` is set to
``0.5``. You can set uniforms from GDScript by calling the function ``set_shader_param()``
on the material corresponding to the shader. The value passed from GDScript takes
precedence over the value used to initialize it in the shader.
::
# called from the MeshInstance
mesh.material.set_shader_param("height_scale", 0.5)
.. note:: Changing uniforms from Spatial nodes is different than in CanvasItem nodes. Here,
we set the material inside the PlaneMesh resource. In other mesh resources you may
need to first access the material by calling ``surface_get_material()``. While in
the MeshInstance you would access the material using ``get_surface_material()`` or
``material_override``.
Remember that the string passed into ``set_shader_param()`` must match the name
of the uniform variable in the :ref:`Shader<class_Shader>`. You can use the uniform variable anywhere
inside your :ref:`Shader<class_Shader>`. Here, we will use it to set the height value instead
of arbitrarily multiplying by ``0.5``.
.. code-block:: glsl
VERTEX.y += height * height_scale;
Now it looks much better.
.. image:: img/noise-low.png
Using uniforms, we can even change the value every frame to animate the height of the terrain.
Combined with :ref:`Tweens <class_Tween>`, this can be especially useful for simple animations.
Interacting with light
----------------------
First, turn wireframe off. To do so, click in the upper-left of the Viewport again, where it says
"Perspective", and select "Display Normal".
.. image:: img/normal.png
Note how the mesh color goes flat. This is because the lighting on it is flat. Let's add a light!
First, we will add an :ref:`OmniLight<class_OmniLight>` to the scene.
.. image:: img/light.png
You can see the light affecting the terrain, but it looks odd. The problem is the light
is affecting the terrain as if it were a flat plane. This is because the light shader uses
the normals from the :ref:`Mesh <class_mesh>` to calculate light.
The normals are stored in the Mesh, but we are changing the shape of the Mesh in the
shader, so the normals are no longer correct. To fix this, we can recalculate the normals
in the shader or use a normal texture that corresponds to our noise. Godot makes both easy for us.
You can calculate the new normal manually in the vertex function and then just set ``NORMAL``.
With ``NORMAL`` set, Godot will do all the difficult lighting calculations for us. We will cover
this method in the next part of this tutorial, for now we will read normals from a texture.
Instead we will rely on the NoiseTexture again to calculate normals for us. We do that by passing in
a second noise texture.
.. code-block:: glsl
uniform sampler2D normalmap;
Set this second uniform texture to another NoiseTexture with another OpenSimplexNoise. But this time, check
off "As Normalmap".
.. image:: img/normal-set.png
Now, becuase this is a normalmap and not a per-vertex normal. We are going to assign it in the ``fragment()``
function. The ``fragment()`` function will be explained in more detail in the next part of this tutorial.
.. code-block:: glsl
void fragment() {
}
When we have normals that correspond to a specific vertex we set ``NORMAL``, but if you have a normalmap
that comes from a texture, set the normal using ``NORMALMAP``. This way Godot will handle the wrapping the
texture around the mesh automatically.
Lastly, in order to ensure that we are reading from the same places on the noise texture and the normalmap
texture, we are going to pass the ``VERTEX.xz`` position from the ``vertex()`` function to the ``fragment()``
function. We do that with varyings.
Above the ``vertex()`` define a ``vec2`` called ``vertex_position``. And inside the ``vertex()`` function
assign ``VERTEX.xz`` to ``vertex_position``.
.. code-block:: glsl
varying vec2 vertex_position;
void vertex() {
...
vertex_position = VERTEX.xz / 2.0;
}
And now we can access ``vertex_position`` from the ``fragment()`` function.
.. code-block:: glsl
void fragment() {
NORMALMAP = texture(normalmap, vertex_position).xyz;
}
With the normals in place the light now reacts to the height of the mesh dynamically.
.. image:: img/normalmap.png
We can even drag the light around and the lighting will update automatically.
.. image:: img/normalmap2.png
Here is the full code for this tutorial. You can see it is not very long as Godot handles
most of the difficult stuff for you.
.. code-block:: glsl
shader_type spatial;
uniform float height_scale = 0.5;
uniform sampler2D noise;
uniform sampler2D normalmap;
varying vec2 vertex_position;
void vertex() {
vertex_position = VERTEX.xz / 2.0;
float height = texture(noise, vertex_position).x * height_scale;
}
void fragment() {
NORMALMAP = texture(normalmap, vertex_position).xyz;
}
That is everything for this part. Hopefully, you now understand the basics of vertex
shaders in Godot. In the next part of this tutorial we will write a fragment function
to accompany this vertex function and we will cover a more advanced technique to turn
this terrain into an ocean of moving waves.

View File

@@ -1,9 +0,0 @@
.. _doc_your_first_visual_shader:
Your first visual shader
========================
Introduction
------------

View File

@@ -0,0 +1,320 @@
.. _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 it's ``METALLIC`` property to ``0.0``. But, water is also highly
reflective, so we will set it's ``ROUGHNESS`` property to by 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.