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

Fire 2: a campfire at night

You will build: Make it a campfire: the flame lights the ground around it with a flickering glow, and 14 embers rise and fade.

The picture produced by the shader in the lesson "Fire 2: a campfire at night"
The target picture. In the lesson you write the shader that draws it, and a match bar shows how close you are.

A flame in a black void looks like a sticker. What makes it feel there is that it lights the world around it, and that it throws off sparks.

Light on the ground. For rays that point down we find where they hit the ground (y = 0), and add warm light that fades with distance from the fire: 1.1 / (1.0 + dot(g, g) * 1.2), where g is the spot on the ground. Multiply it by the flicker so the glow breathes with the flame.

Embers. Each spark is a tiny glowing point that rises and fades. To place 14 of them we again use no memory: spark j has a random speed and start offset from hash21, its life frac(time * speed + offset) goes 0 to 1 and then starts over, its height is 0.2 + life * 2.6, and it wobbles with a sine. How bright is it? Depends on how close the ray passes to the spark. The distance from a point sp to a ray is length(cross(sp - ro, rd)): the same line-distance trick as the bullet holes. A narrow glow exp(-dist * dist * 900.0) makes it a bright dot, and (1 - life) fades it as it rises.

float life = frac(time * (0.35 + 0.25 * hash21(float2(fj, 1.0))) + hash21(float2(fj, 2.0)));
float3 sp = float3((hash21(float2(fj, 3.0)) - 0.5) * 0.8 + 0.25 * sin(life * 6.0 + fj), 0.2 + life * 2.6, (hash21(float2(fj, 4.0)) - 0.5) * 0.8);
float dist = length(cross(sp - ro, rd));
col += float3(1.0, 0.6, 0.2) * exp(-dist * dist * 900.0) * (1.0 - life) * 1.5;

The numbers you need

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

Ground light
for rays with rd.y < -0.01: tg = (0.0 - ro.y) / rd.y; g = (ro + rd * tg).xz; col += float3(1.0, 0.45, 0.12) * flick * 1.1 / (1.0 + dot(g, g) * 1.2) (before the flame march)
Sparks
14 sparks, after the flame: j = 0 to 13, fj = float(j)
Life
life = frac(time * (0.35 + 0.25 * hash21(float2(fj, 1.0))) + hash21(float2(fj, 2.0)))
Position
sp = float3((hash21(float2(fj, 3.0)) - 0.5) * 0.8 + 0.25 * sin(life * 6.0 + fj), 0.2 + life * 2.6, (hash21(float2(fj, 4.0)) - 0.5) * 0.8)
Glow
dist = length(cross(sp - ro, rd)); col += float3(1.0, 0.6, 0.2) * exp(-dist * dist * 900.0) * (1.0 - life) * 1.5
Everything else
as in Fire 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.