Advanced shaders · lesson 11 of 19 · Advanced · about 5 min

Snow 1: a field of snow

You will build: Draw rolling snow dunes with lumpy noise, lit by a low sun with blue shadows, fading into the sky with fog.

The picture produced by the shader in the lesson "Snow 1: a field of snow"
The target picture. In the lesson you write the shader that draws it, and a match bar shows how close you are.

Snow is a height field too, like the sea, but a frozen one. Rolling dunes from a couple of sines, plus noise at two sizes for the lumpy texture: big lumps (noise2(xz * 1.7)) and tiny grain (noise2(xz * 7.0)).

We march a ray over the surface with the same relaxed-step trick as the ocean, but now we stop as soon as we are within a hair of the snow (d < 0.002), and remember that we hit it.

The light is what makes snow look like snow, and it has two rules:

  1. The sun is low, so the bumps cast long soft shading: max(dot(n, sun), 0.0).
  2. Shadows are not grey, they are blue. Snow in shadow is lit by the blue sky, so blend from a blue shadow color to a warm white: lerp(shadow, lit, diff). This one choice is what makes it read as snow.

Distant snow melts into the sky with a bit of fog.

float diff = max(dot(n, sun), 0.0);
float3 shadow = float3(0.55, 0.66, 0.88);    // blue, from the sky
float3 lit = float3(1.0, 0.97, 0.92);        // warm, from the sun
col = float3(0.93, 0.96, 1.0) * lerp(shadow, lit, diff);

The numbers you need

Everything the picture depends on is given, so the challenge is the code, not guessing.

Height
snowH(xz) = 0.16 * sin(xz.x * 0.35 + 0.8) * sin(xz.y * 0.28) + 0.05 * noise2(xz * 1.7) + 0.012 * noise2(xz * 7.0)
Camera
ro = float3(0.0, 1.3, -3.2), rd = normalize(float3(p.x, p.y - 0.6, 1.5)), p = (uv - 0.5) * float2(aspect, 1.0) * 2.0
Sun
normalize(float3(-0.6, 0.35, -0.3))
Sky
lerp(float3(0.78, 0.86, 0.96), float3(0.35, 0.55, 0.85), saturate(rd.y * 2.0 + 0.2))
March
t = 0.5; up to 64 steps: pos = ro + rd * t; d = pos.y - snowH(pos.xz); if d < 0.002 it is a hit (stop); t += d * 0.5; stop if t > 40.0
Normal
e = 0.02; normalize(float3(h(x - e) - h(x + e), 2.0 * e, h(z - e) - h(z + e)))
Snow color
float3(0.93, 0.96, 1.0) * lerp(float3(0.55, 0.66, 0.88), float3(1.0, 0.97, 0.92), max(dot(n, sun), 0.0))
Fog
lerp(col, sky, 1.0 - exp(-t * 0.035))

Try it in your own language

The lesson is written once and shown in every language. Pick yours: Unity (Built-in), Unity (URP), Unreal, Shadertoy / WebGL, OpenGL (LWJGL, raylib), three.js, GameMaker, LÖVE (Lua), Godot, Apple (iOS, macOS), WebGPU / Bevy.

Start coding

Go deeper

Part of the learning path: Game effects: smoke, fire and snow.