Shaders are scary. Not really, but they are. Okay, if that sounds confusing, it’s the fact that with simple inputs, you can get some pretty crazy outputs. I am not an expert with shaders, but I want to use them. I know that they will help me create more special effects that I might otherwise not be able to create as easily or as nicely with just pixel art alone. And while I’m still not sure of the pixel art and shader aesthetic here, I don’t hate it.

shader_type canvas_item;
uniform sampler2D noise;

void fragment() {
	vec2 offset = 0.001 * cos(UV * TIME * 2.0);
	float h = texture(noise, UV - offset).b;
	COLOR = texture(TEXTURE, UV);
	if (h > 0.9 && COLOR.a > 0.0 && COLOR.r < 0.94) {
		COLOR.r *= h;
		COLOR.b *= 1.4;	
	}
}

That’s the shader used in the sample above. And while this isn’t going to be a shader tutorial, I want to go over some lessons I’ve already learned.

Split up your assets

The animated sprite is a single sprite sheet, and all the elements are combine. The circular body, the dangly bits, and the shadow below. In hindsight, I should have split them up into three separate animated sprites, and combine them all together into a single scene. This would have given me more control, and allowed me strategically place shaders where I wanted them.

This is why I have this check COLOR.r < 0.94 in the if statement. This checks the value of red. The arms have more red than the main body, and 0.94 is the cutoff. The shader won’t apply to those pixels. The same thing for the COLOR.a > 0.0 check. That is so nothing will be applied to transparent pixels.

If I had split up the body and the tentacles into separate sprites, I wouldn’t have had to make that check. I can still go back and fix that, and I probably will. But I wanted to share with you the lessons learned before I fixed it. I think it helps to show the process. Not just the end result. I did this, got this result, and this is where I need to improve. It wasn’t just a case of drawing the owl.

Add more inputs

Adding an input is fairly trivial. Inputs are denoted with uniform. In this case, I only have the noise being set. All the other numbers can have inputs. I’m going to go back and add in additional inputs for the COLOR modifications. It will look something like the following.

shader_type canvas_item;
uniform sampler2D noise;
uniform float mod_b = 1.4;
uniform float mod_g = 1.0;
uniform float mod_r = 0.9;

void fragment() {
	vec2 offset = 0.001 * cos(UV * TIME * 2.0);
	float h = texture(noise, UV - offset).b;
	COLOR = texture(TEXTURE, UV);
	if (h > 0.9 && COLOR.a > 0.0 && COLOR.r < 0.94) {
		COLOR.r *= mod_r;
		COLOR.b *= mod_b;	
		COLOR.g *= mod_g;
	}
}

You can see by allowing us to change the inputs above, we can get different results. The below sample is using the inputs of mod_b: 0.5, mod_g: 2, and mod_r: 0.1. I can play around with this and get other results if I want. Maybe this is a poison effect? If I can abstract the shader out even more, I could probably make a generic poisoned shader if I wanted.

Image

I still have a lot to learn in the shader department, but just working through this exercise has provided me with an incentive to learn more about them and how they work. It’s clear that it allows you to dramatically increase the look and feel of your game.

Reconsider how I design assets

Right now, I’m creating assets in Aseprite. And while this works great, it does have a downside. I have to create new assets for different colors. My goal now is to design things that make it easy to use shaders with. That way, I can create an asset once, and have a shader do the extra work of assigning it different colors. Now, I can have a single bullet, and if I want different damage types, I can simply apply a different shader.

This might seem obvious, but it’s not immediately apparent. Most pixel art tutorials and discussions are focused purely on the pixel art, and not the shader side of things. Understanding how to create good pixel art and how to make it easy to use shaders with it is not something I’ve seen anyone discuss (If it’s out there, please share!).

This also helps with color-blind modes. Because I can easily change the colors on the fly, I can work in color-blind support as needed. Rather than regenerate all the assets, I can easily pass in the corrections to the shaders. This isn’t something I’m going to concern myself with right now, but knowing that it can be done is reassuring.

Don’t be afraid of Shaders

No, really, don’t be afraid of them. Take it step by step. There are a lot of resources to learn about shaders in Godot. YouTube has lots of great tutorials, and don’t be afraid to practice. I’ll continue to explore and share.