diff --git a/README.md b/README.md index 02a82bc..325d504 100644 --- a/README.md +++ b/README.md @@ -1,189 +1,38 @@ -# [CIS565 2015F] YOUR TITLE HERE - -**GLSL Ray Marching** +# GLSL Ray Marching **University of Pennsylvania, CIS 565: GPU Programming and Architecture, Project 5** -* (TODO) YOUR NAME HERE -* Tested on: (TODO) **Google Chrome 222.2** on - Windows 22, i7-2222 @ 2.22GHz 22GB, GTX 222 222MB (Moore 2222 Lab) +* Ratchpak (Dome) Pongmongkol +* Tested on: **Google Chrome 46.0.2490.71 (64-bit)** on + OSX El Capitan, 2.4 GHz Intel Core i7, 16 GB 1600 MHz DDR3, NVIDIA GeForce GT 650M 1024 MB (rMBP 15" 2013) -### Live on Shadertoy (TODO) +### Live on Shadertoy -[![](img/thumb.png)](https://www.shadertoy.com/view/TODO) + ### Acknowledgements This Shadertoy uses material from the following resources: -* TODO - -### (TODO: Your README) - - -Instructions (delete me) -======================== - -This is due at midnight on the evening of Monday, October 19. - -**Summary:** In this project, you'll see yet another way in which GPU -parallelism and compute-efficiency can be used to render scenes. -You'll write a program in the popular online shader editor -[Shadertoy](http://www.shadertoy.com/). -Your goal will be to implement and show off different features in a cool and -interesting demo. See Shadertoy for inspiration - and get creative! - -Ray marching is an iterative ray casting method in which objects are -represented as implicit surfaces defined by signed distance functions (SDFs). This -method is widely used in the Shadertoy community to render complex scenes which -are defined in the fragment shader code executed for each pixel. - -**Important Notes:** -* Even though you will be coding in Shadertoy, it is important as always to - save versions of your code so that you do not lose progress! Commit often! -* A significant portion of this project will be in write-up and performance - analysis - don't save it for later. - -**Provided Code:** -The provided code in `raymarch.glsl` is straight from iq's Raymarching -Primitives; see {iq-prim}. It just sets up a simple starter camera. +* http://www.iquilezles.org/www/material/nvscene2008/rwwtt.pdf +* http://www2.compute.dtu.dk/pubdb/views/edoc_download.php/6392/pdf/imm6392.pdf +* https://github.com/dsheets/gloc/blob/master/stdlib/matrix.glsl ### Features - -All features must be visible in your final demo for full credit. - -**Required Features:** - -* Two ray marching methods (comparative analysis required) - * Naive ray marching (fixed step size) {McGuire 4} - * Sphere tracing (step size varies based on signed distance field) {McGuire 6} -* 3 different distance estimators {McGuire 7} {iq-prim} - * With normal computation {McGuire 8} -* One simple lighting computation (e.g. Lambert or Blinn-Phong). -* Union operator {McGuire 11.1} - * Necessary for rendering multiple objects -* Transformation operator {McGuire 11.5} -* Debug views (preferably easily toggleable, e.g. with `#define`/`#if`) - * Distance to surface for each pixel - * Number of ray march iterations used for each pixel - -**Extra Features:** - -You must do at least 10 points worth of extra features. - -* (0.25pt each, up to 1pt) Other basic distance estimators/operations {McGuire 7/11} -* Advanced distance estimators - * (3pts) Height-mapped terrain rendering {iq-terr} - * (3pts) Fractal rendering (e.g. Menger sponge or Mandelbulb {McGuire 13.1}) - * **Note** that these require naive ray marching, if there is no definable - SDF. They may be optimized using bounding spheres (see below). -* Lighting effects - * (3pts) Soft shadowing using secondary rays {iq-prim} {iq-rwwtt p55} - * (3pts) Ambient occlusion (see 565 slides for another reference) {iq-prim} -* Optimizations (comparative analysis required!) - * (3pts) Over-relaxation method of sphere tracing {McGuire 12.1} - * (2pts) Analytical bounding spheres on objects in the scene {McGuire 12.2/12.3} - * (1pts) Analytical infinite planes {McGuire 12.3} - -This extra feature list is not comprehensive. If you have a particular idea -that you would like to implement, please **contact us first** (preferably on -the mailing list). - -## Write-up - -For each feature (required or extra), include a screenshot which clearly -shows that feature in action. Briefly describe the feature and mention which -reference(s) you used. - -### Analysis - -* Provide an analysis comparing naive ray marching with sphere tracing - * In addition to FPS, implement a debug view which shows the "most expensive" - fragments by number of iterations required for each pixel. Compare these. -* Compare time spent ray marching vs. time spent shading/lighting - * This can be done by taking measurements with different parts of your code - enabled (e.g. raymarching, raymarching+shadow, raymarching+shadow+AO). - * Plot this analysis using pie charts or a 100% stacked bar chart. -* For each feature (required or extra), estimate whether branch divergence - plays a role in its performance characteristics, and, if so, point out the - branch in question. - (Like in CUDA, if threads diverge within a warp, performance takes a hit.) -* For each optimization feature, compare performance with and without the - optimization. Describe and demo the types of scenes which benefit from the - optimization. - -**Tips:** - -* To avoid computing frame times given FPS, you can use the - [stats.js bookmarklet](https://github.com/mrdoob/stats.js/#bookmarklet) - to measure frame times in ms. - -### Resources - -You **must** acknowledge any resources you use, including, but not limited to, -the links below. **Do not copy non-trivial code verbatim.** Instead, use the -references to understand the methods. - -For any code/material in the 565 -[slides](http://cis565-fall-2015.github.io/lectures/12-Ray-Marching.pptx), -please reference the source found at the bottom of the slide. - -* {McGuire} - Morgan McGuire, Williams College. - *Numerical Methods for Ray Tracing Implicitly Defined Surfaces* (2014). - [PDF](http://graphics.cs.williams.edu/courses/cs371/f14/reading/implicit.pdf) - * You may credit and use code from this reference. -* {iq-prim} - Iñigo Quílez. - *Raymarching Primitives* (2013). - [Shadertoy](https://www.shadertoy.com/view/Xds3zN) -* {iq-terr} - Iñigo Quílez. - *Terrain Raymarching* (2007). - [Article](http://www.iquilezles.org/www/articles/terrainmarching/terrainmarching.htm) - * You may credit and use code from this reference. -* {iq-rwwtt} - Iñigo Quílez. - *Rendering Worlds with Two Triangles with raytracing on the GPU* (2008). - [Slides](http://www.iquilezles.org/www/material/nvscene2008/rwwtt.pdf) -* {Ashima} - Ashima Arts, Ian McEwan, Stefan Gustavson. - *webgl-noise*. - [GitHub](https://github.com/ashima/webgl-noise) - * You may use this code under the MIT-expat license. - - -## Submit - -### Post on Shadertoy - -Post your shader on Shadertoy (preferably *public*; *draft* will not work). -For your title, come up with your own demo title and use the format -`[CIS565 2015F] YOUR TITLE HERE` (also add this to the top of your README). - -In the Shadertoy description, include the following: - -* A link to your GitHub repository with the Shadertoy code. -* **IMPORTANT:** A copy of the *Acknowledgements* section from above. - * Remember, this is public - strangers will want to know where you got your - material. - -Add a screenshot of your result to `img/thumb.png` -(right click rendering -> Save Image As), and put the link to your -Shadertoy at the top of your README. - -### Pull Request - -**Even though your code is on Shadertoy, make sure it is also on GitHub!** - -1. Open a GitHub pull request so that we can see that you have finished. - The title should be "Submission: YOUR NAME". - * **ADDITIONALLY:** - In the body of the pull request, include a link to your repository. -2. Send an email to the TA (gmail: kainino1+cis565@) with: - * **Subject**: in the form of `[CIS565] Project N: PENNKEY`. - * Direct link to your pull request on GitHub. - * Estimate the amount of time you spent on the project. - * If there were any outstanding problems, or if you did any extra - work, *briefly* explain. - * Feedback on the project itself, if any. +- Soft Shadow +- Ambient Occlusion +- Naive ray marching (fixed step size) & Sphere tracing (step size varies based on signed distance field) + - Over-relaxation method of sphere tracing +- 7 distance estimators. +- Blinn phong lighting +- Union Operator +- Transformation Operator +- Debug Views + +## Performance Analysis +###Fixed step size Vs Sphere tracing + +These images are displayed in debug mode to display the number of iterations, from white (requires less iterations) to black (requires more iterations). It is noticable that with fixed step size (Left), the screen is mostly black-gray, meaning it requires more iteration to reach the object. Meanwhile, even sphere tracing requires less iteration, it is noticable that the algorithm is expensive around the edges of the object. + +#### Over-relaxation method +Unexpectedly, over-relaxation (with k = 1.2) does not speed up the program much (The different is around 2-5 fps, from 30 fps). I suspect that this is due to the branch divergence that is caused by its condition checking. diff --git a/img/Adaptive.png b/img/Adaptive.png new file mode 100644 index 0000000..ab96402 Binary files /dev/null and b/img/Adaptive.png differ diff --git a/img/Fixed.png b/img/Fixed.png new file mode 100644 index 0000000..b1f02c9 Binary files /dev/null and b/img/Fixed.png differ diff --git a/img/src.png b/img/src.png new file mode 100644 index 0000000..8590073 Binary files /dev/null and b/img/src.png differ diff --git a/raymarch.glsl b/raymarch.glsl index aac90c8..d841f89 100644 --- a/raymarch.glsl +++ b/raymarch.glsl @@ -1,8 +1,401 @@ +#define maxObj 7 +#define maxDist 20.0 +#define epsilon 0.00001 +#define diffuseCoeff 0.5 +#define ambientCoeff 0.3 +#define specularCoeff 0.2 +#define shadowSample 16 +#define shadowScale 8.0 +#define minStep 0.0001 +#define step 0.01 + +#define ADAPTIVE_STEP + +//displaying 1.0 - t/maxdist +//#define DEBUG_DISTANCE + +//displaying 1.0 - i/maxIter +//#define DEBUG_MARCHITERATION + +//#define OVERRELAXATION + +#ifdef FIXED_STEP + #define maxIter 5000 +#endif + +#ifdef ADAPTIVE_STEP + #define maxIter 500 +#endif + + +struct Material{ + vec3 clr; + float specPwr; +}; + +struct Object{ + Material m; + + // 0 - sphere, 1 - plane, 2 - box, 3 - roundedBox, 4 - Torus, 5 - Cylinder, 6 - Wheel + int type; + + mat4 invT; + float det; + + //these are properties needed for calculating SD + float r; + float R; + vec3 v; +}; + +struct Intersection{ + Material m; + float sd; +}; + +Object obj[maxObj]; + + +//determinant from https://github.com/dsheets/gloc/blob/master/stdlib/matrix.glsl +float determinant(mat2 m) { + return m[0][0]*m[1][1] - m[1][0]*m[0][1] ; + } + +float determinant(mat4 m) { + mat2 a = mat2(m); + mat2 b = mat2(m[2].xy,m[3].xy); + mat2 c = mat2(m[0].zw,m[1].zw); + mat2 d = mat2(m[2].zw,m[3].zw); + float s = determinant(a); + return s*determinant(d-(1.0/s)*c*mat2(a[1][1],-a[0][1],-a[1][0],a[0][0])*b); +} + +void calcInvTAndDet(out mat4 invT, out float det, in vec3 translate, in vec3 scale){ + mat4 s = mat4(1.0); + mat4 t = mat4(1.0); + + //translate + t[3][0] = translate.x; + t[3][1] = translate.y; + t[3][2] = translate.z; + + //scale + s[0][0] = scale.x; + s[1][1] = scale.y; + s[2][2] = scale.z; + + det = determinant(t * s); + + //translate + t[3][0] = -translate.x; + t[3][1] = -translate.y; + t[3][2] = -translate.z; + + //scale + s[0][0] = 1.0/scale.x; + s[1][1] = 1.0/scale.y; + s[2][2] = 1.0/scale.z; + + invT = s * t; +} + +void initScene(){ + //declare material & objects here + Material rDiffuse; + rDiffuse.clr = vec3(1.0, 0.0, 0.0); + rDiffuse.specPwr = 0.0; + + Material gDiffuse; + gDiffuse.clr = vec3(0.0, 1.0, 0.0); + gDiffuse.specPwr = 10.0; + + Material shinyOrange; + shinyOrange.clr = vec3(1.0, 0.64, 0.0); + shinyOrange.specPwr = 300.0; + + Material wDiffuse; + wDiffuse.clr = vec3(1.0, 1.0, 1.0); + wDiffuse.specPwr = 0.0; + + Object plane1; + plane1.type = 1; + plane1.m = rDiffuse; + plane1.v = vec3(0.0, 1.0, 0.0); + calcInvTAndDet(plane1.invT, plane1.det, vec3(0.0), vec3(1.0)); + + Object sphere1; + sphere1.type = 0; + sphere1.m = shinyOrange; + sphere1.r = 0.25; + calcInvTAndDet(sphere1.invT, sphere1.det, vec3(1.0,0.25,-1.0), vec3(1.0)); + + Object box1; + box1.type = 2; + box1.m = gDiffuse; + box1.v = vec3(0.5, 0.25, 0.25); + calcInvTAndDet(box1.invT, box1.det, vec3(-1.0,0.5,-1.0), vec3(1.0)); + + Object box2; + box2.type = 3; + box2.m = shinyOrange; + box2.v = vec3(0.5, 0.25, 0.25); + box2.r = 0.25; + calcInvTAndDet(box2.invT, box2.det, vec3(-1.0,0.5,1.0), vec3(0.5)); + + Object torus1; + torus1.type = 4; + torus1.m = shinyOrange; + torus1.r = 0.1; + torus1.R = 0.3; + calcInvTAndDet(torus1.invT, torus1.det, vec3(1.0,0.3,1.0), vec3(1.0)); + + Object cylinder1; + cylinder1.type = 5; + cylinder1.m = shinyOrange; + cylinder1.r = 0.2; + cylinder1.R = 0.3; + calcInvTAndDet(cylinder1.invT, cylinder1.det, vec3(0.0,0.5,0.0), vec3(1.0)); + + Object wheel1; + wheel1.type = 6; + wheel1.m = wDiffuse; + wheel1.r = 0.1; + wheel1.R = 0.2; + calcInvTAndDet(wheel1.invT, wheel1.det, vec3(2.0,0.25,0.0), vec3(1.0)); + + obj[0] = plane1; + obj[1] = sphere1; + obj[2] = box1; + obj[3] = box2; + obj[4] = torus1; + obj[5] = cylinder1; + obj[6] = wheel1; +} + + +vec3 MultiplyMat(mat4 m, vec3 v){ + return vec3(m * vec4(v, 1)); +} + +///////////////////////////////////////////////////////////////////////////////////////////// + +float sphereDist(vec3 p, float r){ + return length(p) - r; +} + +float planeDist(vec3 p, vec3 n){ + return dot(p,n); +} + +float boxDist(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 roundedBoxDist(vec3 p, vec3 b, float r){ + return length(max(abs(p)-b,0.0))-r; +} + +float torusDist(vec3 p, float r, float R) { + return length(vec2(length(p.xz) - R, p.y)) - r; +} + + +float cylinderDist(vec3 p, float r, float e) { + vec2 d = abs(vec2(length(p.xz), p.y)) - vec2(r, e); + return min(max(d.x,d.y), 0.0) + length(max(d, vec2(0.0, 0.0))); +} + +float length8(vec2 v) { + v *= v; v *= v; v *= v; + return pow(v.x + v.y, 1.0 / 8.0); +} + +float wheelDist(vec3 p, float r, float R) { + return length8(vec2(length(p.xz) - R, p.y)) - r; +} + + +///////////////////////////////////////////////////////////////////////////////////////////// + +Intersection opUnion(Intersection a, Intersection b){ + if(a.sd < b.sd) + return a; + else + return b; +} + +Intersection findClosestSD(in vec3 p){ + //union to get the result + Intersection isx; + isx.sd = maxDist; + + + for(int i = 0; i < maxObj; i++){ + Intersection tmp; + tmp.m = obj[i].m; + + vec3 tP = MultiplyMat(obj[i].invT, p); + + if(obj[i].type == 0) + tmp.sd = sphereDist(tP, obj[i].r) * obj[i].det; + else if(obj[i].type == 1) + tmp.sd = planeDist(tP, obj[i].v) * obj[i].det; + else if(obj[i].type == 2) + tmp.sd = boxDist(tP, obj[i].v) * obj[i].det; + else if(obj[i].type == 3) + tmp.sd = roundedBoxDist(tP, obj[i].v, obj[i].r) * obj[i].det; + else if(obj[i].type == 4) + tmp.sd = torusDist(tP, obj[i].r, obj[i].R) * obj[i].det; + else if(obj[i].type == 5) + tmp.sd = cylinderDist(tP, obj[i].r, obj[i].R) * obj[i].det; + else if(obj[i].type == 6) + tmp.sd = wheelDist(tP, obj[i].r, obj[i].R) * obj[i].det; + + isx = opUnion(isx, tmp); + } + + return isx; +} + +vec3 getNormal(vec3 pos){ + vec3 evec = vec3( epsilon, 0.0, 0.0 ); + vec3 nor = vec3( + findClosestSD(pos+evec.xyy).sd, + findClosestSD(pos+evec.yxy).sd, + findClosestSD(pos+evec.yyx).sd); + nor -= findClosestSD(pos).sd; + nor /= epsilon; + + return normalize(nor); +} + +//from http://www2.compute.dtu.dk/pubdb/views/edoc_download.php/6392/pdf/imm6392.pdf +float softShadow(vec3 ro, vec3 rd){ + float shadow = 1.0; + float t = step; + for(int i = 0; i < shadowSample; i++) + { + float d = findClosestSD(ro + rd*t).sd; + if(d < epsilon) return 0.0; + shadow = min(shadow, shadowScale*d/t); + t += clamp(t, 0.0, 0.05); + } + return clamp(shadow, 0.0, 1.0); +} + +//from http://www2.compute.dtu.dk/pubdb/views/edoc_download.php/6392/pdf/imm6392.pdf +float ambientOcclusion(vec3 p, vec3 n){ + float d = 0.0; + float occlusion = 0.0; + float denom = 0.5; + + for(int i = 0; i < 5; i++){ + float k = float(i); + float d = findClosestSD(p + n*k).sd; + occlusion += (k*step - d) * denom; + denom *= 0.5; + } + return 1.0 - clamp(occlusion, 0.0, 1.0); +} + +vec3 getColor(in vec3 p, in vec3 view, in Material m){ + vec3 lpos = vec3(2.0, 2.0, 2.0); + vec3 lcol = vec3(1.0, 1.0, 1.0); + + vec3 n = getNormal(p); + vec3 ldir = normalize(lpos - p); + vec3 vdir = normalize(view - p); + + //blinn phong + //diffuse + float diffuse = clamp(dot(ldir, n), 0.0, 1.0); + diffuse *= softShadow(p, ldir); + diffuse *= ambientOcclusion(p, n); + + //specular + float specular = 0.0; + if(m.specPwr > 0.0){ + vec3 hdir = normalize(ldir + vdir); + float specAngle = max(dot(hdir, n), 0.0); + specular = pow(specAngle, m.specPwr); + } + + return diffuseCoeff * diffuse * m.clr * lcol + + specularCoeff * specular * lcol + + ambientCoeff * m.clr; +} + + +float getStep(vec3 p){ + +#ifdef FIXED_STEP + return step; +#endif + +#ifdef ADAPTIVE_STEP + return max(findClosestSD(p).sd, minStep); +#endif + +} + vec3 render(in vec3 ro, in vec3 rd) { - // TODO - return rd; // camera ray direction debug view + float t = 0.0; + float dt = 0.0; + vec3 p; + Intersection isx; + +#ifdef OVERRELAXATION + float k = 1.2; + float prevdt = k * getStep(ro); +#endif + + initScene(); + + for(int i = 0; i < maxIter; i++) + { + dt = getStep(p); + +#ifdef OVERRELAXATION + k = dt > prevdt ? k : 1.0; + prevdt = k * dt; + t += k * dt; +#endif + +#ifndef OVERRELAXATION + t += dt; +#endif + + if(t > maxDist || dt < epsilon) break; + + p = ro + (rd * t); + isx = findClosestSD(p); + if(isx.sd < epsilon){ +#ifdef DEBUG_DISTANCE + return vec3(1.0 - t/maxDist); +#endif + +#ifdef DEBUG_MARCHITERATION + return vec3(1.0 - float(i)/float(maxIter)); +#endif + + return getColor(p, ro, isx.m); + } + } + + #ifdef DEBUG_DISTANCE + return vec3(0.0); + #endif + + #ifdef DEBUG_MARCHITERATION + return vec3(0.0); + #endif + + return vec3(0.8, 0.9, 1.0); // Sky color } +///////////////////////////////////////////////////////////////////////////////////////////// + mat3 setCamera(in vec3 ro, in vec3 ta, float cr) { // Starter code from iq's Raymarching Primitives // https://www.shadertoy.com/view/Xds3zN @@ -41,7 +434,7 @@ void mainImage(out vec4 fragColor, in vec2 fragCoord) { // render vec3 col = render(ro, rd); - col = pow(col, vec3(0.4545)); + //col = pow(col, vec3(0.4545)); fragColor = vec4(col, 1.0); }