~shockham/Physo

2445c94e5a0c27b66a87198ec9e5493cae3ab6a2 — shockham 3 years ago 8784ab5
Add basic PBR shader
2 files changed, 256 insertions(+), 158 deletions(-)

M src/lib.rs
M src/shaders/frag.glsl
M src/lib.rs => src/lib.rs +1 -1
@@ 35,7 35,7 @@ pub fn start() -> Result<(), JsValue> {
        let tick_time = instant::now();
        render_items[0].set_uniform(
            "time".to_string(),
            Uniform::Float((tick_time / 500f64) as f32),
            Uniform::Float((tick_time / 1000f64) as f32),
        );
        renderer.draw(&render_items).unwrap();
    });

M src/shaders/frag.glsl => src/shaders/frag.glsl +255 -157
@@ 1,19 1,9 @@
// Basis of this code is from: https://www.shadertoy.com/view/XlKSDR
precision mediump float;

const int MAX_MARCHING_STEPS = 255;
const float MIN_DIST = 0.0;
const float MAX_DIST = 100.0;
const float EPSILON = 0.0001;
const int MAX_ITERS = 8;
const float HALF_PI =  1.5707964;

const float distance = 20.0;
const float noise = 0.15;
const float displ = 0.0;
const float light = 0.5;
const float ncolor = 0.0;
const float round = 0.6;
const float size = 1.0;
#define saturate(x) clamp(x, 0.0, 1.0)
#define PI 3.14159265359

const vec2 dimensions = vec2(1000.0, 1000.0);

uniform float time;


@@ 21,202 11,310 @@ uniform float time;
varying vec3 vposition;
varying vec3 vcolor;

int id = 0;
//------------------------------------------------------------------------------
// Distance field functions
//------------------------------------------------------------------------------

float sdPlane(in vec3 p) {
    return p.y;
}

float sphere(vec3 p, float s) {
float sdSphere(in vec3 p, float s) {
    return length(p) - s;
}

float box(vec3 p, vec3 b) {
  vec3 d = abs(p) - b;
  return min(max(d.x,max(d.y,d.z)),0.0) + length(max(d,0.0));
float sdTorus(in vec3 p, in vec2 t) {
    return length(vec2(length(p.xz) - t.x, p.y)) - t.y;
}

float disp(vec3 p, float amt) {
    return sin(amt*p.x)*sin(amt*p.y)*sin(amt*p.z);
vec2 opUnion(vec2 d1, vec2 d2) {
    return d1.x < d2.x ? d1 : d2;
}

mat3 rotateY(float theta) {
    float c = cos(theta);
    float s = sin(theta);
    return mat3(
        vec3(c, 0, s),
        vec3(0, 1, 0),
        vec3(-s, 0, c)
vec2 scene(in vec3 position) {
    vec2 scene = opUnion(
          vec2(sdPlane(position), 1.0),
          vec2(sdSphere(position - vec3(0.0, 0.4, 0.0), 0.4), 12.0)
    );
    return scene;
}

//------------------------------------------------------------------------------
// Ray casting
//------------------------------------------------------------------------------

float shadow(in vec3 origin, in vec3 direction) {
    float hit = 1.0;
    float t = 0.02;
    
    for (int i = 0; i < 1000; i++) {
        float h = scene(origin + direction * t).x;
        if (h < 0.001) return 0.0;
        t += h;
        hit = min(hit, 10.0 * h / t);
        if (t >= 2.5) break;
    }

float onion(float sdf, float thickness) {
    return abs(sdf)-thickness;
    return clamp(hit, 0.0, 1.0);
}

float octa(vec3 p, in float s) {
    p = abs(p);
    return (p.x+p.y+p.z-s)*0.57735027;
}
vec2 traceRay(in vec3 origin, in vec3 direction) {
    float material = -1.0;

    float t = 0.02;
    
    for (int i = 0; i < 1000; i++) {
        vec2 hit = scene(origin + direction * t);
        if (hit.x < 0.002 || t > 20.0) break;
        t += hit.x;
        material = hit.y;
    }

    if (t > 20.0) {
        material = -1.0;
    }

vec3 rep(in vec3 p, in vec3 c) {
    vec3 q = mod(p,c)-0.5*c;
    return q;
    return vec2(t, material);
}

float scene(vec3 p) {
    vec3 q = rep(p, vec3(2.0, 0.0, 2.0));
    vec3 rp = rotateY(time / 4.0 + (sin(time) * 0.1)) * q;
vec3 normal(in vec3 position) {
    vec3 epsilon = vec3(0.001, 0.0, 0.0);
    vec3 n = vec3(
          scene(position + epsilon.xyy).x - scene(position - epsilon.xyy).x,
          scene(position + epsilon.yxy).x - scene(position - epsilon.yxy).x,
          scene(position + epsilon.yyx).x - scene(position - epsilon.yyx).x);
    return normalize(n);
}

	float o_sphere = sphere(rp, 1.0);
//------------------------------------------------------------------------------
// BRDF
//------------------------------------------------------------------------------

    return o_sphere;
float pow5(float x) {
    float x2 = x * x;
    return x2 * x2 * x;
}

float shortest_dist(vec3 eye, vec3 dir, float start, float end) {
    float depth = start;
    for (int i = 0; i < MAX_MARCHING_STEPS; i++) {
        float dist = scene(eye + depth * dir);
        if (dist < EPSILON || depth >=  end) break;
        depth += dist / (1.0 + displ);
    }
    return depth;
float D_GGX(float linearRoughness, float NoH, const vec3 h) {
    // Walter et al. 2007, "Microfacet Models for Refraction through Rough Surfaces"
    float oneMinusNoHSquared = 1.0 - NoH * NoH;
    float a = NoH * linearRoughness;
    float k = linearRoughness / (oneMinusNoHSquared + a * a);
    float d = k * k * (1.0 / PI);
    return d;
}

vec3 estimate_normal(vec3 p) {
    vec2 e = vec2(1.0,-1.0)*0.5773*0.0005;
    return normalize( e.xyy * scene(p + e.xyy) +
                      e.yyx * scene(p + e.yyx) +
                      e.yxy * scene(p + e.yxy) +
                      e.xxx * scene(p + e.xxx) );
float V_SmithGGXCorrelated(float linearRoughness, float NoV, float NoL) {
    // Heitz 2014, "Understanding the Masking-Shadowing Function in Microfacet-Based BRDFs"
    float a2 = linearRoughness * linearRoughness;
    float GGXV = NoL * sqrt((NoV - a2 * NoV) * NoV + a2);
    float GGXL = NoV * sqrt((NoL - a2 * NoL) * NoL + a2);
    return 0.5 / (GGXV + GGXL);
}

vec3 phong_contrib(vec3 k_d, vec3 k_s, float alpha, vec3 p, vec3 eye,
                          vec3 light_pos, vec3 light_intensity) {
    vec3 N = estimate_normal(p);
    vec3 L = normalize(light_pos - p);
    vec3 V = normalize(eye - p);
    vec3 R = normalize(reflect(-L, N));

    float dotLN = dot(L, N);
    float dotRV = dot(R, V);

    if (dotLN < 0.0) {
        return vec3(0.0, 0.0, 0.0);
    }

    if (dotRV < 0.0) {
        return light_intensity * (k_d * dotLN);
    }
    return light_intensity * (k_d * dotLN + k_s * pow(dotRV, alpha));
vec3 F_Schlick(const vec3 f0, float VoH) {
    // Schlick 1994, "An Inexpensive BRDF Model for Physically-Based Rendering"
    return f0 + (vec3(1.0) - f0) * pow5(1.0 - VoH);
}


float softshadow(vec3 eye, vec3 dir, float mint, float tmax ) {
    float res = 1.0;
    float t = mint;
    for(int i = 0; i < 16; i++) {
        float h = scene(eye + dir * t);
        res = min(res, 8.0 * h / t);
        t += clamp(h, 0.02, 0.10);
        if(h < 0.001 || t > tmax) break;
    }
    return clamp(res, 0.0, 1.0);
float F_Schlick(float f0, float f90, float VoH) {
    return f0 + (f90 - f0) * pow5(1.0 - VoH);
}

float Fd_Burley(float linearRoughness, float NoV, float NoL, float LoH) {
    // Burley 2012, "Physically-Based Shading at Disney"
    float f90 = 0.5 + 2.0 * linearRoughness * LoH * LoH;
    float lightScatter = F_Schlick(1.0, f90, NoL);
    float viewScatter  = F_Schlick(1.0, f90, NoV);
    return lightScatter * viewScatter * (1.0 / PI);
}

float calc_AO(vec3 pos, vec3 nor) {
    float occ = 0.0;
    float sca = 1.0;
    for(int i=0; i<5; i++) {
        float hr = 0.01 + 0.12*float(i)/4.0;
        vec3 aopos =  nor * hr + pos;
        float dd = scene(aopos);
        occ += -(dd-hr)*sca;
        sca *= 0.95;
    }
    return clamp( 1.0 - 3.0*occ, 0.0, 1.0 );
float Fd_Lambert() {
    return 1.0 / PI;
}

float rand(vec2 co) {
    return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453);
//------------------------------------------------------------------------------
// Indirect lighting
//------------------------------------------------------------------------------

vec3 Irradiance_SphericalHarmonics(const vec3 n) {
    // Irradiance from "Ditch River" IBL (http://www.hdrlabs.com/sibl/archive.html)
    return max(
          vec3( 0.754554516862612,  0.748542953903366,  0.790921515418539)
        + vec3(-0.083856548007422,  0.092533500963210,  0.322764661032516) * (n.y)
        + vec3( 0.308152705331738,  0.366796330467391,  0.466698181299906) * (n.z)
        + vec3(-0.188884931542396, -0.277402551592231, -0.377844212327557) * (n.x)
        , 0.0);
}

vec3 lighting(vec3 k_a, vec3 k_d, vec3 k_s, float alpha, vec3 p, vec3 eye) {
    const vec3 ambient_light = vec3(0.6);
    vec3 color = ambient_light * k_a;
    vec3 normal = estimate_normal(p);
vec2 PrefilteredDFG_Karis(float roughness, float NoV) {
    // Karis 2014, "Physically Based Material on Mobile"
    const vec4 c0 = vec4(-1.0, -0.0275, -0.572,  0.022);
    const vec4 c1 = vec4( 1.0,  0.0425,  1.040, -0.040);

    float occ = calc_AO(p, normal);
    vec4 r = roughness * c0 + c1;
    float a004 = min(r.x * r.x, exp2(-9.28 * NoV)) * r.x + r.y;

    vec3 light_pos = vec3(4.0 * sin(time),
                          5.0,
                          4.0 * cos(time));
    vec3 light_intensity = vec3(light);
    return vec2(-1.04, 1.04) * a004 + r.zw;
}

//------------------------------------------------------------------------------
// Tone mapping and transfer functions
//------------------------------------------------------------------------------

vec3 Tonemap_ACES(const vec3 x) {
    // Narkowicz 2015, "ACES Filmic Tone Mapping Curve"
    const float a = 2.51;
    const float b = 0.03;
    const float c = 2.43;
    const float d = 0.59;
    const float e = 0.14;
    return (x * (a * x + b)) / (x * (c * x + d) + e);
}

	color = mix(color, normal, ncolor);
	if(id == 1) {
		color = mix(color, vec3(1.0), 0.9);
	} else {
		color = mix(color, vec3(1.0), 0.1);
	}
vec3 OECF_sRGBFast(const vec3 linear) {
    return pow(linear, vec3(1.0 / 2.2));
}

    color += phong_contrib(k_d, k_s, alpha, p, eye,
                                  light_pos,
                                  light_intensity);
    color = mix(
        color,
        color * occ * softshadow(p, normalize(light_pos), 0.02, 5.0),
        light + tan(sin(time / 7.2)) * 4.0
//------------------------------------------------------------------------------
// Rendering
//------------------------------------------------------------------------------

float floorColor(vec3 pos) {
    float cbSize = 10.0;//2.0 + 6.0 * cos(time);
    return mod(
        floor(cbSize * pos.z) + floor(cbSize * pos.x), 
        2.0 * sin(time / 20.0)
    );
}

    color = mix(color, vec3(rand(vposition.xy * time)), noise);
vec3 render(in vec3 origin, in vec3 direction, out float distance) {
    // Sky gradient
    vec3 color = vec3(0.65, 0.85, 1.0) + direction.y * 0.72;

    // (distance, material)
    vec2 hit = traceRay(origin, direction);
    distance = hit.x;
    float material = hit.y;

    // We've hit something in the scene
    if (material > 0.0) {
        vec3 position = origin + distance * direction;

        vec3 v = normalize(-direction);
        vec3 n = normal(position);
        vec3 l = normalize(vec3(0.6, 0.7, -0.7));
        vec3 h = normalize(v + l);
        vec3 r = normalize(reflect(direction, n));

        float NoV = abs(dot(n, v)) + 1e-5;
        float NoL = saturate(dot(n, l));
        float NoH = saturate(dot(n, h));
        float LoH = saturate(dot(l, h));

        vec3 baseColor = vec3(0.0);
        float roughness = 0.0;
        float metallic = 0.0;

        float intensity = 2.0;
        float indirectIntensity = 0.64;

        if (material < 4.0)  {
            // Checkerboard floor
            float f = floorColor(position);
            baseColor = 0.4 + f * vec3(0.2);
            roughness = 0.1;
        } else if (material < 16.0) {
            // Metallic objects
            baseColor = vec3(0.0, 0.7, 0.8);
            //roughness = 1.2;
            roughness = 0.4 + (sin(time) / 2.0);
            metallic = 1.0 + cos(time);
        }

        float linearRoughness = roughness * roughness;
        vec3 diffuseColor = (1.0 - metallic) * baseColor.rgb;
        vec3 f0 = 0.04 * (1.0 - metallic) + baseColor.rgb * metallic;

        float attenuation = shadow(position, l);

        // specular BRDF
        float D = D_GGX(linearRoughness, NoH, h);
        float V = V_SmithGGXCorrelated(linearRoughness, NoV, NoL);
        vec3  F = F_Schlick(f0, LoH);
        vec3 Fr = (D * V) * F;

        // diffuse BRDF
        vec3 Fd = diffuseColor * Fd_Burley(linearRoughness, NoV, NoL, LoH);

        color = Fd + Fr;
        color *= (intensity * attenuation * NoL) * vec3(0.98, 0.92, 0.89);

        // diffuse indirect
        vec3 indirectDiffuse = Irradiance_SphericalHarmonics(n) * Fd_Lambert();

        vec2 indirectHit = traceRay(position, r);
        vec3 indirectSpecular = vec3(0.65, 0.85, 1.0) + r.y * 0.72;
        if (indirectHit.y > 0.0) {
            if (indirectHit.y < 4.0)  {
                vec3 indirectPosition = position + indirectHit.x * r;
                // Checkerboard floor
                float f = floorColor(indirectPosition);
                indirectSpecular = 0.4 + f * vec3(0.6);
            } else if (indirectHit.y < 16.0) {
                // Metallic objects
                indirectSpecular = vec3(0.0, 0.7, 0.8);
            }
        }

        // indirect contribution
        vec2 dfg = PrefilteredDFG_Karis(roughness, NoV);
        vec3 specularColor = f0 * dfg.x + dfg.y;
        vec3 ibl = diffuseColor * indirectDiffuse + indirectSpecular * specularColor;

        color += ibl * indirectIntensity;
    }

    return color;
}

//------------------------------------------------------------------------------
// Setup and execution
//------------------------------------------------------------------------------

vec4 render(vec3 cam_pos, vec3 v_dir) {
    float dist = shortest_dist(cam_pos, v_dir, MIN_DIST, MAX_DIST);
mat3 setCamera(in vec3 origin, in vec3 target, float rotation) {
    vec3 forward = normalize(target - origin);
    vec3 orientation = vec3(sin(rotation), cos(rotation), 0.0);
    vec3 left = normalize(cross(forward, orientation));
    vec3 up = normalize(cross(left, forward));
    return mat3(left, up, forward);
}

    if (dist > MAX_DIST - EPSILON) {
        return vec4(0.0, 0.0, 0.0, 0.0);
    }
void main() {
    vec2 p = vposition.xy;

    vec3 p = cam_pos + dist * v_dir;
    vec3 color = lighting(vec3(0.2), vec3(0.2), vec3(1.0), 20.0, p, cam_pos);
    return vec4(color, 1.0);
}
    // Camera position and "look at"
    vec3 origin = vec3(0.0, 0.6, 0.0);
    vec3 target = vec3(0.0);

mat4 view_matrix(vec3 eye, vec3 center, vec3 up) {
    vec3 f = normalize(center - eye);
    vec3 s = normalize(cross(f, up));
    vec3 u = cross(s, f);
    return mat4(
        vec4(s, 0.0),
        vec4(u, 0.0),
        vec4(-f, 0.0),
        vec4(0.0, 0.0, 0.0, 1)
    );
}
    origin.x += 1.7 * cos(time * 0.2);
    origin.z += 1.7 * sin(time * 0.2);

vec3 ray_dir(float fieldOfView, vec2 size, vec2 fragCoord) {
    vec2 xy = fragCoord - size / 2.0;
    float z = size.y / tan(radians(fieldOfView) / 2.0);
    return normalize(vec3(xy, -z));
}
    mat3 toWorld = setCamera(origin, target, 0.0);
    vec3 direction = toWorld * normalize(vec3(p.xy, 2.0));

void main() {
    vec3 dir = ray_dir(45.0, dimensions, vposition.xy * dimensions + (dimensions / 2.0));
    // Render scene
    float distance;
    vec3 color = render(origin, direction, distance);

    // Tone mapping
    color = Tonemap_ACES(color);

    vec2 input_cam_pos = vec2(-2.4, 1.0);// * cos(time / 25.0);
    vec3 cam_pos = vec3(
        cos(input_cam_pos.x) * cos(input_cam_pos.y),
        sin(input_cam_pos.y),
        sin(input_cam_pos.x) * cos(input_cam_pos.y)
    ) * distance;
    // Exponential distance fog
    color = mix(color, 0.8 * vec3(0.7, 0.8, 1.0), 1.0 - exp2(-0.011 * distance * distance));

    mat4 view_mat = view_matrix(cam_pos, vec3(5.0, -10.0, 5.0), vec3(0.0, 1.0, 0.0));
    vec3 v_dir = (view_mat * vec4(dir, 0.0)).xyz;
    // Gamma compression
    color = OECF_sRGBFast(color);

    gl_FragColor = render(cam_pos, v_dir);
    gl_FragColor = vec4(color, 1.0);
}