0.00
60.0 fps

Fractal cube

Real-time path traced cubes.

Log in to post a comment.

precision highp float;

uniform float iTime;
uniform vec2  iResolution;

#define PATH_LENGTH 4
#define SAMPLES_PER_PIXEL 16

#define MAX_DIST 1e10
#define PI 3.14159265359
#define MAX_FLOAT 1e5
#define EPSILON 0.001

#define LAMBERTIAN 0.
#define METAL 1.
#define DIFFUSE_LIGHT 2.

uniform float gridScale;// value=7, min=3, max=16, step=1
uniform float gridFill;// value=0.6, min=0.1, max=0.6, step=0.01
uniform float lightFill;// value=0.9, min=0.75, max=1, step=0.01

uniform float matType;// value=1, min=0, max=1, step=1 (Lambertian, Metal)
uniform float matRoughness;// value=0.5, min=0.5, max=1, step=0.01

uniform float colorScale;// value=0.25, min=0, max=1, step=0.01
uniform float colorOffset;// value=4., min=0, max=6.4, step=0.01

uniform float floorBrightness;// value=0.03, min=0.025, max=0.05, step=0.01
uniform float floorRoughness;// value=0.2, min=0.02, max=0.2, step=0.001
uniform float floorType;// value=2, min=0, max=2, step=1 (Water, Solid, Checkerboard)

float seed;
float mat_hash;

// Hash without Sine
// MIT License...
// Copyright (c)2014 David Hoskins.
// https://www.shadertoy.com/view/4djSRW

float hash1(float p) {
    p = fract(p * .1031);
    p *= p + 33.33;
    p *= p + p;
    return fract(p);
}

vec2 hash2(inout float p) {
    vec3 p3 = fract(vec3(p+=1.) * vec3(.1031, .1030, .0973));
    p3 += dot(p3, p3.yzx + 33.33);
    return fract((p3.xx+p3.yz)*p3.zy);
}

vec3 hash3(inout float p) {
    vec3 p3 = fract(vec3(p+=1.) * vec3(.1031, .1030, .0973));
    p3 += dot(p3, p3.yzx+33.33);
    return fract((p3.xxy+p3.yzz)*p3.zyx);
}

float gpuIndepentHash(float p) {
    p = fract(p * .1031);
    p *= p + 19.19;
    p *= p + p;
    return fract(p);
}

mat2 rot(float a) {
    return mat2(cos(a), sin(a), -sin(a), cos(a));
}

// Plane
float iPlane(in vec3 ro, in vec3 rd, in vec2 distBound, inout vec3 normal,
in vec3 planeNormal, in float planeDist) {
    float a = dot(rd, planeNormal);
    float d = -(dot(ro, planeNormal)+planeDist)/a;
    if (a > 0. || d < distBound.x || d > distBound.y) {
        return MAX_DIST;
    } else {
        normal = planeNormal;
        return d;
    }
}

float grid(in vec3 ro, in vec3 rd, in vec2 distBound, inout vec3 normal) {
    float cubeGridScale = gridScale;
    const int steps = 20;

    vec3 ros = ro * cubeGridScale;
    vec3 pos = floor(ros + (all(lessThan(abs(ro), vec3(1.)))?0.:distBound.x-EPSILON) * cubeGridScale * rd);
    vec3 ri = 1.0/rd;
    vec3 rs = sign(rd);
    vec3 dis = (pos-ros + 0.5 + rs*0.5) * ri;

    float res = 0.0, grid_seed;
    vec3 mm = vec3(0.0);
    bool hit = false;

    for (int i=0; i<steps; i++) {
        mm = step(dis.xyz, dis.yxy) * step(dis.xyz, dis.zzx);
        dis += mm * rs * ri;
        pos += mm * rs;

        vec3 hash_pos = pos + .5;
        hash_pos = abs(hash_pos);

        // grid_seed = dot(hash_pos, vec3(sqrt(5.), sqrt(2.), sqrt(3.)));
        // grid_seed = dot(hash_pos, vec3(1));
        grid_seed = dot(hash_pos, vec3(2,2,3));
        grid_seed += floor(iTime * .1 + grid_seed * (1./gridScale));
        float grid_hash = hash1(grid_seed);
        if (all(lessThan(abs(pos+.5), vec3(cubeGridScale))) && grid_hash > gridFill) {
            hit = true;
            break;
        }
        else if (max(abs(pos.x), max(abs(pos.y), abs(pos.z))) > cubeGridScale + 1.) break;
    }

    if (hit) {
        // intersect the cube
        vec3 mini = (pos-ros + 0.5 - 0.5*vec3(rs))*ri;
        float t = max (mini.x, max (mini.y, mini.z)) / cubeGridScale;

        mat_hash = hash1(-grid_seed);

        normal = -mm*rs;
        //  matCol *= .5;
        return t;
    } else {
        return MAX_DIST;
    }
}


// Box:             https://www.shadertoy.com/view/ld23DV
float iBox(in vec3 ro, in vec3 rd, in vec2 distBound, inout vec3 normal,
in vec3 boxSize) {

    mat3 rf = mat3(0.8164965, -0.5773504, -0.0000000,
    0.4082484, 0.5773502, -0.7071068,
    0.4082484, 0.5773502, 0.7071068);

    ro.xz*=rot(0.1*iTime);
    ro *= rf;

    rd.xz*=rot(0.1*iTime);
    rd *= rf;


    vec3 m = sign(rd)/max(abs(rd), 1e-8);
    vec3 n = m*ro;
    vec3 k = abs(m)*boxSize;

    vec3 t1 = -n - k;
    vec3 t2 = -n + k;

    float tN = max(max(t1.x, t1.y), t1.z);
    float tF = min(min(t2.x, t2.y), t2.z);

    if (tN > tF || tF <= 0.) {
        return MAX_DIST;
    } else {
        if (tN <= distBound.y) {
            // 	normal = -sign(rd)*step(t1.yzx,t1.xyz)*step(t1.zxy,t1.xyz);

            tN = grid(ro, rd, vec2(tN < 0. ? 0. : tN, tF), normal);

            normal = rf * normal;
            normal.xz*=rot(-0.1*iTime);

            return tN >= distBound.x ? tN : MAX_DIST;
        } else {
            return MAX_DIST;
        }
    }
}

//
// Ray tracer helper functions
//

float FresnelSchlickRoughness(float cosTheta, float F0, float roughness) {
    return F0 + (max((1. - roughness), F0) - F0) * pow(abs(1. - cosTheta), 5.0);
}

vec3 cosWeightedRandomHemisphereDirection(const vec3 n, inout float seed) {
    vec2 r = hash2(seed);
    vec3  uu = normalize(cross(n, abs(n.y) > .5 ? vec3(1., 0., 0.) : vec3(0., 1., 0.)));
    vec3  vv = cross(uu, n);
    float ra = sqrt(r.y);
    float rx = ra*cos(6.28318530718*r.x);
    float ry = ra*sin(6.28318530718*r.x);
    float rz = sqrt(1.-r.y);
    vec3  rr = vec3(rx*uu + ry*vv + rz*n);
    return normalize(rr);
}

vec3 modifyDirectionWithRoughness(const vec3 normal, const vec3 n, const float roughness, inout float seed) {
    vec2 r = hash2(seed);

    vec3  uu = normalize(cross(n, abs(n.y) > .5 ? vec3(1., 0., 0.) : vec3(0., 1., 0.)));
    vec3  vv = cross(uu, n);

    float a = roughness*roughness;

    float rz = sqrt(abs((1.0-r.y) / clamp(1.+(a - 1.)*r.y, .00001, 1.)));
    float ra = sqrt(abs(1.-rz*rz));
    float rx = ra*cos(6.28318530718*r.x);
    float ry = ra*sin(6.28318530718*r.x);
    vec3  rr = vec3(rx*uu + ry*vv + rz*n);

    vec3 ret = normalize(rr);
    return dot(ret, normal) > 0. ? ret : normalize(ret + 2. * dot(ret, normal) * n);
}

vec2 randomInUnitDisk(inout float seed) {
    vec2 h = hash2(seed) * vec2(1, 6.28318530718);
    float phi = h.y;
    float r = sqrt(h.x);
    return r*vec2(sin(phi), cos(phi));
}

//
// Scene description
//

vec2 opU(vec2 d, float iResult) {
    return (iResult < d.y) ? vec2(d.x, iResult) : d;
}

vec2 worldhit(in vec3 ro, in vec3 rd, in vec2 dist, out vec3 normal) {
    vec2 d = dist;

    d = opU(d, iBox        (ro-vec3(0, .4, 0.), rd, d.xy, normal, vec3(1.)));
    d = opU(d, iPlane      (ro, rd, d.xy, normal, vec3(0, 1, 0), 1.35));

    return d;
}

