Graphics effects in the four elements - Shaders in 2D game
Tags: gamedev, programming, tutorial, design
Published: 2023-05-30 18:38:53

Hello everyone! We are a small team that has been developing a 2D strategy game called [Norland ](https://store.steampowered.com/app/1857090/Norland/)- a medieval kingdom simulator - for several years now.

This is our second article about 2D rendering of various effects in our game. You can read the previous article [here](https://www.norlandgame.com/post/simulation-of-three-dimensional-buildings-in-a-2d-game). Just to remind you, the game is a 2D one and is being developed on the Game Maker Studio 2 engine.

Today, we will be discussing four elements - water, fire, earth, and air. What graphical effects do they encompass? Let's find out.

## Water

Currently, water doesn't have a profound impact on the gameplay in Norland - you can't build structures on it, that's about it. You can't catch fish or construct ships yet. Nevertheless, it still needs to be rendered somehow.

It all started with the development of a mockup (a fake screenshot of the game drawn in a graphics editor). As the initial water reference, I used a screenshot from the game Graveyard Keeper, adjusted to our color palette.

Artists created the tiles for the shoreline for us, but they struggled with the actual water surface. So, I had to come up with its appearance myself.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u42mm4zdokjjvwg54zkg.png)

_I recommend using the "Match Color" function in Photoshop. It was specifically with the help of this feature that I adapted the color scheme of the Graveyard Keeper screenshot to our desired colors._

The coastline tiles were drawn by our artists, but they had some difficulties with creating realistic water surfaces. Therefore, I had to come up with the appearance of the water myself.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cvw0ktx1wtnv03qaz6ym.png)

I had a rough understanding of how to create a shader for water - a seamless texture with "caustics" is overlaid on water tiles, and waves and ripples are simulated by moving the UV coordinates of the water surface over a special noise texture (effect commonly known as Displacement).

I found the caustics on [OpenGameArt](https://opengameart.org/node/12994), but I don't remember where I got the texture for shifting the UV coordinates - they are found online under various names (e.g., bump mapping water texture).

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ea4c9rdckz0oinzzd4i8.png)

The effect is simple - at each point, I take the r and b components of the Displacement texture color, which represent the shift value, and add it to the texture coordinates of the caustics. Additionally, the Displacement texture is looped and constantly shifted in a certain direction (which corresponds to the direction of water ripples).

![The final effect in the game engine](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x9e29g6822ftkpjbegom.gif)

_The final effect in the game engine_

![The same effect in SHADERed](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s48wg642sljymt2ck86j.gif)

_The same effect in SHADERed_

