Wireframe

A guide to recognising your shaders

By Helen Carmichael. Posted

Going Medieval is a voxel-based, colony-building sim set in an alternate medieval Europe. Despite our studio’s previous development experience, we were novices in using shaders in a 3D game. Here, I’ll explain how we got the effects we wanted using a shader editor and some of the tricks we picked up along the way. Before all that, though, let’s look at exactly what a shader is…

WHAT'S A SHADER THEN?

The term ‘shader’ was coined by 3D animators at Pixar in the late 1980s, and eventually became more widely available in the computer graphics cards of the early 2000s. Shaders are scripts that are typically coded for and run on a computer’s graphics processing unit (GPU). The GPU continuously takes instructions from your shaders to determine the changing colour of each pixel on the screen.

There are two basic types of shaders. There’s the pixel shader, in which the traits of a pixel are described (colour, Z-depth, and alpha value), and a vertex shader, responsible for defining the position and colour of a vertex – the point where two or more lines or rays come together.

Other types of shader you may encounter include compute shaders, which run on the graphics card outside the normal rendering pipeline, and are used for massively parallel GPGPU algorithms, or to accelerate parts of game rendering; and geometry shaders, which have the unique ability to create new geometry on the fly using the output of a vertex shader as input. Shader nodes are mathematical calculations that translate the values, colours, and vectors into parameters for the render engine to understand.

Left: using Amplify Shader Editor Voronoi node to generate clouds. Right: using a Photoshop noise texture. The difference in performance is considerable.

Shaders were historically coded in a special shader language, such as Cg or HLSL. The conventions for these languages are different from typical scripting for games, and use GPU logic rather than processor logic. As a result, writing shaders is arguably a neglected area of game development compared to some other specialisations.

STARTING FROM SCRATCH

Before I began developing Going Medieval, I had zero experience of using shaders. One of the key factors in deciding to make Going Medieval was to figure out our team’s capabilities, and then ask ‘Is it going to look good enough?’. We didn’t want to hire a large art team, and high-end art wasn’t one of our design pillars – our focus was on gameplay and systems. For the purposes of pinning down the design, I asked myself, ‘How can we make a game where I can make all the art in, say, five weeks?’ Let’s make something low-poly; a project where the pipeline can be controlled by a small team of maybe two or three people.

Our experience of how shaders worked came from using programs like 3D Studio Max, Maya, and Blender. With these programs, you can model how materials interact with light (whether they’re shiny or dull), and apply textures. Shaders determine how the appearance of a material varies with the angle of the light, among other things. The topic of shaders can be daunting for some people. I’m not a programmer, so I don’t code my shaders – in fact, I know experienced coders who still prefer to use shader editors, because shaders require such a specific type of programming.

I looked at a couple of tutorials and understood the programming basics, but I decided this wasn’t a good use of my time. Of course, if you’re able to learn how to code shaders, then you could do far more than you can with a shader editor, but for our purposes, the editor did a great deal and delivered what we needed for our project.

WHY NOT USE UNITY SHADERS?

Going Medieval is made in Unity, which has its own shaders. We started out using these, and the first problem we had was that we needed a specific shader to use with voxel-based terrain. As we went along, we started searching for other shaders online – we needed a transparent one, and one that would pulsate and glow… after some hand-animating and scripting specific properties, we thought: is there an easier way to do this?

An example of the nodes used to achieve the glow effect.

First, I experimented with Shader Graph, a tool in Unity that allows you to write shaders with minimal or no coding. It works pretty well, but the problem at the time was it only supported the Universal Render Pipeline (URP) and the High-Definition Render Pipeline (HDRP), and we had problems with those because they were in early phases of development. We therefore reverted to the Built-in Render Pipeline (BIRP), but this didn’t work with Shader Graph. We also had a few concerns about how often this software would be updated and whether we would be blocked by it at some points (although it has actually been nicely updated and works well, as it turns out). At any rate, we decided to try the Amplify Shader Editor, and then the fun began.

The team that makes Amplify Shader Editor helped us a lot. They’re responsive on Discord, and have a great system where if you select a bunch of nodes in Unity, copy them, and paste them in chat, it will reply with a link. Pasting that link in Unity will copy those nodes. It’s user-friendly for support issues.

GPU USE

Because I didn’t know what I was doing to begin with, we made a lot of complex shaders that did some interesting stuff. But these turned out to be pretty overpowered, and debugging shaders isn’t particularly straightforward. Some coders will go into the graphics card to debug it (in OpenGL), to find out what part of the graphics card is working hardest. I couldn’t jump to that level.

A solution I used is a program called RenderDoc (renderdoc.org) which integrates with Unity and offers an assessment of how many nanoseconds a single item on screen takes to draw. This helped a lot, because, within the Amplify Shader Editor, there are a lot of nodes you can combine, and some nodes have a higher cost in GPU usage than others. Here’s an example: Amplify Shader Editor can generate a Voronoi noise texture, which is great for clouds. It’s mathematical, it’s crisp, and it turns out to be really heavy on the GPU. Replacing it with a simple noise texture from Photoshop worked wonders for our game’s performance. Of course, that’s not to say there wouldn’t be cases for using the Voronoi texture, but in an RTS where you don’t see much of the clouds, it didn’t really make sense.

