GLSL Procedural Marble Texture with Sine and Noise
Procedural marble in computer graphics is commonly generated by perturbing a periodic function, such as a sine wave, using fractal noise or turbulence. In GLSL, this technique simulates the natural metamorphic bands found in stone by shifting regular spatial coordinates with multi-octave noise. By feeding distorted coordinates into a sine function and mapping the output to a color gradient, fragment shaders can render realistic, infinite-resolution marble surfaces entirely in real time.
The Mathematical Formula
The canonical formula developed by Ken Perlin for procedural marble is:
\[\text{Intensity} = \sin\left(x \cdot \text{frequency} + \text{amplitude} \cdot \text{turbulence}(p)\right)\]
To normalize this output from the standard sine range of \([-1, 1]\) into a normalized \([0, 1]\) range suitable for texture interpolation, the formula is modified to:
\[\text{Intensity} = 0.5 + 0.5 \cdot \sin\left(x \cdot \text{frequency} + \text{amplitude} \cdot \text{turbulence}(p)\right)\]
Formula Components
- Base Coordinate (\(x\) or \(x + y + z\)): Determines the base direction and density of the marble's linear striations.
- Frequency: Scales the spatial coordinates to control the density or number of striping bands.
- Turbulence Function (\(\text{turbulence}(p)\)): An accumulation of multiple octaves of noise, typically using the absolute value of noise (Perlin turbulence) or standard Fractional Brownian Motion (fBm): \[\text{turbulence}(p) = \sum_{i=0}^{n} \frac{|\text{noise}(2^i \cdot p)|}{2^i}\]
- Amplitude (Distortion Factor): Determines the severity of the waviness and chaos injected into the veins.
GLSL Implementation
Below is a complete implementation showing how the turbulence and modulation formulas integrate into a fragment shader:
// Basic 2D pseudo-random hash
float hash(vec2 p) {
return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453123);
}
// Value noise function
float noise(vec2 p) {
vec2 i = floor(p);
vec2 f = fract(p);
vec2 u = f * f * (3.0 - 2.0 * f);
return mix(mix(hash(i + vec2(0.0, 0.0)), hash(i + vec2(1.0, 0.0)), u.x),
mix(hash(i + vec2(0.0, 1.0)), hash(i + vec2(1.0, 1.0)), u.x), u.y);
}
// Turbulence: summed octaves using absolute noise for sharp veins
float turbulence(vec2 p) {
float sum = 0.0;
float freq = 1.0;
float amp = 1.0;
for (int i = 0; i < 5; i++) {
sum += abs(noise(p * freq) * 2.0 - 1.0) * amp;
freq *= 2.0;
amp *= 0.5;
}
return sum;
}
// Procedural marble pattern generator
vec3 marble(vec2 uv) {
float frequency = 4.0;
float amplitude = 8.0;
// Core formula: modulate sine with turbulence
float t = uv.x * frequency + amplitude * turbulence(uv);
float marbleIntensity = 0.5 + 0.5 * sin(t);
// Sharpen veins using a non-linear exponent
marbleIntensity = pow(marbleIntensity, 0.7);
// Map intensity to stone colors
vec3 veinColor = vec3(0.1, 0.15, 0.2);
vec3 baseColor = vec3(0.9, 0.92, 0.95);
return mix(veinColor, baseColor, marbleIntensity);
}Vein Customization Techniques
- Sharp vs. Soft Veins: Using standard noise yields
smooth, blended transitions. Wrapping the noise in
abs()inside the loop creates creases and sharply defined vein boundaries. - Vein Alignment: Replacing \(x\) with a linear combination such as \((uv.x + uv.y)\) rotates the primary direction of the marble striations diagonally.
- Contrast Modulation: Passing the final intensity
through functions like
pow(marbleIntensity, gamma)orsmoothstep(edge0, edge1, marbleIntensity)controls the tightness, contrast, and prominence of individual veins across the material.