Hello and welcome to another episode in the Godot Dough Basics tutorial series Godot Tutorials is not affiliated with or sponsored by Godot Game Engine. In this episode, we will take a soft look into shaders.
A shader is a program or process that runs in the graphics pipeline and tells the computer how to render each pixel. In Godot, we have three different shader types. We have spatial canvas, atom and particles. In this episode, we will only be taking a look at canvas item. Now the first line in your shader file should be Shader underscore type followed by canvas underscore item and you have to end your line with a semicolon when dealing with shaders.
We have two different types of shader loops.
The first is the fragment loop and you use the fragment loop when you want to color your image pixels. The second loop is the vertex loop and you use that to position your vertices.
I want to introduce four different types of global variables that shaders come with the first discolor and this is the output color of your pixels. The second variable is time, and that is the time since the shader has started not to be confused with a Delta time. The third variable is Vertex and that is the position of your vertex and the fourth is UV, which is your UV coordinate and it is read only UV kwatinetz is probably new.
So let me softly introduce this to you. The UV coordinate system is just taking the letters of the you and the letter V and denote those for the axis of your texture. So think of the U as your x axis and the V as your Y axis.
Keep in mind that each individual pixel has a coordinate and images are made up of many, many pixels.
Let's go ahead and take a look at the UV coordinate system. So over here we have our texture image to the top left of our texture image.
We have the coordinates zero point zero and zero point zero to the top right corner of our texture image. We have the coordinates one point zero and zero point zero to the bottom right corner of our texture image. We have the kwatinetz one point zero and one point zero. And to the bottom left of our texture image, we have the kwatinetz zero point zero and one point zero and all the other pixels in between are a variation between zero point zero and one point zero in the given you or v axis.
Godot comes with many different methods.
I'm going to introduce for the first is the same method which takes in a float value. The second is the cosine method.
The third is the tangent method.
And the last method I want to introduce is the mix method. It takes in to vector for data types as the first two arguments.
The last argument will be a float data type value and with the mix method you can sort of create in a sense, moving gradients.
And we'll take a look at that later in this episode.
Now, when you want to declare variables outside of the vertex loop or the fragment loop, you're going to need to do two things before you start naming your variables.
The first is declaring the initialization type.
The second is declaring the data type. And then after that, you can create the name of your variable and then a sign of a value depending on your initialization type.
When dealing with shaders, we do have three different initialization types. You declare these variables when you're outside of the fragment loop and the vertex loop.
The first initialization type is the constant type and it is used to declare a value that never changes. The second type is varying. You use that when you want to declare a value that you wish to send between the vertex and fragment loop. And the last type is uniform. And you use that when you want a value or rather when you want to pass data into your shader files. Let's go ahead and take a look at an example. In the first example, I wish to create a color variable that holds a vector for data type.
So in this case I declare the initialization type, which is constant, followed by the data type, which in this case is vector four, followed by the name of our variable, which I use the name Color White, and then I go ahead and I sign it.
The data type vector for and all values are 1.0 because I would like the color white to be assigned to a variable color white to be used in most likely the fragment and you can do the same thing for the other initialization types. This is the initialization type for varying so varying data type variable name, same thing for uniform initialization type data type variable name. Now when you're declaring a variable and said one of your loops in this case, either vertex loop or fragment loop, you can omit the initialization type.
So in this example, we're declaring our color weight again. In this case, vector four, which is our data type, followed by the name of our variable, which is color white. And then we assign a. The values of the vector for data type, and we do the same thing for speed in this case, followed by the name of our variable. Now, let's go ahead and take a look at an example of creating basic shaders. So in this example, we've already created some basic shaders so we can take a look at the code structure. But let's go ahead and take a look at how to create a shader. So what you're going to want to do is add a Sprite onto the century.
Go ahead and pick your Sprite in the canvas item under material.
You're going to click on empty. You're going to pick new shader material.
You're going to click on the image inside the material.
You're going to get a pop up. You're going to see Shader followed by Empting. You're going to go ahead and click on that click. And you Shader click on the shader text or icon. And the Shader is going to pop up on your screen right now, it's giving you an error because it's expecting the Shader type. So go ahead and write that. In this case, I went ahead and created the Shader type canvass I had on property. And we're just letting the Shader know that we will be dealing with 2D. Now, once you've done that, basically the sky's the limit.
There's a lot you can do with Schrader's. I'm going to go ahead and add links into the description down below so you can get more into how to create custom shaders in your games. But in this case, let's do something fairly simple. We want to change the colors of our image. So we're going to go ahead and choose the fragment loop. Remember, open close bracket. So excuse me, I'm going to have to put the data type because when dealing with shaders, they are expecting the data type of your variables and data type of your functions.
Now, in this case, we want to change the color of our sprites. In this case, we're going to use the global variable color and it takes in a vector for data type.
And in this case, let's say we want to change it to all black. So as you can see here in one line, we basically were able to change the color of our Sprite image from white to black. I'm going to go ahead and turn this invisble and we're going to look at a simple example. So in this case, on our screen, we can see a image. It's basically the same white square image. But this time inside of our fragment loop, we decided to use the UV global variable and the UV variable is a vector to data type.
So it basically holds our values from the up axis and the verses, which is the X and Y. And as you can see here on the bottom right, our coordinate systems one point zero and one point zero. And so if we were to replace that here, you'll notice that everything's white. But because we're using the UV variable, it does something special when we combine it into a vector for it's basically allowing us to paint each individual pixel. So as you can see here, bottom right is white top left, which again is zero point zero and zero point zero will be blue.
And so you can see that we're painting our colors based on the ranges of zero point zero and one point zero. And that's how we're able to get this gradient color image.
So let's take a look at a more complicated example in this case, I have a simple white image, but it's oscillating between white and blue or a darker shade of blue.
And in this case, I created two constant values color. One color, too, in this case white, and a shade of blue inside of our fragment loop.
We are basically assigning time inside of our sign method and assigning that to a time variable.
We're also making sure that time is not less than zero, because since sine goes from positive to negative, back to positive, we won't be able to change colors quite quickly. We're going to have a little pause until we get back into the positive range and to make sure that we stay positive all the time. In this case, we're making sure that if time is less than zero, then we just inversed back our time value into positive range again. And then after that we use the mix method onto our global color variable. In this case, color one, which is white color, too, which is our dark shade of blue.
And then time and with this method, we able to get this effect. Right now it doesn't seem like anything, but imagine you have something like an injury effects.
So let's go ahead and change that to red. And it's a little slow.
So what we can do is, in fact, multiply our time by a value to speed it up and you can see we have this flashing effect. And so you can do quite a lot of things with shaders that you normally wouldn't be able to do through your gd file certain things, not all things. Leslie, let's go ahead and take a look at an example when dealing with the verticle Sloup, so in this case we declare our vertical loop with the void or data type because our loop isn't returning back anything. We use the global vertical variable and we add on to it a vector to data type because we're only dealing in a 2D coordinate system in the x axis.
Notice how we're using cosine. Doesn't matter what we really use, we could use sine as well. We're just changing how it starts. But ultimately it still gives us the same effect. Regardless whether you use cosine or something we pass in the value time inside of it. We multiply it by the distance and pixels. We would like to see our image moving. In this case, I have one thousand, but I could change that to one hundred and notice how we're only moving one hundred pixels. And of course, I don't want to move in the Y axis direction, so I left that to zero.
But you could add things here as well in order to give different moving effects. In this case, if I were to change this into sine, you can see how we're sort of moving in a circle.
If I make these values bigger, it will be a bigger circle. Let me zoom out.
There you go. Bigger circle.
And as you can see through one line of code, we were able to move our image around. But I want you to notice something here, even though our image is moving around.
Notice our own node is still positioned here.
And so even though we're moving, we're moving in relation to our noad position where it should be had we turn this off.
So let me go ahead and just comment that out so you can see where our images place or image is placed here.
Our borders are there, our collision detection will be here in this box, but our separate image is being painted around that. So something to keep in mind. Well, that's all I have for you in this episode. Thank you so much for joining me. Thank you for clicking the like button. And thank you for clicking the subscribe button. I'm going to go ahead and upload this onto GitHub, so please feel free to click on that and the description down below. And I look forward to seeing you in the next episode, have an amazing day.