Going with the flow - The water in Pathfinder: Kingmaker
Today's update is all about water rendering in our game. Water is the most complicated rendering object from a technical point of view. To make the magic happen we use various subsystems of the game engine and thoroughly prepare both procedural and baked textures. Besides, we need a combination of programming skills, the abilities of our technical artists, lots of patience and a bit of luck. For our update, let's not dwell on trivial things, such as calculations of water lighting. Instead we are going to tell you about the difference between water rendering and rendering of regular geometry. Let's dive right in!
Perfectly clear water is usually almost colorless, and only if its layer becomes thicker, it gets a bluish shade. However, there are only a few places on Earth, where you can find such clear water in a natural environment, e.g. Blue Lake in New Zealand. In most cases, water contains tiny particles of other substances, which can color it into various shades. This effect is especially noticeable in rivers and streams, where running water carries more of such particles.
However, water color depends not only on particles of other substances, but also on some other parameters, and the most relevant for us is density. To make things easier, we'll make the assumption that the water color depends on depth (layer thickness) multiplied by density. In shallow water it becomes easier to see what's beneath, and in deeper places we can see more color of the water itself. The density is adjusted by our artists, and as for the depth, we calculate it via shader, using z-buffer.
Reflect or refract?
Actually, we need both reflection AND refraction. The hard part is this: while rendering water surfaces, we need to prepare two textures - one with the reflections, and another with all the objects that are located underwater. If we approach this problem bluntly, we can just render all the objects located underwater to the refraction texture, and all the objects located above the water to the reflection texture, adding a mirror camera matrix. If we choose this approach, we'll need to draw each object 3 times: 1. to the refraction texture, 2. to the reflection texture and 3. to the screen.
However, after giving this some thought, we came up with a better solution. We moved the water textures in the rendering queue, so they are rendered after all the other objects. When the other objects are drawn, the we draw the water, so we just copy everything we've drawn before on the screen to the new texture and use it as a refraction texture. This way, we have fewer objects to draw. We can't do this with the reflections, though, so we render all the objects to a separate texture. At this stage another question appears: should we calculate the light for these objects? We decided it consumes too many resources, especially when taking into account the minimal hardware requirements for our game. That's why we draw all the reflections in monochrome, and artists can adjust the colors later.
Distortion is adjusted by artists as well, but this parameter, just like density, depends on depth, so we again turn to the z-buffer to calculate proper distortion.
Twice in the same river
So, now we can set the water color, reflection, and refraction. The only thing left to do is to make water flow.
In the first iteration, we animated texture coordinates of bump-textures. If this animation is done right, we can also adjust water for lakes and ponds.
In Pathfinder, there are lots of locations with rivers and streams. We wanted the player to see that water curves around the stones and readily flows through all the turns of a watercourse. To do that, we used flow maps. They contain encoded vectors of flow direction. Artists can draw these flow maps by themselves, which gives them more opportunities to control the looks of the flow.
What else should we add…
What we have now, looks rather good already, but still not good enough - something's missing.
To draw foam, we use 4-channel texture, where each channel contains a foam texture with different density. While rendering, we once again use z-buffer to estimate water depth, and in the locations with minimal depth (usually near the shore) we add foam. Less depth means increased foam density.
But if we use depth as our only reference, sometimes we can't show the foam everywhere our artists want to see it. That's why we added a special mask to one of the channels of the flow map. This mask determines, where the foam will be shown, regardless the depth. This helps to highlight the spots with stronger flow.
Combined together, it looks like this
What if I want to walk in there?
This part of the content is still under construction, so we'll describe this process, using examples and imagination. ☺
In previous updates you may have noticed our characters can already interact with grass. To establish this kind of interaction, we draw special textures, which are projected on the location from above. When a character moves, he leaves a trail in this texture. Then, when we render grass, we take information from this texture and animate the grass, if it happens to grow under the character's trail.
Interaction with water works the same way.
Let's kill all the things and dump the bodies in the water!
Yeah, about that... killing people underwater is less subtle than you may think, because the blood shows up in the water. We run actual fluid simulation and make the blood flow downstream.
And that concludes today's update. We hope you've enjoyed it! If you have any questions or comments - feel free to leave them on our forum, and we'll be happy to answer!
Hail to the kings!
Non-game image sources: