Sometimes in game creation, we have to repeat the same task again and again for every asset we are creating: modeling, uv mapping, texture painting and so on. It can be tempting to try to automatize (at least partially) some of these tasks, especially when some assets are almost always the same ones.
This recently happened to me while working on my adventure game "A Thief's Melody", when I tried to create foam around every island in the game world. I quickly realized that it would be a time consuming task, and I asked myself if I could find a faster way to deal with it.
So I experimented with procedural texturing and automatic uv mapping in order to create a cartoonish foam without too much effort on uv mapping. Thus, in this article, I'll explain my thinking process and the different choices I've made to create this:
The final foam rendering in my game.
Here, I use Unity as a game engine, Blender for modeling and texturing, and Krita to paint textures.
I - Problem
My game takes place in an ocean-covered world, so having foam on the shores seems necessary. My biggest problem here is that my game is still being modified and island shapes may change or new islands may appear. So, I want to find a way to create foam easily and fast enough. Moreover, I may change the designs many times for a single island, and I don't want to have to re-create foam meshes from scratch every time.
A while ago, some friends pointed to me the Rime VFX breakdown video by Simon Trümpler. There are many incredible tricks in this one. The foam in particular is very well crafted, and the effect is stunning (as long as you go for a stylized rendering of course). The only problem I saw with this approach is that it is a bit daunting to create seamless uv maps for each foam mesh. Indeed, in Blender, when you want a ring/circular mesh to have a seamless mapping, you need to perfectly align manually the uv map so that the image repeats itself without breaking the pattern. It's OK for a single asset, but for many unique islands and rocks in my game, it's more than I can handle. In addition, with cyclic seamless uv mapping, if you need to tweak the wave length afterwards, you can't scale texture coordinates as you want; you are limited to integer multiples. I have to find something else.
Process of scaling uv map to get rid of the seam. Perfectly matching both sides of the seam can be quite long for many assets.
II - What I want
Ideally, I aim at:
An easy way to create foam in Blender, with as few clicks as possible. Ideally, I would like to only handle modeling, and no uv mapping at all.
Having moving waves without (too) obvious repeating pattern
Easy parameter tweaks in order to change wave length, speed, or look.
Graphically, as I use deferred shading which is not very transparency-friendly, I want a pure colored foam with some sharp edges. So I'll use clipping based on texture values. Here's what I'll try to achieve:
III - Base texture with world coordinates
As usual, my approach is far from perfect and is only a compromise between what matters to me and what is technically reachable (to me too :p). My first idea was to use automatic uv coordinates, such as vertex world coordinates, which works pretty well for my procedural terrain mapping (which I might explain too one day, hopefully). However, world coordinates do not embed any kind of information about an hypothetical "privileged" direction. This means I can't have a "classical" foam texture with "lines" along the shore edge like the one below, because it would be OK on some sides of an island, but not on the other sides:
In this texture there is a strong global direction (horizontal). The corresponding in-game look with world coordinates doesn't work.
When I use automatic mapping, I must have some neutral/uniform texture (direction wise) for example something like this:
A simple foam texture with no main direction. The corresponding in-game look is a bit better...
OK, this is better, but it's still a bit stiff and disappointing, but that's the price to pay for an automatic method. Anyways, it doesn't seem to be enough to get visually interesting foam.
IV - Adding procedural waves from v coordinate
Now, if I want some lines along the shore, or the foam moving towards (or away from) the shore, it is absolutely mandatory that I have an additional "direction" information embedded in the mesh. This is also necessary to make the foam "vanish" when it is far away from the shore. This specific direction changes for each rendered triangle, so I need to encapsulate it in a way or another on the mesh vertices. Thus, I guess I still need to use a basic uv mapping to specify this "shore direction". To achieve this, I only need 1 texture coordinate, which I choose to set to v (see picture below). The idea is to have v=0 on the outer edge loop of the foam (the "sea side"), v = 1 on the middle edge loop of the foam (the "ground side"), and v = 2 for the edge loop that will be "inside" the island.
The basic texture mapping. Here I don't really care about the seam problem (you can see it at the bottom of the mesh). I only work on the v coordinate.
So the movement towards or away from the shore will be built from this simple texture coordinate. The easiest way to create the famous "foam lines" is so use a sinus of v. To make it move, I offset it depending on the current time and the wave speed.
sinus(frequency*v + speed*time) ; clipped at 0
Keep in mind that, in order to get sharp edges, I clip any fragment value below a certain threshold.
Changing the clipping threshold creates larger or smaller waves
Changing the frequency creates more or less waves
Now if I want the sinus foam to wash away as it enters the sea, I need to raise the sinus according to the v coordinate, like this for example:
sinus(frequency*v + speed*time)+v ; clipped at a good threshold t. Waves get thinner as they vanish in the sea
Now let's have fun a little bit by adding noise to this initial shader, in order to make it more "natural". As a noise-based texture, I use a simple RGB seamless noise texture that I made with GIMP using the "solid noise" generator:
Seamless noise made with GIMP.
From this, I can randomize the speed, the frequency or any other parameter of the sinus. Randomizing the frequency is quite interesting; it is a way to locally add more or less wave lines like this:
Adding (world) noise to frequency: sinus((frequency+noise(xz))*v + speed*time)+v; where