In this part, we’re going to explore the specifics of creating a shader in Godot.
Godot’s renderer runs on OpenGL or Vulkan from Godot 4.0. These libraries are low-level graphical Application Programming Interfaces (API) that interact with the graphic card drivers without us needing to write low-level code ourselves. We write OpenGL and Vulkan shaders in a language called GLSL: Graphics Library Shading Language. Godot’s ShaderScript shader language looks like GLSL, but it is more straightforward than it. It handles some repetitive details for you in the background.
When you write a shader in Godot, the engine translates the code for the GPU. It analyzes and merges your code into more complex, GLSL-based shaders written by the engine programmers. That way, you only have to code the shader’s core logic, without the extra boilerplate of lower-level languages. That’s also why you can write a shader for a 3D object with no code and still have the object draw correctly.
GLSL, its DirectX cousin HLSL, and the Godot shading language resemble C. Lines end with semi-colons, you wrap blocks in angular brackets and conditional statements and loops in parentheses. We’ll cover some of the syntax’s details as we teach you to code specific effects. It’s quite limited, so you can learn it as you go.
In Godot, to create a shader, you have to create a ShaderMaterial
and assign a new Shader
resource to it.
The material exposes controllable parameters to the Inspector and scripting languages. The shader resource contains the shader code.
Let’s break down a short yet complete shader that emulates the Modulate property of 2D nodes in Godot. This program corresponds to the “Multiply” blending mode found in drawing applications.
shader_type canvas_item; uniform vec4 multiply_color: hint_color; void fragment() { vec4 texture_color = texture(TEXTURE, UV); vec3 multiplier = mix(vec3(1.0), multiply_color.rgb, multiply_color.a); COLOR.rgb = texture_color.rgb * multiplier; COLOR.a = texture_color.a; }
ℹ The code tells you why we call this blending mode “Multiply”: the final color is the result of a multiplication!
Let’s break down the code above line-by-line. Don’t worry if you don’t get all the details right now. We are going to skim through it to give you a rough sense of how a shader works on a technical level. We dive into more details in other tutorials in this course.
Every shader starts with a shader_type
statement that tells Godot what the intended use of the program is. Here, it’s a shader that applies to nodes that inherit from the class CanvasItem
, that is to say, 2D nodes.
shader_type canvas_item;
The uniform is a value that can we can modify from outside the shader. It appears in the inspector when you expand the shader material, and you can modify it with GDScript and C# code. It’s of type vec4
, that is to say, an array of four values. We can use four-dimensional vectors to store colors with an alpha channel. The four values correspond to the red, green, blue, and alpha channels of the color.
uniform vec4 multiply_color: hint_color;
The fragment
function is your fragment shader. In Godot, you can write your vertex and fragment shaders in a single file. It’s a function that takes each fragment, which we talked about in the previous lesson and modify it.
We use the function to tint every pixel that gets drawn on the screen.
void fragment() {
We first use the texture
function to sample the color of the current fragment from the sprite or texture that’s attached to our node.
vec4 texture_color = texture(TEXTURE, UV);
We then calculate a second color that’s going to multiply our sampled fragment. To do so, we use the mix
function, which interpolates linearly between values, blending colors, or vectors. Here, we mix opaque white with our multiply_color
, using its alpha channel as the interpolation factor.
vec3 multiplier = mix(vec3(1.0), multiply_color.rgb, multiply_color.a);
We finally output the product of our two colors using the built-in COLOR
output variable. This built-in controls the color of the pixels the graphics card draws on the screen.
COLOR.rgb = texture_color.rgb * multiplier; COLOR.a = texture_color.a; // Preserves the texture's transparency
Here is the result: