Domain-Warped Noise
by GameDev.net · GLSL ES 3.00 (WebGL2) · 25 Aug 2026
Run the shader to adjust these controls.
What it demonstrates
Layered noise is isotropic: statistically the same in every direction, and after a while every fbm surface looks like the same grey cloud. Domain warping breaks that by distorting the coordinate before sampling rather than post processing the result. The field gains flow, swirls, and stretched filaments while the noise function itself is untouched.
Shader inputs
void mainImage(out vec4 fragColor, in vec2 fragCoord)
Called once per pixel. Write the colour to fragColor.
-
iResolutionvec3 - Viewport size in pixels (z is the pixel aspect ratio).
-
iTimefloat - Seconds since the shader started.
-
iTimeDeltafloat - Seconds since the previous frame.
-
iFrameRatefloat - Frames per second, smoothed.
-
iFrameint - Frames rendered since the start.
-
iMousevec4 - Mouse position: xy while held, zw of the last click.
-
iDatevec4 - Year, month, day, and seconds within the day.
-
iChannel0sampler2D - Texture bound to channel 0.
-
iChannel1sampler2D - Texture bound to channel 1.
-
iChannel2sampler2D - Texture bound to channel 2.
-
iChannel3sampler2D - Texture bound to channel 3.
-
iChannelResolutionvec3[4] - Pixel size of each bound channel texture.
-
iChannelTimefloat[4] - Playback time of each channel, in seconds.
-
iSampleRatefloat - Audio sample rate, always 44100.
Main Image6
uniform float uWarpStrength; // @param 0.0..1.5 = 0.7 "Warp strength"
uniform int uOctaves; // @param 1..8 = 5 "Octaves"
uniform float uScale; // @param 1.0..8.0 = 3.0 "Base scale"
float hash21(vec2 cell) {
return fract(sin(dot(cell, vec2(12.9898, 78.233))) * 43758.5453);
}
float valueNoise(vec2 p) {
vec2 cell = floor(p);
vec2 local = fract(p);
vec2 weight = local * local * (3.0 - 2.0 * local);
float bottom = mix(hash21(cell), hash21(cell + vec2(1.0, 0.0)), weight.x);
float top = mix(hash21(cell + vec2(0.0, 1.0)), hash21(cell + vec2(1.0, 1.0)), weight.x);
return mix(bottom, top, weight.y);
}
float fbm(vec2 p) {
float sum = 0.0;
float amplitude = 1.0;
float normalization = 0.0;
for (int i = 0; i < 8; i++) {
if (i >= uOctaves) break;
sum += amplitude * valueNoise(p);
normalization += amplitude;
amplitude *= 0.5;
p *= 2.0;
}
return sum / max(normalization, 0.0001);
}
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
vec2 uv = fragCoord / iResolution.xy;
uv.x *= iResolution.x / iResolution.y;
vec2 p = uv * uScale + vec2(iTime * 0.03, 0.0);
vec2 warp = vec2(fbm(p + vec2(1.7, 9.2)), fbm(p + vec2(8.3, 2.8)));
float field = fbm(p + uWarpStrength * warp);
vec3 deep = vec3(0.05, 0.10, 0.20);
vec3 mid = vec3(0.20, 0.48, 0.60);
vec3 bright = vec3(0.95, 0.86, 0.62);
vec3 color = mix(deep, mid, smoothstep(0.25, 0.55, field));
color = mix(color, bright, smoothstep(0.60, 0.85, field));
fragColor = vec4(color, 1.0);
}
Learn from this shader
How it works
The shader evaluates fbm twice at offset coordinates to build a two component vector, then adds a scaled version of it to the coordinate used for the final fbm call. Because the offset is itself smooth, nearby fragments are displaced by similar amounts and the field stays continuous instead of tearing. The constant offsets matter: without them both components would be identical and the warp could only push along one diagonal. Warp strength is measured in noise units, so a value near one samples up to roughly one lattice cell away.
Try changing
Hold Warp strength at zero to see the plain fbm underneath, then raise it slowly. Small values give gentle flow; large values fold the field into itself and produce the marbled look. Raise Octaves and note that warping preserves fine detail rather than smearing it. In a fork, feed the warped result back through another warp for the classic two level version, or drive the offsets from time for a swirling flow.
Using it in a game
This is the cheapest way to make procedural smoke, lava, magic effects, nebulae, and stylised water look directed rather than random. Cost is the honest catch: two extra fbm evaluations means roughly three times the work of the field alone, so bake to a flowmap or a texture when the effect covers much of the screen.
Explore the techniques
Continue with curated explanations and progressively related examples.
Discussion