March 14, 2019

An Example of a 2D Greyscale VisualShader in Godot 3.1

If you know anything about the implementation of custom shaders in the game engine Godot, you know that they have consistently pushed to become more approachable and easier to use through some traditional and some untraditional methods.

These efforts are important and bring a new level of power to a lot of game devs, however the efforts have also resulted in a lot of churn between Godot versions. In July, 2018 we saw the re-introduction of the VisualShader tool which allows us to use a simple UI to build up complex shaders visually.

The tool is simple and leaves a bit to be desired (for example, it's not possible to have a uniform boolean flag (e.g. IsGreyscale) to toggle shader behavior in VisualShader, but it is in a shader written in the normal shader language.

Regardless, I think VisualShader is great for what it is and could benefit from more documentation so I'd here is a simple example of a VisualShader which modifies the rendering of a sprite to make it greyscale.

At the end of this tutorial, hopefully you'll be able to turn any sprite in your Godot game to be grey.

This example will focus on the "Spin" button in my game.  

This is a Sprite node with the texture set to a full color asset. Once we're done, we'll have an identical button with all the colors turned to grey!

In your project, select your Sprite and check out the inspector. You'll want to look for the CanvasItem header, which contains low-level rendering options for this Sprite. Under the Material dropdown, you'll want to click New ShaderMaterial. If you're not familiar with materials, the idea is that a material holds various properties related to the rendering of your object like whether or not it interacts with light or in our case, what shader to use.

Once you've gotten your new ShaderMaterial click on the dropdown near the Shader field and select New VisualShader which will give you access to the graph-based shader editor. If you pick New Shader instead, you'll get access to the text-based shader editor.

Look in the bottom of your editor, near the other tabs like Output, Debugger, etc you should now see VisualShader (as long as your Sprite is still selected in the inspector.)

By default, your graph will be empty except for an Output node. but it's easy to create new nodes by clicking the Add Node... button. However, before we dive into that, look back in your inspector and make sure the Mode of your shader is set to CanvasItem, this gives our graph different Inputs which will let us deal with our 2D sprite.

Once you're all set up your inspector should look something like this...

If your inspector looks different, for example you don't see the hierarchical view of CanvasItem > Material > Shader then make sure you're using Godot 3.1 and not an older 3.1 release.

At this point we can build up a graph in the VisualShader editor.

Above we see four nodes, each added via the Add Node... button. If you've never written a shader before, it's important to understand that this logical graph will be ran once for each pixel of your sprite. So when we're building up this logic we're only thinking about one given point on the sprite and the modifications we're doing are only happening to that one point. The graph is re-ran for each pixel and the results of all those runs is what's rendered to the screen at the end of a frame.

The first node is an Input node with the uv type selected.

Any Input value is something coming from outside our shader, they're the values which are provided by settings things like the Texture of the Sprite.

If you're not familiar with the concept of a uv then a simple way to think of it is that in a 2D world we'd use x and y to denote a pixel on a texture, but in the 3D world we already use x, y, and z to position an object, so we use u and v instead.

The uv data is just coordinates on a texture. Since they're just coordinates (i.e. numbers) they're not much use until we map them to the data from our Sprite's Texture. The second node takes care of this.

As an aside, once you're created the second Node via the Add Node... button, you'll have a few options about where this texture's data comes from. If you select the default Texture2D option, then you're saying the data wont come from the screen (i.e. the entire game screen) and it wont come from the sprite's Texture2D, it'll come from a new parameter to the shader. If you grab the output rgb value from that texture and connect it to the final Output node, you'll see a new Shader Param in the inspector. This is an automatically created uniform, any time you want to pass data into your shader from your other scripts, you'll use a uniform of some form.

If you want to pass in other data which isn't supplied in Inputs then check out the Uniforms section in the Add Node... menu. Keep in mind that you wont see the Shader Param field from your uniform show up unless it's connected to the Output node.

Anywho, flip your Texture node to Texture2D and that will be the Sprite's texture. Once we have that we'll have our first output ready, the alpha field. Connect the Texture's alpha to the Output's alpha. We're not interested in messing with any alpha values so there's nothing to do here except pass the original value through.

However the rgb value is of interest to us. We'll make our final node, a DotProduct. I'll be honest, I don't know why exactly this works to turn the rgb value to grey. The base of this VisualShader is a port of this answer on the Godot forums. All credit goes to timoschwarzer. If you want, you can try to read the Wikipedia page on dot products, but I doubt it'll help.

With that bit of mystery, let's connect up our last node.

We've hard-coded the same value's from Timo's answer. Then finally we can take the dot product and allow it to be implicitly converted back into a vector to fulfill the color field on the Output node.

via Gfycat

There we go, we've created our first VisualShader in Godot 3.1! If you want, you can go into the inspector and click the dropdown near the Material to save this material so you can easily apply it to other sprites.