Shaders introduction in Godot!

Hello everyone, welcome back.

Today's post is about shaders introduction so if you are already familiar with shaders, well then it's a pleasure talking to you. You can safely skip this one.

Okay for this I will be using Godot version 4.1.




Intro

Okay before we dive into the topic let me clarify something first. Just because I have put Godot in the title does not mean that whatever I will talk about will only apply to Godot.

Remember, shaders are basically maths and Mathematics is universal. It does not have any platform boundaries. So it can be used in Godot, Unity, Unreal, Blender basically every 2D or 3D platform.

What are shaders?

Shaders are program that runs on the GPU that determines how it will render or output any 2D or 3D objects on your screen.

In Godot there are 5 types of shaders.

Spatial, Canvas item, Particle, Sky and Fog.

Shader types in Godot

  • Spatial shaders are used to render 3D objects.
  • Canvas item shaders are used for any 2D objects including UI.
  • Particle shaders are used to render particles.
  • Sky shaders are used to render skybox and hence it is my favourite type.
  • Finally Fog shaders are used to render volumetric fog.

In each shader types, there will be processor functions.

In Godot there are following processors



We will going to talk about the 2 most common ones which will be present in almost all game engines.

Vertex processor and Fragment processor.

What is a Vertex processor?

In Unity it's called Vertex context and it a basically a function or method which gets called for each vertex of our mesh.

And Vertex processor is basically used to determine how our mesh geometry will be rendered.

What is Fragment processor?

In Unity it's called Fragment context and it is basically a function or method which gets called for each fragment or simply put each pixels of our rendered mesh.

And it is basically used to determine surface properties of our mesh like albedo, metallic roughness etc.

Ways to create custom shader in Godot

In Godot there are 2 ways to create our own custom shaders.

1st by writing the shader code and second by using Visual shader editor which is a node based interface very similar to Unity's shader graph.

We will going to create a simple Flag shader which will deform as if it is waving in the wind using both methods.

I have the Flag node in our scene which has a MeshInstance3D for our pole and flag. The flag mesh also has some extra geometry so we can deform it and I have create it in a way so that it matches the aspect ratio of the texture which we will apply on it, so that we don’t have to worry about matching the aspect ratio for now.

Using shading language

First let’s write our own shader and for that Right click > Create new > Resource. Then we will search for Shader in the list and hit create. We will keep the mode Spatial here because our mesh is 3D, then we have template and the default one will provide us with our Fragment processor.

I will give a proper name to our shader, flag.

Now we have our shader but in most of the game engines, almost in all of them, we cannot directly apply our shader to the mesh, so we need to create a Material for that. So again Right click > Create new > Resource. Then in the list select ShaderMaterial, I will call it flag and then select .material extension and hit save.

Double click our newly created material, it will open up in Inspector dock and here it has a Shader property, Let’s assign our flag shader to it. Then I will assign our material to the flag mesh.

So the material will tell Godot how to render our flag mesh and material is the asset or resource that holds the actual shader code.

Okay let’s double click our flag shader and it will open up in a shader editor window.

Here, we have defined type of shader, and we want to modify the look of our mesh so we will use Fragment processor. It will be called for every pixel of our flag.

Now Godot uses a purpose built shading language which is based on GLSL AKA OpenGL Shading Language and it has similar syntax like C language.

So Godot’s shading language is strictly typed so you must define the type of you variables and it won’t implicitly cast one datatype to another, you have to explicitly cast it.

Meaning float values will not automatically be cast into integer or vice versa. Also it is not a free form language meaning functions must be defined before we can call them.

Okay now notice that our flag is only rendered from this side. Most game engines will render the geometry this way, to save the performance but here we need to render the other side of our flag. For that we will set the render mode to render both side, so we will set render_mode to cull_disabled to render the back face as well.

shader_type spatial;
render_mode cull_disabled;

Now we want to apply the texture so let’s define a sampler2D, I will call it main_texturesampler2D will hold the actual reference of our image resource and we want to apply our texture from the inspector and for that we will use uniform keyword.

uniform sampler2D main_texture;

Then, in our Fragment processor, we want to apply that image so we want to assign it to the ALBEDO which is the built in variable but we cannot just assign our main_texture to it, because it just holds our texture resource.

First we need to sample it in the actual texture datatype so we will texture(), then we need to pass what I want to sample so main_texture and the second argument is on which coordinates we want to sample it, we will sample it using the actual UVs of our mesh. Now texture() will return vec4 because it has red, green, blue and alpha channel but our ALBEDO accepts vec3, so we need to cast it or we can just assign rgb channel like following. We can also use xyz, it is basically the same thing.

void fragment() {
ALBEDO = texture(main_texture, UV).rgb;
}

Now our shader material will have a slot for our main_texture, from which we can assign our texture. Now we want to make our flag wave and for that we have to modify the vertices of our mesh and to modify the vertices we need Vertex processor.

