Creating Procedural Smoke in GLSL
Smoke behaves very differently from fire.
Fire is energetic.
Smoke drifts slowly.
It spreads outward.
It fades into the air.
Instead of sharp movements, smoke relies on soft transitions and gradual changes.
Fortunately, procedural shaders are excellent at producing exactly these kinds of effects.
What Makes Smoke Look Like Smoke?
Smoke is built from a few simple ideas.
Smooth noise.
Slow movement.
Soft edges.
Transparent layers.
Every one of these can be created mathematically.
Starting with UV Coordinates
As always, we'll begin with the UV coordinates.
vec2 uv = vUv;
These coordinates will be animated over time.
Moving the Smoke
Smoke rises slowly.
Instead of moving quickly like fire, we'll use a gentle speed.
uv.y -=
uTime * 0.1;
The movement is much slower.
This immediately changes the feeling of the animation.
Creating the Base Pattern
Generate a smooth FBM texture.
float smoke =
fbm(
uv * 3.0
);
The result already resembles soft cloudy shapes.
Distorting the Coordinates
Real smoke curls as it rises.
Add a small distortion.
uv.x +=
fbm(
uv * 2.0
) * 0.15;
The smoke now drifts from side to side.
Small distortions usually look better than large ones.
Making the Bottom Stronger
Smoke is usually denser near its source.
Create a vertical gradient.
float fade =
1.0 -
vUv.y;
The bottom becomes brighter.
The top gradually fades away.
Combining Everything
Multiply the smoke by the fade.
smoke *= fade;
Now the smoke naturally disappears into the sky.
Softening the Edges
Smoke rarely has hard boundaries.
Use smoothstep().
smoke =
smoothstep(
0.2,
0.8,
smoke
);
The edges become much softer.
The overall effect feels more realistic.
Complete Shader
#ifdef GL_ES
precision mediump float;
#endif
uniform float uTime;
varying vec2 vUv;
void main(){
vec2 uv = vUv;
uv.y -= uTime * 0.1;
uv.x += fbm(uv * 2.0) * 0.15;
float smoke = fbm(uv * 3.0);
float fade = 1.0 - vUv.y;
smoke *= fade;
smoke = smoothstep(0.2,0.8,smoke);
gl_FragColor = vec4(vec3(smoke),1.0);
}
Even in grayscale, the result resembles drifting smoke.
Adding Smoke Colours
Smoke doesn't have to be pure gray.
vec3 color = mix(
vec3(0.1,0.1,0.12),
vec3(0.8,0.8,0.85),
smoke
);
Dark gray creates dense smoke.
Light gray represents thinner areas.
Creating Colored Smoke
Nothing says smoke has to be gray.
Try blue.
vec3(0.2,0.3,0.8)
Or purple.
vec3(0.6,0.3,0.8)
These work well for magical or fantasy effects.
Creating Thick Smoke
Increase the FBM frequency.
fbm(
uv * 5.0
);
The smoke gains more detail.
Lower the frequency for large soft clouds.
Layering Multiple Smoke Fields
Professional shaders often combine several smoke layers.
Each layer moves at a different speed.
Each has a different scale.
Together they create depth and richness.
Where Is Procedural Smoke Used?
Smoke shaders appear in many projects.
- Campfires.
- Steam.
- Fog.
- Clouds.
- Explosions.
- Dust.
- Magical effects.
- Atmospheric backgrounds.
- Video games.
- Motion graphics.
Many of these effects differ only in color and movement speed.
Try These Experiments
Slow the animation.
Speed it up.
Create darker smoke.
Create blue magical smoke.
Layer several FBM patterns.
Add domain warping.
Observe how subtle changes dramatically affect the final result.
A Small Challenge
Can you create these effects?
- Campfire smoke.
- Steam from hot water.
- Thick industrial smoke.
- Magical mist.
- A drifting fog bank.
All of them begin with the same procedural techniques.
Today we created procedural smoke using animated FBM, coordinate distortion, gradients, and smooth transitions.
Although the shader shares many ideas with the fire shader, slowing the movement and softening the edges completely changes the final appearance.
This demonstrates how flexible procedural graphics can be.
Leave Creating Procedural Smoke in GLSL to:
Read more #coding posts
Best Posts From hey2d
We have not curated any of hey2d's posts yet. But you can encourage our curation team to review posts by visiting them regularly and by referring other readers. Because we give priority to frequently read content.
More Posts From hey2d
- Fragment Shaders Made Simple: What gl-FragColor Does
- Understanding gl-Position: My First Real Vertex Shader
- Creating Procedural Galaxies and Nebulas in GLSL
- Creating a Procedural Night Sky and Stars in GLSL
- Creating Procedural Clouds in GLSL
- Adding Reflections and Highlights to Procedural Water in GLSL
- Creating Procedural Water in GLSL
- Creating Procedural Smoke in GLSL
- Creating Procedural Fire in GLSL
- Creating Procedural Wood Grain in GLSL
- Creating a Procedural Marble Texture in GLSL
- Domain Warping in GLSL: Distorting Noise to Create Organic Worlds
- Fractal Brownian Motion in GLSL: Building Rich Procedural Noise
- Understanding Gradient Noise in GLSL
- Creating Smooth Value Noise in GLSL
- Creating Random Values in GLSL: The Building Blocks of Procedural Graphics
- Combining Translation, Rotation, and Scaling in GLSL
- Scaling Shapes in GLSL: Making Procedural Graphics Grow and Shrink
- Rotating Shapes in GLSL: Understanding Coordinate Rotation
- Moving Shapes in GLSL: Understanding Coordinate Translation