#version 300 es
precision highp float;
out vec4 fragColor;
uniform sampler2D prgm1Texture;
uniform vec2 resolution;
uniform float time;
void main(void) {
vec2 uv = gl_FragCoord.xy / resolution.xy;
vec4 tex = texture(prgm1Texture, uv);
vec3 col = tex.xyz / tex.w;
col = mix(col, pow(col, vec3(0.4545)), 0.5);
fragColor = vec4(col, 1.0);
}
#version 300 es
precision highp float;
uniform sampler2D prgm1Texture;
uniform float time;
uniform vec2 resolution;
uniform vec4 mousedrag;
const float PI = acos(-1.0);
const float TAU = PI * 2.0;
const float HALF_PI = PI * 0.5;
out vec4 fragColor;
const vec3 sunDir = normalize(vec3(0.8, 0.5, -1.0));
const vec3 sunCol = vec3(0.5, 0.7, 1.0);
struct Ray { vec3 ro; vec3 rd; };
struct Hit { int id; vec3 p; vec3 n; float t; };
struct Material { vec3 albedo; bool is_metal; float fuzz; bool isGlass; float refID; vec3 phong; };
float mtime = 0.0;
float sphereIntersect(Ray ray, vec4 sph) {
vec3 c = sph.xyz;
float r = sph.w;
vec3 co = ray.ro - c;
float b = dot(co, ray.rd);
float d = b * b - dot(co, co) + r * r;
if (d < 0.0) { return -1.0; }
float sd = sqrt(d);
vec2 t = vec2(-b - sd, -b + sd);
if (t.x < 0.0) { return t.y; }
return t.x;
}
float planeIntersect(Ray ray, vec3 n) {
return -dot(ray.ro, n) / dot(ray.rd, n);
}
vec3 sphereNormal(vec3 p, vec4 sph) {
return normalize(p - sph.xyz);
}
float sphereShadow(vec3 ro, vec3 rd, vec4 sph, float k) {
vec3 oc = ro - sph.xyz;
float b = dot(oc, rd);
float c = dot(oc, oc) - sph.w * sph.w;
float h = b * b - c;
float d = -sph.w + sqrt(max(0.0, sph.w * sph.w - h));
float t = -b - sqrt(max(0.0, h));
return (t < 0.0) ? 1.0 : smoothstep(0.0, 1.0, k * d / t);
}
float fresnel(Ray ir, Hit hit, float eta) {
float r0 = (1.0 - eta) / (1.0 + eta);
r0 *= r0;
float cos_t = dot(-ir.rd, hit.n);
cos_t = clamp(cos_t, 0.0, 1.0);
return r0 + (1.0 - r0) * pow(1.0 - cos_t, 5.0);
}
float hash21(vec2 p) {
vec3 p3 = fract(vec3(p.xyx) * 0.1031);
p3 += dot(p3, p3.yzx + 33.33);
return fract((p3.x + p3.y) * p3.z);
}
vec2 hash22(vec2 p) {
vec3 p3 = fract(vec3(p.xyx) * vec3(0.1031, 0.1030, 0.0973));
p3 += dot(p3, p3.yzx + 33.33);
return fract((p3.xx + p3.yz) * p3.zy);
}
float hash31(vec3 p) {
p = fract(p * 0.1031);
p += dot(p, p.zyx + 31.32);
return fract((p.x + p.y) * p.z);
}
vec2 hash12(float p) {
vec3 p3 = fract(vec3(p) * vec3(0.1031, 0.1030, 0.0973));
p3 += dot(p3, p3.yzx + 33.33);
return fract((p3.xx + p3.yz) * p3.zy);
}
vec3 sphereRND(vec3 p) {
float f = hash31(p);
float theta = TAU * hash21(vec2(f * 0.3482, f * 2.18622));
float phi = acos(1.0 - 2.0 * hash21(vec2(f * 1.9013, f * 0.94312)));
float x = sin(phi) * cos(theta);
float y = sin(phi) * sin(theta);
float z = cos(phi);
return vec3(x,y,z);
}
vec4 spheres[] = vec4[](
vec4(vec3(0.0), 0.7),
vec4(vec3(0.0), 0.7),
vec4(vec3(0.0), 0.7),
vec4(vec3(0.0), 0.7),
vec4(vec3(0.0), 0.7),
vec4(vec3(0.0), 0.7)
);
// materials[id] is the material of the object with this id
const Material materials[] = Material[](
Material(vec3(0.7, 0.7, 0.7), true, 0.50, false, 0.0, vec3(1.0, 2.0, 10.0)),
Material(vec3(0.5, 0.4, 0.3), false, 0.00, false, 0.0, vec3(1.0, 10.0, 10.0)),
Material(vec3(1.0, 1.0, 1.0), false, 0.00, true, 1.4, vec3(0.0, 1.0, 100.0)),
Material(vec3(0.4, 0.7, 0.3), true, 0.50, false, 0.0, vec3(1.0, 0.5, 50.0)),
Material(vec3(0.8, 0.8, 0.8), true, 0.20, false, 0.0, vec3(1.0, 2.0, 72.0)),
Material(vec3(0.3, 0.2, 0.9), false, 0.00, false, 0.0, vec3(1.0, 0.0, 0.0)),
Material(vec3(0.9, 0.3, 0.2), true, 0.05, false, 0.0, vec3(1.0, 20.0, 1000.0))
);
void closest(float test_t, int test_id, inout float t, inout int id) {
if (test_t > 0.0 && test_t < t) {
t = test_t;
id = test_id;
}
}
bool sceneIntersect(Ray ray, inout Hit hit) {
float angle = TAU / float(6);
float t = 10000.0;
int id = -1;
vec3 groundNormal = vec3(0.0, 1.0, 0.0);
closest(planeIntersect(ray, groundNormal), 0, t, id);
for (int i = 0; i < 6; i++) {
float bounce = fract(mousedrag.w * 0.8 + sin(float(i) / float(6) - 0.25)) - 0.5;
spheres[i].xyz = vec3(
cos(float(-i) * angle) * 2.0,
spheres[i].w + 3.0 * (0.25 - bounce * bounce),
sin(float(-i) * angle) * 2.0
);
vec4 sph = spheres[i];
closest(sphereIntersect(ray, sph), i + 1, t, id);
}
vec3 p = ray.ro + ray.rd * t;
vec3 n = groundNormal;
if (id > 0) {
n = sphereNormal(p, spheres[id - 1]);
}
hit = Hit(id, p, n, t);
return id >= 0;
}
vec3 background(Ray ray) {
vec3 ca = vec3(0.95, 0.65, 0.55);
vec3 cb = vec3(0.3, 0.6, 1.0);
float f = dot(ray.rd, vec3(0.0, 1.0, 0.0));
f = max(0.0, f);
f = pow(f, 0.3);
vec3 col = mix(ca, cb, f);
f = dot(ray.rd, sunDir);
f = max(0.0, f);
float k = smoothstep(0.9, 0.999, f * f);
col += vec3(1.0, 0.8, 0.4) * k * 0.1;
col += step(0.9997, f);
return clamp(col, 0.0, 1.0);
}
float shadow(Hit hit) {
Ray ray = Ray(hit.p + hit.n * 0.001, sunDir);
float s = 1.0;
for (int i = 0; i < 6; i++) {
s *= sphereShadow(ray.ro, ray.rd, spheres[i], 12.0);
}
return s;
}
vec3 directLighting(Ray ir, Hit hit, Material mat, vec3 surfaceColor) {
float kd = mat.phong.x;
float ks = mat.phong.y;
float sh = mat.phong.z;
vec3 v = normalize(ir.ro - hit.p);
vec3 r = reflect(-sunDir, hit.n);
vec3 h = normalize(sunDir + v);
float diff = kd * max(0.0, dot(sunDir, hit.n));
float spec = ks * pow(max(0.0, dot(hit.n, h)), sh);
float cos_t = dot(h, sunDir);
vec3 r0 = mat.is_metal ? surfaceColor : vec3(0.1);
vec3 fre = r0 + (1.0 - r0) * pow(abs(clamp(1.0 - cos_t, 0.0, 1.0)), 5.0);
float shad = shadow(hit);
vec3 col = vec3(0.0);
if (!mat.is_metal) { col += surfaceColor * sunCol * diff * shad; }
col += sunCol * diff * spec * shad * fre;
return clamp(col, 0.0, 1.0);
}
Ray lambertScatter(Ray ir, Hit hit, Material mat) {
vec3 ro, rd;
rd = hit.n + sphereRND(hit.p + time);
if (dot(rd, rd) < 0.001) { rd = hit.n; }
rd = normalize(rd);
ro = hit.p + hit.n * 0.001;
return Ray(ro, rd);
}
Ray metalScatter(Ray ir, Hit hit, Material mat) {
vec3 rd = reflect(ir.rd, hit.n);
vec3 f = mat.fuzz * sphereRND(hit.p + time);
rd = normalize(rd + f);
vec3 ro = hit.p + hit.n * 0.001;
return Ray(ro, rd);
}
Ray glassScatter(Ray ir, Hit hit, Material mat) {
vec3 rd, ro;
float eta = mat.refID;
if (dot(ir.rd, hit.n) < 0.0) {
eta = 1.0 / eta;
if (fresnel(ir, hit, eta) > hash31(hit.p + time)) {
rd = reflect(ir.rd, hit.n);
ro = hit.p + hit.n * 0.001;
} else {
rd = refract(ir.rd, hit.n, eta);
ro = hit.p - hit.n * 0.001;
}
} else { // inside the sphere
rd = refract(ir.rd, -hit.n, eta);
ro = hit.p + hit.n * 0.001;
}
return Ray(ro, rd);
}
float hash(vec2 uv) {
return fract(sin(dot(uv.xy, vec2(12.9898, 78.233))) * 43758.5453123);
}
vec2 truchetPattern(vec2 uv, float index) {
index = fract(((index - 0.5) * 2.0));
if (index > 0.75) { uv = vec2(1.0) - uv; }
else if (index > 0.50) { uv = vec2(1.0 - uv.x, uv.y); }
else if (index > 0.25) { uv = 1.0 - vec2(1.0 - uv.x, uv.y); }
return uv;
}
float curvedTruchet(vec2 uv) {
vec2 ouv = uv;
uv *= 1.0;
vec2 ipos = floor(uv);
vec2 fpos = fract(uv);
vec2 tile = truchetPattern(fpos, hash(ipos));
float a = (
(step(length(tile), 0.6) - step(length(tile), 0.4)) +
(step(length(tile - vec2(1.0)), 0.6) - step(length(tile - vec2(1.0)), 0.4))
);
float curves = a / length(ouv * 0.8) * 0.5;
return clamp(curves, 0.0, 1.0);
}
vec3 surfaceTexture(Hit hit) {
if (hit.id == 0) { // Checker texture
vec2 uv = hit.p.xz;
float ct = curvedTruchet(uv);
return mix(vec3(0.1, 0.1, 0.3), vec3(1.0), ct);
}
return vec3(1.0);
}
vec3 render(Ray ray){ // color of one ray sample
const int depth = 12; // number of rays
vec3 colorMask = vec3(1.0);
vec3 accumulatedColor = vec3(0.0);
for (int i = 0; i < depth; i++) {
Hit hit;
if (sceneIntersect(ray, hit)) {
Material mat = materials[hit.id];
vec3 surfaceColor = mat.albedo * surfaceTexture(hit);
accumulatedColor += directLighting(ray, hit, mat, surfaceColor * colorMask);
colorMask *= surfaceColor;
if (mat.is_metal) {
ray = metalScatter(ray, hit, mat);
} else if (mat.isGlass) {
ray = glassScatter(ray, hit, mat);
} else {
ray = lambertScatter(ray, hit, mat);
}
} else {
accumulatedColor += colorMask * background(ray);
break;
}
}
return accumulatedColor;
}
vec3 getCameraPosition(float mx, float my) {
float acc = 0.0;
const float camHeight = 0.5;
const float camDistanceRadius = 6.5;
float camPosX = sin(mx * PI * 2.0) * camDistanceRadius;
float camPosY = camHeight + (my * 0.5 + 0.5) * 5.0;
float camPosZ = cos(mx * PI * 2.0) * camDistanceRadius;
vec3 ro = vec3(camPosX, camPosY, camPosZ);
return ro;
}
mat3 calcLookAtMatrix(vec3 origin, vec3 target, float roll) {
vec3 rr = vec3(sin(roll), cos(roll), 0.0);
vec3 ww = normalize(target - origin);
vec3 uu = normalize(cross(ww, rr));
vec3 vv = normalize(cross(uu, ww));
return mat3(uu, vv, ww);
}
vec3 getCameraTarget(vec2 uv, vec3 ro, float aaSamples) {
vec3 col = vec3(0.0);
vec3 camTarget = vec3(0.0, 0.5, 0.0);
float camRoll = 0.0; float fov = 1.25;
mat3 camMatrix = calcLookAtMatrix(ro, camTarget, camRoll);
for (float i = 0.0; i < aaSamples; i++) {
vec2 rnd = (1.0 / resolution.x) * (-1.0 + 2.0 * hash12(i + time));
vec3 rd = normalize(camMatrix * vec3(uv + rnd, fov));
Ray ray = Ray(ro, rd);
col += render(ray);
}
col /= aaSamples;
return col;
}
void main(void) {
vec2 uv = gl_FragCoord.xy / resolution.xy;
vec4 tex = texture(prgm1Texture, uv);
if (mousedrag.z == 1.0) { tex *= 0.0; }
uv -= 0.5;
uv.x *= resolution.x / resolution.y;
vec3 ro = getCameraPosition(mousedrag.x, mousedrag.y);
vec3 col = vec3(0.0);
col += getCameraTarget(uv, ro, 8.0);
col = clamp(col, 0.0, 1.0);
tex += vec4(col, 1.0);
fragColor = tex;
}