diff --git a/README.md b/README.md index 0d3c0fd..de2d90a 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,8 @@ The Shadertoy demo includes a large set of debug flags, where you can turn on: * `OVERRELAX/SPEHRETRACE/NAIVE`: for different raymarching types * `DISTANCE/NORMAL/ITERS`: for different debug displays +Check out the Shadertoy demo: there's animation! + [![](img/thumb.png)][shadertoy] [shadertoy]: https://www.shadertoy.com/view/XISSRc @@ -118,7 +120,9 @@ that location. For this raymarch we must use a naive method (with fixed step size), since the function does not compute a distance from a point on the ray to the terrain (it -only defines the terrain height at a given point). +only defines the terrain height at a given point). That makes this technique +very, very slow -- especially when the other two raymarches (initial object and +soft shadow) can be optimized with sphere tracing. #### Sphere Overrelaxation @@ -127,13 +131,19 @@ Reference: McQuire ["Implicitly Defined Surfaces"][mcguire], Keinert ["Enhanced The number of ray march iterations displayed as grayscale, with darker areas indicating fewer iterations before the surface is considered intersected. -![](img/iters_spheretracing.png) +(This method is not currently working...) -![](img/iters_overrelax.png) +### Performance -(This method is not currently working.) +Performance data was taken from the following four scenes: -### Performance +![](img/scenes-all4.png) + +Data was taken with [stats.js][statsjs]. Unfortunately it was hard to get a good +reading by using the ms timer in statsjs, so I mostly relied on taking the FPS +counter and calculating millisecond timing by 1000/FPS. + + [statsjs]: https://github.com/mrdoob/stats.js/ Render times for different scenes grouped by raymarch type: @@ -159,16 +169,21 @@ Main takeaways: light. * stats.js wasn't great for finding timing data: all of the shadow-only timings - are actually bound by the 60fps cap. + are actually bound by the 60fps cap. I intended to also take + no-shadow/no-terrain data, but given this finding it would not have been very + useful. * There sphere scene was similar to the close scene in composition, but had only one object rather than many. The difference in time between those two are likely due ot repeated distance function computations. +Another comparison I should have done would be a box and a menger sponge of the +same size. + Since there's no use in directly comparing sphere-traced and naive times, this is a graph that scales render times by the full version of that raymarch type: -![](img/perf_scaled.png) +![](img/perf_scaled_times.png) ### Bonus images @@ -180,6 +195,11 @@ Normals for each geometry, computed with the equation given in McGuire (8). ![](img/debug_normals.png) +Some geometry with iteration displayed as color (white indicates more raymarches +before intersection). + +![](img/iters_spheretracing.png) + Naive ray marching with a step size of 0.1 (too large), got wireframes instead of solid shapes: diff --git a/img/scenes-all4.png b/img/scenes-all4.png new file mode 100644 index 0000000..e5e3d0a Binary files /dev/null and b/img/scenes-all4.png differ diff --git a/img/thumb.png b/img/thumb.png index 09f6e0e..6b6bb86 100644 Binary files a/img/thumb.png and b/img/thumb.png differ diff --git a/raymarch.glsl b/raymarch.glsl index 1de46bc..6a6ed27 100644 --- a/raymarch.glsl +++ b/raymarch.glsl @@ -1,14 +1,15 @@ -/********************************** Constants *********************************/ +/********************************** Constants *********************************/ const vec3 light = vec3(2.0, 5.0, -1.0); const vec3 color = vec3(0.85, 0.85, 0.85); const vec3 ambient = vec3(0.05, 0.05, 0.05); -const vec3 background = vec3(0.2, 0.5, 0.5); -const vec3 ground = vec3(0.2, 0.8, 0.8); +const vec3 background = vec3(0.3, 0.5, 0.5); +const vec3 ground = vec3(0.8, 0.8, 0.2); const vec3 scene2cam = vec3(.35, 2.5, -2); //const vec3 scene2cam = vec3(2.5, 2.2, -.8); const float EPSILON = 0.01; const float TMIN = 0.01; +const float TMAX = 20.0; // Debug flags #define OVERRELAX 0 @@ -19,13 +20,47 @@ const float TMIN = 0.01; #define NORMAL 0 #define ITERS 0 -#define SHADOW 0 +#define SHADOW 1 + +#define PLANE 0 +#define HEIGHTMAP 1 #define FIXEDCAM 1 #define SCENE1 0 #define SCENE2 1 +/***************************** Geometry Combinators *************************** + * McGuire 11: http://graphics.cs.williams.edu/courses/cs371/f14/reading/implicit.pdf + */ + +// set union of two geometry +float setunion(float d1, float d2) { + return min(d1, d2); +} + +// intersection of two geometry +float intersect(float d1, float d2) { + return max(d1, d2); +} + +// set subtraction of d2 from d1 +float subtract(float d1, float d2) { + return max(d1, -d2); +} + +// transformation OF P by applying the inverse transform +vec3 tr(vec3 p, vec3 translate) { + return p - translate; +} + +// transformation OF P by magic rotatiion tricks +vec3 rot(vec3 p, float x) { + float t = iGlobalTime/3.; + vec3 offset = x * vec3(cos(t), 0, sin(t)) + vec3(0,1,0); + return tr(p, offset); +} + /*************************** Signed distance functions *********************** * McGuire: http://graphics.cs.williams.edu/courses/cs371/f14/reading/implicit.pdf * iq: http://www.iquilezles.org/www/articles/distfunctions/distfunctions.htm @@ -49,6 +84,28 @@ float sdBox(vec3 p, vec3 b) { return min(dmax, 0.0) + length(max(d, vec3(0, 0, 0))); } +// http://www.iquilezles.org/www/articles/menger/menger.htm +float sdSponge(in vec3 p, float scale) { + p = p * scale; + float t = sdBox(p, vec3(1.)); + + float s = 1.0; + for( int m=0; m<3; m++ ) + { + vec3 a = mod( p*s, 2.0 )-1.0; + s *= 3.0; + vec3 r = abs(1.0 - 3.0*abs(a)); + + float da = max(r.x,r.y); + float db = max(r.y,r.z); + float dc = max(r.z,r.x); + float c = (min(da,min(db,dc))-1.0)/s; + + t = max(t,c); + } + return t/scale; +} + float sdRoundedBox(vec3 p, vec3 b, float r) { return length(max(abs(p) - b, vec3(0, 0, 0))) - r; } @@ -81,30 +138,6 @@ float sdTorus88(vec3 p, vec2 t) { return length8(q)-t.y; } -/***************************** Geometry Combinators *************************** - * McGuire 11: http://graphics.cs.williams.edu/courses/cs371/f14/reading/implicit.pdf - */ - -// set union of two geometry -float setunion(float d1, float d2) { - return min(d1, d2); -} - -// intersection of two geometry -float intersect(float d1, float d2) { - return max(d1, d2); -} - -// set subtraction of d2 from d1 -float subtract(float d1, float d2) { - return max(d1, -d2); -} - -// transformation OF P by applying the inverse transform -vec3 tr(vec3 p, vec3 translate) { - return p - translate; -} - /********************************** Raymarch ********************************** * McGuire: http://graphics.cs.williams.edu/courses/cs371/f14/reading/implicit.pdf */ @@ -112,34 +145,41 @@ vec3 tr(vec3 p, vec3 translate) { float nearestIntersection(in vec3 p) { #if SCENE1 float t = sdSphere(p, 0.5); - //t = setunion(t, sdPlane (tr(p, vec3(-2.0)), vec3(.0, 1.0, .0)) ); + #if PLANE + t = setunion(t, sdPlane (tr(p, vec3(-2.0)), vec3(.0, 1.0, .0)) ); + #endif t = setunion(t, sdTorus (tr(p, vec3(-1.3,.8,-1.3)), .4, .15) ); t = setunion(t, sdTorus88 (tr(p, vec3(.0,.0,-1.5)), vec2(.35,.2)) ); t = setunion(t, sdCapsule (tr(p, vec3(.6, .0, .6)), vec3(1.0), vec3(.5), .2) ); t = setunion(t, sdRoundedBox(tr(p, vec3(-1.0, 1.0, .0)), vec3(.2), .1) ); t = setunion(t, sdCylinder (tr(p, vec3(1.2,.0,.0)), .5, .2) ); t = setunion(t, sdTriPrism (tr(p, vec3(.0,.0,1.5)), vec2(.4, .4)) ); + t = setunion(t, sdSponge (tr(p, vec3(-.45,1, .85)), 4.0)); #endif + #if SCENE2 float t = sdCylinder(tr(p, vec3(-.1, .25, 0)), 1.5, .25); - //t = setunion(t, sdPlane (tr(p, vec3(-2.0)), vec3(.0, 1.0, .0)) ); - t = subtract(t, sdTriPrism (tr(p, vec3(-.1,1,0)), vec2(1., 1.)) ); - - t = setunion(t, sdSphere (tr(p, vec3(1,1,0)), .2) ); - t = setunion(t, sdTorus (tr(p, vec3(.4, 1,0)), .15, .1) ); - t = setunion(t, sdTorus88 (tr(p, vec3(-.2, 1,0)), vec2(.16, .06)) ); - t = setunion(t, sdRoundedBox(tr(p, vec3(-.75,1,0)), vec3(.08), .06) ); - t = setunion(t, sdBox (tr(p, vec3(-1.15, 1,0)), vec3(.12)) ); + t = subtract(t, sdTriPrism (tr(p, vec3(-.1,1,0)), vec2(1., 1.)) ); + + #if PLANE + t = setunion(t, sdPlane (tr(p, vec3(-2.0)), vec3(.0, 1.0, .0)) ); + #endif + + t = setunion(t, sdSphere (rot(p, 1.4 ), .2) ); + t = setunion(t, sdTorus (rot(p, .7 ), .15, .1) ); + t = setunion(t, sdTorus88 (rot(p, .1 ), vec2(.16, .06)) ); + t = setunion(t, sdRoundedBox(rot(p, -.4 ), vec3(.08), .06) ); + t = setunion(t, sdBox (rot(p, -.85 ), vec3(.12)) ); + t = setunion(t, sdSponge (rot(p, -1.35), 8.0) ); #endif return t; } // Naive iteration vec2 raymarch_naive(in vec3 ro, in vec3 rd) { - const float tmax = 30.0; const float tstep = 0.01; int iters = 0; - for (float t = 0.1; t < tmax; t += tstep) { + for (float t = 0.1; t < TMAX; t += tstep) { iters++; vec3 p = ro + rd * t; float intersection = nearestIntersection(p); @@ -152,7 +192,6 @@ vec2 raymarch_naive(in vec3 ro, in vec3 rd) { // Sphere tracing vec2 raymarch_sphere(in vec3 ro, in vec3 rd) { - const float tmax = 30.0; float t = 0.0; for (int i = 0; i < 100; i++) { vec3 p = ro + rd * t; @@ -162,7 +201,7 @@ vec2 raymarch_sphere(in vec3 ro, in vec3 rd) { } else if (intersection > 0.0) { t += intersection; } - if (t > tmax) { + if (t > TMAX) { break; } } @@ -173,7 +212,6 @@ vec2 raymarch_sphere(in vec3 ro, in vec3 rd) { // http://erleuchtet.org/~cupe/permanent/enhanced_sphere_tracing.pdf vec2 raymarch_overrelax(in vec3 ro, in vec3 rd) { int iters = 0; - const float tmax = 30.0; float t = 0.1; float dt = 0.0; float extrastep = 0.0; @@ -215,7 +253,7 @@ vec2 raymarch_overrelax(in vec3 ro, in vec3 rd) { } else if (intersection > 0.0) { t += intersection; } - if (t > tmax) { + if (t > TMAX) { return vec2(-1,-1); } } @@ -251,7 +289,6 @@ vec3 lambert(in vec3 p, in vec3 n, in vec3 c) { // http://www.iquilezles.org/www/articles/rmshadows/rmshadows.htm float shadowMarch(in vec3 ro, in vec3 rd) { - const float tmax = 10.0; const float k = 8.0; float shadow = 1.0; @@ -261,12 +298,12 @@ float shadowMarch(in vec3 ro, in vec3 rd) { vec3 p = ro + rd * t; float intersection = nearestIntersection(p); if (intersection > 0.0 && intersection < TMIN) { - return 0.0; + return .1; } else if (intersection > 0.0) { t += intersection; shadow = min(shadow, k*intersection/t); } - if (t > tmax) { + if (t > TMAX) { break; } } @@ -278,7 +315,7 @@ vec3 shadow(in vec3 p, in vec3 color) { float lightdist = distance(p, light); float shadow = shadowMarch(p, lightdir); - return clamp(shadow * color, 0.0, 1.0) + ambient; + return shadow * color + ambient; } // http://www.iquilezles.org/www/articles/terrainmarching/terrainmarching.htm @@ -299,14 +336,13 @@ vec3 heightn(vec3 p) { } vec2 heightmarch(in vec3 ro, in vec3 rd) { - const float tmax = 30.0; const float tstep = 0.01; int iters = 0; - for (float t = 0.1; t < tmax; t += tstep) { + for (float t = 0.1; t < TMAX; t += tstep) { iters++; vec3 rayp = ro + rd * t; float height = heightf(rayp.xz); - + if (rayp.y < height) { return vec2(t, iters); } @@ -318,31 +354,35 @@ vec3 render(in vec3 ro, in vec3 rd) { vec2 ray = raymarch(ro, rd); float d = ray.x; vec3 p = ro + rd*d; - - vec3 basecolor = color; - + + vec3 basecolor = color; + if (d < 0.0) { + #if HEIGHTMAP ray = heightmarch(ro, rd); d = ray.x; if (ray.y < 0.0) { return background; } - + vec3 rayp = ro + rd * d; float y = heightf(p.xz); p = vec3(rayp.x, y, rayp.z); basecolor = background; vec3 n = heightn(p); vec3 c = lambert(p, n, ground); + vec3 orig = c; #if SHADOW c = shadow(p, c); - c = clamp(c, 0.0, 1.0); #endif return c; + #else + return background; + #endif } - + float iters = ray.y; - + #if ITERS return vec3(iters/20.); #endif @@ -370,7 +410,6 @@ vec3 render(in vec3 ro, in vec3 rd) { #if SHADOW c = shadow(p - 3.0*rd*EPSILON, c); - c = clamp(c, 0.0, 1.0); #endif return c;