Procedural Textures

Wood, marble, and beyond

A photograph of wood grain is finite. It has edges. Zoom in too far and you see pixels. Tile it and you see seams. A procedural texture has none of these limitations. It exists everywhere, at every scale, generated fresh for each pixel from pure mathematics.

The techniques in this chapter let you create infinite, seamless materials: wood with realistic grain, marble with organic veins, stone with natural variation. These textures can tile perfectly because they were never tiled at all. They can be rendered at any resolution because they are defined by functions, not samples.

Wood Grain

Wood grain arises from the tree's growth rings combined with the way the log was cut. The rings create periodic patterns. The cutting angle creates the stretching and warping we see in finished wood.

We can simulate this with two elements: a periodic function for the rings, and noise to add organic variation.

float wood(vec2 p) {
  float grain = fbm(vec2(p.x * 10.0, p.y * 1.0));
  return sin(p.x * 20.0 + grain * 5.0) * 0.5 + 0.5;
}
glsl

Interactive: Wood Grain

Wood grain: noise stretched in one axis combined with sine waves

The key insight is stretching the noise coordinates. Noise sampled with (x * 10, y * 1) varies rapidly in x but slowly in y, creating the elongated grain pattern characteristic of wood. The sine function creates the periodic rings, and the noise offsets them to break up the regularity.

Adjust the grain scale to control how much the pattern varies across the surface. Increase the ring frequency for tighter growth rings. The noise amount determines how wavy those rings become.

Marble

Marble's distinctive veining comes from mineral deposits that flowed through the stone as it formed. The veins follow paths that are neither straight nor random: they are smooth curves with organic variation.

The trick to marble is feeding noise into a sine function:

float n = fbm(uv * frequency) * turbulence;
float veins = sin(uv.x * 10.0 + n);
glsl

Interactive: Marble

Marble: noise feeding into sine creates organic veins

Without the noise term, you would see straight parallel lines. The noise distorts the input to the sine function, bending those lines into organic curves. The pow function on the result sharpens the veins, controlling how suddenly they transition from light to dark.

Higher turbulence values create more chaotic veining. Lower values produce gentler, more flowing patterns. The frequency of the base noise determines how many veins appear in a given area.

Cellular and Voronoi Patterns

Voronoi diagrams divide space into cells, where each cell contains all points closest to a particular seed point. This pattern appears throughout nature: in giraffe spots, dried mud cracks, cell walls, and foam bubbles.

float voronoi(vec2 p) {
  vec2 i = floor(p);
  vec2 f = fract(p);
  float minDist = 1.0;
  
  for (int y = -1; y <= 1; y++) {
    for (int x = -1; x <= 1; x++) {
      vec2 neighbor = vec2(float(x), float(y));
      vec2 point = hash2(i + neighbor);
      float d = length(neighbor + point - f);
      minDist = min(minDist, d);
    }
  }
  return minDist;
}
glsl

Interactive: Voronoi Texture

Voronoi: distance to the nearest random point in each cell

The algorithm works by dividing space into a grid, placing a random point in each cell, then finding the distance to the nearest point. The grid ensures we only need to check neighboring cells, making the computation efficient enough for real-time rendering.

The distance to the nearest point creates a smooth gradient within each cell. The difference between the closest and second-closest distances highlights the cell edges. Coloring each cell based on its seed point creates distinct regions.

Domain Warping

Domain warping is perhaps the most powerful technique in procedural texturing. The idea is simple: instead of sampling noise at the original coordinates, we first distort those coordinates using more noise.

vec2 warp = vec2(
  fbm(p),
  fbm(p + vec2(5.2, 1.3))
);
vec2 warped = p + warp * amount;
float result = fbm(warped);
glsl

Interactive: Domain Warping

Domain warping: using noise to distort the input coordinates themselves

At zero warp amount, you see standard fbm noise. As you increase the warping, the texture begins to flow and swirl. The patterns develop an organic quality that plain noise cannot achieve: they look like ink in water, or clouds forming, or heat rising.

The offset vector vec2(5.2, 1.3) in the second fbm call ensures the two warp components are uncorrelated. Without this offset, the warping would push equally in both directions, creating a less interesting result.

Domain warping can be applied recursively: warp the coordinates, sample noise, use that to warp again, and so on. Each iteration adds another layer of complexity to the pattern.

Material Presets

Combining these techniques creates convincing material simulations. Each material uses a specific recipe of noise layers, domain warping, and color mapping.

Material Gallery

Layered noise at different frequencies with Voronoi cracks

Stone layers multiple frequencies of noise: large-scale variation for the overall color, medium-scale for surface texture, and fine-scale for granular detail. Voronoi cracks add the appearance of weathering.

Rust uses aggressive domain warping to create the chaotic, patchy appearance of corrosion. Multiple color stops and noise-driven transitions simulate the varied oxidation states.

Fabric simulates the warp and weft of woven thread using high-frequency sine waves. The alternating pattern is achieved by checking which half of a period we are in, switching between horizontal and vertical emphasis.

Clouds use domain warping with smoothstep to create soft, billowing edges. The warp coordinates themselves are animated to create drifting motion.

Layering Techniques

Complex textures rarely come from a single noise call. They are built up in layers, each adding a different aspect of the final appearance.

Interactive: Layering Demo

Toggle layers to see how they combine into complex textures

Start with a base noise at low frequency for overall structure. Add detail noise at high frequency for surface texture. Mix in Voronoi for cellular structure or cracks. Apply color variation using yet another noise layer to shift hues across the surface.

The key is restraint. Each layer should add something meaningful. Too many layers create visual mud. The best procedural textures use the minimum number of operations to achieve their effect.

Putting It in Motion

Procedural textures animate beautifully because they are functions of space. Add time to the input coordinates, and the patterns flow. Add time to the warp amount, and the distortion pulses. Add time to the color mapping, and the surface shimmers.

Animated Procedural

Animated domain warping creates flowing, organic motion

Animate the domain warp coordinates rather than the final noise coordinates for the most organic motion. The pattern seems to flow and reform rather than simply sliding across the screen.

The Procedural Mindset

Creating procedural textures is part art, part engineering. You need to understand what makes a material look the way it does: the physics of wood growth, the geology of marble formation, the biology of cellular structures. Then you translate that understanding into mathematical operations.

Start simple. A single noise call with thoughtful color mapping often suffices. Add complexity only when the result demands it. Every operation should serve a purpose you can articulate.

The reward is textures that are infinitely detailed, perfectly seamless, and endlessly variable. Change a single parameter and you have a new material. Add time and you have animation. These are not recordings of reality but programs that generate it.

Key Takeaways

  • Wood grain: stretch noise in one direction, use it to offset periodic rings
  • Marble: feed noise into sine to create flowing, organic veins
  • Voronoi: divide space into cells based on distance to random seed points
  • Domain warping: distort coordinates with noise before sampling more noise
  • Layer different noise frequencies and types to build complex materials
  • Each layer should serve a specific purpose: structure, detail, variation, or color
  • Animate by adding time to warp coordinates for flowing, organic motion
  • Procedural textures are infinite, seamless, and controllable