diff --git a/README.md b/README.md index ae3da83..954f711 100644 --- a/README.md +++ b/README.md @@ -1,81 +1,20 @@ # CIS 566 Homework 2: Implicit Surfaces +Name : Samantha Lee +PennKey : smlee18 -## Objective -- Gain experience with signed distance functions -- Experiment with animation curves +Inspiration: https://www.youtube.com/watch?t=596&fbclid=IwAR1bL0yg79VJTeRl3MopTtCPIDUA2mSWdGRWKI08Wkx3qX8kYcUEaylCWIo&v=zlZR8nePEOY&feature=youtu.be&ab_channel=PratulDesigns -## Base Code +Live Demo: https://18smlee.github.io/hw02-raymarching-sdfs/ -Please feel free to use this code as a base (https://www.shadertoy.com/view/fsdXzM) +## Cylinders +- Performed opRepLim on a block of cylinders and smooth subtracted them from the floor plane +- Did the same function on a block of cylinders with a slightly smaller radius to give the illusion that they are coming in and out of the floor +- Applied a cosine function to the cylinder heights and floored the input in order to keep the cylinder tops unwarped -The code we have provided for this assignment features the following: -- A square that spans the range [-1, 1] in X and Y that is rendered with a -shader that does not apply a projection matrix to it, thus rendering it as the -entirety of your screen -- TypeScript code just like the code in homework 1 to set up a WebGL framework -- Code that passes certain camera attributes (listed in the next section), -the screen dimensions, and a time counter to the shader program. -## Assignment Requirements -- __(10 points)__ Modify the provided `flat-frag.glsl` to cast rays from a -virtual camera. We have set up uniform variables in your shader that take in -the eye position, reference point position, and up vector of the `Camera` in -the provided TypeScript code, along with a uniform that stores the screen width -and height. Using these uniform variables, and only these uniform variables, -you must write a function that uses the NDC coordinates of the current fragment -(i.e. its fs_Pos value) and projects a ray from that pixel. Refer to the [slides -on ray casting](https://docs.google.com/presentation/d/e/2PACX-1vSN5ntJISgdOXOSNyoHimSVKblnPnL-Nywd6aRPI-XPucX9CeqzIEGTjFTwvmjYUgCglTqgvyP1CpxZ/pub?start=false&loop=false&delayms=60000&slide=id.g27215b64c6_0_107) -from CIS 560 for reference on how to cast a ray without an explicit -view-projection matrix. You'll have to compute your camera's Right vector based -on the provided Up vector, Eye point, and Ref point. You can test your ray -casting function by converting your ray directions to colors using the formula -`color = 0.5 * (dir + vec3(1.0, 1.0, 1.0))`. If your screen looks like the -following image, your rays are being cast correctly: -![](rayDir.png) -- __(70 points)__ Create a scene using raymarched signed distance functions. -The subject of your scene should be based on some reference image, such as a -shot from a movie or a piece of artwork. Your scene should incorporate the -following elements: - - The SDF combination operation Smooth Blend. - - Basic Lambertian reflection using a hard-coded light source and SDF surface normals. - - Animation of at least one element of the scene, with at least two Toolbox Functions - used to control the animation(s). - - Hard-edged shadows cast by shapes in the scene onto one another using a shadow-feeler ray. +## Pendulum +- Smooth blended a sphere and a rod together to create a pendulum +- Applied a smoothstep pulsing function to the y coordinate of the pendulum and a cos to the z coordinate to create a swinging motion -For the next assignment you will build upon this scene with procedural textures and more -advanced lighting and reflection models, so don't worry if your scene looks a bit drab -given the requirements listed above. - -- __(10 points)__ Following the specifications listed -[here](https://github.com/pjcozzi/Articles/blob/master/CIS565/GitHubRepo/README.md), -create your own README.md, renaming this file to INSTRUCTIONS.md. Don't worry -about discussing runtime optimization for this project. Make sure your -README contains the following information: - - Your name and PennKey - - Citation of any external resources you found helpful when implementing this - assignment. - - A link to your live github.io demo (refer to the pinned Piazza post on - how to make a live demo through github.io) - - An explanation of the techniques you used to model and animate your scene. - -## Useful Links -- [IQ's Article on SDFs](http://www.iquilezles.org/www/articles/distfunctions/distfunctions.htm) -- [IQ's Article on Smooth Blending](http://www.iquilezles.org/www/articles/smin/smin.htm) -- [IQ's Article on Useful Functions](http://www.iquilezles.org/www/articles/functions/functions.htm) -- [Breakdown of Rendering an SDF Scene](http://www.iquilezles.org/www/material/nvscene2008/rwwtt.pdf) - - -## Submission -Commit and push to Github, then submit a link to your commit on Canvas. Remember -to make your own README! - -## Inspiration -- [Alien Corridor](https://www.shadertoy.com/view/4slyRs) -- [The Evolution of Motion](https://www.shadertoy.com/view/XlfGzH) -- [Fractal Land](https://www.shadertoy.com/view/XsBXWt) -- [Voxel Edges](https://www.shadertoy.com/view/4dfGzs) -- [Snail](https://www.shadertoy.com/view/ld3Gz2) -- [Cubescape](https://www.shadertoy.com/view/Msl3Rr) -- [Journey Tribute](https://www.shadertoy.com/view/ldlcRf) -- [Stormy Landscape](https://www.shadertoy.com/view/4ts3z2) -- [Generators](https://www.shadertoy.com/view/Xtf3Rn) +## Notes +I really liked the look of the 3D rendered satisfying videos - so I took inspiration from one I found online! In the future I will be timing the swing of the pendulum to follow exactly the wave of the cylinders. I would also like to add more balls to scene to make it a bit more complex looking. diff --git a/images/inspiration.PNG b/images/inspiration.PNG new file mode 100644 index 0000000..42107ea Binary files /dev/null and b/images/inspiration.PNG differ diff --git a/images/satisfying.PNG b/images/satisfying.PNG new file mode 100644 index 0000000..9467984 Binary files /dev/null and b/images/satisfying.PNG differ diff --git a/package-lock.json b/package-lock.json index e1a47a3..2bd6998 100644 --- a/package-lock.json +++ b/package-lock.json @@ -57,12 +57,6 @@ "fastq": "^1.6.0" } }, - "@types/dat.gui": { - "version": "0.7.7", - "resolved": "https://registry.npmjs.org/@types/dat.gui/-/dat.gui-0.7.7.tgz", - "integrity": "sha512-CxLCme0He5Jk3uQwfO/fGZMyNhb/ypANzqX0yU9lviBQMlen5SOvQTBQ/Cd9x5mFlUAK5Tk8RgvTyLj1nYkz+w==", - "dev": true - }, "@types/eslint": { "version": "7.28.0", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-7.28.0.tgz", @@ -709,11 +703,6 @@ "resolved": "https://registry.npmjs.org/cubic-hermite/-/cubic-hermite-1.0.0.tgz", "integrity": "sha1-hOOy8nKzFFToOTuZu2rtRRaMFOU=" }, - "dat.gui": { - "version": "0.7.7", - "resolved": "https://registry.npmjs.org/dat.gui/-/dat.gui-0.7.7.tgz", - "integrity": "sha512-sRl/28gF/XRC5ywC9I4zriATTsQcpSsRG7seXCPnTkK8/EQMIbCu5NPMpICLGxX9ZEUvcXR3ArLYCtgreFoMDw==" - }, "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -2686,11 +2675,6 @@ } } }, - "stats-js": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/stats-js/-/stats-js-1.0.1.tgz", - "integrity": "sha512-EAwEFghGNv8mlYC4CZzI5kWghsnP8uBKXw6VLRHtXkOk5xySfUKLTqTkjgJFfDluIkf/O7eZwi5MXP50VeTbUg==" - }, "statuses": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", diff --git a/src/main.ts b/src/main.ts index fe526f9..c4f1eae 100644 --- a/src/main.ts +++ b/src/main.ts @@ -61,8 +61,8 @@ function main() { // Initial call to load scene loadScene(); - const camera = new Camera(vec3.fromValues(0, 0, -10), vec3.fromValues(0, 0, 0)); - + const camera = new Camera(vec3.fromValues(-6 * 0.8, 7 * 0.8, 10 * 0.8), vec3.fromValues(0, 0, 0)); + const renderer = new OpenGLRenderer(canvas); renderer.setClearColor(164.0 / 255.0, 233.0 / 255.0, 1.0, 1); gl.enable(gl.DEPTH_TEST); diff --git a/src/shaders/flat-frag.glsl b/src/shaders/flat-frag.glsl index 50434bd..79852e3 100644 --- a/src/shaders/flat-frag.glsl +++ b/src/shaders/flat-frag.glsl @@ -8,6 +8,480 @@ uniform float u_Time; in vec2 fs_Pos; out vec4 out_Col; -void main() { - out_Col = vec4(0.5 * (fs_Pos + vec2(1.0)), 0.5 * (sin(u_Time * 3.14159 * 0.01) + 1.0), 1.0); +// Ray Constants +const float PI = 3.141592653589793238; +const int MAX_RAY_STEPS = 150; +const float EPSILON = 0.0001; + +// Useful ray structs +struct Ray +{ + vec3 origin; + vec3 direction; +}; + +struct Intersection +{ + vec3 position; + vec3 normal; + float distance_t; + int material_id; + vec3 color; +}; + +struct DirectionalLight +{ + vec3 dir; + vec3 color; + float ambient; + float specular; // ranges 0 to 1 depending on how much specularity you want + int hasShadow; // 1 if it casts a shadow, 0 if it doesn't cast a shadow +}; + +struct Material +{ + vec3 color; + float specular; + float shininess; +}; + +struct Object +{ + Material material; + int id; +}; + +// Operations + +float opSmoothSubtraction( float d1, float d2, float k ) { + float h = clamp( 0.5 - 0.5*(d2+d1)/k, 0.0, 1.0 ); + return mix( d2, -d1, h ) + k*h*(1.0-h); } + +float smin(float a, float b, float k) +{ + float h = clamp( 0.5+0.5*(b-a)/k, 0.0, 1.0 ); + return mix( b, a, h ) - k*h*(1.0-h); +} + +float opSmoothIntersection( float d1, float d2, float k ) { + float h = clamp( 0.5 - 0.5*(d2-d1)/k, 0.0, 1.0 ); + return mix( d2, d1, h ) + k*h*(1.0-h); +} + +vec3 opRep( in vec3 p, in vec3 c) +{ + vec3 q = mod(p+0.5*c,c)-0.5*c; + return q; +} + +vec3 opRepLim( vec3 pos, vec3 freq, vec3 limitA, vec3 limitB) +{ + vec3 q = pos - freq*clamp(round(pos/freq), limitA, limitB); + return q; +} + +float sawtooth_wave(float x, float freq, float amplitude) +{ + return (x * freq - floor(x * freq)) * amplitude; +} + +float cubicPulse( float c, float w, float x ) +{ + x = abs(x - c); + if (x > w) return 0.0; + x /= w; + return 1.0 - x * x *(3.0 - 3.0 *x); +} + +float pcurve( float x, float a, float b ) +{ + float k = pow(a + b, a + b) / (pow(a, a) * pow(b,b)); + return k * pow(x, a) * pow(1.0 - x, b); +} + +mat3 rotationX( in float angle ) { + return mat3( 1.0, 0, 0, + 0, cos(angle), -sin(angle), + 0, sin(angle), cos(angle)); +} + +mat3 rotationY( in float angle ) { + return mat3( cos(angle), 0, sin(angle), + 0, 1.0, 0, + -sin(angle), 0, cos(angle)); +} + +mat3 rotationZ( in float angle ) { + return mat3( cos(angle), -sin(angle), 0, + sin(angle), cos(angle), 0, + 0, 0, 1.0); +} + +vec3 rgb(vec3 color) { + return vec3(color.x / 255.0, color.y / 255.0, color.z / 255.0); +} + +// SDF objects + +float sdfSphere(vec3 query_position, vec3 position, float radius) +{ + return length(query_position - position) - radius; +} + +float sdfBox(vec3 query_position, vec3 pos, vec3 size) +{ + vec3 q = abs(query_position - pos) - size; + return min(max(q.x,max(q.y,q.z)), 0.0) + length(max(q,0.0)); +} + +float sdfPlane(vec3 query_position, vec3 pos, vec3 norm, float h ) +{ + vec3 q = query_position - pos; + return dot(q,normalize(norm)) + h; +} + +float sdfRoundBox( vec3 query_position, vec3 pos, vec3 size, float round ) +{ + vec3 q = abs(query_position - pos) - size; + return length(max(q,0.0)) + min(max(q.x,max(q.y,q.z)),0.0) - round; +} + +float sdfRoundedCylinder( vec3 query_position, vec3 p, float ra, float rb, float h ) +{ + vec3 q = query_position - p; + vec2 d = vec2( length(q.xz)-2.0*ra+rb, abs(q.y) - h ); + return min(max(d.x,d.y),0.0) + length(max(d,0.0)) - rb; +} + +float sdfCapsule( vec3 query_position, vec3 p, vec3 a, vec3 b, float r ) +{ + vec3 q = query_position - p; + vec3 pa = q - a, ba = b - a; + float h = clamp( dot(pa,ba)/dot(ba,ba), 0.0, 1.0 ); + return length( pa - ba*h ) - r; } + +float sdfPendulum(vec3 query_position, vec3 position, float amp, float radius) +{ + float swingDisplacementY = 1.6; + float swingDisplacementX = 8.0 / 1.8; + + float pulse_a = -1.8; + float pulse_b = 1.0; + float pulse_v = smoothstep(pulse_a, pulse_b,cos( 0.5 * 0.05 * u_Time )); + float pulse_h = amp * cos(0.25 * 0.05 * u_Time ); + + vec3 pendulumBottom = vec3(0.0); + vec3 pendulumTop = vec3(0.0, 8.0, 0.0); + + pendulumBottom = vec3(pendulumBottom.x, pulse_v * swingDisplacementY, pulse_h * swingDisplacementX); + + float cap = sdfCapsule(query_position, position, pendulumBottom, pendulumTop, radius); + float sphere = sdfSphere(query_position, pendulumBottom + position, radius * 10.0); + + return smin(cap, sphere, 0.1); +} + +float sdCappedTorus(vec3 query_position, vec3 pos, vec2 sc, float ra, float rb) +{ + vec3 q = query_position - pos; + q.x = abs(q.x); + float k = (sc.y*q.x>sc.x*q.y) ? dot(q.xy,sc) : length(q.xy); + return sqrt( dot(q,q) + ra*ra - 2.0*ra*k ) - rb; +} + +float sdfGong(vec3 query_position, vec3 pos, mat3 gongTransform, out float innerGong, out float baseGong, out float beams) { + float gongRadius = 0.7; + float gongDepth = 0.1; + + vec3 gongPos = gongTransform * pos; + vec3 gongQuery = gongTransform * query_position; + baseGong = sdfRoundedCylinder(gongQuery, gongPos, gongRadius, 0.05, gongDepth); + innerGong = sdfRoundedCylinder(gongQuery, vec3(gongPos.x, gongPos.y - 0.1, gongPos.z), gongRadius * 0.7, 0.05, gongDepth * 1.2); + + vec3 beamSize_h = vec3(2.3, 0.1, 0.15); + vec3 beamSize_v = vec3(0.08, 3.0, 0.08); + float beam_h1 = sdfRoundBox(query_position, vec3(pos.x, pos.y - 2.0, pos.z), beamSize_h, 0.03); + float beam_h2 = sdfRoundBox(query_position, vec3(pos.x, pos.y + 2.0, pos.z), beamSize_h, 0.03); + float beam_h3 = sdfRoundBox(query_position, vec3(pos.x, pos.y + 2.6, pos.z), beamSize_h, 0.03); + float beam_v1 = sdfRoundBox(query_position, vec3(pos.x - 1.8, pos.y, pos.z), beamSize_v, 0.03); + float beam_v2 = sdfRoundBox(query_position, vec3(pos.x + 1.8, pos.y, pos.z), beamSize_v, 0.03); + + float gong = min(baseGong, innerGong); + float beamsHorizontal = min(beam_h1, min(beam_h2, beam_h3)); + float beamsVertical = min(beam_v1, beam_v2); + beams = smin(beamsVertical, beamsHorizontal, 0.05); + return min(gong, beams); +} + +float sceneSDF(vec3 queryPos, out Object obj) +{ + vec3 center = vec3(0.0, 0.0, 0.0); + vec3 floorPos = vec3(0.0, 0.0, 0.0); + float floorHeight = 1.0; + float floorPlane = sdfPlane(queryPos, floorPos, vec3(0.0, 1.0, 0.0), floorHeight); + float zPlane = sdfPlane(queryPos, vec3(floorPos.x + 50.0, floorPos.y, floorPos.z), vec3(-1.0, 0.0, 0.0), floorHeight); + float yPlane = sdfPlane(queryPos, vec3(floorPos.x, floorPos.y, floorPos.z - 50.0), vec3(0.0, 0.0, 1.0), floorHeight); + float walls = smin(yPlane, zPlane, 0.5); + float floorAndWalls = smin(floorPlane, walls, 0.5); + + obj.id = 1; + + // Implement bounding sphere to optimize + float bounding_box= sdfBox(queryPos, center, vec3(10.0)); + if (bounding_box < EPSILON) { + + // Repeating box size parameters + float cylinderBlockSize = 4.0; + + // Set bounds of the repeating shelf blocks + vec3 limitA = vec3(-1.0, 0.0, -1.0) * cylinderBlockSize; + vec3 limitB = vec3(1.0, 0.0, 1.0) * cylinderBlockSize; + float repSpace = 0.9; + vec3 repeatingPos = opRepLim(queryPos, vec3(repSpace), limitA, limitB); + + vec3 floorCylinder_p = vec3(0.0, -1.0, 0.0); + float floorCylinder_h = 2.0; + float floorCylinder_r = 0.15; + float floorCylinder_edge = 0.1; + float floorCylinders = sdfRoundedCylinder(repeatingPos, floorCylinder_p, floorCylinder_r, floorCylinder_edge, floorCylinder_h); + + vec3 cylinder_p = vec3(0.0, -1.0, 0.0); + float cylinder_r = floorCylinder_r * 0.9; + float stair_length = repSpace; + float stair_freq = 0.5; + float stair_val = 0.05 * u_Time + floor((queryPos.x - (repSpace + cylinder_r * 2.0) - 0.02) / stair_length) + 2.0; + float dynamic_height = cos(stair_freq* stair_val); + float cylinder_h = 1.0 + dynamic_height; + float cylinder_edge = 0.05; + + float cylinders = sdfRoundedCylinder(repeatingPos, cylinder_p, cylinder_r, cylinder_edge, cylinder_h); + + // Create a swinging pendulum + vec3 pendulumPos = vec3(0.0, 0.0, 0.0); + float pendulum = sdfPendulum(queryPos, pendulumPos, 1.1, 0.05); + + // Create gongs + vec3 gong1Pos = vec3(0.0, 1.5, 5.6); + mat3 gong1Transform = rotationX(PI/2.0); + vec3 gong2Pos = vec3(0.0, 1.5, -5.6); + mat3 gong2Transform = rotationX(3.0 * PI / 2.0); + + float innerGong1; + float baseGong1; + float beams1; + float gong1 = sdfGong(queryPos, gong1Pos, gong1Transform, innerGong1, baseGong1, beams1); + + float innerGong2; + float baseGong2; + float beams2; + float gong2 = sdfGong(queryPos, gong2Pos, gong2Transform, innerGong2, baseGong2, beams2); + + // Put it all together + floorPlane = opSmoothSubtraction(floorCylinders, floorPlane, 0.05); + floorAndWalls = smin(floorPlane, walls, 1.0); + float allCylinders = min(cylinders, floorAndWalls); + float innerGongs = min(innerGong1, innerGong2); + float baseGongs = min(baseGong1, baseGong2); + float beams = min(beams1, beams2); + float gongs = min(gong1, gong2); + float pendulumAndGongs = min(pendulum, gongs); + + // Assign object materials + float final = min(allCylinders, pendulumAndGongs); + + if (final == floorPlane) { obj.id = 1;} + else if (final == cylinders) {obj.id = 2;} + else if (final == innerGongs) {obj.id = 3;} + else if (final == baseGongs) {obj.id = 4;} + else if (final == beams) {obj.id = 5;} + else if (final == pendulum) {obj.id = 6;} + + return final; + } + return floorAndWalls; +} + +Ray getRay(vec2 uv) +{ + Ray r; + + vec3 look = normalize(u_Ref - u_Eye); + vec3 camera_RIGHT = cross(look, u_Up); + + float FOV = PI / 4.0; + + float aspect_ratio = u_Dimensions.x / u_Dimensions.y; + vec3 screen_vertical = u_Up * tan(FOV); + vec3 screen_horizontal = camera_RIGHT * aspect_ratio * tan(FOV); + vec3 screen_point = (look + uv.x * screen_horizontal + uv.y * screen_vertical); + + r.origin = u_Eye; + r.direction = normalize(screen_point - u_Ref); + + return r; +} + +vec3 estimateNormal(vec3 p, out Object obj) +{ + vec2 d = vec2(0.0, EPSILON); + float x = sceneSDF(p + d.yxx, obj) - sceneSDF(p - d.yxx, obj); + float y = sceneSDF(p + d.xyx, obj) - sceneSDF(p - d.xyx, obj); + float z = sceneSDF(p + d.xxy, obj) - sceneSDF(p - d.xxy, obj); + + return normalize(vec3(x, y, z)); +} + +// Light and Shadows +float shadow(vec3 ro, vec3 rd, float maxt, float k, out Object obj) +{ + float res = 1.0; + for(float t=0.015; t 0.0) + { + return intersection.color; + } + + return vec3(0.0f); +} + +void main() { + vec2 uv = vec2(fs_Pos.x, fs_Pos.y); + out_Col = vec4(getSceneColor(uv), 1.0); + Ray r = getRay(uv); + //out_Col = vec4(0.5 * (r.direction + vec3(1.0, 1.0, 1.0)), 1.0); +} \ No newline at end of file