Thursday Report: Splashing Into Shaders

On Thursdays I write up a short report on my developments and thoughts if to do nothing more than keep myself accountable. Enjoy!

Making progress with deciding how the garden would be pieced together felt difficult without knowing exactly how a key feature would play: water.

No garden would be complete without water features, and a natural world wouldn’t feel right without ponds, rivers, lakes existing in and in between it.

Water has been the bane of all developers probably since it became a necessity. Solid ground requires, at a minimum, a texture that reflects the ground itself. Maybe some nice lighting. Water presents challenges that a static texture of water can’t solve — perhaps most importantly because it’s not static. It’s translucent, it reflects light, and it reacts dynamically based on it’s surroundings. I would say these are the key to presenting convincing water.

‘Don’t Starve’ takes a stylized approach, using 2D images of waves tiled in front of one another, moving in an almost puppet-show way. It’s a striking solution for a 2D-styled world, and fits in perfectly with the game’s kooky, macabre tone.

I imitated this in ‘Kingdom Ka’ during the head-swapping scene for the small-scale water needed for the figure to pull heads from. It’s a quirky solution that suits the absurd nature of the scene well. It’s immediately recognizable, despite sharing very little in common with actual water.

The salesman delivers.

And while this approach suits these two cases, I had my hesitations when thinking if this would be the way forward for Walled Gardens.

Although still a heavily stylized game, it’s objectives are different. I want visiting a pond or a river to evoke those feelings we get when sitting next to water in a quiet corner of a forest. It brings a certain calm, and invites you to stop for thought. Don’t Starve’s waves say ‘Danger!’ in a world where you’re constantly on your toes. You can’t watch 2D cut-outs of water for any longer than a moment before wanting to move on.

I knew then that I would need to dive into the world of shaders to achieve something that would feel both in-line with the style of the world, but also something that felt like real, calming water.

As it turns out, scripting shaders is a whole other kettle of fish to work I’ve done previously. Since water would like be the main shader (and possibly the only shader) in the game, I didn’t think it would be worth learning an entirely new language — there’s people who dedicate their working lives to this sort of thing. So, I dipped into some shorter tutorials, found a few pieces of open source code, and kind of stitched and all together, slightly changing values and inserting multiplications randomly where it seemed right to see what it all might result in.

And hey, it kind of worked. Making changes and seeing how code reacts triggers a sort of boyish excitement in me, so it was a good way to spend some late afternoons.

The first breakthrough was having a reflection set up. A simple flipped portion of an image goes a long way in looking like water. The gist is that the game captures the pixels from the screen directly above the shader and inverts them horizontally, all in realtime.

There’s definitely something magical about seeing the characters reflection suddenly appear in the water. The trouble with this solution is, as it relies on the actual pixels on the screen, it flips and breaks when things aren’t exactly what it is expecting.

For instance, if the camera moves so that the ‘water’ is too far up the screen, the shader doesn’t have the information on what to reflect, as it would be out of shot. The result is the shader stretches the few pixels that are available, resulting in visual glitches that wouldn’t be passable in a final game.

Oops!

This makes sense. The source I based the code on was for a side scrolling game, where the water level remains constant. The shader also stretches things in exaggerated ways when the camera is zoomed in and out, likely because so much new information is introduced or taken away.

So maybe reflections can wait for another day. I’m sure the solution is for the game to reflect the scene rather than the pixels on the screen, which I’ll need to look into.

So I looked to focusing on the surface of the water itself. As water flows, it skews and shapes the light around it. ‘Displacement mapping’ in shader-talk is where a texture is used to instruct how the shader should manipulate the pixels beneath it. You can say, for instance, that the texture should react the reds and greens in a texture . Then, you pass that texture to move slowly across, the sprite below.

Think of it almost like those funhouse mirrors at carnivals where you stretch and distort your face or body in different ways by moving yourself up and down. Only here, it’s the mirror that is the one that’s moving.

Example of a displacement texture.

So I quickly drew out a sort of pond-like sprite with the stones sitting at the bottom, and mapped out a polygon to match its shape that the shader would occupy.

The texture separated from the game.

After some value tweaking, it looked good! Satisfying like water. It even masks my hideous stone texture underneath somewhat. Trouble is that it’s also unsatisfying like treacle. The gloopiness of the texture gives it this glossy, old-school 3D look that feels entirely out of place for the rest of the game.

Treacle pond.

With some further tuning, I was able to get something that looked much more crispy, and more fitting.

The water as it looks now.

I think the sharper look suits the game’s 2D style much better, although I don’t see this as the final product. I’m sure I’ll be going back for some further tinkering to get something that works better.

For now though, I’m happy to finally have something functional and adaptable. I can draw out rivers and ponds, throw a shader on top and very quickly have some effective water.

Ready, no doubt, for some self-reflection, and countless fishing hours.


The next report will be on the 15th, as I’ll need to focus on some commissions this coming week. See you then!

Follow me on Twitter.