Shader Programming, Volume 11
A Snow Shader for a Chocolate Rabbit
Alright, so we know a little bit about how to manipulate vertices. Let’s use what we’ve learned to create a shader that simulates how an object might look if it got snowed on!
Personally, I like rabbits. So let’s dump a bunch of snow on one of those.
I found the book’s shader to be pretty lacking in functionality, so we’ll also take a look at what improvements can be made to it.
Chapter 5.4
Implementing a Snow Shader
The effect has two main steps to it: coloring all triangles that are facing upward white to look like snow, then extruding their vertices upward to simulate the effect of snow accumulation.
Create a new shader, attach that to a new material, and apply it to an object of your choosing. (Note: I’d recommend using the bunny model I used because I noticed that some models, like the Soldier model we used previously, seem to use a different scale.)
Here’s the book’s shader code.
This is a good starting point. First, an explanation of the less obvious variables:
- _SnowDirection: the vector along which snow will accumulate, usually (0, 1, 0, 0) in the direction of the sky.
- _SnowLevel: determines how much the normal of a triangle or its vertices need to be aligned with _SnowDirection in order to be affected by the Snow shader.
Let’s take a look at the surf() function first. Basically all it’s doing is grabbing the normal of each triangle, determining whether it is aligned with the _SnowDirection enough, as determined by the _SnowLevel variable. If it is, it colors it white.
The vert() function works by the same principles, really. In this case we have to first convert our _SnowDirection to world space. (Note: I disagree with this, I’ll explain why soon.) Then it’s just a matter of extruding the vertex in the direction of the half-vector of the _SnowDirection and vertex normal. The amount it extrudes is determined by the _SnowDepth variable.
Now let’s talk about why this shader isn’t very good.
The first thing that I noticed about the effects of this shader is that there are non-snow vertices being extruded. The rabbit itself looks to be getting fatter, rather than just the snow. Take a look at the nose, for example. Or the eye. Those features should not be growing.
You’ll also notice that the snow is accumulating outward too much. Snow doesn’t really accumulate like this — it would fall off.
Here’s an example of what snow accumulation more generally looks like:
Notice how the snow curves inward as it gets taller. That’s what I want to see in a snow shader, personally. Or at least, I should have enough external control over the shader that I can achieve this look.
Another problem I found is that the _SnowLevel variable affects the depth of the snow, when it should really only affect the coverage. This also results in negative values of _SnowLevel extruding the object inward. And it looks terrifying.
Nope. None of that.
Nope nope nope.
Furthermore, it doesn’t make much sense that the _SnowDirection vector isn’t normalized. And it’s pretty annoying that this shader doesn’t work as well when I rotate the rabbit. Hm.
Anyway, let’s clean this up a bit.
Here’s what I’ve done to remedy these problems:
First off, you’ll notice there is a _SnowPuffiness variable now. It determines how the vertex normal factors into the direction that the snow accumulates. This gives us control over whether the snow can expand outward or inward or just straight up. It calculates this in line 55.
Converting the _SnowDirection to object space in line 47 helps make the shader work better for different object rotations.
In line 58 I use calculate the snowFactor variable, which takes the amount the _SnowDirection aligns with the vertex’s normal (between 0 and 1) and calculates the third power of it, then uses that value to determine how much we should extrude the vertex, with a maximum of _SnowDepth. This creates a sort of easing into the snow extrusion rather than a sharp cut-off between areas vertices are extruded and vertices that aren’t.
Other than that, I made the surf() and vert() functions agree more about whether a vertex should be extruded or not, and the process has been made more readable.
Let’s take a look.
The first variable I manipulate is the _SnowLevel. Notice how it doesn’t affect the depth of the snow, only the coverage.
The second variable I mess with is the _SnowDepth. It doesn’t extrude areas that aren’t covered in snow — our rabbit’s face remains intact.
Last I alter the new _SnowPuffiness variable, which lets me change how the snow accumulates on the rabbit.
I think these changes make the shader more useful in other contexts. It’s a more generalized solution. I had a lot of fun fixing up the book’s recommended shader code and I learned a lot more that way, so mission accomplished, book!
Let me know in the comments if you come up with more improvements! I’d love to see an even better snow shader. :)
Up next: Vertex and Fragment Shaders!