diff --git a/pathtracing/reverse/README.md b/pathtracing/reverse/README.md new file mode 100644 index 0000000..45a8302 --- /dev/null +++ b/pathtracing/reverse/README.md @@ -0,0 +1,7 @@ +# Reverse Path Tracing + +

+ +

+ +SDF based ray marching reverse path tracer. Capable of direct and indirect diffuse, specular and refractive ray bounces. \ No newline at end of file diff --git a/pathtracing/reverse/buffer-a-glsl b/pathtracing/reverse/buffer-a-glsl new file mode 100644 index 0000000..b2e8220 --- /dev/null +++ b/pathtracing/reverse/buffer-a-glsl @@ -0,0 +1,215 @@ + +const int DEPTH = 32; +const int AA = 8; + +const vec2 epsilon = vec2(1e-3, 0); + +const float planeNear = 1e-3; +const float planeFar = 1e2; +const float fov = 82.0; + +const float PI = 3.141592653589793; + +const float pdf = 2.0 * PI; + +#define UNION(a, b) ((a.x) < (b.x) ? (a) : (b)) +#define SDF vec2 + +#define MATERIAL_WHITE 0 +#define MATERIAL_RED 1 +#define MATERIAL_GREEN 2 +#define MATERIAL_BLUE 3 +#define MATERIAL_MIRROR 10 +#define MATERIAL_GLASS 20 +#define MATERIAL_EMISSION 255 + +SDF sdfScene(in vec3 pos) +{ + vec3 q = abs(pos - vec3(0,0.99,-1)) - vec3(0.5, 0.01, 0.5); + SDF li0 = SDF(length(max(q, 0.0)) + min(max(q.x, max(q.y, q.z)), 0.0), MATERIAL_EMISSION); + + SDF sp1 = SDF(length(pos + vec3( 0.5, 0.5, 1)) - 0.5, MATERIAL_WHITE); + SDF sp2 = SDF(abs(length(pos + vec3(-0.5, 0.75, 1)) - 0.25) - epsilon[0], MATERIAL_GLASS); + SDF sp3 = SDF(length(pos + vec3( 0.5,-0.25, 1)) - 0.25, MATERIAL_MIRROR); + + SDF pl0 = SDF( pos.y + 1.0, MATERIAL_WHITE); + SDF pl1 = SDF(-pos.y + 1.0, MATERIAL_WHITE); + SDF pl2 = SDF( pos.x + 1.0, MATERIAL_RED); + SDF pl3 = SDF(-pos.x + 1.0, MATERIAL_GREEN); + SDF pl4 = SDF(-pos.z, MATERIAL_BLUE); + SDF pl5 = SDF( pos.z + 4.1, MATERIAL_WHITE); + + SDF box = UNION(pl0, pl1); + box = UNION(box, pl2); + box = UNION(box, pl3); + box = UNION(box, pl4); + box = UNION(box, pl5); + + box = UNION(box, sp1); + box = UNION(box, sp2); + box = UNION(box, sp3); + + return UNION(li0, box); +} + +vec3 sceneNormal(in vec3 pos) +{ + return normalize(sdfScene(pos)[0] - vec3( + sdfScene(pos + epsilon.xyy)[0], + sdfScene(pos + epsilon.yxy)[0], + sdfScene(pos + epsilon.yyx)[0] + )); +} + +// project vector b onto vector a +vec3 project(in vec3 a, in vec3 b) +{ + return a * (dot(a, b) / dot(a, a)); +} + +// construct a 3D coordinate system with the input up being the "upwards" facing vector +// which will be directly stored in w. +// Mathematically this function will create two non linear vectors of up and generate an orthonormal +// basis using gram-schmidt. +// This function assumes "up" being already normalized +void construct_orthonormal_basis(in vec3 up, out vec3 u, out vec3 v, out vec3 w) +{ + w = up; + + vec3 n2 = normalize(cross(w, vec3(0, 1.0, 1.0))); // build perpendicular vector from w + vec3 n3 = cross(w, n2); // create 2nd vector perpendicular to w and n2 + + // gram schmidt + u = n2 - (project(w, n2)); + v = n3 - project(w, n3) - project(u, n3); +} + +// generate a normalized vector within the bounds of the hemisphere with radius of 1. +// Z-Coordinate will be "upwards". +// radius determines the maximum radius the output vector will have. +// NOTE: shrinkin radius will still result in the output to be normalized +vec3 cosine_weighted_hemisphere() +{ + float u1 = random(); + float u2 = random(); + + float r = sqrt(u1); + float theta = 2.0 * PI * u2; + + float x = r * cos(theta); + float y = r * sin(theta); + + return vec3(x, y, sqrt(1.0 - u1)); +} + +vec3 generate_brdf_ray_direction(in vec3 normal, in vec3 incident) { + vec3 hemisphere = cosine_weighted_hemisphere(); + + vec3 u, v, w; + construct_orthonormal_basis(normal, u, v, w); + + return u * hemisphere.x + v * hemisphere.y + w * hemisphere.z; +} + +vec3 tracePath(in vec3 ro, in vec3 rd) +{ + vec3 col = vec3(0); + vec3 thr = vec3(1); + + for (int k = 0; k < DEPTH; k++) + { + vec3 pos = ro + rd * planeNear; + float t = planeNear; + for (int i = 0; i < 256 && t < planeFar; i++) + { + SDF h = sdfScene(pos); + + if (h[0] < epsilon[0]) + { + vec3 nor = sceneNormal(pos); + + switch(int(h[1])) + { + case MATERIAL_MIRROR: + rd = reflect(rd, nor); + ro = pos - nor * epsilon[0]; + break; + case MATERIAL_GLASS: + if (dot(nor, rd) > random()) + { + rd = refract(rd, -nor, 0.2); + ro = pos + nor * epsilon[0] * 4.0; + } + else + { + rd = reflect(rd, nor); + ro = pos - nor * epsilon[0]; + } + break; + default: + rd = -generate_brdf_ray_direction(nor, rd); + ro = pos - nor * epsilon[0]; + break; + } + + float cosTheta = abs(dot(nor, -rd)) * pdf / PI * 0.6667; + + switch(int(h[1])) + { + case MATERIAL_EMISSION: + col += thr * 10.0; + break; + case MATERIAL_WHITE: + thr *= cosTheta; + break; + case MATERIAL_RED: + thr *= vec3(0.9, 0.1, 0.1) * cosTheta; + break; + case MATERIAL_GREEN: + thr *= vec3(0.1, 0.9, 0.1) * cosTheta; + break; + case MATERIAL_BLUE: + thr *= vec3(0.1, 0.1, 0.9) * cosTheta; + break; + default: + break; + } + break; + } + + t += h[0]; + pos += rd * h[0]; + } + + float m = max(thr.x, max(thr.y, thr.z)); + if (random() > m) + break; + } + return col; +} + +void mainImage(out vec4 fragColor, in vec2 fragCoord) +{ + SEED = (fragCoord.x * fragCoord.y)/iResolution.y + iTime; + + vec3 col = vec3(0); + vec3 ro = vec3(0,0,-3.999); + + // take multiple samples per pixel + // offset each ray direction slightly inside pixel + // in order to anti aliase + for (int i = 0; i < AA; i++, SEED++) + { + vec2 off = vec2(random(), random()) * 0.5; + vec2 uv = (fragCoord + off -0.5 * iResolution.xy) / iResolution.y; + vec3 rd = normalize(vec3(uv, 1.0/tan(fov * 0.008726646259971648))); + + col += tracePath(ro, rd); + } + col /= float(AA); + + vec3 prev = texture(iChannel0, fragCoord.xy/iResolution.xy).rgb; + col = mix(prev, col, 1.0/max(float(iFrame), 1.0)); + + fragColor = vec4(col, 1.0); +} \ No newline at end of file diff --git a/pathtracing/reverse/common.glsl b/pathtracing/reverse/common.glsl new file mode 100644 index 0000000..dd1b1ea --- /dev/null +++ b/pathtracing/reverse/common.glsl @@ -0,0 +1,8 @@ + +float SEED = 0.0; + +float random() +{ + SEED ++; + return fract(cos(SEED) * 2474.0); +} \ No newline at end of file diff --git a/pathtracing/reverse/image.glsl b/pathtracing/reverse/image.glsl new file mode 100644 index 0000000..e180b4a --- /dev/null +++ b/pathtracing/reverse/image.glsl @@ -0,0 +1,30 @@ +float luminance(vec3 v) +{ + return dot(v, vec3(0.2126f, 0.7152f, 0.0722f)); +} + +vec3 reinhard_jodie(vec3 v) +{ + float l = luminance(v); + vec3 tv = v / (1.0f + v); + return mix(v / (1.0f + l), tv, tv); +} + +float tentKernel(in vec2 xy, in float r) +{ + return (1.0 - abs(xy.x/r)) * (1.0 - abs(xy.y/r)) / (r*r); +} + +float gaussianWeight(in float delta, in float sigma) +{ + return exp(-delta*delta/(2.0 * sigma * sigma)) * 2.5 * sigma; +} + +void mainImage(out vec4 fragColor, in vec2 fragCoord) +{ + vec2 uv = fragCoord.xy/iResolution.xy; + + vec3 color = texture(iChannel0, uv).rgb; + + fragColor = vec4(reinhard_jodie(color), 1); +} \ No newline at end of file diff --git a/pathtracing/reverse/overview.png b/pathtracing/reverse/overview.png new file mode 100644 index 0000000..0f5ff72 Binary files /dev/null and b/pathtracing/reverse/overview.png differ diff --git a/pathtracing/reverse/src/buffer-a-glsl b/pathtracing/reverse/src/buffer-a-glsl new file mode 100644 index 0000000..b2e8220 --- /dev/null +++ b/pathtracing/reverse/src/buffer-a-glsl @@ -0,0 +1,215 @@ + +const int DEPTH = 32; +const int AA = 8; + +const vec2 epsilon = vec2(1e-3, 0); + +const float planeNear = 1e-3; +const float planeFar = 1e2; +const float fov = 82.0; + +const float PI = 3.141592653589793; + +const float pdf = 2.0 * PI; + +#define UNION(a, b) ((a.x) < (b.x) ? (a) : (b)) +#define SDF vec2 + +#define MATERIAL_WHITE 0 +#define MATERIAL_RED 1 +#define MATERIAL_GREEN 2 +#define MATERIAL_BLUE 3 +#define MATERIAL_MIRROR 10 +#define MATERIAL_GLASS 20 +#define MATERIAL_EMISSION 255 + +SDF sdfScene(in vec3 pos) +{ + vec3 q = abs(pos - vec3(0,0.99,-1)) - vec3(0.5, 0.01, 0.5); + SDF li0 = SDF(length(max(q, 0.0)) + min(max(q.x, max(q.y, q.z)), 0.0), MATERIAL_EMISSION); + + SDF sp1 = SDF(length(pos + vec3( 0.5, 0.5, 1)) - 0.5, MATERIAL_WHITE); + SDF sp2 = SDF(abs(length(pos + vec3(-0.5, 0.75, 1)) - 0.25) - epsilon[0], MATERIAL_GLASS); + SDF sp3 = SDF(length(pos + vec3( 0.5,-0.25, 1)) - 0.25, MATERIAL_MIRROR); + + SDF pl0 = SDF( pos.y + 1.0, MATERIAL_WHITE); + SDF pl1 = SDF(-pos.y + 1.0, MATERIAL_WHITE); + SDF pl2 = SDF( pos.x + 1.0, MATERIAL_RED); + SDF pl3 = SDF(-pos.x + 1.0, MATERIAL_GREEN); + SDF pl4 = SDF(-pos.z, MATERIAL_BLUE); + SDF pl5 = SDF( pos.z + 4.1, MATERIAL_WHITE); + + SDF box = UNION(pl0, pl1); + box = UNION(box, pl2); + box = UNION(box, pl3); + box = UNION(box, pl4); + box = UNION(box, pl5); + + box = UNION(box, sp1); + box = UNION(box, sp2); + box = UNION(box, sp3); + + return UNION(li0, box); +} + +vec3 sceneNormal(in vec3 pos) +{ + return normalize(sdfScene(pos)[0] - vec3( + sdfScene(pos + epsilon.xyy)[0], + sdfScene(pos + epsilon.yxy)[0], + sdfScene(pos + epsilon.yyx)[0] + )); +} + +// project vector b onto vector a +vec3 project(in vec3 a, in vec3 b) +{ + return a * (dot(a, b) / dot(a, a)); +} + +// construct a 3D coordinate system with the input up being the "upwards" facing vector +// which will be directly stored in w. +// Mathematically this function will create two non linear vectors of up and generate an orthonormal +// basis using gram-schmidt. +// This function assumes "up" being already normalized +void construct_orthonormal_basis(in vec3 up, out vec3 u, out vec3 v, out vec3 w) +{ + w = up; + + vec3 n2 = normalize(cross(w, vec3(0, 1.0, 1.0))); // build perpendicular vector from w + vec3 n3 = cross(w, n2); // create 2nd vector perpendicular to w and n2 + + // gram schmidt + u = n2 - (project(w, n2)); + v = n3 - project(w, n3) - project(u, n3); +} + +// generate a normalized vector within the bounds of the hemisphere with radius of 1. +// Z-Coordinate will be "upwards". +// radius determines the maximum radius the output vector will have. +// NOTE: shrinkin radius will still result in the output to be normalized +vec3 cosine_weighted_hemisphere() +{ + float u1 = random(); + float u2 = random(); + + float r = sqrt(u1); + float theta = 2.0 * PI * u2; + + float x = r * cos(theta); + float y = r * sin(theta); + + return vec3(x, y, sqrt(1.0 - u1)); +} + +vec3 generate_brdf_ray_direction(in vec3 normal, in vec3 incident) { + vec3 hemisphere = cosine_weighted_hemisphere(); + + vec3 u, v, w; + construct_orthonormal_basis(normal, u, v, w); + + return u * hemisphere.x + v * hemisphere.y + w * hemisphere.z; +} + +vec3 tracePath(in vec3 ro, in vec3 rd) +{ + vec3 col = vec3(0); + vec3 thr = vec3(1); + + for (int k = 0; k < DEPTH; k++) + { + vec3 pos = ro + rd * planeNear; + float t = planeNear; + for (int i = 0; i < 256 && t < planeFar; i++) + { + SDF h = sdfScene(pos); + + if (h[0] < epsilon[0]) + { + vec3 nor = sceneNormal(pos); + + switch(int(h[1])) + { + case MATERIAL_MIRROR: + rd = reflect(rd, nor); + ro = pos - nor * epsilon[0]; + break; + case MATERIAL_GLASS: + if (dot(nor, rd) > random()) + { + rd = refract(rd, -nor, 0.2); + ro = pos + nor * epsilon[0] * 4.0; + } + else + { + rd = reflect(rd, nor); + ro = pos - nor * epsilon[0]; + } + break; + default: + rd = -generate_brdf_ray_direction(nor, rd); + ro = pos - nor * epsilon[0]; + break; + } + + float cosTheta = abs(dot(nor, -rd)) * pdf / PI * 0.6667; + + switch(int(h[1])) + { + case MATERIAL_EMISSION: + col += thr * 10.0; + break; + case MATERIAL_WHITE: + thr *= cosTheta; + break; + case MATERIAL_RED: + thr *= vec3(0.9, 0.1, 0.1) * cosTheta; + break; + case MATERIAL_GREEN: + thr *= vec3(0.1, 0.9, 0.1) * cosTheta; + break; + case MATERIAL_BLUE: + thr *= vec3(0.1, 0.1, 0.9) * cosTheta; + break; + default: + break; + } + break; + } + + t += h[0]; + pos += rd * h[0]; + } + + float m = max(thr.x, max(thr.y, thr.z)); + if (random() > m) + break; + } + return col; +} + +void mainImage(out vec4 fragColor, in vec2 fragCoord) +{ + SEED = (fragCoord.x * fragCoord.y)/iResolution.y + iTime; + + vec3 col = vec3(0); + vec3 ro = vec3(0,0,-3.999); + + // take multiple samples per pixel + // offset each ray direction slightly inside pixel + // in order to anti aliase + for (int i = 0; i < AA; i++, SEED++) + { + vec2 off = vec2(random(), random()) * 0.5; + vec2 uv = (fragCoord + off -0.5 * iResolution.xy) / iResolution.y; + vec3 rd = normalize(vec3(uv, 1.0/tan(fov * 0.008726646259971648))); + + col += tracePath(ro, rd); + } + col /= float(AA); + + vec3 prev = texture(iChannel0, fragCoord.xy/iResolution.xy).rgb; + col = mix(prev, col, 1.0/max(float(iFrame), 1.0)); + + fragColor = vec4(col, 1.0); +} \ No newline at end of file diff --git a/pathtracing/reverse/src/common.glsl b/pathtracing/reverse/src/common.glsl new file mode 100644 index 0000000..dd1b1ea --- /dev/null +++ b/pathtracing/reverse/src/common.glsl @@ -0,0 +1,8 @@ + +float SEED = 0.0; + +float random() +{ + SEED ++; + return fract(cos(SEED) * 2474.0); +} \ No newline at end of file diff --git a/pathtracing/reverse/src/image.glsl b/pathtracing/reverse/src/image.glsl new file mode 100644 index 0000000..e180b4a --- /dev/null +++ b/pathtracing/reverse/src/image.glsl @@ -0,0 +1,30 @@ +float luminance(vec3 v) +{ + return dot(v, vec3(0.2126f, 0.7152f, 0.0722f)); +} + +vec3 reinhard_jodie(vec3 v) +{ + float l = luminance(v); + vec3 tv = v / (1.0f + v); + return mix(v / (1.0f + l), tv, tv); +} + +float tentKernel(in vec2 xy, in float r) +{ + return (1.0 - abs(xy.x/r)) * (1.0 - abs(xy.y/r)) / (r*r); +} + +float gaussianWeight(in float delta, in float sigma) +{ + return exp(-delta*delta/(2.0 * sigma * sigma)) * 2.5 * sigma; +} + +void mainImage(out vec4 fragColor, in vec2 fragCoord) +{ + vec2 uv = fragCoord.xy/iResolution.xy; + + vec3 color = texture(iChannel0, uv).rgb; + + fragColor = vec4(reinhard_jodie(color), 1); +} \ No newline at end of file