Documentation

Shader Example: Parallex Mapping

Parallex mapping with procedural heightmap.

Shader Example: Parallex Mapping

Snippets

Shadertoy

// Shadertoy: parallax mapping demo without textures
// Paste into https://www.shadertoy.com/ as the Image shader.

float heightMap(vec2 uv)
{
    // Procedural height field
    float h = 0.0;
    h += 0.50 * sin(uv.x * 10.0) * sin(uv.y * 10.0);
    h += 0.25 * sin(uv.x * 20.0 + 1.0) * sin(uv.y * 20.0 + 0.7);
    return h * 0.5 + 0.5;
}

vec3 normalFromHeight(vec2 uv)
{
    float e = 0.002;
    float h  = heightMap(uv);
    float hx = heightMap(uv + vec2(e, 0.0));
    float hy = heightMap(uv + vec2(0.0, e));

    vec3 dpdx = vec3(e, 0.0, hx - h);
    vec3 dpdy = vec3(0.0, e, hy - h);

    return normalize(cross(dpdx, dpdy));
}

vec2 parallaxOffset(vec2 uv, vec3 viewDirTS)
{
    float heightScale = 0.08;

    // Basic parallax mapping
    float h = heightMap(uv);
    return uv - viewDirTS.xy / max(viewDirTS.z, 0.05) * ((h - 0.5) * heightScale);
}

vec3 albedo(vec2 uv)
{
    // Checker + gradient for visibility
    vec2 g = floor(uv * 8.0);
    float checker = mod(g.x + g.y, 2.0);
    vec3 a = mix(vec3(0.15, 0.18, 0.22), vec3(0.75, 0.80, 0.90), checker);
    a *= 0.7 + 0.3 * heightMap(uv);
    return a;
}

mat3 camera(vec3 ro, vec3 ta)
{
    vec3 f = normalize(ta - ro);
    vec3 r = normalize(cross(vec3(0.0, 1.0, 0.0), f));
    vec3 u = cross(f, r);
    return mat3(r, u, f);
}

void mainImage(out vec4 fragColor, in vec2 fragCoord)
{
    vec2 p = (2.0 * fragCoord - iResolution.xy) / iResolution.y;

    // Camera
    float t = iTime * 0.8;
    vec3 ro = vec3(1.8 * cos(t), 0.9, 1.8 * sin(t));
    vec3 ta = vec3(0.0, 0.0, 0.0);
    mat3 cam = camera(ro, ta);

    vec3 rd = normalize(cam * vec3(p, 1.8));

    // Intersect plane z=0 in tangent/object space
    // Plane basis: x,y = tangent plane, z = height direction
    float planeZ = 0.0;
    float tt = (planeZ - ro.z) / rd.z;

    vec3 col = vec3(0.02, 0.03, 0.05);

    if (tt > 0.0)
    {
        vec3 hit = ro + rd * tt;
        vec2 uv = hit.xy;

        // Tile UVs
        uv *= 1.2;

        // View dir in tangent space for a flat XY plane
        vec3 viewDirTS = normalize(-rd);

        // Apply parallax
        vec2 puv = parallaxOffset(uv, viewDirTS);

        // Normal + lighting from displaced UV
        vec3 n = normalFromHeight(puv);
        vec3 l = normalize(vec3(0.6, 0.7, 0.5));
        vec3 v = normalize(-rd);
        vec3 h = normalize(l + v);

        float diff = max(dot(n, l), 0.0);
        float spec = pow(max(dot(n, h), 0.0), 48.0);

        vec3 base = albedo(puv);
        col = base * (0.15 + 0.85 * diff) + 0.35 * spec;

        // Slight vignette / fade
        col *= exp(-0.08 * tt);
    }

    fragColor = vec4(pow(col, vec3(0.4545)), 1.0);
}