Procedural Textures — Perlin Noise, Voronoi and Fractals
In this tutorial, you'll learn about Procedural Textures. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.
Procedural textures are computer-generated patterns created algorithmically rather than from image files, enabling infinite, tileable, and memory-efficient surface detail through noise functions and mathematical patterns.
What You'll Learn & Why It Matters
In this tutorial, you will learn how to generate procedural textures using Perlin noise, Voronoi diagrams, fractal Brownian motion, and turbulence. You will implement these in GLSL for real-time use in shaders.
Real-world use: Games like Minecraft and No Mans Sky generate entire worlds procedurally. Durga Antivirus Pro uses procedural noise for generating unique visualization patterns in its security dashboard.
Prerequisites
- Shader Programming (previous)
- Texture Mapping (previous)
- Basic probability
Learning Path
flowchart LR A[Texture Mapping] --> B[Procedural Textures] B --> C[Voxel Rendering] B --> D[Real-Time GI] B --> E[Shader Programming] B:::current classDef current fill:#f90,color:#fff,stroke:#333,stroke-width:2px
Why Procedural Textures?
Procedural textures have several advantages over image textures:
- Infinite resolution: No pixelation at any zoom level
- No memory cost: Generated on the fly, no texture uploads
- Infinite variety: Change parameters to get unique results
- Tileable by design: Seamless tiling with the right functions
- 3D textures: Natural volume textures for solid objects
Value Noise
The simplest noise function generates random values at integer grid points and interpolates between them:
float random(vec2 st)
{
return fract(sin(dot(st.xy, vec2(12.9898, 78.233))) * 43758.5453123);
}
float value_noise(vec2 p)
{
vec2 i = floor(p);
vec2 f = fract(p);
f = f * f * (3.0 - 2.0 * f); // Smoothstep
float a = random(i);
float b = random(i + vec2(1.0, 0.0));
float c = random(i + vec2(0.0, 1.0));
float d = random(i + vec2(1.0, 1.0));
return mix(mix(a, b, f.x), mix(c, d, f.x), f.y);
}
Perlin Noise
Perlin noise improves on value noise by using gradients instead of random values, producing smoother results:
vec2 random_gradient(vec2 p)
{
float angle = random(p) * 6.2831853;
return vec2(cos(angle), sin(angle));
}
float perlin_noise(vec2 p)
{
vec2 i = floor(p);
vec2 f = fract(p);
f = f * f * (3.0 - 2.0 * f);
vec2 g00 = random_gradient(i + vec2(0.0, 0.0));
vec2 g10 = random_gradient(i + vec2(1.0, 0.0));
vec2 g01 = random_gradient(i + vec2(0.0, 1.0));
vec2 g11 = random_gradient(i + vec2(1.0, 1.0));
float n00 = dot(g00, f - vec2(0.0, 0.0));
float n10 = dot(g10, f - vec2(1.0, 0.0));
float n01 = dot(g01, f - vec2(0.0, 1.0));
float n11 = dot(g11, f - vec2(1.0, 1.0));
return mix(mix(n00, n10, f.x), mix(n01, n11, f.x), f.y) * 0.5 + 0.5;
}
flowchart TD A[Grid Points] --> B[Assign Random Values/Gradients] B --> C[Interpolate Between Grid Points] C --> D[Smooth Noise Field] D --> E[Sum Octaves for FBM] E --> F[Apply to Texture Color]
Fractal Brownian Motion (FBM)
FBM layers multiple noise octaves at different scales and amplitudes:
float fbm(vec2 p, int octaves)
{
float value = 0.0;
float amplitude = 0.5;
float frequency = 1.0;
for (int i = 0; i < octaves; i++)
{
value += amplitude * perlin_noise(p * frequency);
amplitude *= 0.5;
frequency *= 2.0;
}
return value;
}
void main()
{
vec2 uv = TexCoords * 5.0;
float n = fbm(uv, 6);
vec3 color = vec3(n * 0.8 + 0.1, n * 0.5 + 0.2, n * 0.2 + 0.1);
FragColor = vec4(color, 1.0);
}
Expected output: A cloud-like texture with self-similar detail at multiple scales. Nearby areas are similar (smooth), but large-scale structure varies across the texture.
Voronoi / Worley Noise
Voronoi noise creates cell-like patterns by computing distance to the nearest seed point:
float voronoi_noise(vec2 p)
{
vec2 i = floor(p);
vec2 f = fract(p);
float minDist = 1.0;
for (int x = -1; x <= 1; x++)
{
for (int y = -1; y <= 1; y++)
{
vec2 neighbor = vec2(float(x), float(y));
vec2 point = random_gradient(i + neighbor);
point = 0.5 + 0.5 * sin(vec2(0.0, 1.0) + point * 6.2831);
vec2 diff = neighbor + point - f;
float dist = length(diff);
minDist = min(minDist, dist);
}
}
return minDist;
}
Expected output: Irregular cell-like patterns resembling biological tissue, stone tiles, or cobblestone.
Texture Generation with FBM + Voronoi
void main()
{
vec2 uv = TexCoords * 3.0;
// Marble texture: noise + sine wave
float marble = sin(uv.x * 4.0 + fbm(uv * 2.0, 4) * 2.0) * 0.5 + 0.5;
vec3 marbleColor = mix(vec3(0.9, 0.9, 0.9), vec3(0.1, 0.1, 0.1), marble);
// Wood texture: concentric rings + noise perturbation
float rings = sin(length(uv - 0.5) * 20.0 + perlin_noise(uv * 3.0) * 0.5);
vec3 woodColor = mix(vec3(0.6, 0.3, 0.1), vec3(0.3, 0.15, 0.05), rings * 0.5 + 0.5);
FragColor = vec4(marbleColor, 1.0);
}
Domain Warping
Apply noise to texture coordinates for organic distortion:
void main()
{
vec2 uv = TexCoords * 4.0;
vec2 warp = vec2(
fbm(uv + vec2(0.0, 0.0), 4),
fbm(uv + vec2(5.2, 1.3), 4)
);
vec2 distorted = uv + warp * 0.3;
float n = fbm(distorted, 5);
FragColor = vec4(vec3(n), 1.0);
}
Common Errors & Mistakes
1. Visible Grid Artifacts
Mistake: Using value noise without smoothstep interpolation, causing visible grid patterns.
Fix: Apply the smoothstep f * f * (3.0 - 2.0 * f) to fractional coordinates to create smooth transitions between grid cells.
2. Incorrect Octave Summation
Mistake: Adding FBM octaves without reducing amplitude, creating values outside the 0-1 range.
Fix: Use geometric progression: amplitude *= 0.5, frequency *= 2.0 per octave. Clamp the final result or normalize by max possible value.
3. Seam Artifacts at Tile Boundaries
Mistake: Procedural textures that do not tile seamlessly, showing visible seams.
Fix: Use modulo arithmetic on coordinates or ensure the noise function is periodic. For Perlin noise, use fract(p) for tiling.
4. Integer Overflow in Random Functions
Mistake: Using large integer multiplications in hash functions causing overflow on some GPUs.
Fix: Use medium-precision floats and appropriate scaling. Test on multiple GPU vendors to ensure consistent results.
Practice Questions
Question 1
What is the difference between value noise and Perlin noise?
Show answer
Value noise assigns random values to grid points and interpolates. Perlin noise assigns random gradients (direction vectors) and computes dot products, producing smoother, more natural-looking results without grid artifacts.Question 2
How does fractal Brownian motion create self-similar detail?
Show answer
FBM sums multiple noise octaves at decreasing amplitudes and increasing frequencies. Each octave adds detail at a smaller scale, creating a pattern that looks similar at any zoom level (statistical self-similarity).Question 3
What is domain warping used for?
Show answer
Domain warping distorts texture coordinates using noise before sampling, creating organic, fluid-like distortions. It produces effects like swirling smoke, flowing water, and marble veining.Question 4
How does Voronoi noise generate cell patterns?
Show answer
Voronoi noise places random seed points in space and computes the distance from each pixel to the nearest seed. The resulting distance field creates cell-like boundaries where distances to different seeds are equal.Challenge
Create a procedural terrain texture generator that combines FBM, Voronoi, and domain warping to produce realistic terrain types: rocky mountains, sandy deserts, grassy plains, and snowy peaks. Use altitude and slope to determine biome blending.
FAQ
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.
Author: DodaTech | Last updated: June 21, 2026
DodaTech tutorials are built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro — security tools used by millions worldwide.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro