I’m Ian, and I’ve taken the task of leading the art on Slime Rancher. Today I’d like to share a bit about shaders for you to enjoy. Particularly, the water shaders I’ve built for this game.
Now first, I got to say, shaders can get super complicated. Polygon count is no longer the main bottleneck for computers these days, but instead complicated shaders tend to be the big choke. With that in mind you have to be careful with your shader budget. However, shaders are also super rewarding to work on! A simple change can have dramatic effects on your final result and seeing those happen in real time is very satisfying.
So let’s take a look at the shader for Slime Rancher’s ocean.
Whoa. Like I said, shaders can get super complicated. I use Shader Forge from the Unity asset store to do all my shader work. I can’t recommend it enough!
Slime Rancher is also not a realistically rendered game, but that doesn’t mean I don’t want my materials to seem REAL. I want them to feel like actual substances but still evoke the game’s illustrative/painty style.
The ocean is a very important character in Slime Rancher as it takes up a very large portion of the screen real estate. Thus it gets a pretty large budget! Let’s break it down a bit so you can see what’s going on.
First we need to get the waves flowing right. By blending a few distorted perlin noise maps and panning them in a defined direction, I end up with an undulating grey scale map.
The black parts will be the lowest portions of my waves while the whites will be the highest. I take this map, multiply it by a max wave height value, and also the normal direction of the ocean mesh. The results are that the vertices bob up and down!
(Checkerboard just to illustrate the mesh distortion)
Using this same map, I lerp between a flat surface and some scrolling normal map textures. So the black becomes smooth, and the white/high parts of the wave become rough.
And that’s all we need for the surface detail of the ocean. Let’s move on to the color!
First, to get the color right, we need depth data. This is very important for the look of the water and is the corner stone of almost every further step in the ocean’s final look.
The scene depth is a node that produces a grey-scale image where everything white is really close to the mesh’s surface while everything far away is black. I’ve used a remap node to clamp past a certain distance to black. Anything past this point, is not really a concern to the shader, but adjusting it’s value can produce varying degrees of water clearness.
You should note that the vertex offsetting from earlier is effecting the depth testing. This is good! It’ll help with making the water distort in a pleasing way.
Now before we use this depth information, we need to take a snap shot of what’s behind the mesh.
The scene color node gives us the exact color information of the pixels below the mesh and using that, we can layer it upon itself. By offsetting these layers in cardinal directions we can produce a very fast and cheap blur effect.
By multiplying the offset value for the blur with the depth map information, we can make the pixels near the surface of the water not as blurry as those in the deeper parts. Swank!
Combine the offset further with the normal data we created earlier…
…and we get fake refraction! Now we’re getting some watery-looking water!
At this point, the color data is combined again with the depth data, but instead it’s multiplied with a deep blue.
The deepest parts of the water burn into the abyss as the red values from the color data are removed. The result is a color elimination as we want to simulate the red portion of light to get absorbed by the depths of the water.
We also take the depth data, reverse it, and multiply it by a fresnel ramp to get the color values of the deep portions of the water.
This will be the light from the sky bouncing off the water’s surface.
When you ADD theses last two steps together, you get…
Great! We have our diffuse value!
Let’s take the depth data again and find the edges of the water. This is done by remaping the 0 to 1 data from the depths to a fraction of it’s range. This should leave with only the tippy-top of the depth data.
Multiply our new edge data by the normal noise blue value and we get some nice edge foam. Helps make the transition from the water to ground pleasing. As a note, I’ve posterized the result to get a crisp, cartoon style to the foam.
Almost done!
We need some fake light refraction on the surface of the objects just below the water. A texture is projected on to the objects below using the depth data converted from world transform to local.
The results are a scrolling texture of water ripples. Speaking of ripples, I’ve offseted the UVs of the texture by the normal map again to give it a distortion.
Now we need to combine this with the depth data again. But this time, we’re not looking for the edges, we want the surfaces away from the edge. So we remap to the bottom of the scale.
I, of course, clamped the max depth a bit so that only surfaces near the water’s top have the light refraction. With this, and a reflection probe, we have our emissive map!
Combine it all together with a bit of specular and you have very pleasing, dynamic water!
Now the level designer can just move objects around to their heart’s content all while having very clean results.
Thank you for your time and I hope you enjoyed this look in to a bit of what’s going on behind the scenes of Slime Rancher!