I recreated this shader in [SHADERed](https://shadered.org/), and if you're interested, you can view its implementation and experiment with your own parameter values and textures. The project can be downloaded [here](https://drive.google.com/file/d/1TCJSmq2-2n_aVowinFmVGEKDFOUa2P3A/view?usp=share_link).

![You can see the code of the fragment shader by double-clicking on "Simple" in the project's "Pipeline."](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hw2fkmb3dxth00hlns54.png)

_You can see the code of the fragment shader by double-clicking on "Simple" in the project's "Pipeline."_ 

## Fire

Initially, to create the effect of burning houses, only particles were used. However, I quickly encountered a problem: to achieve a dense and vibrant fire effect, a significant number of particles were required. This, in turn, negatively affected the game's performance (Game Maker Studio 2.3 is not the most performant engine), especially when a fire spread across half of the city. Moreover, I wasn't satisfied with the resulting effect - the fire seemed insubstantial and didn't blend well with the environment. You can see the results of this work in the game's [announcement trailer](https://youtu.be/rUvyOVP0HTo?t=55).

So, to avoid game lag, it was necessary to reduce the number of particles. However, even with the initial particle count, the effect still appeared insufficiently dense. Therefore, a different approach was needed. In the end, I came up with a solution that involved a small number of particles and a special fire shader applied to the building texture, creating the desired density of the effect.

The effect consists of several components:

1. Creating a fire texture from Perlin noise, overlaying this texture on the building, and animating the result (simple upward movement of the fire);
2. Darkening the building texture and subsequently "dissolving" it (effect commonly known as Dissolve) to fully destroy the building engulfed in fire;
3. Several types of particles - smoke, scattered sparks from the fire, and an older fire effect that was used on a much smaller scale than before.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qnh776kv4ketjyv8jwre.gif)

_Please adhere to..._

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1y10h4rf4njiwqeozyn3.gif)

_...safety protocols_

I also [transferred](https://drive.google.com/file/d/11rkRGRJuUVubSrlyWhXPRkHP8E9Cpo2p/view?usp=share_link) this effect to SHADERed.

## Earth

If you simply apply a tiled texture to the surface, it doesn't matter how good the texture is - as the camera moves away, it will look unpleasant. To achieve an acceptable result, it is necessary to somehow break the obvious repetitiveness of the pattern.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wijh0qls0ae3atje237t.png)

_Example of poor tiling_

Initially, I had the idea to generate various-shaped and colored grass clumps and then randomly scatter them across the entire map. I was satisfied with this approach - it lasted for almost a year, but as the game is getting closer to release, it was time to revisit this task for polishing.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hkv7qsattc06pkoi5lg5.png)

_These pre-drawn "blots" were randomly distributed across the map_

The downside of using clumps became evident - to ensure even coverage of a map sized 31,500 x 22,500, a large number of decals would be required, and they would inevitably intersect with each other, creating overdraw (excessive rendering of pixels that won't be visible on the screen due to object overlap). And it turned out that there might not be enough of them. Moreover, the shape of these clumps is predetermined, so creating new ones would require manual work in the editor.

Therefore, I came up with another solution - generating a diverse surface pattern directly in the [shader](https://codefile.io/f/9500T3K2h7) 
(GLSL Fragment).

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/eudl0lpwil05903q0bxu.jpg)

The code for this shader is relatively simple - a tiled Perlin noise texture is fed into it, and the [smoothstep](https://thebookofshaders.com/glossary/?search=smoothstep) function is used to cut off areas that are darker than a threshold value.

I perform this operation twice, but the second time, the noise texture is scaled and offset differently to avoid repeating patterns. Then, the spots are colored with designated colors and blended together based on alpha, and finally, a tiled grass texture is overlaid.

In addition to the texture itself, various decals such as grass clumps, rocks, and dirt spots are placed on top of the grass. However, they are required in much smaller quantities than before.

Here's the result before and after the effect.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nqons545cpzlmkuo4j67.png)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/onm6ukwhs331lksg93kd.png)

## Air

Actually, this section is not specifically about air but rather about color grading. However, I needed to align it with the overall concept of natural elements in the game ;)

LUT (Look-Up Table) is a technique that is well-known in the photography and videography communities. Its purpose is the same everywhere – to colorize an image in a specific way.

A LUT is essentially a texture (usually 512x512 pixels) that encodes the entire RGB color range. Imagine it as a cube divided into layers. The texture may look something like this:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zcfx07lmhtb7fb3i5atl.png)

_Neutral LUT texture that will not change colors after applying the color correction shader_

The idea is that each RGB color corresponds to a specific pixel in this texture (the RGB components of the color are used as coordinates in the texture). For example, for the color red (#ff0000), it would be the top-right pixel in the first square.

The job of a color grading shader is to change the color of each pixel in the image to the color corresponding to its pixel in the LUT texture. The only thing you need to do manually is prepare these LUT textures, but it's a fairly straightforward process.

For example, let's say we want to create the effect of a sunset with a shift towards red tones. We take a screenshot from the game, load it into a photo editor, and start playing with color parameters using various tools like Hue/Saturation, Brightness, Contrast, etc. Personally, I would focus on shifting the Hue towards red and increasing saturation, but feel free to let your imagination run wild. You could, for example, saturate only the red color and make everything else gray (hello, Sin City).

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f9lp49kem1dzuquwieso.png)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cu84kaj4u7babttj0uj6.png)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/li7jj9kx2xlqlogfkkpg.png)

Next, you apply the same color adjustments to a neutral LUT texture. The result would look like this:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fbkes79g17v9xtqf19fx.png)

_LUT texture to which color adjustment settings have been applied_

Now, if you [pass the render ](https://codefile.io/f/9xq8aIErt8)of our game through the color grading shader with this texture, the game will look exactly as the image was adjusted in the photo editor.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z8zbz4yyb1y33r2e6o68.jpg)

In Norland, we use a set of five different LUT textures, each corresponding to a specific time of day – morning, day, evening, sunset, night – as well as a special texture for artificial lighting. At any given time, two textures are active and blended together to achieve a smooth transition in color grading. For example, if morning starts at 6 am and day starts at 12 pm, at 8 am the color grading will be a mix of 33% morning LUT and 66% day LUT.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jw8lhkku1hgyp3bhqp5k.png)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qcjcz2luz62lo5pimiy8.gif)

There are many other effects in the game (artificial lighting, rain, blood, equipment damage, etc.) of varying complexity, but there's still much work to be done (I'm currently brainstorming how to create snow and winter). But that's a story for another time.

I hope you found it interesting! Thank you!