added your first spatial shader
|
Before Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 45 KiB |
|
Before Width: | Height: | Size: 117 KiB |
|
Before Width: | Height: | Size: 119 KiB |
|
Before Width: | Height: | Size: 5.0 KiB |
|
Before Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 9.4 KiB |
|
Before Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 19 KiB |
@@ -11,5 +11,4 @@ Shading
|
||||
intro_to_shaders_water_workshop
|
||||
screen-reading_shaders
|
||||
migrating_to_godot_shader_language
|
||||
vertex_displacement_with_shaders
|
||||
advanced_postprocessing
|
||||
|
||||
@@ -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.
|
||||
|
||||
BIN
tutorials/shading/your_first_shader/img/PBR.png
Normal file
|
After Width: | Height: | Size: 50 KiB |
BIN
tutorials/shading/your_first_shader/img/albedo.png
Normal file
|
After Width: | Height: | Size: 103 KiB |
BIN
tutorials/shading/your_first_shader/img/cos.png
Normal file
|
After Width: | Height: | Size: 66 KiB |
BIN
tutorials/shading/your_first_shader/img/cos4.png
Normal file
|
After Width: | Height: | Size: 87 KiB |
BIN
tutorials/shading/your_first_shader/img/dark-water.png
Normal file
|
After Width: | Height: | Size: 121 KiB |
BIN
tutorials/shading/your_first_shader/img/fresnel.png
Normal file
|
After Width: | Height: | Size: 117 KiB |
BIN
tutorials/shading/your_first_shader/img/iconuv.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
tutorials/shading/your_first_shader/img/light.png
Normal file
|
After Width: | Height: | Size: 37 KiB |
BIN
tutorials/shading/your_first_shader/img/noise-flat.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
tutorials/shading/your_first_shader/img/noise-low.png
Normal file
|
After Width: | Height: | Size: 56 KiB |
BIN
tutorials/shading/your_first_shader/img/noise-mesh.png
Normal file
|
After Width: | Height: | Size: 65 KiB |
BIN
tutorials/shading/your_first_shader/img/noise-normal-light.png
Normal file
|
After Width: | Height: | Size: 69 KiB |
BIN
tutorials/shading/your_first_shader/img/noise-normal-sub.png
Normal file
|
After Width: | Height: | Size: 74 KiB |
BIN
tutorials/shading/your_first_shader/img/noise-normal.png
Normal file
|
After Width: | Height: | Size: 67 KiB |
BIN
tutorials/shading/your_first_shader/img/noise-set.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
tutorials/shading/your_first_shader/img/noise.png
Normal file
|
After Width: | Height: | Size: 70 KiB |
BIN
tutorials/shading/your_first_shader/img/normal-set.png
Normal file
|
After Width: | Height: | Size: 8.4 KiB |
BIN
tutorials/shading/your_first_shader/img/normal.png
Normal file
|
After Width: | Height: | Size: 5.3 KiB |
BIN
tutorials/shading/your_first_shader/img/normalmap.png
Normal file
|
After Width: | Height: | Size: 91 KiB |
BIN
tutorials/shading/your_first_shader/img/normalmap2.png
Normal file
|
After Width: | Height: | Size: 92 KiB |
BIN
tutorials/shading/your_first_shader/img/plane-sub-set.png
Normal file
|
After Width: | Height: | Size: 5.5 KiB |
BIN
tutorials/shading/your_first_shader/img/plane-sub.png
Normal file
|
After Width: | Height: | Size: 44 KiB |
BIN
tutorials/shading/your_first_shader/img/plane.png
Normal file
|
After Width: | Height: | Size: 5.8 KiB |
BIN
tutorials/shading/your_first_shader/img/plastic.png
Normal file
|
After Width: | Height: | Size: 112 KiB |
BIN
tutorials/shading/your_first_shader/img/rim.png
Normal file
|
After Width: | Height: | Size: 111 KiB |
BIN
tutorials/shading/your_first_shader/img/shader-error.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
tutorials/shading/your_first_shader/img/specular-toon.png
Normal file
|
After Width: | Height: | Size: 110 KiB |
BIN
tutorials/shading/your_first_shader/img/wave1.png
Normal file
|
After Width: | Height: | Size: 112 KiB |
BIN
tutorials/shading/your_first_shader/img/wave2.png
Normal file
|
After Width: | Height: | Size: 74 KiB |
BIN
tutorials/shading/your_first_shader/img/wave3.png
Normal file
|
After Width: | Height: | Size: 92 KiB |
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
.. _doc_your_first_particles_shader:
|
||||
|
||||
Your first Particles shader
|
||||
===========================
|
||||
|
||||
Introduction
|
||||
------------
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
.. _doc_your_first_visual_shader:
|
||||
|
||||
Your first visual shader
|
||||
========================
|
||||
|
||||
Introduction
|
||||
------------
|
||||
|
||||
|
||||
@@ -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.
|
||||