Advanced shaders · lesson 14 of 19 · Advanced · about 5 min
Fire 1: a flame you can see into
You will build: March a ray through a swaying, turbulent flame column and add up its glow: hot at the base, cooler at the tip, on a dark night.
A flame is not a surface and it does not reflect light: it makes light. So we do ray marching again, but with a different job. Instead of asking "where is the surface?" every step along the ray adds glow, as much as the flame is dense and hot there.
The shape of the flame comes from a density function:
- a column that narrows as it rises,
w = 0.8 * pow(1.0 - k, 0.9)(kis height from 0 to 1); - pushed sideways by a slow sway noise, so the whole flame leans;
- eaten away by turbulence: fractal noise that scrolls downwards through the flame as time passes,
p.y * 1.3 - time * 2.6, which makes the flame features appear to rise. The noise is subtracted from the density, so tongues of flame break away from the column.
The color depends on temperature. Something hot glows white-yellow, cooler is orange, then red, and cold is black. A cheap fit of this "black-body" ramp: float3(1.5 * h, h * h, 0.3 * h * h * h). Raising h to powers makes red appear first, then green, then blue, as it gets hotter.
The flame gets cooler towards the top and at its edges (heat = d * (1 - 0.6 * height)). Because fire is see-through, light behind it still shows (the transmittance T again), but it is thin, so it blocks little.
float3 fireColor(float h)
{
return float3(1.5 * h, 1.0 * h * h, 0.3 * h * h * h); // dark red, orange, yellow-white
}The numbers you need
Everything the picture depends on is given, so the challenge is the code, not guessing.
- Flame density
flameD(p) in the Toolbox: a tapering column with sway and turbulence- Camera
ro = float3(0.0, 1.0, -3.6), rd = normalize(float3(p.x, p.y * 0.9 - 0.1, 1.7)), p = (uv - 0.5) * float2(aspect, 1.0) * 2.0- Night
lerp(float3(0.01, 0.01, 0.025), float3(0.03, 0.03, 0.07), saturate(rd.y + 0.3))- Flicker
flick = 0.9 + 0.1 * sin(time * 17.0) * sin(time * 7.3)- March
44 steps: t = 2.3 + float(i) * 0.075; d = flameD(ro + rd * t); if d > 0.01: heat = d * (1.0 - saturate(pos.y / 2.2) * 0.6); emit += T * fireColor(heat) * d * 0.075 * 6.0 * flick; T *= exp(-d * 0.075 * 1.5)- Result
night * T + emit
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 codingGo deeper
Part of the learning path: Game effects: smoke, fire and snow.