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;
}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);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;
}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);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