Skip to content

Procedural Textures — Perlin Noise, Voronoi and Fractals

DodaTech Updated 2026-06-21 7 min read

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

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

Are procedural textures better than image textures?

Neither is universally better. Procedural textures are resolution-independent, memory-free, and infinitely tileable but harder to author for specific looks. Image textures provide precise artist control but use memory and can show pixelation.

Can procedural textures be used in games?

Yes, extensively. Minecraft uses procedural noise for world generation. AAA games use procedural textures for terrain, skyboxes, water, fire, and damage decals that must be unique each time.

What is simplex noise?

Simplex noise is Ken Perlin's improved noise algorithm that scales to higher dimensions more efficiently. It uses a simplex grid (triangles in 2D, tetrahedra in 3D) instead of a square grid, reducing computational complexity from O(2^n) to O(n^2).

How do I make a procedural texture tileable?

Ensure your noise function is evaluated on a periodic domain. For FBM, use fract(p) on the input coordinates or wrap using modulo. Alternatively, sample noise on a toroidal topology for seamless tiling.


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