Here let me define a vec3 position and I will assign the original vertex position using VERTEX which is the built in variable that will provide each vertex position in local space.

Now for the vertices at top of our mesh, we have same Y and Z value but the X will be different so we will use the X value with sin(). sin() will return values between -1 and 1 and now you can see our flag deforms like this actual flag.

void vertex() {
vec3 position = VERTEX;
position.z = sin(position.x);
VERTEX = position;
}

Now we want to deform it over time so I will just add the TIME. TIME will give use the time in seconds.

And now our flag will start to wave, now I want to control the speed and strenght of the waves and we want to control them from the inspector so let’s define 2 uniforms wave_frequency and wave_amplitude.

Then multiply our TIME with wave_frequency and multiply the whole equation with wave_amplitude.

And now we can control the wave speed and wave strength from the inspector, however it looks like our flag is moving in the opposite direction of the wind so I will just negate TIME like following.

uniform float wave_frequency = 2.0;
uniform float wave_amplitude = 1.0;

void vertex() {
vec3 position = VERTEX;
position.z = sin(position.x - TIME * wave_frequency) * wave_amplitude;
VERTEX = position;
}

Now you will notice that our flag is not attached with our pole. To fix that we will use the our UVs. So let’s multiply our entire equation with UV.X, and our shader will finally look like this.

flag.gdshader

shader_type spatial;
render_mode cull_disabled;

uniform float wave_frequency = 2.0;
uniform float wave_amplitude = 1.0;
uniform sampler2D main_texture;

void vertex() {
vec3 position = VERTEX;
position.z = sin(position.x - TIME * wave_frequency) * wave_amplitude * UV.x;
VERTEX = position;
}

void fragment() {
ALBEDO texture(main_texture, UV).rgb;
}

And we have our waving flag. Pretty cool!

Using visual shader

Alright now let’s do the same thing with Visual shader so Right click > Create new > Resource. Then search for visual shader and hit create. Keep the Mode to Spatial, I will call it flag_visual_shader. Now I will use the same material so I will just drag our visual shader and assign it to our material.

Now again, we want to render both faces of our mesh, so select our visual shader, in the Inspector, go to Modes and set Cull to Disabled. And now both of our flag faces will be rendered. Then we will go to the Fragment processor, we can change the processor from the dropdown at top left of the window.

In Fragment processor we want to apply our texture from the Inspector so let’s create a Texture2DParameter node. Let’s call it main_texture and we will able to see this parameter in the inspector, keep in mind though that it will only appear after you used it in master node.

Then again we have same problem that I cannot feed the output to our Albedo, we need to sample it so we will create a Texture2D node. Then make sure that we have selected SamplerPort.

Then take our main_texture and feed it into sampler2D, then take its output and feed it into Albedo.

Our Fragment processor will look like this at the and.



Now let’s deform our mesh and for that let’s go to Vertex processor.

Then we will create a vertex node, well actually it is Input node and we can access various built in variables. Take its output and feed it into Vector3DDecompose node, it will split our vector into X, Y and Z.

Now I am going to follow the same logic from our flag shader. So first let’s create 2 float parameters for our wave_frequency and wave_amplitude.We can set default values by checking this Default Value Enabled and then set the default value.

Now we will multiply our wave_frequency with Time. Make sure you have selected float operation for multiply. Then we will subtract our vertex position’s X with this multiply. Then take Subtract node’s output and feed it into Sin node. Then we will multiply our sine values with our wave_amplitude.

Now we want this new values to use as Z so let’s compose or create a Vector3 and for that we need Vector3Compose node. It basically combines X,Y and Z and give us Vector3. Take our Multiply node’s output and feed it into Z and we will use same X and Y from our Vector3Decompose node. Then take our Vector3Compose node’s output and feed in into Vertex slot.

Now we have same problem as before so let’s create a UV node. We want X value of our UVs so take its output and feed it into Vector2Decompose node. Then we will take its X and multiply it with our equation and finally take its output and feed it into the Z of our Vector3Compose node.

Our Vertex processor will look something like this at the end.



And now we have the same effect using visual shader. Yay!

Conclusion

Now you can use either of the 2 methods to create your custom shaders, however I will encourage you to learn shading language a bit, because right now I find visual shader a bit restrictive for example we don’t have a Twirl UV node or Rotate UV node.

Now we can of course write our custom logic using Expression and GlobalExpression nodes, but to write that we at least need to have basic knowledge of shading language, so yeah it is a bit counter intuitive in that sense.

We can use custom addons to overcome that and yours truly is also working hard to get those missing nodes in the core engine itself, but it will take time.

Okay, so now you have basic idea of shaders and I think now you will be ready to dive into documentation without finding it overwhelming.

Trouble following along? Check out this detailed tutorial!



Thank you so much for reading!

Comments