diff --git a/README.md b/README.md index 06fcfd4..67bd45b 100644 --- a/README.md +++ b/README.md @@ -1,373 +1,56 @@ -------------------------------------------------------------------------------- CIS565: Project 5: WebGL ------------------------------------------------------------------------------- -Fall 2013 -------------------------------------------------------------------------------- -Due Friday 11/08/2013 -------------------------------------------------------------------------------- - -------------------------------------------------------------------------------- -NOTE: -------------------------------------------------------------------------------- -This project requires any graphics card with support for a modern OpenGL -pipeline. Any AMD, NVIDIA, or Intel card from the past few years should work -fine, and every machine in the SIG Lab and Moore 100 is capable of running -this project. - -This project also requires a WebGL capable browser. The project is known to -have issues with Chrome on windows, but Firefox seems to run it fine. +![Part 1 Results](resources/waveResult.png) +![Part 2 Results](resources/globeResult.png) -------------------------------------------------------------------------------- INTRODUCTION: ------------------------------------------------------------------------------- -In this project, you will get introduced to the world of GLSL in two parts: -vertex shading and fragment shading. The first part of this project is the -Image Processor, and the second part of this project is a Wave Vertex Shader. - -In the first part of this project, you will implement a GLSL vertex shader as -part of a WebGL demo. You will create a dynamic wave animation using code that -runs entirely on the GPU. - -In the second part of this project, you will implement a GLSL fragment shader -to render an interactive globe in WebGL. This will include texture blending, -bump mapping, specular masking, and adding a cloud layer to give your globe a -uniquie feel. - -------------------------------------------------------------------------------- -CONTENTS: -------------------------------------------------------------------------------- -The Project4 root directory contains the following subdirectories: - -* part1/ contains the base code for the Wave Vertex Shader. -* part2/ contains the base code for the Globe Fragment Shader. -* resources/ contains the screenshots found in this readme file. +This project contains 2 main portions. The first part is a visualization of various math functions. +The second portion is an interactive globe with orbiting moon and skybox. Read on for more details! +PART 1 - VISUALIZING FUNCTIONS ------------------------------------------------------------------------------- -PART 1 REQUIREMENTS: -------------------------------------------------------------------------------- - -In Part 1, you are given code for: - -* Drawing a VBO through WebGL -* Javascript code for interfacing with WebGL -* Functions for generating simplex noise - -You are required to implement the following: - -* A sin-wave based vertex shader: - -![Example sin wave grid](resources/sinWaveGrid.png) - -* A simplex noise based vertex shader: - -![Example simplex noise wave grid](resources/oceanWave.png) - -* One interesting vertex shader of your choice - -------------------------------------------------------------------------------- -PART 1 WALKTHROUGH: -------------------------------------------------------------------------------- -**Sin Wave** +For this part, I have implemented shaders for the following using glsl: +* A sin-wave based vertex shader +* A simplex noise based vertex shader +* A custom vertex shader that behaves like waves -* For this assignment, you will need the latest version of Firefox. -* Begin by opening index.html. You should see a flat grid of black and white - lines on the xy plane: +Click the links below for live demos: -![Example boring grid](resources/emptyGrid.png) +* [Sin/Cos](http://mikeychen.net/WebGL/MathFunctionsVisualization/vert_wave.html) -* In this assignment, you will animate the grid in a wave-like pattern using a - vertex shader, and determine each vertex’s color based on its height, as seen - in the example in the requirements. -* The vertex and fragment shader are located in script tags in `index.html`. -* The JavaScript code that needs to be modified is located in `index.js`. -* Required shader code modifications: - * Add a float uniform named u_time. - * Modify the vertex’s height using the following code: +* [Simplex Noise](http://mikeychen.net/WebGL/MathFunctionsVisualization/simplex_wave.html) - ```glsl - float s_contrib = sin(position.x*2.0*3.14159 + u_time); - float t_contrib = cos(position.y*2.0*3.14159 + u_time); - float height = s_contrib*t_contrib; - ``` +* [Waves](http://mikeychen.net/WebGL/MathFunctionsVisualization/custom_wave.html) - * Use the GLSL mix function to blend together two colors of your choice based - on the vertex’s height. The lowest possible height should be assigned one - color (for example, `vec3(1.0, 0.2, 0.0)`) and the maximum height should be - another (`vec3(0.0, 0.8, 1.0)`). Use a varying variable to pass the color to - the fragment shader, where you will assign it `gl_FragColor`. -* Required JavaScript code modifications: - * A floating-point time value should be increased every animation step. - Hint: the delta should be less than one. - * To pass the time to the vertex shader as a uniform, first query the location - of `u_time` using `context.getUniformLocation` in `initializeShader()`. - Then, the uniform’s value can be set by calling `context.uniform1f` in - `animate()`. - -**Simplex Wave** - -* Now that you have the sin wave working, create a new copy of `index.html`. - Call it `index_simplex.html`, or something similar. -* Open up `simplex.vert`, which contains a compact GLSL simplex noise - implementation, in a text editor. Copy and paste the functions included - inside into your `index_simplex.html`'s vertex shader. -* Try changing s_contrib and t_contrib to use simplex noise instead of sin/cos - functions with the following code: - -```glsl -vec2 simplexVec = vec2(u_time, position); -float s_contrib = snoise(simplexVec); -float t_contrib = snoise(vec2(s_contrib,u_time)); -``` - -**Wave Of Your Choice** - -* Create another copy of `index.html`. Call it `index_custom.html`, or - something similar. -* Implement your own interesting vertex shader! In your README.md with your - submission, describe your custom vertex shader, what it does, and how it - works. - -------------------------------------------------------------------------------- -PART 2 REQUIREMENTS: +PART 2 - GLOBE ------------------------------------------------------------------------------- -In Part 2, you are given code for: - -* Reading and loading textures -* Rendering a sphere with textures mapped on -* Basic passthrough fragment and vertex shaders -* A basic globe with Earth terrain color mapping -* Gamma correcting textures -* javascript to interact with the mouse - * left-click and drag moves the camera around - * right-click and drag moves the camera in and out - -You are required to implement: +For this part, I have implemented the following features: * Bump mapped terrain * Rim lighting to simulate atmosphere * Night-time lights on the dark side of the globe * Specular mapping * Moving clouds +* Orbiting Moon with texture mapping and bump mapping +* Skybox around the entire scene -You are also required to pick one open-ended effect to implement: - -* Procedural water rendering and animation using noise -* Shade based on altitude using the height map -* Cloud shadows via ray-tracing through the cloud map in the fragment shader -* Orbiting Moon with texture mapping and shadow casting onto Earth -* Draw a skybox around the entire scene for the stars. -* Your choice! Email Liam and Patrick to get approval first - -Finally in addition to your readme, you must also set up a gh-pages branch -(explained below) to expose your beautiful WebGL globe to the world. - -Some examples of what your completed globe renderer will look like: - -![Completed globe, day side](resources/globe_day.png) - -Figure 0. Completed globe renderer, daylight side. - -![Completed globe, twilight](resources/globe_twilight.png) - -Figure 1. Completed globe renderer, twilight border. - -![Completed globe, night side](resources/globe_night.png) - -Figure 2. Completed globe renderer, night side. - -------------------------------------------------------------------------------- -PART 2 WALKTHROUGH: -------------------------------------------------------------------------------- - -Open part2/frag_globe.html in Firefox to run it. You’ll see a globe -with Phong lighting like the one in Figure 3. All changes you need to make -will be in the fragment shader portion of this file. - -![Initial globe](resources/globe_initial.png) - -Figure 3. Initial globe with diffuse and specular lighting. - -**Night Lights** +Click the link below for a live demo: -The backside of the globe not facing the sun is completely black in the -initial globe. Use the `diffuse` lighting component to detect if a fragment -is on this side of the globe, and, if so, shade it with the color from the -night light texture, `u_Night`. Do not abruptly switch from day to night; -instead use the `GLSL mix` function to smoothly transition from day to night -over a reasonable period. The resulting globe will look like Figure 4. -Consider brightening the night lights by multiplying the value by two. +* [Globe](http://mikeychen.net/WebGL/Globe/frag_globe.html) -The base code shows an example of how to gamma correct the nighttime texture: - -```glsl -float gammaCorrect = 1/1.2; -vec4 nightColor = pow(texture2D(u_Night, v_Texcoord), vec4(gammaCorrect)); -``` - -Feel free to play with gamma correcting the night and day textures if you -wish. Find values that you think look nice! - -![Day/Night without specular mapping](resources/globe_nospecmap.png) - -Figure 4. Globe with night lights and day/night blending at dusk/dawn. - -**Specular Map** - -Our day/night color still shows specular highlights on landmasses, which -should only be diffuse lit. Only the ocean should receive specular highlights. -Use `u_EarthSpec` to determine if a fragment is on ocean or land, and only -include the specular component if it is in ocean. - -![Day/Night with specular mapping](resources/globe_specmap.png) - -Figure 5. Globe with specular map. Compare to Figure 4. Here, the specular -component is not used when shading the land. - -**Clouds** - -In day time, clouds should be diffuse lit. Use `u_Cloud` to determine the -cloud color, and `u_CloudTrans` and `mix` to determine how much a daytime -fragment is affected by the day diffuse map or cloud color. See Figure 6. - -In night time, clouds should obscure city lights. Use `u_CloudTrans` and `mix` -to blend between the city lights and solid black. See Figure 7. - -Animate the clouds by offseting the `s` component of `v_Texcoord` by `u_time` -when reading `u_Cloud` and `u_CloudTrans`. - -![Day with clouds](resources/globe_daycloud.png) - -Figure 6. Clouds with day time shading. - -![Night with clouds](resources/globe_nightcloud.png) - -Figure 7. Clouds observing city nights on the dark side of the globe. - -**Bump Mapping** - -Add the appearance of mountains by perturbing the normal used for diffuse -lighting the ground (not the clouds) by using the bump map texture, `u_Bump`. -This texture is 1024x512, and is zero when the fragment is at sea-level, and -one when the fragment is on the highest mountain. Read three texels from this -texture: once using `v_Texcoord`; once one texel to the right; and once one -texel above. Create a perturbed normal in tangent space: - -`normalize(vec3(center - right, center - top, 0.2))` - -Use `eastNorthUpToEyeCoordinates` to transform this normal to eye coordinates, -normalize it, then use it for diffuse lighting the ground instead of the -original normal. - -![Globe with bump mapping](resources/globe_bumpmap.png) - -Figure 8. Bump mapping brings attention to mountains. - -**Rim Lighting** - -Rim lighting is a simple post-processed lighting effect we can apply to make -the globe look as if it has an atmospheric layer catching light from the sun. -Implementing rim lighting is simple; we being by finding the dot product of -`v_Normal` and `v_Position`, and add 1 to the dot product. We call this value -our rim factor. If the rim factor is greater than 0, then we add a blue color -based on the rim factor to the current fragment color. You might use a color -something like `vec4(rim/4, rim/2, rim/2, 1)`. If our rim factor is not greater -than 0, then we leave the fragment color as is. Figures 0,1 and 2 show our -finished globe with rim lighting. - -For more information on rim lighting, -read http://www.fundza.com/rman_shaders/surface/fake_rim/fake_rim1.html. - -------------------------------------------------------------------------------- -GH-PAGES -------------------------------------------------------------------------------- -Since this assignment is in WebGL you will make your project easily viewable by -taking advantage of GitHub's project pages feature. - -Once you are done you will need to create a new branch named gh-pages: - -`git branch gh-pages` - -Switch to your new branch: - -`git checkout gh-pages` - -Create an index.html file that is either your renamed frag_globe.html or -contains a link to it, commit, and then push as usual. Now you can go to - -`.github.io/` - -to see your beautiful globe from anywhere. - -------------------------------------------------------------------------------- -README -------------------------------------------------------------------------------- -All students must replace or augment the contents of this Readme.md in a clear -manner with the following: - -* A brief description of the project and the specific features you implemented. -* At least one screenshot of your project running. -* A 30 second or longer video of your project running. To create the video you - can use http://www.microsoft.com/expression/products/Encoder4_Overview.aspx -* A performance evaluation (described in detail below). - -------------------------------------------------------------------------------- PERFORMANCE EVALUATION ------------------------------------------------------------------------------- -The performance evaluation is where you will investigate how to make your -program more efficient using the skills you've learned in class. You must have -performed at least one experiment on your code to investigate the positive or -negative effects on performance. - -We encourage you to get creative with your tweaks. Consider places in your code -that could be considered bottlenecks and try to improve them. - -Each student should provide no more than a one page summary of their -optimizations along with tables and or graphs to visually explain any -performance differences. +I used a simple timing mechanism to time the execution duration (rendering) of each feature in part 2. +Below are the results. -------------------------------------------------------------------------------- -THIRD PARTY CODE POLICY -------------------------------------------------------------------------------- -* Use of any third-party code must be approved by asking on the Google groups. - If it is approved, all students are welcome to use it. Generally, we approve - use of third-party code that is not a core part of the project. For example, - for the ray tracer, we would approve using a third-party library for loading - models, but would not approve copying and pasting a CUDA function for doing - refraction. -* Third-party code must be credited in README.md. -* Using third-party code without its approval, including using another - student's code, is an academic integrity violation, and will result in you - receiving an F for the semester. +| Earth | Moon | Skybox | +| ----- | ---- | ------ | +| 23ms | 8ms | 4ms | +REFERENCES ------------------------------------------------------------------------------- -SELF-GRADING -------------------------------------------------------------------------------- -* On the submission date, email your grade, on a scale of 0 to 100, to Liam, - liamboone@gmail.com, with a one paragraph explanation. Be concise and - realistic. Recall that we reserve 30 points as a sanity check to adjust your - grade. Your actual grade will be (0.7 * your grade) + (0.3 * our grade). We - hope to only use this in extreme cases when your grade does not realistically - reflect your work - it is either too high or too low. In most cases, we plan - to give you the exact grade you suggest. -* Projects are not weighted evenly, e.g., Project 0 doesn't count as much as - the path tracer. We will determine the weighting at the end of the semester - based on the size of each project. - - ---- -SUBMISSION ---- -As with the previous project, you should fork this project and work inside of -your fork. Upon completion, commit your finished project back to your fork, and -make a pull request to the master repository. You should include a README.md -file in the root directory detailing the following - -* A brief description of the project and specific features you implemented -* At least one screenshot of your project running. -* A link to a video of your project running. -* Instructions for building and running your project if they differ from the - base code. -* A performance writeup as detailed above. -* A list of all third-party code used. -* This Readme file edited as described above in the README section. +For doing rim lighting, [this article](http://www.fundza.com/rman_shaders/surface/fake_rim/fake_rim1.html) was referred to. I also consulted +a [General WebGL Tutorial](https://developer.mozilla.org/en-US/docs/Web/WebGL) found here. Finally, the skybox images were generated with +[Spacescape](http://sourceforge.net/projects/spacescape/). diff --git a/part1/WE-settings.xml b/part1/WE-settings.xml new file mode 100644 index 0000000..a903ae8 --- /dev/null +++ b/part1/WE-settings.xml @@ -0,0 +1,102 @@ + + + + false + false + false + true + true + true + true + true + false + true + true + 1 + false + true + false + true + true + false + false + false + false + true + true + true + false + true + true + true + true + false + true + false + true + false + false + false + true + true + false + true + false + true + false + false + true + false + false + false + false + -1 + false + true + false + false + true + true + false + 50 + false + false + false + false + false + false + false + false + false + false + false + false + false + true + false + false + false + false + true + false + false + false + false + false + false + false + false + false + true + 2 + true + false + True + True + True + True + True + True + false + + \ No newline at end of file diff --git a/part1/custom_wave.html b/part1/custom_wave.html new file mode 100644 index 0000000..8b1f696 --- /dev/null +++ b/part1/custom_wave.html @@ -0,0 +1,49 @@ + + + +Custom Wave + + + + + +
+ + + + + + + + + + + + diff --git a/part1/part1.sln b/part1/part1.sln new file mode 100644 index 0000000..34c1a2e --- /dev/null +++ b/part1/part1.sln @@ -0,0 +1,41 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2012 +Project("{E24C65DC-7377-472B-9ABA-BC803B73C61A}") = "part1", "http://localhost:57772", "{966EC6A4-83C7-4DA0-AD4D-2794E42A56CE}" + ProjectSection(WebsiteProperties) = preProject + UseIISExpress = "true" + TargetFrameworkMoniker = ".NETFramework,Version%3Dv4.0" + Debug.AspNetCompiler.VirtualPath = "/localhost_57772" + Debug.AspNetCompiler.PhysicalPath = "D:\Dropbox\UPenn\CIS-565 (GPU Programming)\Assignments\Project5-WebGL\part1\" + Debug.AspNetCompiler.TargetPath = "PrecompiledWeb\localhost_57772\" + Debug.AspNetCompiler.Updateable = "true" + Debug.AspNetCompiler.ForceOverwrite = "true" + Debug.AspNetCompiler.FixedNames = "false" + Debug.AspNetCompiler.Debug = "True" + Release.AspNetCompiler.VirtualPath = "/localhost_57772" + Release.AspNetCompiler.PhysicalPath = "D:\Dropbox\UPenn\CIS-565 (GPU Programming)\Assignments\Project5-WebGL\part1\" + Release.AspNetCompiler.TargetPath = "PrecompiledWeb\localhost_57772\" + Release.AspNetCompiler.Updateable = "true" + Release.AspNetCompiler.ForceOverwrite = "true" + Release.AspNetCompiler.FixedNames = "false" + Release.AspNetCompiler.Debug = "False" + SlnRelativePath = "D:\Dropbox\UPenn\CIS-565 (GPU Programming)\Assignments\Project5-WebGL\part1\" + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{0DAD7304-27EA-4F17-B077-FC3E29C5660A}" + ProjectSection(SolutionItems) = preProject + WE-settings.xml = WE-settings.xml + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {966EC6A4-83C7-4DA0-AD4D-2794E42A56CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {966EC6A4-83C7-4DA0-AD4D-2794E42A56CE}.Debug|Any CPU.Build.0 = Debug|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/part1/part1.v11.suo b/part1/part1.v11.suo new file mode 100644 index 0000000..3b7bf1b Binary files /dev/null and b/part1/part1.v11.suo differ diff --git a/part1/simplex_wave.html b/part1/simplex_wave.html new file mode 100644 index 0000000..10c8e6d --- /dev/null +++ b/part1/simplex_wave.html @@ -0,0 +1,89 @@ + + + +Simplex Wave + + + + + +
+ + + + + + + + + + + + diff --git a/part1/vert_wave.html b/part1/vert_wave.html index 57107ca..ea887d9 100644 --- a/part1/vert_wave.html +++ b/part1/vert_wave.html @@ -14,21 +14,30 @@ attribute vec2 position; uniform mat4 u_modelViewPerspective; - + uniform float u_time; + + varying vec4 color; + void main(void) { - float height = 0.0; + float s_contrib = sin(position.x*2.0*3.14159 + 2.0*u_time) / 1.2; + float t_contrib = cos(position.y*2.0*3.14159 + 2.0*u_time) / 1.2; + float height = s_contrib * t_contrib; gl_Position = u_modelViewPerspective * vec4(vec3(position, height), 1.0); + + float offsetHeight = height + 1.0; + float alpha = offsetHeight / 2.0; + color = mix(vec4(0.5, 1.0, 0.3, 1.0), vec4(0.5, 0.8, 1.0, 1.0), alpha); } diff --git a/part1/vert_wave.js b/part1/vert_wave.js index b90b9cf..e8ea282 100644 --- a/part1/vert_wave.js +++ b/part1/vert_wave.js @@ -3,9 +3,10 @@ /*global window,document,Float32Array,Uint16Array,mat4,vec3,snoise*/ /*global getShaderSource,createWebGLContext,createProgram*/ - var NUM_WIDTH_PTS = 32; - var NUM_HEIGHT_PTS = 32; - + var NUM_WIDTH_PTS = 80; + var NUM_HEIGHT_PTS = 80; + var delta = 0.005; + var time = 0.0; var message = document.getElementById("message"); var canvas = document.getElementById("canvas"); var context = createWebGLContext(canvas, message); @@ -21,7 +22,6 @@ var persp = mat4.create(); mat4.perspective(45.0, 0.5, 0.1, 100.0, persp); - var eye = [2.0, 1.0, 3.0]; var center = [0.0, 0.0, 0.0]; var up = [0.0, 0.0, 1.0]; @@ -31,6 +31,7 @@ var positionLocation = 0; var heightLocation = 1; var u_modelViewPerspectiveLocation; + var u_timeLocation; (function initializeShader() { var program; @@ -40,7 +41,7 @@ var program = createProgram(context, vs, fs, message); context.bindAttribLocation(program, positionLocation, "position"); u_modelViewPerspectiveLocation = context.getUniformLocation(program,"u_modelViewPerspective"); - + u_timeLocation = context.getUniformLocation(program,"u_time"); context.useProgram(program); })(); @@ -136,12 +137,14 @@ var mv = mat4.create(); mat4.multiply(view, model, mv); var mvp = mat4.create(); + mat4.multiply(persp, mv, mvp); - + time = time + delta; + /////////////////////////////////////////////////////////////////////////// // Render context.clear(context.COLOR_BUFFER_BIT | context.DEPTH_BUFFER_BIT); - + context.uniform1f(u_timeLocation, time); context.uniformMatrix4fv(u_modelViewPerspectiveLocation, false, mvp); context.drawElements(context.LINES, numberOfIndices, context.UNSIGNED_SHORT,0); diff --git a/part2/debug skybox/purpleNebula_back6.png b/part2/debug skybox/purpleNebula_back6.png new file mode 100644 index 0000000..63fd3d5 Binary files /dev/null and b/part2/debug skybox/purpleNebula_back6.png differ diff --git a/part2/debug skybox/purpleNebula_bottom4.png b/part2/debug skybox/purpleNebula_bottom4.png new file mode 100644 index 0000000..0b0ffd2 Binary files /dev/null and b/part2/debug skybox/purpleNebula_bottom4.png differ diff --git a/part2/debug skybox/purpleNebula_front5.png b/part2/debug skybox/purpleNebula_front5.png new file mode 100644 index 0000000..45b1c15 Binary files /dev/null and b/part2/debug skybox/purpleNebula_front5.png differ diff --git a/part2/debug skybox/purpleNebula_left2.png b/part2/debug skybox/purpleNebula_left2.png new file mode 100644 index 0000000..ae79698 Binary files /dev/null and b/part2/debug skybox/purpleNebula_left2.png differ diff --git a/part2/debug skybox/purpleNebula_right1.png b/part2/debug skybox/purpleNebula_right1.png new file mode 100644 index 0000000..502e4bc Binary files /dev/null and b/part2/debug skybox/purpleNebula_right1.png differ diff --git a/part2/debug skybox/purpleNebula_top3.png b/part2/debug skybox/purpleNebula_top3.png new file mode 100644 index 0000000..06aa3e5 Binary files /dev/null and b/part2/debug skybox/purpleNebula_top3.png differ diff --git a/part2/debug skybox/test.docx b/part2/debug skybox/test.docx new file mode 100644 index 0000000..c65e067 Binary files /dev/null and b/part2/debug skybox/test.docx differ diff --git a/part2/frag_globe.html b/part2/frag_globe.html index 6aa5609..e68d33d 100644 --- a/part2/frag_globe.html +++ b/part2/frag_globe.html @@ -77,21 +77,57 @@ vec3 normal = normalize(v_Normal); // normalized eye-to-position vector in camera coordinates vec3 eyeToPosition = normalize(v_Position); - - float diffuse = clamp(dot(u_CameraSpaceDirLight, normal), 0.0, 1.0); + + // retrieve normals from bump map + float center = length(texture2D(u_Bump, v_Texcoord)); + float right = length(texture2D(u_Bump, vec2(v_Texcoord.s + 1.0/1024.0, v_Texcoord.t))); + float top = length(texture2D(u_Bump, vec2(v_Texcoord.s, v_Texcoord.t + 1.0/512.0))); + vec3 perturbedNormal = normalize(vec3(center - right, center - top, 0.2)); + perturbedNormal = normalize(eastNorthUpToEyeCoordinates(v_positionMC, normal) * perturbedNormal); + + float diffuse = clamp(dot(u_CameraSpaceDirLight, perturbedNormal), 0.0, 1.0); vec3 toReflectedLight = reflect(-u_CameraSpaceDirLight, normal); float specular = max(dot(toReflectedLight, -eyeToPosition), 0.0); specular = pow(specular, 20.0); - float gammaCorrect = 1.0/1.2; //gamma correct by 1/1.2 + float gammaCorrect = 1.8/2.2; //gamma correct by 1/1.2 vec3 dayColor = texture2D(u_DayDiffuse, v_Texcoord).rgb; vec3 nightColor = texture2D(u_Night, v_Texcoord).rgb; //apply gamma correction to nighttime texture nightColor = pow(nightColor,vec3(gammaCorrect)); - vec3 color = ((0.6 * diffuse) + (0.4 * specular)) * dayColor; + + // retrieving cloud colors + vec2 cloudTexcoord = v_Texcoord + vec2(u_time * 0.35, 0.0); + vec3 cloudColor = texture2D(u_Cloud, cloudTexcoord).rgb; + vec3 cloudTrans = texture2D(u_CloudTrans, cloudTexcoord).rgb; + + // retrieve specular map + vec3 specVal = texture2D(u_EarthSpec, v_Texcoord).rgb; + + vec3 color = vec3(0,0,0); + float dayColorDSCoeff = ((0.6 * diffuse) + (0.4 * specular)); + float dayColorDiffuseCoeff = diffuse; + float dayColorCoeff = 1.0; + + // mix + dayColorCoeff = mix(dayColorDiffuseCoeff, dayColorDSCoeff, length(specVal)); // mix diffuse and diffuse + specular coeff + color = mix(nightColor, dayColorCoeff*dayColor, diffuse*2.0); // mix between night and day colors + vec3 dayTimeCloud = mix(cloudColor, color, cloudTrans); + vec3 nightTimeCloud = mix(vec3(0,0,0), color, cloudTrans); + vec3 mixedCloud = mix(nightTimeCloud, dayTimeCloud , diffuse*2.0); + color = mix(mixedCloud, color, cloudTrans); + + + // rim lighting + float rimFactor = dot(v_Normal, v_Position) + 1.3; + + if (rimFactor > 0.0) + color = color + vec3(rimFactor/4.0, rimFactor/2.0, rimFactor/2.0); + + gl_FragColor = vec4(color, 1.0); } @@ -111,6 +147,128 @@ } + + + + + + + + + + diff --git a/part2/frag_globe.js b/part2/frag_globe.js index 1d8a877..8ca9c2c 100644 --- a/part2/frag_globe.js +++ b/part2/frag_globe.js @@ -40,6 +40,22 @@ var view = mat4.create(); mat4.lookAt(eye, center, up, view); + // earth vertex and fragment shaders uniforms + var u_DayDiffuseLocation; + var u_NightLocation; + var u_CloudLocation; + var u_CloudTransLocation; + var u_EarthSpecLocation; + var u_BumpLocation; + + // moon vertex and fragment shaders uniforms + var u_MoonDiffuseLocation; + var u_MoonBumpLocation; + + // skybox + var u_texSamplerLocation; + + // shared var positionLocation; var normalLocation; var texCoordLocation; @@ -48,15 +64,9 @@ var u_ViewLocation; var u_PerspLocation; var u_CameraSpaceDirLightLocation; - var u_DayDiffuseLocation; - var u_NightLocation; - var u_CloudLocation; - var u_CloudTransLocation; - var u_EarthSpecLocation; - var u_BumpLocation; var u_timeLocation; - - (function initializeShader() { + + function initializeEarthShader() { var vs = getShaderSource(document.getElementById("vs")); var fs = getShaderSource(document.getElementById("fs")); @@ -78,14 +88,42 @@ u_CameraSpaceDirLightLocation = gl.getUniformLocation(program,"u_CameraSpaceDirLight"); gl.useProgram(program); - })(); + } + + function initializeMoonShader() { + var vs = getShaderSource(document.getElementById("moonVS")); + var fs = getShaderSource(document.getElementById("moonFS")); + + var program = createProgram(gl, vs, fs, message); + positionLocation = gl.getAttribLocation(program, "Position"); + normalLocation = gl.getAttribLocation(program, "Normal"); + texCoordLocation = gl.getAttribLocation(program, "Texcoord"); + u_ModelLocation = gl.getUniformLocation(program, "u_Model"); + u_ViewLocation = gl.getUniformLocation(program, "u_View"); + u_PerspLocation = gl.getUniformLocation(program, "u_Persp"); + u_InvTransLocation = gl.getUniformLocation(program, "u_InvTrans"); + u_MoonDiffuseLocation = gl.getUniformLocation(program, "u_Diffuse"); + u_MoonBumpLocation = gl.getUniformLocation(program, "u_Bump"); + u_timeLocation = gl.getUniformLocation(program, "u_time"); + u_CameraSpaceDirLightLocation = gl.getUniformLocation(program, "u_CameraSpaceDirLight"); + + gl.useProgram(program); + } - var dayTex = gl.createTexture(); - var bumpTex = gl.createTexture(); - var cloudTex = gl.createTexture(); - var transTex = gl.createTexture(); - var lightTex = gl.createTexture(); - var specTex = gl.createTexture(); + function initializeSkyboxShader() { + var vs = getShaderSource(document.getElementById("skyboxVS")); + var fs = getShaderSource(document.getElementById("skyboxFS")); + + var program = createProgram(gl, vs, fs, message); + positionLocation = gl.getAttribLocation(program, "Position"); + texCoordLocation = gl.getAttribLocation(program, "Texcoord"); + u_ModelLocation = gl.getUniformLocation(program, "u_Model"); + u_ViewLocation = gl.getUniformLocation(program, "u_View"); + u_PerspLocation = gl.getUniformLocation(program, "u_Persp"); + u_texSamplerLocation = gl.getUniformLocation(program, "u_textureSampler"); + + gl.useProgram(program); + } function initLoadedTexture(texture){ gl.bindTexture(gl.TEXTURE_2D, texture); @@ -98,9 +136,34 @@ gl.bindTexture(gl.TEXTURE_2D, null); } + // initialize a non-power of 2 (NPOT) texture + function initLoadedNPOTTexture(texture) { + gl.bindTexture(gl.TEXTURE_2D, texture); + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, texture.image); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + gl.bindTexture(gl.TEXTURE_2D, null); + } + + function initSkyboxTexture(texture) { + gl.bindTexture(gl.TEXTURE_2D, texture); + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, texture.image); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + gl.bindTexture(gl.TEXTURE_2D, null); + + } + + var numberOfIndices; - (function initializeSphere() { + function initializeSphere() { function uploadMesh(positions, texCoords, indices) { // Positions var positionsName = gl.createBuffer(); @@ -173,7 +236,7 @@ uploadMesh(positions, texCoords, indices); numberOfIndices = indicesIndex; - })(); + }; var time = 0; var mouseLeftDown = false; @@ -234,13 +297,14 @@ document.onmousemove = handleMouseMove; - function animate() { + function drawEarth() { /////////////////////////////////////////////////////////////////////////// // Update var model = mat4.create(); mat4.identity(model); - mat4.rotate(model, 23.4/180*Math.PI, [0.0, 0.0, 1.0]); + mat4.scale(model, [1.0, 1.0, 1.0]); + mat4.rotate(model, 23.4 / 180 * Math.PI, [0.0, 0.0, 1.0]); mat4.rotate(model, Math.PI, [1.0, 0.0, 0.0]); mat4.rotate(model, -time, [0.0, 1.0, 0.0]); var mv = mat4.create(); @@ -254,7 +318,7 @@ var lightdest = vec4.create(); vec3.normalize(lightdir); mat4.multiplyVec4(view, [lightdir[0], lightdir[1], lightdir[2], 0.0], lightdest); - lightdir = vec3.createFrom(lightdest[0],lightdest[1],lightdest[2]); + lightdir = vec3.createFrom(lightdest[0], lightdest[1], lightdest[2]); vec3.normalize(lightdir); /////////////////////////////////////////////////////////////////////////// @@ -268,49 +332,366 @@ gl.uniform3fv(u_CameraSpaceDirLightLocation, lightdir); + // The value you set into the GLSL sampler uniform is index of texture unit + // to which you have to bind the desired texture. gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D, dayTex); gl.uniform1i(u_DayDiffuseLocation, 0); + gl.activeTexture(gl.TEXTURE1); gl.bindTexture(gl.TEXTURE_2D, bumpTex); gl.uniform1i(u_BumpLocation, 1); + gl.activeTexture(gl.TEXTURE2); gl.bindTexture(gl.TEXTURE_2D, cloudTex); gl.uniform1i(u_CloudLocation, 2); + gl.activeTexture(gl.TEXTURE3); gl.bindTexture(gl.TEXTURE_2D, transTex); gl.uniform1i(u_CloudTransLocation, 3); + gl.activeTexture(gl.TEXTURE4); gl.bindTexture(gl.TEXTURE_2D, lightTex); gl.uniform1i(u_NightLocation, 4); + gl.activeTexture(gl.TEXTURE5); gl.bindTexture(gl.TEXTURE_2D, specTex); gl.uniform1i(u_EarthSpecLocation, 5); - gl.drawElements(gl.TRIANGLES, numberOfIndices, gl.UNSIGNED_SHORT,0); + + gl.uniform1f(u_timeLocation, time); + + gl.drawElements(gl.TRIANGLES, numberOfIndices, gl.UNSIGNED_SHORT, 0); + + } + + function drawMoon() + { + /////////////////////////////////////////////////////////////////////////// + // Update + var model = mat4.create(); + mat4.identity(model); + + mat4.rotate(model, 5.14 / 180 * Math.PI, [0.0, 0.0, 1.0]); + mat4.rotate(model, Math.PI, [1.0, 0.0, 0.0]); + mat4.rotate(model, -0.5*time, [0.0, 1.0, 0.0]); + mat4.translate(model, [5.0, 0.0, 0.0]); + mat4.scale(model, [0.27, 0.27, 0.27]); + mat4.rotate(model, 5.0*time, [0.0, 1.0, 0.0]); // rotation + + var mv = mat4.create(); + mat4.multiply(view, model, mv); + + var invTrans = mat4.create(); + mat4.inverse(mv, invTrans); + mat4.transpose(invTrans); + + var lightdir = vec3.create([1.0, 0.0, 1.0]); + var lightdest = vec4.create(); + vec3.normalize(lightdir); + mat4.multiplyVec4(view, [lightdir[0], lightdir[1], lightdir[2], 0.0], lightdest); + lightdir = vec3.createFrom(lightdest[0], lightdest[1], lightdest[2]); + vec3.normalize(lightdir); + + /////////////////////////////////////////////////////////////////////////// + // Render + gl.uniformMatrix4fv(u_ModelLocation, false, model); + gl.uniformMatrix4fv(u_ViewLocation, false, view); + gl.uniformMatrix4fv(u_PerspLocation, false, persp); + gl.uniformMatrix4fv(u_InvTransLocation, false, invTrans); + gl.uniform3fv(u_CameraSpaceDirLightLocation, lightdir); + + gl.activeTexture(gl.TEXTURE6); + gl.bindTexture(gl.TEXTURE_2D, moonDiffuseTex); + gl.uniform1i(u_MoonDiffuseLocation, 6); + + gl.activeTexture(gl.TEXTURE7); + gl.bindTexture(gl.TEXTURE_2D, moonBumpTex); + gl.uniform1i(u_MoonBumpLocation, 7); + + gl.uniform1f(u_timeLocation, time); + + gl.drawElements(gl.TRIANGLES, numberOfIndices, gl.UNSIGNED_SHORT, 0); + + } + + function drawSkybox() { + + // set up vertices + var vertBuffer = gl.createBuffer(); + var vertices = [ + // Front face + -1.0, -1.0, 1.0, + 1.0, -1.0, 1.0, + 1.0, 1.0, 1.0, + -1.0, 1.0, 1.0, + + // Back face + -1.0, -1.0, -1.0, + -1.0, 1.0, -1.0, + 1.0, 1.0, -1.0, + 1.0, -1.0, -1.0, + + // Top face + -1.0, 1.0, -1.0, + -1.0, 1.0, 1.0, + 1.0, 1.0, 1.0, + 1.0, 1.0, -1.0, + + // Bottom face + -1.0, -1.0, -1.0, + 1.0, -1.0, -1.0, + 1.0, -1.0, 1.0, + -1.0, -1.0, 1.0, + + // Right face + 1.0, -1.0, -1.0, + 1.0, 1.0, -1.0, + 1.0, 1.0, 1.0, + 1.0, -1.0, 1.0, + + // Left face + -1.0, -1.0, -1.0, + -1.0, -1.0, 1.0, + -1.0, 1.0, 1.0, + -1.0, 1.0, -1.0 + ]; + + gl.bindBuffer(gl.ARRAY_BUFFER, vertBuffer); + gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW); + gl.vertexAttribPointer(positionLocation, 3, gl.FLOAT, false, 0, 0); + gl.enableVertexAttribArray(positionLocation); + + // set up indices + var indexBuffer = gl.createBuffer(); + var indices = [ + 0, 1, 2, 0, 2, 3, // front + 4, 5, 6, 4, 6, 7, // back + 8, 9, 10, 8, 10, 11, // top + 12, 13, 14, 12, 14, 15, // bottom + 16, 17, 18, 16, 18, 19, // right + 20, 21, 22, 20, 22, 23 // left + ]; + + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer); + gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW); + + // set up texture coordinates + var textureCoordsBuffer = gl.createBuffer(); + var textureCoordinates = [ + // Used debug skybox to figure out these texture coordinates! + // Front + 0.0, 0.0, + 1.0, 0.0, + 1.0, 1.0, + 0.0, 1.0, + // Back + 1.0, 0.0, + 1.0, 1.0, + 0.0, 1.0, + 0.0, 0.0, + // Top + 0.0, 1.0, + 0.0, 0.0, + 1.0, 0.0, + 1.0, 1.0, + // Bottom + 0.0, 0.0, + 1.0, 0.0, + 1.0, 1.0, + 0.0, 1.0, + // Right + 1.0, 0.0, + 1.0, 1.0, + 0.0, 1.0, + 0.0, 0.0, + // Left + 0.0, 0.0, + 1.0, 0.0, + 1.0, 1.0, + 0.0, 1.0 + ]; + + gl.bindBuffer(gl.ARRAY_BUFFER, textureCoordsBuffer); + gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(textureCoordinates), gl.STATIC_DRAW); + gl.vertexAttribPointer(texCoordLocation, 2, gl.FLOAT, false, 0, 0); + gl.enableVertexAttribArray(texCoordLocation); + + /////////////////////////////////////////////////////////////////////////// + // Render + var model = mat4.create(); + mat4.identity(model); + mat4.scale(model, [25.0, 25.0, 25.0]); + gl.uniformMatrix4fv(u_ModelLocation, false, model); + gl.uniformMatrix4fv(u_ViewLocation, false, view); + gl.uniformMatrix4fv(u_PerspLocation, false, persp); + + // front + gl.activeTexture(gl.TEXTURE8); + gl.bindTexture(gl.TEXTURE_2D, skyFrontTex); + gl.uniform1i(u_texSamplerLocation, 8); + gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0); // byte offset + + // back + gl.activeTexture(gl.TEXTURE9); + gl.bindTexture(gl.TEXTURE_2D, skyBackTex); + gl.uniform1i(u_texSamplerLocation, 9); + gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 12); // 6 verts, 2 bytes per vert + + // top + gl.activeTexture(gl.TEXTURE10); + gl.bindTexture(gl.TEXTURE_2D, skyTopTex); + gl.uniform1i(u_texSamplerLocation, 10); + gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 24); + + // bot + gl.activeTexture(gl.TEXTURE11); + gl.bindTexture(gl.TEXTURE_2D, skyBotTex); + gl.uniform1i(u_texSamplerLocation, 11); + gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 36); + + // right + gl.activeTexture(gl.TEXTURE12); + gl.bindTexture(gl.TEXTURE_2D, skyRightTex); + gl.uniform1i(u_texSamplerLocation, 12); + gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 48); + + // left + gl.activeTexture(gl.TEXTURE13); + gl.bindTexture(gl.TEXTURE_2D, skyLeftTex); + gl.uniform1i(u_texSamplerLocation, 13); + gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 60); + } + + // animate is responsible for drawing the earth and moon + function animate() { + + // Earth + var start = new Date().getMilliseconds(); + initializeEarthShader(); + initializeSphere(); + drawEarth(); + var end = new Date().getMilliseconds(); + var totaltime = end - start; + console.log('Earth Rendering Execution time: ' + totaltime); + + // Moon + var start = new Date().getMilliseconds(); + initializeMoonShader(); + initializeSphere(); + drawMoon(); + var end = new Date().getMilliseconds(); + var totaltime = end - start; + console.log('Moon Rendering Execution time: ' + totaltime); + + // Skybox + var start = new Date().getMilliseconds(); + initializeSkyboxShader(); + drawSkybox(); + var end = new Date().getMilliseconds(); + var totaltime = end - start; + console.log('Skybox Rendering Execution time: ' + totaltime); time += 0.001; + window.requestAnimFrame(animate); } - var textureCount = 0; - - function initializeTexture(texture, src) { + var earthTextureCount = 0; + var moonTextureCount = 0; + var skyboxTextureCount = 0; + + // earth textures + var dayTex; + var bumpTex; + var cloudTex; + var transTex; + var lightTex; + var specTex; + + // moon textures + var moonDiffuseTex; + var moonBumpTex; + + // skybox textures + var skyFrontTex; + var skyBackTex; + var skyTopTex; + var skyBotTex; + var skyRightTex; + var skyLeftTex; + + (function createEarthTexture() { + dayTex = gl.createTexture(); + bumpTex = gl.createTexture(); + cloudTex = gl.createTexture(); + transTex = gl.createTexture(); + lightTex = gl.createTexture(); + specTex = gl.createTexture(); + }()); + + (function initializeEarthTextures() { + initializeTexture(dayTex, "earthmap1024.png", "earth"); + initializeTexture(bumpTex, "earthbump1024.png", "earth"); + initializeTexture(cloudTex, "earthcloud1024.png", "earth"); + initializeTexture(transTex, "earthtrans1024.png", "earth"); + initializeTexture(lightTex, "earthlight1024.png", "earth"); + initializeTexture(specTex, "earthspec1024.png", "earth"); + }()); + + (function createMoonTexture() { + moonDiffuseTex = gl.createTexture(); + moonBumpTex = gl.createTexture(); + }()); + + (function initializeMoonTextures() { + initializeTexture(moonDiffuseTex, "moonmap.jpg", "moon"); + initializeTexture(moonBumpTex, "moonbumpmap.jpg", "moon"); + }()); + + (function createSkyboxTexture() { + skyFrontTex = gl.createTexture(); + skyBackTex = gl.createTexture(); + skyTopTex = gl.createTexture(); + skyBotTex = gl.createTexture(); + skyRightTex = gl.createTexture(); + skyLeftTex = gl.createTexture(); + }()); + + (function initializeSkyboxTexture() { + initializeTexture(skyFrontTex, "purpleNebula_front5.png", "skybox"); + initializeTexture(skyBackTex, "purpleNebula_back6.png", "skybox"); + initializeTexture(skyTopTex, "purpleNebula_top3.png", "skybox"); + initializeTexture(skyBotTex, "purpleNebula_bottom4.png", "skybox"); + initializeTexture(skyRightTex, "purpleNebula_right1.png", "skybox"); + initializeTexture(skyLeftTex, "purpleNebula_left2.png", "skybox"); + }()); + + + // type = 1 for earth texture, otherwise moon texture + function initializeTexture(texture, src, type) { texture.image = new Image(); texture.image.onload = function() { - initLoadedTexture(texture); + + if (type == "earth") { + earthTextureCount++; + initLoadedTexture(texture); + } + else if (type == "moon") { + moonTextureCount++; + initLoadedNPOTTexture(texture); + } + else if (type == "skybox") { + skyboxTextureCount++; + initSkyboxTexture(texture); + } + // Animate once textures load. - if (++textureCount === 6) { + if (earthTextureCount === 6 && moonTextureCount == 2 && skyboxTextureCount == 6) { animate(); } } texture.image.src = src; } - - initializeTexture(dayTex, "earthmap1024.png"); - initializeTexture(bumpTex, "earthbump1024.png"); - initializeTexture(cloudTex, "earthcloud1024.png"); - initializeTexture(transTex, "earthtrans1024.png"); - initializeTexture(lightTex, "earthlight1024.png"); - initializeTexture(specTex, "earthspec1024.png"); + }()); diff --git a/part2/moonbumpmap.jpg b/part2/moonbumpmap.jpg new file mode 100644 index 0000000..6bd2f83 Binary files /dev/null and b/part2/moonbumpmap.jpg differ diff --git a/part2/moonmap.jpg b/part2/moonmap.jpg new file mode 100644 index 0000000..50f0188 Binary files /dev/null and b/part2/moonmap.jpg differ diff --git a/part2/purpleNebula_back6.png b/part2/purpleNebula_back6.png new file mode 100644 index 0000000..874230b Binary files /dev/null and b/part2/purpleNebula_back6.png differ diff --git a/part2/purpleNebula_bottom4.png b/part2/purpleNebula_bottom4.png new file mode 100644 index 0000000..7e0681a Binary files /dev/null and b/part2/purpleNebula_bottom4.png differ diff --git a/part2/purpleNebula_front5.png b/part2/purpleNebula_front5.png new file mode 100644 index 0000000..00e698c Binary files /dev/null and b/part2/purpleNebula_front5.png differ diff --git a/part2/purpleNebula_left2.png b/part2/purpleNebula_left2.png new file mode 100644 index 0000000..7f21ca6 Binary files /dev/null and b/part2/purpleNebula_left2.png differ diff --git a/part2/purpleNebula_right1.png b/part2/purpleNebula_right1.png new file mode 100644 index 0000000..100756c Binary files /dev/null and b/part2/purpleNebula_right1.png differ diff --git a/part2/purpleNebula_top3.png b/part2/purpleNebula_top3.png new file mode 100644 index 0000000..3dd0a6e Binary files /dev/null and b/part2/purpleNebula_top3.png differ diff --git a/resources/globeResult.png b/resources/globeResult.png new file mode 100644 index 0000000..13fe53e Binary files /dev/null and b/resources/globeResult.png differ diff --git a/resources/waveResult.png b/resources/waveResult.png new file mode 100644 index 0000000..544c3bb Binary files /dev/null and b/resources/waveResult.png differ