Trees, grass, bushes, and cloth flutter in the wind together thanks to a global variable.

Another area where I initially went overboard was in making a unique shader for every item in the game. This is expensive in terms of GPU use because the game has to jump from shader to shader as it draws items on screen. In fact, the ideal is to have as few shaders as possible, even though most GPUs are powerful and can do a lot. To find the sweet spot, a good approach is to carry out your debugging on a minimum specification PC (ours had an Nvidia 650, a graphics card that has been around since 2012).

RETRO VISUAL GOALS

We wanted Going Medieval to be stylised and eye-catching. We also wanted it to be ‘retro’, and capture a late-1990s or early 2000s nostalgia for 3D games. Iron Gate Studio’s Valheim, developed roughly around the time Going Medieval was, did what I was trying to do, but much better! It had that nice retro feel, but with all the bells and whistles seen in modern games.

In early 2000s 3D games, developers used a few interesting tricks, such as billboards – a type of visual asset that’s always facing the camera. A lot of games would use this to save polygon count. In other cases, coders would use a PNG of a sphere, and it would always turn towards you. Sometimes you can tell that it’s not really a sphere, but I used that type of technique in the game from time to time. It wasn’t that we needed the polygon count to be lower, it was more of a nice solution to that problem which added to the retro feel.

Notice how the pine branches have a slight downward tilt during winter because of the snow.

CONTRAST AND GLOW

One of our inspirations for Going Medieval’s art style was The Legend of Zelda: Breath of the Wild. I love its combination of cel-shaded and realistic textures that make everything readable on screen, which is important when working with non-realistic graphics. Thinking about hue and saturation is key – in earlier builds of Going Medieval, we had things that looked nice individually but didn’t work on screen.

Together with our artist, I looked at the game in black and white in order to understand contrast. This is one area where custom shaders helped; we had an issue with picking out individual settlers when zoomed out, but adding a subtle glow that increases as you zoom out solved this. Enemies also have a glow, but they glow in red. It was important for them to be clearly readable.

IT'S IN THE TREES

A lot of shader work went into the trees in Going Medieval. I wanted to have a wind effect on everything in the game, and there are shaders that already do that by making things wobble around. But I wanted to have more control over it, so I created the effects in Amplify. It’s a step-by-step process, and there are some good tutorials around. We wanted to do a couple of things to our trees using shaders:

1) React to the wind. This is done using a vertex shader that displaces the tree so that it appears to sway. The alternative is to actually animate your trees – but that is horrible because it’s CPU-heavy. Vertex shaders, on the other hand, are cheap in terms of processing power, are widely used in games, and can work wonders on a lot of things, including flags, grass, and so on. We can tie these to a global weather manager variable, and vary the wind speed universally, too. Game items then sway or flutter in the wind in concert.

2) React to the season. Leaves emerge, grow, and eventually turn to autumn colours, finally fall, and we see branches laden with snow. These are also achieved within the shader, and tied to a global variable relating to seasons. We can do other things with that, including vary the colour of the terrain, and change the seasonal look of evergreen trees.

The Witcher 3: Wild Hunt made clever use of vertex shaders to make its swaying trees, though I noticed that all of its trees sway in the same way whether or not they have leaves. Thinking about this helped me decide to vary the swaying of my own trees depending on leaf cover, whether they’re weighed down with snow, and so on. It may seem odd to have this smorgasbord of inspiration, from The Witcher to Breath of the Wild, but each game had specific components that I learned from, which fed into the eventual feel of Going Medieval.

Rain is offset by the heightmap. The brightest square on the heightmap on the left is the roofed structure in the image on the right.

SEASONS, WEATHER AND DAYLIGHT

Unity’s lighting system is pretty good. But here’s what we did from a shader standpoint: in any script, you can make a global variable, and you can tell any shader to listen to that variable. So, we made a 24-hour clock, and all of the shaders listened to that 24-hour variable, and we could associate anything to it. For instance, I noticed our settlers would become lost in the shadows in the hours close to darkness, around dawn and dusk. At those times, we made them glow a bit more.

Initially, I thought I’d make a particle system to make rain over the whole map – but then I realised making millions of rain particles in Unity was a bad idea. The next solution was to make the rain only fall in front of the camera. It looked fantastic – until you paused the game. Then all of the droplets moved with the camera, which looked ugly.

Finally, one of our programmers wrote a small geometry shader and a small compute shader, which draws just one droplet and copies it millions of times. This isn’t drawn within the world: it’s drawn ‘over’ everything. It also uses the height of the terrain and any roofs, and offsets the height of the precipitation by that amount. Helpfully, this means it doesn’t rain or snow indoors. If you could see the topology of the rain, it would be quite strange, mapping the shape of the surfaces in the game, but all of this is hidden from the player!

This solution means that when you pause the game and travel through space, you also travel through the rain or snow, giving the impression that it’s falling around you. It’s not necessarily a problem that you can find a lot of information about online, since different teams have a variety of creative solutions.

From Wireframe store

Subscribe