float checkerBoard(vec2 p) {
    return mod(floor(p.x) + floor(p.y), 2.);
}

vec3 getSkyColor(vec3 rd) {
    float amb = 6. - 2. * rd.y;
    float sun = clamp(dot(normalize(vec3(.4, .7, 1.2)), rd), 0., 1.);

    sun = (pow(sun, 4.) + 20.*pow(sun, 32.));

    return (.6 + .4 * cos(12.5663706144 * colorScale + colorOffset +  vec3(0., 0.6, 1.2))) * sun + amb;
}

//
// Simple ray tracer
//

vec3 render(in vec3 ro, in vec3 rd) {
    vec3 col = vec3(0), normal, albedo, emit;
    vec3 emitted = vec3(0);
    float type, roughness;

    for (int i=0; i<PATH_LENGTH; i++) {
        vec2 res = worldhit(ro, rd, vec2(.0001, MAX_DIST), normal);

        if (res.y < MAX_DIST) {
            ro +=  rd * res.y;

            if (ro.y < -1.34) {
                // floor
                albedo = vec3(floorBrightness / (1. + 2. * length(ro.xz)));
                if (floorType < 1.5) roughness = floorRoughness;
                else roughness = floorRoughness + (4. * floorRoughness *  checkerBoard((ro.xz * rot(PI*.25))));

                if (floorType < .5) {
                    float ws = .15*sin(length(ro.xz) * 20. - iTime * 4.);
                    float wc = .15*sin(length(ro.xz) * 20. - iTime * 4.);
                    normal = normalize(vec3(ws, 10.-ws-wc, wc));
                }
            } else {
                // cube
                const float ga = 0.39996322972865332;
                if (matType < .5) {
                    albedo = .5 + .4 * cos(mat_hash * (12.5663706144 * colorScale) + colorOffset +   vec3(0., 0.6, 1.2));
                    albedo *= albedo;
                } else {
                    type = 1.;
                    albedo = .4 + .3 * cos((12.5663706144 * colorScale) + colorOffset +  vec3(0., 0.6, 1.2));
                    albedo *= albedo;
                    roughness = mat_hash * mat_hash * matRoughness;
                }
                if (hash1(mat_hash) > lightFill) {
                    emit = (.5 + .4 * cos((12.5663706144 * colorScale) + colorOffset +  vec3(0., 0.6, 1.2))) * 50.;
                }
            }

            emitted += i == 0 ? emit : col * emit;
            col = i == 0 ? albedo : col * albedo;

            if (matType < LAMBERTIAN+.5 && ro.y > -1.34) {
                rd = cosWeightedRandomHemisphereDirection(normal, seed);
            } else {
                rd = modifyDirectionWithRoughness(normal, reflect(rd, normal), roughness, seed);
            }
        } else {
            return emitted + col * getSkyColor(rd);
        }
        //      if(dot(col,col) < 0.0001) return emitted + col * getSkyColor(rd);
    }
    return emitted;
}

void main() {

    float fpd = 5.;
    vec3  col = vec3(0);

    for (int i=0; i<SAMPLES_PER_PIXEL; i++) {
        seed = iTime + 100.*(float(i)*2.399+hash1(gl_FragCoord.x)+hash1(-gl_FragCoord.y));
        // AA
        vec2 p = (2. * gl_FragCoord.xy - iResolution) / iResolution.y;
        p += 2.*hash2(seed)/iResolution.y;

        vec3 ro = vec3(0., .25, 6.);
        vec3 rd = normalize(vec3(p.x, p.y, -2.5));
        vec3 normal;

        // DOF
        vec3 fp = ro + rd * fpd;
        ro = ro + vec3(randomInUnitDisk(seed), 0.)*.05;
        rd = normalize(fp - ro);

        vec3 outcol = render(ro, rd);

        outcol = max(vec3(0), outcol - 0.004);
        outcol = (outcol*(6.2*outcol + .5)) / (outcol*(6.2*outcol+1.7) + 0.06);

        col += outcol;
    }

    col *= 1./float(SAMPLES_PER_PIXEL);

    // col += vec3(hash1(seed)) * 0.2;

    gl_FragColor = vec4(col, 1);
}