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) or smoothstep(edge0, edge1, marbleIntensity) controls the tightness, contrast, and prominence of individual veins across the material.