diff --git a/IQ_example_annotated.glsl b/IQ_example_annotated.glsl new file mode 100644 index 0000000..64afcfa --- /dev/null +++ b/IQ_example_annotated.glsl @@ -0,0 +1,350 @@ +// Created by inigo quilez - iq/2013 +// License Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License. + +// A list of usefull distance function to simple primitives, and an example on how to +// do some interesting boolean operations, repetition and displacement. +// +// More info here: http://www.iquilezles.org/www/articles/distfunctions/distfunctions.htm + +float sdPlane( vec3 p ) +{ + return p.y; +} + +float sdSphere( vec3 p, float s ) +{ + return length(p)-s; +} + +float sdBox( 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 sdEllipsoid( in vec3 p, in vec3 r ) +{ + return (length( p/r ) - 1.0) * min(min(r.x,r.y),r.z); +} + +float udRoundBox( vec3 p, vec3 b, float r ) +{ + return length(max(abs(p)-b,0.0))-r; +} + +float sdTorus( vec3 p, vec2 t ) +{ + return length( vec2(length(p.xz)-t.x,p.y) )-t.y; +} + +float sdHexPrism( vec3 p, vec2 h ) +{ + vec3 q = abs(p); +#if 0 + return max(q.z-h.y,max((q.x*0.866025+q.y*0.5),q.y)-h.x); +#else + float d1 = q.z-h.y; + float d2 = max((q.x*0.866025+q.y*0.5),q.y)-h.x; + return length(max(vec2(d1,d2),0.0)) + min(max(d1,d2), 0.); +#endif +} + +float sdCapsule( vec3 p, vec3 a, vec3 b, float r ) +{ + vec3 pa = p-a, ba = b-a; + float h = clamp( dot(pa,ba)/dot(ba,ba), 0.0, 1.0 ); + return length( pa - ba*h ) - r; +} + +float sdTriPrism( vec3 p, vec2 h ) +{ + vec3 q = abs(p); +#if 0 + return max(q.z-h.y,max(q.x*0.866025+p.y*0.5,-p.y)-h.x*0.5); +#else + float d1 = q.z-h.y; + float d2 = max(q.x*0.866025+p.y*0.5,-p.y)-h.x*0.5; + return length(max(vec2(d1,d2),0.0)) + min(max(d1,d2), 0.); +#endif +} + +float sdCylinder( vec3 p, vec2 h ) +{ + vec2 d = abs(vec2(length(p.xz),p.y)) - h; + return min(max(d.x,d.y),0.0) + length(max(d,0.0)); +} + +float sdCone( in vec3 p, in vec3 c ) +{ + vec2 q = vec2( length(p.xz), p.y ); + float d1 = -q.y-c.z; + float d2 = max( dot(q,c.xy), q.y); + return length(max(vec2(d1,d2),0.0)) + min(max(d1,d2), 0.); +} + +float sdConeSection( in vec3 p, in float h, in float r1, in float r2 ) +{ + float d1 = -p.y - h; + float q = p.y - h; + float si = 0.5*(r1-r2)/h; + float d2 = max( sqrt( dot(p.xz,p.xz)*(1.0-si*si)) + q*si - r2, q ); + return length(max(vec2(d1,d2),0.0)) + min(max(d1,d2), 0.); +} + + +float length2( vec2 p ) +{ + return sqrt( p.x*p.x + p.y*p.y ); +} + +float length6( vec2 p ) +{ + p = p*p*p; p = p*p; + return pow( p.x + p.y, 1.0/6.0 ); +} + +float length8( vec2 p ) +{ + p = p*p; p = p*p; p = p*p; + return pow( p.x + p.y, 1.0/8.0 ); +} + +float sdTorus82( vec3 p, vec2 t ) +{ + vec2 q = vec2(length2(p.xz)-t.x,p.y); + return length8(q)-t.y; +} + +float sdTorus88( vec3 p, vec2 t ) +{ + vec2 q = vec2(length8(p.xz)-t.x,p.y); + return length8(q)-t.y; +} + +float sdCylinder6( vec3 p, vec2 h ) +{ + return max( length6(p.xz)-h.x, abs(p.y)-h.y ); +} + +//---------------------------------------------------------------------- + +float opS( float d1, float d2 ) +{ + return max(-d2,d1); +} + +vec2 opU( vec2 d1, vec2 d2 ) +{ + return (d1.x0.0 ) tmax = min( tmax, tp1 ); + float tp2 = (1.6-ro.y)/rd.y; if( tp2>0.0 ) { if( ro.y>1.6 ) tmin = max( tmin, tp2 ); + else tmax = min( tmax, tp2 ); } +#endif + + float precis = 0.002; + float t = tmin; + float m = -1.0; + for( int i=0; i<50; i++ ) + { + vec2 res = map( ro+rd*t ); + if( res.xtmax ) break; + t += res.x; + m = res.y; + } + + if( t>tmax ) m=-1.0; + return vec2( t, m ); +} + +// returns a shadow value between 0.0 and 1.0. 1.0 is lit, 0.0 is unlit +// +float softshadow( in vec3 ro, in vec3 rd, in float mint, in float tmax ) +{ + float res = 1.0; + float t = mint; + for( int i=0; i<16; i++ ) + { + float h = map( ro + rd*t ).x; + res = min( res, 8.0*h/t ); + t += clamp( h, 0.02, 0.10 ); // IQ's version doesn't let you go very far, does it? why? + if( h<0.001 || t>tmax ) break; + } + return clamp( res, 0.0, 1.0 ); + +} + +vec3 calcNormal( in vec3 pos ) +{ + vec3 eps = vec3( 0.001, 0.0, 0.0 ); + vec3 nor = vec3( + map(pos+eps.xyy).x - map(pos-eps.xyy).x, + map(pos+eps.yxy).x - map(pos-eps.yxy).x, + map(pos+eps.yyx).x - map(pos-eps.yyx).x ); + return normalize(nor); +} + +float calcAO( in vec3 pos, in 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; // compute a sample distance + vec3 aopos = nor * hr + pos; // compute the sample position + float dd = map( aopos ).x; // get the distance function at the sample position + occ += -(dd-hr)*sca; // occlusion += distance function - sample distance * falloff term + sca *= 0.95; + } + return clamp( 1.0 - 3.0*occ, 0.0, 1.0 ); +} + + + + +vec3 render( in vec3 ro, in vec3 rd ) +{ + vec3 col = vec3(0.8, 0.9, 1.0); + vec2 res = castRay(ro,rd); + float t = res.x; + float m = res.y; + if( m>-0.5 ) + { + vec3 pos = ro + t*rd; + vec3 nor = calcNormal( pos ); + vec3 ref = reflect( rd, nor ); + + // material + col = 0.45 + 0.3*sin( vec3(0.05,0.08,0.10)*(m-1.0) ); + + if( m<1.5 ) + { + + float f = mod( floor(5.0*pos.z) + floor(5.0*pos.x), 2.0); + col = 0.4 + 0.1*f*vec3(1.0); + } + + // lighitng + float occ = calcAO( pos, nor ); + vec3 lig = normalize( vec3(-0.6, 0.7, -0.5) ); + float amb = clamp( 0.5+0.5*nor.y, 0.0, 1.0 ); + float dif = clamp( dot( nor, lig ), 0.0, 1.0 ); + float bac = clamp( dot( nor, normalize(vec3(-lig.x,0.0,-lig.z))), 0.0, 1.0 )*clamp( 1.0-pos.y,0.0,1.0); + float dom = smoothstep( -0.1, 0.1, ref.y ); + float fre = pow( clamp(1.0+dot(nor,rd),0.0,1.0), 2.0 ); + float spe = pow(clamp( dot( ref, lig ), 0.0, 1.0 ),16.0); + + dif *= softshadow( pos, lig, 0.02, 2.5 ); + dom *= softshadow( pos, ref, 0.02, 2.5 ); + + vec3 brdf = vec3(0.0); + brdf += 1.20*dif*vec3(1.00,0.90,0.60); + brdf += 1.20*spe*vec3(1.00,0.90,0.60)*dif; + brdf += 0.30*amb*vec3(0.50,0.70,1.00)*occ; + brdf += 0.40*dom*vec3(0.50,0.70,1.00)*occ; + brdf += 0.30*bac*vec3(0.25,0.25,0.25)*occ; + brdf += 0.40*fre*vec3(1.00,1.00,1.00)*occ; + brdf += 0.02; + col = col*brdf; + + col = mix( col, vec3(0.8,0.9,1.0), 1.0-exp( -0.0005*t*t ) ); + + } + + return vec3( clamp(col,0.0,1.0) ); +} + +mat3 setCamera( in vec3 ro, in vec3 ta, float cr ) +{ + vec3 cw = normalize(ta-ro); + vec3 cp = vec3(sin(cr), cos(cr),0.0); + vec3 cu = normalize( cross(cw,cp) ); + vec3 cv = normalize( cross(cu,cw) ); + return mat3( cu, cv, cw ); +} + +void mainImage( out vec4 fragColor, in vec2 fragCoord ) +{ + vec2 q = fragCoord.xy/iResolution.xy; + vec2 p = -1.0+2.0*q; + p.x *= iResolution.x/iResolution.y; + vec2 mo = iMouse.xy/iResolution.xy; + + float time = 15.0 + iGlobalTime; + + // camera + vec3 ro = vec3( -0.5+3.5*cos(0.1*time + 6.0*mo.x), 0.0, 0.5 + 3.5*sin(0.1*time + 6.0*mo.x) ); // camera position + vec3 ta = vec3( 0, -1, 0 ); // camera aim + + // camera-to-world transformation + mat3 ca = setCamera( ro, ta, 0.0 ); + + // ray direction + vec3 rd = ca * normalize( vec3(p.xy,2.0) ); + + // render + vec3 col = render( ro, rd ); + + col = pow( col, vec3(0.4545) ); + + fragColor=vec4( col, 1.0 ); +} \ No newline at end of file diff --git a/README.md b/README.md index 02a82bc..0e1e97c 100644 --- a/README.md +++ b/README.md @@ -1,189 +1,113 @@ -# [CIS565 2015F] YOUR TITLE HERE - -**GLSL Ray Marching** +# [CIS565 2015F] 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) +* Kangning Li +* Tested on: Google Chrome 46.0.2490.71 m on Windows 10, i7-4790 @ 3.6GHz 16GB, GTX 970 4096MB -### Live on Shadertoy (TODO) +### Live on Shadertoy -[![](img/thumb.png)](https://www.shadertoy.com/view/TODO) +[![](img/thumb.png)](https://www.shadertoy.com/view/XlBSzc) ### Acknowledgements This Shadertoy uses material from the following resources: +* 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) +* Iñigo Quílez. + *Raymarching Primitives* (2013). + [Shadertoy](https://www.shadertoy.com/view/Xds3zN) +* Iñigo Quílez. + *Terrain Raymarching* (2007). + [Article](http://www.iquilezles.org/www/articles/terrainmarching/terrainmarching.htm) +* 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) +* Lukasz Jaroslaw Tomczak, Technical University of Denmark. + *GPU Ray Marching of Distance Fields* (2012). + [PDF](http://www2.compute.dtu.dk/pubdb/views/edoc_download.php/6392/pdf/imm6392.pdf) -* TODO - -### (TODO: Your README) - - -Instructions (delete me) -======================== +### Overview -This is due at midnight on the evening of Monday, October 19. +This shadertoy is an introductory exercise to raytracing by ray marching against signed distance functions. It features: +*support for cubes, spheres, infinite planes, height/terrain functions, and menger sponges +*transformations and union for each "primitive" type +*lambert shading with soft/hard shadows and ambient occlusion +*an option to switch between naive ray marching and spherical distance ray marching +*a handful of debugging features -**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! +### Modeling Features -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. +#### cubes, spheres, infinite planes, and modeling transformations +These are referenced from Morgan McGuire's *Numerical Methods for Ray Tracing Implicitly Defined Surfaces* +Each potential function evaluator has been modified to take in modeling transformations along with the point whose distance is being computed. Transformations supported include translation, euler angle rotation, and non-uniform scaling. -**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. +#### height/terrain function +![](img/sine_height.png) +The height/terrain function draws from Iñigo Quílez's article *Terrain Raymarching.* At the moment it uses a sine function over the x and z coordinates of the point instead of a perlin noise function as a compact proof of concept. The same transformations are supported. -**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. +#### menger sponge +![](img/sponge.png) +The menger sponge function iteratively computes a distance for a menger sponge. The default computation is only to the 3rd iteration. +The same transformations are supported. -### Features +### Lighting Features -All features must be visible in your final demo for full credit. +#### Lambert shading +![](img/lambert.png) +A single light is supported. Lambert shading is computationally very cheap. -**Required Features:** +#### Ambient Occlusion +![](img/ao.png) +Given a point that needs to be shaded, the shadertoy approximates ambient occlusion by sampling the distance function at points along the surface point's normal direction. This is heavily based on the ambient occlusion method described in Lukasz Jaroslaw Tomczak's *GPU Ray Marching of Distance Fields* and implemented in Iñigo Quílez's *Raymarching Primitives.* -* 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 +#### Soft Shadows +![](img/ao_soft_shadows.png) +Given a point that needs to be shaded, the shadertoy approximates soft shadows by raymarching towards the light and using the point's distance from an occluding object to "soften" the shadow. This is heavily based on the soft shadow method described in Lukasz Jaroslaw Tomczak's *GPU Ray Marching of Distance Fields* and implemented in Iñigo Quílez's *Raymarching Primitives.* -**Extra Features:** +#### Spherical Ray Marching +![](img/sphere_menger.png) +Spherical ray marching attempts to speed up the ray marching by taking larger steps bounded by a distance function evaluation at each step. However, this can lead to unusual results with the terrain function and spheres with extreme nonuniform scale. +Based on the description in Morgan McGuire's *Numerical Methods for Ray Tracing Implicitly Defined Surfaces.* -You must do at least 10 points worth of extra features. +### Debugging -* (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 raymarcher includes numerous debugging features, all of which can be toggled from the defines at the top of the file. -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). +#### Normals +![](img/normals.png) -## Write-up +#### Distance +![](img/distance.png) +Distance is similar to a depth test. -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. +#### Steps +![](img/steps_naive.png) +Steps visualizes the number of steps the ray marching method took to reach each rendered point. The scale can be modified to change the contrast. ### 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. - +#### Naive Ray Marching vs. Spherical Tracing +In the default scene provided, with all lighting settings enabled, spherical tracing was considerably faster than naive ray marching, averaging around 60 ms per frame in a full circle pass as opposed to the naive method's 170 ms per frame on average. -## Submit +For both marching methods, computation time substantially decreased the closer the camera got to the scene objects, presumably because each ray march was "bottoming out" sooner. -### Post on Shadertoy +![](img/near.png) +![](img/far.png) -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). +The "steps" debugging setting also yields some interesting observations. While the difference in brightness clearly indicates that the spherical method takes significantly fewer steps per ray, it also indicates from the light 'haze' around the edges of objects that rays marching near edges of objects tend to "decelerate" as they approach the object, taking more steps than rays passing objects from further away. -In the Shadertoy description, include the following: +![](img/steps_sphere.png) +![](img/steps_naive.png) -* 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. +#### Time spent raymarching vs. Time spent on lighting computation +![](img/charts/stage_time.png) -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. +When using naive raymarching the vast majority of time per frame is spent on raymarching. Ambient occlusion and soft shadows are both relatively inexpensive. Since both computations perform a variant on the naive raymarch, however, each may also be influenced by the scene configuration. Ambient occlusion, for example, only addresses samples marching along a point's normal out to a hardcoded number of samples. Shadows, meanwhile, march towards the light until the ray march reaches the light or strikes an occluding object. Thus, unoccluded points very far from the light source have more expensive shadow computations. -### Pull Request +#### Branch Divergence Estimates -**Even though your code is on Shadertoy, make sure it is also on GitHub!** +Two of the above features are likely to demonstrate branch divergence performance hits: shadowing, and the distance function for the menger sponge. Soft shadowing breaks out of the ray march towards the light depending on the point in the scene. Threads computing points close to each other in the scene (and close to each other in screen space) should have less divergence the closer they are together, with the most divergence in the "soft shadow" region. -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. +The menger sponge, on the other hand, features branch divergence in a much more extreme way that likely contributes to its computational expense. The sponge is computed at each iteration by assessing the bounding box of the sponge iteration and checking against each of the 22 sub-iteration boxes within this iteration's box. This check determines the bounding box of the next iteration that must be computed. \ No newline at end of file diff --git a/img/ao.png b/img/ao.png new file mode 100644 index 0000000..f110677 Binary files /dev/null and b/img/ao.png differ diff --git a/img/ao_soft_shadows.png b/img/ao_soft_shadows.png new file mode 100644 index 0000000..ac09614 Binary files /dev/null and b/img/ao_soft_shadows.png differ diff --git a/img/charts/stage_time.csv b/img/charts/stage_time.csv new file mode 100644 index 0000000..dd616c7 --- /dev/null +++ b/img/charts/stage_time.csv @@ -0,0 +1,5 @@ +"";"naive marching + lambert";"ambient occlusion";"soft shadow" +"pure lambert";"120";"0";"0" +"lambert + AO";"120";"30";"20" +"lambert + shadow";"120";"0";"20" +"lambert + AO + shadow";"120";"30";"20" diff --git a/img/charts/stage_time.pdf b/img/charts/stage_time.pdf new file mode 100644 index 0000000..3458676 Binary files /dev/null and b/img/charts/stage_time.pdf differ diff --git a/img/charts/stage_time.png b/img/charts/stage_time.png new file mode 100644 index 0000000..b5d2686 Binary files /dev/null and b/img/charts/stage_time.png differ diff --git a/img/distance.png b/img/distance.png new file mode 100644 index 0000000..e9d2c5e Binary files /dev/null and b/img/distance.png differ diff --git a/img/far.png b/img/far.png new file mode 100644 index 0000000..a5b97c0 Binary files /dev/null and b/img/far.png differ diff --git a/img/lambert.png b/img/lambert.png new file mode 100644 index 0000000..8b68506 Binary files /dev/null and b/img/lambert.png differ diff --git a/img/near.png b/img/near.png new file mode 100644 index 0000000..3435600 Binary files /dev/null and b/img/near.png differ diff --git a/img/normals.png b/img/normals.png new file mode 100644 index 0000000..2414e66 Binary files /dev/null and b/img/normals.png differ diff --git a/img/sine_height.png b/img/sine_height.png new file mode 100644 index 0000000..72ed3c3 Binary files /dev/null and b/img/sine_height.png differ diff --git a/img/sphere_menger.png b/img/sphere_menger.png new file mode 100644 index 0000000..74aa468 Binary files /dev/null and b/img/sphere_menger.png differ diff --git a/img/sponge.png b/img/sponge.png new file mode 100644 index 0000000..a7227be Binary files /dev/null and b/img/sponge.png differ diff --git a/img/steps_naive.png b/img/steps_naive.png new file mode 100644 index 0000000..0eb4ac5 Binary files /dev/null and b/img/steps_naive.png differ diff --git a/img/steps_sphere.png b/img/steps_sphere.png new file mode 100644 index 0000000..5e5593e Binary files /dev/null and b/img/steps_sphere.png differ diff --git a/img/thumb.png b/img/thumb.png index 9ec8ed0..63e805f 100644 Binary files a/img/thumb.png and b/img/thumb.png differ diff --git a/notes.txt b/notes.txt new file mode 100644 index 0000000..bbbe04f --- /dev/null +++ b/notes.txt @@ -0,0 +1,70 @@ +TODO: +-get a sphere working [done] +-add material color return in scenegraph check [done] + -and use it in render [done] +-add union op [done] +-add plane check [done] +-add cube check [done] +-add normal [done] +-add light [done] +-add sphere tracing [buggy] + +-extract and generalize transformation operator. see if it fixes the sphere tracing problem? + -no good. can't generalize transformation op when scale is nonuniform + -besides, it's only a problem with nonuniform scaled spheres + +-add height-mapped terrain [done. kinda.] + -add a function that, given an xz coordinate, computes pseudorandom noise [done?] + -just using sin functions for now. perlin noise later? + -add a primitive that takes all 3 transforms and does the height mapped thing [done] + +-add fractal menger sponge + -primitive that should take in transforms and an iteration depth + -come up with an iterative method for computing menger depth + -something something something iterate if the point is in some region + +-add soft shadows [done. but not very pretty] + -intuition from the slides: march towards the light from point + -accumulate distance to the occluder. use that to determine shadow strength + -IQ's slides back this up: use samples of distance to closest geometry along line from surface to light + -also, the reading article source thing + -you can multiply the shadow term right onto the lambert computation. + -may need to move ambient outside of lambert shading + -TODO: + -modify the scene so you'll actually be able to see shadows [done] + -add a hard shadow function. takes in light, ray origin, ray direction. keep epsilon and max consistent! [done] + -add a soft shadow function. [done] + +-add ambient occlusion [done. but not very pretty] + -intuition from IQ's slides: + -sample nearest points along the point normal + -compare these to camera -> point distance + -overall intuition: + -use distance function to estimate if other things are close to the point to be shaded + +-add debugging [done] and do some profiling + +-distance to surface (depth basically?) [done] + +-ray march iterations [done] + +DATA + +javascript:(function(){var script=document.createElement('script');script.onload=function(){var stats=new Stats();stats.setMode( 1);stats.domElement.style.cssText='position:fixed;left:0;top:0;z-index:10000';document.body.appendChild(stats.domElement);requestAnimationFrame(function loop(){stats.update();requestAnimationFrame(loop)});};script.src='//rawgit.com/mrdoob/stats.js/master/build/stats.min.js';document.head.appendChild(script);})() + +-oddly, the menger sponge works with sphere mode and it's FAST + +NAIVE RAY MARCHING VS SPHERE TRACING +-naive: around 170 ms per frame in a full circle pass +-sphere: around 60 ms per frame in a full circle pass + +TIME SPENT RAY MARCHING VS SHADING/LIGHTING +-pure lambert: around 120 ms per frame in a full circle pass + +-shadow: about 150 ms per frame in a full circle pass + +-AO: about 130 ms per frame in a full circle pass + +-shadow + AO: about 170 ms per frame in a full circle pass + +BRANCH DIVERGENCE \ No newline at end of file diff --git a/raymarch.glsl b/raymarch.glsl index aac90c8..42fc0ab 100644 --- a/raymarch.glsl +++ b/raymarch.glsl @@ -1,6 +1,630 @@ -vec3 render(in vec3 ro, in vec3 rd) { - // TODO - return rd; // camera ray direction debug view +/*utils***********************************************************************/ +#define EPSILON 0.002 +#define MAXDISTANCE 20.0 + +// debug rendering +#define DEBUGDISTANCE 0 +#define DEBUGSTEPS 0 +#define DEBUGSTEPSRANGE 2000.0 +#define DEBUGNORMALS 0 + +// render options +#define LAMBERT 1 +#define SOFTSHADOW 1 +#define AMBIENTOCCLUSION 1 + +// advanced distsance functions and stepping method +#define RENDERFRACTAL 1 +#define RENDERHEIGHTFUNCTION 1 +#define SPHERESTEP 1 + +mat3 eulerXYZRotationMatrix(in vec3 rotation) { + //https://en.wikipedia.org/wiki/Euler_angles#Rotation_matrix + mat3 eulerXYZ; + float c1 = cos(rotation.x); + float c2 = cos(rotation.y); + float c3 = cos(rotation.z); + float s1 = sin(rotation.x); + float s2 = sin(rotation.y); + float s3 = sin(rotation.z); + + eulerXYZ[0][0] = c2 * c3; + eulerXYZ[0][1] = s1 * s3 + c1 * c3 * s2; + eulerXYZ[0][2] = c3 * s1 * s2 - c1 * s3; + + eulerXYZ[1][0] = -s2; + eulerXYZ[1][1] = c1 * c2; + eulerXYZ[1][2] = c2 * s1; + + eulerXYZ[2][0] = c2 * c3; + eulerXYZ[2][1] = c1 * s2 * s3 - c3 * s1; + eulerXYZ[2][2] = c1 * c3 + s1 * s2 * s3; + + return eulerXYZ; +} + +mat3 eulerZYXRotationMatrix(in vec3 rotation) { + //https://en.wikipedia.org/wiki/Euler_angles#Rotation_matrix + mat3 eulerZYX; + float c1 = cos(rotation.x); + float c2 = cos(rotation.y); + float c3 = cos(rotation.z); + float s1 = sin(rotation.x); + float s2 = sin(rotation.y); + float s3 = sin(rotation.z); + + eulerZYX[0][0] = c1 * c2; + eulerZYX[0][1] = c2 * s1; + eulerZYX[0][2] = -s2; + + eulerZYX[1][0] = c1 * s2 * s3 - c3 * s1; + eulerZYX[1][1] = c1 * c3 + s1 * s2 * s3; + eulerZYX[1][2] = c2 * s3; + + eulerZYX[2][0] = s1 * s3 + c1 * c3 * s2; + eulerZYX[2][1] = c3 * s1 * s2 - c1 * s3; + eulerZYX[2][2] = c2 * c3; + + return eulerZYX; +} + +// for generating a pseudorandom color given a point. +// from http://stackoverflow.com/questions/4200224/random-noise-functions-for-glsl +float pseudoRand(vec2 xz) { + return fract(sin(dot(xz.xy, vec2(12.9898, 78.233))) * 43758.5453); +} + +float sinSin(vec2 xz) { + return sin(xz[0]) * sin(xz[1]); +} + +/*primitive distance estimators***********************************************/ +// each takes in transformations and outputs the distance from the point to the +// primitive in world space. + +// unit sphere has radius of 1 +float sphere(in vec3 point, in vec3 translation, in vec3 rotation, in vec3 scale) { + // transform the point into local coordinates + vec3 localPoint = point; + localPoint -= translation; // untranslate + localPoint = eulerZYXRotationMatrix(-1.0 * rotation) * localPoint; // unrotate + localPoint.x /= scale.x; // unscale + localPoint.y /= scale.y; + localPoint.z /= scale.z; + + // compute distance from the unit sphere + vec3 localDist = localPoint - normalize(localPoint); + + // transform into world space + // the distance is along the vector point - center, so use that in the scaleback? + localDist.x *= scale.x; + localDist.y *= scale.y; + localDist.z *= scale.z; + if (length(localPoint) < 1.0) return -1.0 * length(localDist); + return length(localDist); +} + +// unit plane is at 0, 0, 0 and has y up normal +float plane(in vec3 point, in vec3 translation, in vec3 rotation) { + // plane is easier to deal with since there's no scale. yay! + vec3 up = vec3(0.0, 1.0, 0.0); + mat3 rotationMat = eulerZYXRotationMatrix(rotation); + up = rotationMat * up; + return dot(point - translation, up); +} + +float cube(in vec3 point, in vec3 translation, in vec3 rotation, in vec3 scale) { + // transform the point into local coordinates + vec3 localPoint = point; + localPoint -= translation; // untranslate + localPoint = eulerZYXRotationMatrix(-1.0 * rotation) * localPoint; // unrotate + // no unscale: we can use the scale to determine the sidelengths of the box + + vec3 firstQuadrantCorner = vec3(scale.x * 0.5, scale.y * 0.5, scale.z * 0.5); + vec3 d = abs(localPoint) - firstQuadrantCorner; + return min(max(max(d.x, d.y), d.z), 0.0) + length(max(d, vec3(0.0, 0.0, 0.0))); +} + +float heightFunction(in vec3 point, in vec3 translation, in vec3 rotation, in vec3 scale) { + // transform the point into local coordinates + vec3 localPoint = point; + localPoint -= translation; // untranslate + localPoint = eulerZYXRotationMatrix(-1.0 * rotation) * localPoint; // unrotate + localPoint.x /= scale.x; // unscale + localPoint.y /= scale.y; + localPoint.z /= scale.z; + + // compute a height value + vec3 nearPt = localPoint; + nearPt.y = sinSin(localPoint.xz); + + // get distance vector + vec3 localDist = localPoint - nearPt; + + // scale back + localDist.x *= scale.x; + localDist.y *= scale.y; + localDist.z *= scale.z; + return length(localDist) * localDist.y / abs(localDist.y); +} + +// computes the distance from point to the axis aligned cube defined by min and max. +// helper for menger sponge. +float distInCube(in vec3 point, in vec3 minCorner, in vec3 maxCorner) { + vec3 center = (maxCorner + minCorner) / 2.0; + vec3 Q1 = maxCorner - center; + vec3 d = abs(point - center) - Q1; + return min(max(max(d.x, d.y), d.z), 0.0) + length(max(d, vec3(0.0, 0.0, 0.0))); +} + +// menger sponge cube of dimensions in scale of iteration depth at most 4 +float fractalMenger(in vec3 point, in vec3 translation, in vec3 rotation, in vec3 scale) { + // transform the point into local coordinates + vec3 localPoint = point; + localPoint -= translation; // untranslate + localPoint = eulerZYXRotationMatrix(-1.0 * rotation) * localPoint; // unrotate + + vec3 maxCorner = vec3(scale.x * 0.5, scale.y * 0.5, scale.z * 0.5); + vec3 minCorner = -maxCorner; + + // at each iteration, compute if the point is in any of the 20 subcubes + // update maxCorner and minCorner. + // if at any point it is not in a subcube, the point is not "inside" the sponge, so break. + float currDist = 1000.0; + for (int i = 3; i > 0; i--) { + vec3 dimensions = (maxCorner - minCorner) / 3.0; + vec3 currCorner = minCorner; + // bottom 8 + /*********** + * 1 2 3 -> +x + * 8 4 | +z + * 7 6 5 V + ***********/ + // 1 + currDist = min(distInCube(localPoint, currCorner, currCorner + dimensions), currDist); + if (currDist <= EPSILON && i > 1) { + minCorner = currCorner; + maxCorner = currCorner + dimensions; + currDist = 1000.0; + continue; + } + + // 2 + currCorner.x += dimensions.x; + currDist = min(distInCube(localPoint, currCorner, currCorner + dimensions), currDist); + if (currDist <= EPSILON && i > 1) { + minCorner = currCorner; + maxCorner = currCorner + dimensions; + currDist = 1000.0; + continue; + } + // 3 + currCorner.x += dimensions.x; + currDist = min(distInCube(localPoint, currCorner, currCorner + dimensions), currDist); + if (currDist <= EPSILON && i > 1) { + minCorner = currCorner; + maxCorner = currCorner + dimensions; + currDist = 1000.0; + continue; + } + + // 4 + currCorner.z += dimensions.z; + currDist = min(distInCube(localPoint, currCorner, currCorner + dimensions), currDist); + if (currDist <= EPSILON && i > 1) { + minCorner = currCorner; + maxCorner = currCorner + dimensions; + currDist = 1000.0; + continue; + } + // 5 + currCorner.z += dimensions.z; + currDist = min(distInCube(localPoint, currCorner, currCorner + dimensions), currDist); + if (currDist <= EPSILON && i > 1) { + minCorner = currCorner; + maxCorner = currCorner + dimensions; + currDist = 1000.0; + continue; + } + // 6 + currCorner.x -= dimensions.x; + currDist = min(distInCube(localPoint, currCorner, currCorner + dimensions), currDist); + if (currDist <= EPSILON && i > 1) { + minCorner = currCorner; + maxCorner = currCorner + dimensions; + currDist = 1000.0; + continue; + } + // 7 + currCorner.x -= dimensions.x; + currDist = min(distInCube(localPoint, currCorner, currCorner + dimensions), currDist); + if (currDist <= EPSILON && i > 1) { + minCorner = currCorner; + maxCorner = currCorner + dimensions; + currDist = 1000.0; + continue; + } + // 8 + currCorner.z -= dimensions.z; + currDist = min(distInCube(localPoint, currCorner, currCorner + dimensions), currDist); + if (currDist <= EPSILON && i > 1) { + minCorner = currCorner; + maxCorner = currCorner + dimensions; + currDist = 1000.0; + continue; + } + + // middle 4 + /*********** + * 1 x 2 -> +x + * x x | +z + * 4 x 3 V + ***********/ + currCorner.y += dimensions.y; + + // 1 + currCorner.z -= dimensions.z; + currDist = min(distInCube(localPoint, currCorner, currCorner + dimensions), currDist); + if (currDist <= EPSILON && i > 1) { + minCorner = currCorner; + maxCorner = currCorner + dimensions; + currDist = 1000.0; + continue; + } + // 2 + currCorner.x += dimensions.x + dimensions.x; + currDist = min(distInCube(localPoint, currCorner, currCorner + dimensions), currDist); + if (currDist <= EPSILON && i > 1) { + minCorner = currCorner; + maxCorner = currCorner + dimensions; + currDist = 1000.0; + continue; + } + // 3 + currCorner.z += dimensions.z + dimensions.z; + currDist = min(distInCube(localPoint, currCorner, currCorner + dimensions), currDist); + if (currDist <= EPSILON && i > 1) { + minCorner = currCorner; + maxCorner = currCorner + dimensions; + currDist = 1000.0; + continue; + } + // 4 + currCorner.x -= dimensions.x + dimensions.x; + currDist = min(distInCube(localPoint, currCorner, currCorner + dimensions), currDist); + if (currDist <= EPSILON && i > 1) { + minCorner = currCorner; + maxCorner = currCorner + dimensions; + currDist = 1000.0; + continue; + } + // top 8 + /*********** + * 1 2 3 -> +x + * 8 4 | +z + * 7 6 5 V + ***********/ + currCorner.y += dimensions.y; + + // 1 + currCorner.z -= dimensions.z + dimensions.z; + currDist = min(distInCube(localPoint, currCorner, currCorner + dimensions), currDist); + if (currDist <= EPSILON && i > 1) { + minCorner = currCorner; + maxCorner = currCorner + dimensions; + currDist = 1000.0; + continue; + } + // 2 + currCorner.x += dimensions.x; + currDist = min(distInCube(localPoint, currCorner, currCorner + dimensions), currDist); + if (currDist <= EPSILON && i > 1) { + minCorner = currCorner; + maxCorner = currCorner + dimensions; + currDist = 1000.0; + continue; + } + // 3 + currCorner.x += dimensions.x; + currDist = min(distInCube(localPoint, currCorner, currCorner + dimensions), currDist); + if (currDist <= EPSILON && i > 1) { + minCorner = currCorner; + maxCorner = currCorner + dimensions; + currDist = 1000.0; + continue; + } + // 4 + currCorner.z += dimensions.z; + currDist = min(distInCube(localPoint, currCorner, currCorner + dimensions), currDist); + if (currDist <= EPSILON && i > 1) { + minCorner = currCorner; + maxCorner = currCorner + dimensions; + currDist = 1000.0; + continue; + } + // 5 + currCorner.z += dimensions.z; + currDist = min(distInCube(localPoint, currCorner, currCorner + dimensions), currDist); + if (currDist <= EPSILON && i > 1) { + minCorner = currCorner; + maxCorner = currCorner + dimensions; + currDist = 1000.0; + continue; + } + // 6 + currCorner.x -= dimensions.x; + currDist = min(distInCube(localPoint, currCorner, currCorner + dimensions), currDist); + if (currDist <= EPSILON && i > 1) { + minCorner = currCorner; + maxCorner = currCorner + dimensions; + currDist = 1000.0; + continue; + } + // 7 + currCorner.x -= dimensions.x; + currDist = min(distInCube(localPoint, currCorner, currCorner + dimensions), currDist); + if (currDist <= EPSILON && i > 1) { + minCorner = currCorner; + maxCorner = currCorner + dimensions; + currDist = 1000.0; + continue; + } + // 8 + currCorner.z -= dimensions.z; + currDist = min(distInCube(localPoint, currCorner, currCorner + dimensions), currDist); + if (currDist <= EPSILON && i > 1) { + minCorner = currCorner; + maxCorner = currCorner + dimensions; + currDist = 1000.0; + continue; + } + if (currDist > 0.0) break; + } + return currDist; +} + +/*Operations******************************************************************/ + +// for getting the union of two objects. the one with the smaller distance. +vec4 unionColorDistance(vec4 d1, vec4 d2) { + float minDistance = d2[3]; + vec3 color = d2.rgb; + if (d1[3] < d2[3]) { + color = d1.rgb; + minDistance = d1[3]; + } + return vec4(color, minDistance); +} + +/*Code************************************************************************/ + +// returns the conservative distance to the nearest scene object. +// declare all scene objects in here. +// returns (r, g, b, distance) +vec4 sceneGraphDistanceFunction(in vec3 point) +{ + vec4 returnMe = vec4(0.0, 0.0, 0.0, 22.0); + + vec4 sphere0 = vec4(1.0, 0.0, 0.0, -1.0); + sphere0[3] = sphere(point, vec3(0.0, 0.6, 0.0), vec3(0.0, 0.0, 0.0), vec3(0.5, 0.5, 0.5)); + returnMe = unionColorDistance(returnMe, sphere0); + + vec4 sphere1 = vec4(0.0, 1.0, 0.0, -1.0); + sphere1[3] = sphere(point, vec3(0.0, 0.0, 0.0), vec3(0.0, 0.0, 0.0), vec3(1.0, 0.1, 1.0)); + returnMe = unionColorDistance(returnMe, sphere1); + + vec4 sphere2 = vec4(1.0, 0.0, 1.0, -1.0); + sphere2[3] = sphere(point, vec3(1.6, 0.6, 0.0), vec3(0.0, 0.0, 0.0), vec3(0.5, 0.5, 0.5)); + returnMe = unionColorDistance(returnMe, sphere2); + + vec4 cube0 = vec4(0.0, 1.0, 1.0, -1.0); + cube0[3] = cube(point, vec3(0.8, 1.0, 0.0), vec3(0.0, 0.0, 0.0), vec3(1.0, 0.2, 2.0)); + returnMe = unionColorDistance(returnMe, cube0); + + vec4 plane0 = vec4(0.9, 1.0, 0.9, -1.0); + plane0[3] = plane(point, vec3(0.0, -1.0, 0.0), vec3(0.0, 0.0, 0.0)); + returnMe = unionColorDistance(returnMe, plane0); + + #if RENDERHEIGHTFUNCTION + vec4 heightMap0 = vec4(0.6, 0.6, 0.9, -1.0); + heightMap0[3] = heightFunction(point, vec3(0.0, 6.0, 0.0), vec3(3.14159, 0.0, 0.0), vec3(1.0, 1.0, 1.0)); + returnMe = unionColorDistance(returnMe, heightMap0); + #endif + + #if RENDERFRACTAL + vec4 fractal0 = vec4(1.0, 1.0, 0.0, -1.0); + fractal0[3] = fractalMenger(point, vec3(1.0, 0.0, 1.0), vec3(0.0, 0.0, 0.0), vec3(1.5, 1.5, 1.5)); + returnMe = unionColorDistance(returnMe, fractal0); + #endif + + return returnMe; +} + +vec3 computeNormal(in vec3 point) { + // McGuire 8: you can totally use the gradient of the scenegraph distance function. + vec3 epsilon = vec3(EPSILON, 0.0, 0.0); + vec3 returnMe; + returnMe.x = sceneGraphDistanceFunction(point + epsilon.xyz).a - sceneGraphDistanceFunction(point - epsilon.xyz).a; + returnMe.y = sceneGraphDistanceFunction(point + epsilon.yxz).a - sceneGraphDistanceFunction(point - epsilon.yxz).a; + returnMe.z = sceneGraphDistanceFunction(point + epsilon.yzx).a - sceneGraphDistanceFunction(point - epsilon.yzx).a; + return normalize(returnMe); +} + +float hardShadow(in vec3 rayPosition, in vec3 lightPosition) +{ + float stepSize = 0.01; + float t = EPSILON + stepSize + stepSize; + vec3 stepDir = normalize(lightPosition - rayPosition); + float lightDistance = length(lightPosition - rayPosition); + + for (int i = 0; i < 1000; i++){ + float dist = sceneGraphDistanceFunction(rayPosition + stepDir * t)[3]; + if (dist < EPSILON) return 0.0; // shadowed + if (t > lightDistance) break; + if (t > MAXDISTANCE) { + break; + } + t += stepSize; + } + return 1.0; // unshadowed +} + +float softShadow(in vec3 rayPosition, in vec3 lightPosition) +{ + float stepSize = 0.05; // 100 * 0.1 + 1.0 gives us a max distance of 20.0 + float t = EPSILON + stepSize + stepSize; + vec3 stepDir = normalize(lightPosition - rayPosition); + float lightDistance = length(lightPosition - rayPosition); + float shadowTerm = 1.0; // default to unshadowed + + for (int i = 0; i < 1000; i++){ + float dist = sceneGraphDistanceFunction(rayPosition + stepDir * t)[3]; + shadowTerm = min(shadowTerm, 6.0 * dist / t); + if (dist < EPSILON) break; // shadowed + if (t > lightDistance) break; + if (t > MAXDISTANCE) { + break; + } + t += stepSize; + } + return clamp(shadowTerm, 0.0, 1.0); // unshadowed +} + +// based heavily off of IQ's calcAO function +// intuition: sample along the point's normal for proximity to other objects +// the closer other things are, the stronger the AO term. +// not sure 100% why IQ calculates sample points how he does, +// why he has a falloff, why some of his coefficients exist... +// I know these things influence the shape of the AO, but I'm +// not sure exactly how. +float ambientOcclusion(in vec3 position, in vec3 normal) { + float occlusionFactor = 0.0; + float decayFactor = 1.0; + float stepSize = 0.01; + for (int i = 0; i < 5; i++) { + float sampleT = stepSize + EPSILON + 0.12 * float(i) / 4.0; + float sampleDistance = sceneGraphDistanceFunction(position + normal * sampleT)[3]; + occlusionFactor += -(sampleDistance - sampleT) * decayFactor; + decayFactor *= 0.95; + } + return clamp(1.0 - 3.0 * occlusionFactor, 0.0, 1.0); +} + +// returns a t along the ray that hits the first intersection. +// uses naive stepping [McGuire 4] +// returns (r, g, b, distance), but if distance was maxed out, color is all -1 +vec4 castRayNaive(in vec3 rayPosition, in vec3 rayDirection) +{ + float tmin = 1.0; + float stepSize = 0.01; // 100 * 0.1 + 1.0 gives us a max distance of 10.0 + + float t = tmin; + float distance = 1000.0; + vec3 color = vec3(-1.0, -1.0, -1.0); + for (int i = 0; i < 1000; i++) { + vec4 colorAndDistance = sceneGraphDistanceFunction(rayPosition + rayDirection * t); + distance = colorAndDistance[3]; + + if (distance < EPSILON) { + #if DEBUGSTEPS + color = vec3(float(i)); + break; + #endif + color = colorAndDistance.rgb; + break; + } + if (t > MAXDISTANCE) { + #if DEBUGSTEPS + color = vec3(float(i)); + #endif + break; + } + t += stepSize; + } + return vec4(color, t); +} + +// returns a t along the ray that hits the first intersection +// uses spherical stepping [McGuire 6] +vec4 castRaySphere(in vec3 rayPosition, in vec3 rayDirection) +{ + float tmin = 0.0; + + float t = tmin; + float distance; + vec3 color = vec3(-1.0, -1.0, -1.0); + for (int i = 0; i < 100; i++) { + vec4 colorAndDistance = sceneGraphDistanceFunction(rayPosition + rayDirection * t); + distance = colorAndDistance[3]; + + if (distance < EPSILON) { + #if DEBUGSTEPS + color = vec3(float(i)); + break; + #endif + color = colorAndDistance.rgb; + break; + } + if (t > MAXDISTANCE) { + #if DEBUGSTEPS + color = vec3(float(i)); + #endif + break; + } + t += distance; + } + return vec4(color, t);} + +vec3 lambertShade(in vec3 norm, in vec3 position, in vec3 color, in vec3 sunPosition, in vec3 sunColor) { + return dot(normalize(sunPosition - position), norm) * color * sunColor; +} + +// takes in ray origin, ray direction, and sun position +vec3 render(in vec3 ro, in vec3 rd, in vec3 sunPosition, in vec3 sunColor) { + vec4 materialDistance; + #if SPHERESTEP + materialDistance = castRaySphere(ro, rd); + #else + materialDistance = castRayNaive(ro, rd); + #endif + + vec3 position = ro + rd * materialDistance.a; + vec3 norm = computeNormal(position); + + #if DEBUGNORMALS + return norm; + #elif DEBUGDISTANCE + return vec3(1.0 - materialDistance.a / MAXDISTANCE); + #elif DEBUGSTEPS + return vec3(materialDistance / DEBUGSTEPSRANGE); + #endif + + if (materialDistance.r < 0.0 && materialDistance.g < 0.0 && materialDistance.b < 0.0) { + return vec3(0.9, 1.0, 0.9); + } + // lambert term + vec3 shade = lambertShade(norm, position, materialDistance.rgb, sunPosition, sunColor); + + #if !LAMBERT + shade = vec3(1.0); + #endif + + // shadow term + #if SOFTSHADOW + float shadow = softShadow(position, sunPosition); + shade *= shadow; + #endif + // AO term + #if AMBIENTOCCLUSION + float ao = ambientOcclusion(position, norm); + shade *= ao; + #endif + + // ambient term + if (shade.x <= 0.0 && shade.y <= 0.0 && shade.z <= 0.0) { + shade = materialDistance.rgb * sunColor * 0.02; // ambient term + } + return shade; } mat3 setCamera(in vec3 ro, in vec3 ta, float cr) { @@ -28,9 +652,9 @@ void mainImage(out vec4 fragColor, in vec2 fragCoord) { // camera vec3 ro = vec3( -0.5 + 3.5 * cos(0.1 * time + 6.0 * mo.x), - 1.0 + 2.0 * mo.y, - 0.5 + 3.5 * sin(0.1 * time + 6.0 * mo.x)); - vec3 ta = vec3(-0.5, -0.4, 0.5); + 2.0 * mo.y, + 0.5 + 3.5 * sin(0.1 * time + 6.0 * mo.x)); // camera position + vec3 ta = vec3(0.0, 0.0, 0.0); // camera aim // camera-to-world transformation mat3 ca = setCamera(ro, ta, 0.0); @@ -39,7 +663,7 @@ void mainImage(out vec4 fragColor, in vec2 fragCoord) { vec3 rd = ca * normalize(vec3(p.xy, 2.0)); // render - vec3 col = render(ro, rd); + vec3 col = render(ro, rd, vec3(0.0, 4.0, 0.0), vec3(1.0, 1.0, 1.0)); col = pow(col, vec3(0.4545));