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

Smoke grenade 2: light and a floor

You will build: Light the smoke with a shadow lookup towards the sun, and put a checkered floor and horizon haze behind it.

The picture produced by the shader in the lesson "Smoke grenade 2: light and a floor"
The target picture. In the lesson you write the shader that draws it, and a match bar shows how close you are.

Right now the smoke is a flat grey blob. Real smoke has a bright, sunlit side and a dark core. The recipe is the same one the clouds used: ask how much smoke lies between each point and the sun. One extra density lookup a little towards the sun is enough.

Here the shadow lookup uses baseD (the plain ball) and not the final density. Later we will carve bullet holes into the smoke, but a hole should not change where the shadows fall, and the cheaper function keeps the shader fast.

The smoke needs something behind it to be seen against. We add a floor with a checkerboard (so you can tell later that holes show the scene behind), a warm horizon haze, and the same sky. The floor is a plane at y = -1: for a ray that points down, t = (-1 - ro.y) / rd.y and the point is ro + rd * t. Checker: fmod(floor(g.x) + floor(g.y) + 100.0, 2.0) (the +100 keeps the number positive, and the same trick works in every shader language).

float sh = baseD(pos + sun * 0.3);
float3 lit = lerp(float3(0.22, 0.24, 0.3), float3(0.97, 0.94, 0.88), saturate(1.0 - sh * 1.3));
float a = 1.0 - exp(-d * dt * 6.0);
L += T * a * lit * 0.9;
T *= 1.0 - a;

The numbers you need

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

Sun
normalize(float3(-0.5, 0.8, -0.6))
Shadow
sh = baseD(pos + sun * 0.3)
Light color
lit = lerp(float3(0.22, 0.24, 0.3), float3(0.97, 0.94, 0.88), saturate(1.0 - sh * 1.3))
Each step
a = 1.0 - exp(-d * dt * 6.0); L += T * a * lit * 0.9; T *= 1.0 - a
Floor (rays with rd.y < -0.01)
t = (-1.0 - ro.y) / rd.y; g = (ro + rd * t).xz; c = fmod(floor(g.x) + floor(g.y) + 100.0, 2.0); color = lerp(float3(0.45, 0.42, 0.38), float3(0.62, 0.58, 0.52), c); haze: lerp(color, float3(1.0, 0.82, 0.55), 1.0 - exp(-t * 0.05))
Sky
lerp(float3(1.0, 0.82, 0.55), float3(0.35, 0.58, 0.9), saturate(rd.y * 1.6 + 0.15))
Everything else
as in Smoke 1

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.