Skip to content

Godot Shaders

  • Links

  • Workflow

    • The easiest way to iterate on shaders that I’ve seen is:
      • Have a scene open in the Godot editor where your shaders are attached to some Godot node
      • Maximize the scene so that you don’t see the inspector, scene editor, or file system
      • Modify your shader directly from Godot (using Godot’s text editor).
        • It’ll detect errors, and if none exist, it’ll automatically update your shader without you even having to explicitly save the shader.
    • There is no standard shader debugger. People typically resort to rendering any debugging information visually, either by trying to render text (reference, but for GLSL) or by trying to use colors in creative ways (e.g. coloring a chunk of your image bright pink when a condition is met).
  • varying: this is something passed from the vertex shader to the fragment shader, interpolating its value along the way. E.g. read the comments in this code:

    varying vec2 vertex_coord;
    void vertex() {
    vertex_coord = VERTEX.xy;
    }
    void fragment() {
    COLOR = texture(TEXTURE, UV);
    // Suppose your texture is a 16x16 image and it's attached to a 16x16 Sprite2D that is centered.
    // This will draw the first 11 columns of pixels red. This is because vertex_coord will range
    // from -8 to 8.
    if (vertex_coord.x < 3.0) {
    COLOR.r = 1.0;
    }
    }
  • VERTEX is in local space, meaning if you have a 16x16 texture applied to a centered Sprite2D, it’ll range from (-8, -8) to (8, 8)
  • UV is a vector that is in texture space. While the possible range is (0, 0) to (1, 1), the actual range is based on what you’re rendering. For example, suppose you have a 40x40 spritesheet where each sprite is 10x10. That means you have 16 sprites in this spritesheet. If you attempt to use the upper-leftmost one, the UVs will range from (0, 0) to (0.25, 0.25).

When a sprite is flipped (e.g. with flip_h or flip_v), UV can be “backwards”. E.g. with flip_h, instead of going from (0, 0) to (1, 1), it will go from (1, 0.0) to (0, 1) (i.e. x decreases from left to right). It doesn’t seem like there’s any way to know universally and reliably whether a sprite was flipped before being passed to the shader. You could specify a uniform, but then you would need to keep it in-sync with the Godot side of things. Instead, here’s something I made that works for centered CanvasItems (like centered sprites):

// Option #1
// This checks to see if we're rendering vertices in the right order. E.g. normally, the
// 0th vertex has a VERTEX.x of a negative number (if the sprite is centered).
bool should_h_flip = (VERTEX_ID == 0 && VERTEX.x > 0.0) ||
(VERTEX_ID == 1 && VERTEX.x > 0.0) ||
(VERTEX_ID == 2 && VERTEX.x < 0.0) ||
(VERTEX_ID == 3 && VERTEX.x < 0.0);
// Option #2
// This checks if the vertex positioning matches the UV positioning.
bool should_h_flip = (sign(VERTEX.x) < 0.0 && UV.x == 1.0) ||
(sign(VERTEX.x) > 0.0 && UV.x == 0.0);
// Regardless of which option was chosen above, "undo" the flipping for our fragment shader
// by inverting the vertex coordinate again. Alternatively, just save a `varying bool` so that
// your fragment shader can do something conditionally if it wants.
if (should_h_flip) {
vertex_coord.x *= -1.0;
}

Technically, this only works for centered rectangles since we’re only checking for negative or positive X coordinates, and a centered rectangles will always have 2 vertices with X < 0 and two vertices with X > 0. If you drew a parallelogram, then that wouldn’t necessarily be true. If you didn’t center the rectangle, then you would need to change the checks a bit. However, I don’t think CanvasItem can render as anything other than a square.

(no notes yet)

The short answer is: don’t, for now (reference). textureSize will return the width and height of the entire texture, not just the region that you’re using. You have a few workarounds in the meantime:

  • Crop all textures at runtime to exactly the area that you’re using, that way the shader sees only the cropped texture. On the plus side, your UVs will now be in the range [0, 1] as expected, not something like [0.25, 0.5].
  • Pass a uniform to your shader with any region information. This would need to be updated any time the region changes, which would be tedious to keep in-sync.
  • Use a CanvasGroup as mentioned somewhere in the issues. This would involve changing potentially any renderable in your game, which would be a challenge.
vec3 hsv2rgb(vec3 c) {
vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
}

The HSV values are in the range [0, 1]. See the reference link for code to convert from RGB to HSV.