diff --git a/README.md b/README.md index 25002db..5b313b4 100644 --- a/README.md +++ b/README.md @@ -3,26 +3,185 @@ WebGL Deferred Shading **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) +* Ruoyu Fan +* Tested on: **Google Chrome 54.0.2840.87** on + * Windows 10 x64, i7-4720HQ @ 2.60GHz, 16GB Memory, GTX 970M 3072MB (personal laptop) + +__NOTE:__ my submission requires an additional WebGL extension - `EXT_frag_depth`, used in `defered/ambient.frag.glsl` to properly write depth data into lighting passes' frame buffer to do __inverted depth test__ with __front-face culling__ ### Live Online -[![](img/thumb.png)](http://TODO.github.io/Project5B-WebGL-Deferred-Shading) +[![](img/thumb.png)](https://windydarian.github.io/Project5-WebGL-Deferred-Shading-with-glTF/) + +[Click Me!](https://windydarian.github.io/Project5-WebGL-Deferred-Shading-with-glTF/) + +#### Demo GIF + +![](screenshots/preview.gif) + +### Work Done + +* Basic __Deferred Shading__ pipeline +* __Blinn-Phong shading__ with normal mapping + * Using `clamp(1.0 - light_distance * light_distance / (u_lightRad * u_lightRad), 0.0, 1.0) ` as attenuation model for point lights +* __Bloom__ post-processing effect with __two-pass Gaussian blur__ using three steps: + * First extract bright areas with a threshold + * Then do a __two-pass Gaussian blur__ using separable convolution (vertical then horizontal) + * Use menu option to control blur size (which changes uniform variable `u_scale` passing to `bloom.frag.glsl`) + * Finally combine the blurred image to the original output +* __Scissor test for lighting__: when accumulating shading from each point light source, only render in a rectangle around the light. + * Use `debugScissor` option to toggle scissor visual, or select `6 Light scissors` to show scissor only. + * This is used to compare with my __light proxy__ implementation +* __Light proxy__: instead of rendering a scissored full-screen quad for every light, I render __proxy geometry__ which covers the part of the screen affected by a light (using spheres for point lights), thus __reducing wasted fragments in lighting pass__. + * Using __inverted depth test__ with __front-face culling__ to avoid lighting geometries that are far behind the light, thus further reducing wasted fragments + * This feature requires WebGL's `EXT_frag_depth` extension to write depth data into frame buffer at defered shading stage in order to do the depth test + * Use `useLightProxy` option to toggle on/off and use `useInvertedDepthTestForLightProxy` option to toggle depth test and front face culling for lighting pass +* __Optimized g-buffer__ from 4*vec4 to 2*vec4 by compressing normal to two floats, increasing framerate to __167%__, see below for details + +#### Light Proxy: + +| Render | With quad scissor test| +|------------------|------------------| +| ![](screenshots/proxy1.png) | ![](screenshots/proxy2.png) | + +| With light proxy (no depth test) | light proxy + depth test + front-face culling| +|------------------|------------------| +| ![](screenshots/proxy3.png) | ![](screenshots/proxy4.png) | + +#### Bloom: + +| No bloom | Bloom (size 0.003)| +|------------------|------------------| +| ![](screenshots/nobloom.png) | ![](screenshots/bloom1.png) | + +| Bloom (size 0.01) | Bloom (size 0.05)| +|------------------|------------------| +| ![](screenshots/bloom2.png) | ![](screenshots/bloom3.png) | + +### Optimizing G-Buffer + +The initial g-buffer implementation was 4*vec4 (4 textures), using each one for world position, geometry normal, color map and normal map. But in fact position and color can be stored in vec3, and we can use the information that __the length of a normal is one__ to __compress normal to 2 floats__. + +So I optimized my g-buffer by using 2*vec4 (2 textures) and stored world position, color map, and world normal (applying normal map to geometry normal) into it. + +The g-buffer before optimization was like: + +| | x | y | z | w | +|----------|-----------|-----------|-----------|---------| +| gbuffer0 | pos.x | pox.y | pox.z | nothing | +| gbuffer1 | geomnor.x | geomnor.y | geomnor.z | nothing | +| gbuffer2 | color.r | color.g | color.b | nothing | +| gbuffer3 | normap.x | normap.y | normap.z | nothing | + + +__After my optimization__, the g-buffer is like: + +| 0 | x | y | z | w | +|----------|-----------------------|-----------------------|-----------------------|-----------------| +| gbuffer0 | pos.x | pox.y | pox.z | 2_comp_normal.x | +| gbuffer1 | color.r & normal sign | color.g & normal axis | color.b & normal axis | 2_comp_normal.y | + +A typical way to do a 2-component normal is `normal.xy/normal.z` (and restore by `normalize(normal.x, normal.y, 1.0)`), but if `normal.z` is too small, there will be drastic precision loss for normals in that direction; and we cannot tell if the normal is inverted. So, I used some information from "color map" to "help" the normal. + +What is `normal sign` and `normal axis`? Well, they are just a sign (+/-). Since colors in glsl are unsigned information stored in signed float, I made use of the sign of the floats to store some information about the normal by using a black magic. + +Here is how I compress the normal + +```glsl +// BLACK MAGIC: use color map signs to represent which axis is seen as 1 in normal map +vec2 two_comp_normal; +if (abs(normal.z) > 0.33) +{ + two_comp_normal = normal.xy/normal.z; + colmap.z *= -1.0; + colmap.x *= sign(normal.z); // and use x to store if normal is inverted +} +else if (abs(normal.y) > 0.33) +{ + two_comp_normal = normal.xz/normal.y; + colmap.y *= -1.0; + colmap.x *= sign(normal.y); +} +else +{ + two_comp_normal = normal.yz/normal.x; + colmap.x *= sign(normal.x); +} +``` + +And here is how I restore the normal: + +```glsl +vec3 extractNormal(float nor_x, float nor_y, vec3 colmap) +{ + // Black magic: I colmap sign to prevent normal losing too much precision on a particular axis + if (colmap.z < 0.0) + { + return normalize(vec3(nor_x, nor_y, 1.0)) * sign(colmap.x); + } + else if(colmap.y < 0.0) + { + return normalize(vec3(nor_x, 1.0, nor_y)) * sign(colmap.x); + } + else + { + return normalize(vec3(1.0, nor_x, nor_y)) * sign(colmap.x); + } +} +``` + +Using the sign information as aid, I can store and restore World-space normal in two floats without much precision loss. + +And here is the comparison for them + +| Number of Lights | Gbuffer Size - 4 | Gbuffer Size - 2 | +|------------------|------------------|------------------| +| 50 | 22.2 | 20.4 | +| 100 | 45.5 | 30.3 | +| 200 | 83.3 | 50 | + +![](img/chart_gbuffer.png) + +We can see drastic performance boost here. But this is not a completely correct comparison - I migrated the combination of geometry normal and normal map from lighting stage to copy-to-g-buffer stage. So part of the performance bonus may come from not combining normals for every light. + +### Light proxy + +instead of rendering a scissored full-screen quad for every light, I render __proxy geometry__ which covers the part of the screen affected by a light (using spheres for point lights), thus __reducing wasted fragments in lighting pass__. +* Using __inverted depth test__ with __front-face culling__ to avoid lighting geometries that are far behind the light, thus further reducing wasted fragments +* This feature requires WebGL's `EXT_frag_depth` extension to write depth data into frame buffer at defered shading stage in order to do the depth test +* Use `useLightProxy` option to toggle on/off and use `useInvertedDepthTestForLightProxy` option to toggle depth test and front face culling for lighting pass + +Here are images for comparison (bloom off, 20 lights): + +| Render | With quad scissor test| +|------------------|------------------| +| ![](screenshots/proxy1.png) | ![](screenshots/proxy2.png) | + +| With light proxy (no depth test) | light proxy + depth test + front-face culling| +|------------------|------------------| +| ![](screenshots/proxy3.png) | ![](screenshots/proxy4.png) | + +Given 200 lights and bloom off, these are performance comparison: + +| Quad scissor | Light proxy | Light proxy with depth test | +|--------------|-------------|-----------------------------| +| 76.9ms | 66.7ms | 43.5ms | -### Demo Video/GIF +![](img/chart_proxy.png) -[![](img/video.png)](TODO) +### Bloom -### (TODO: Your README) +I implemented 2-pass Gaussian Blur with adjustable size. -*DO NOT* leave the README to the last minute! It is a crucial part of the -project, and we will not be able to grade you without a good README. +Please refer to `glsl/post/bloom.frag.glsl`! -This assignment has a considerable amount of performance analysis compared -to implementation work. Complete the implementation early to leave time! +| No bloom | Bloom (size 0.003)| +|------------------|------------------| +| ![](screenshots/nobloom.png) | ![](screenshots/bloom1.png) | +| Bloom (size 0.01) | Bloom (size 0.05)| +|------------------|------------------| +| ![](screenshots/bloom2.png) | ![](screenshots/bloom3.png) | ### Credits diff --git a/glsl/clear.frag.glsl b/glsl/clear.frag.glsl index b4e4ff3..9719c57 100644 --- a/glsl/clear.frag.glsl +++ b/glsl/clear.frag.glsl @@ -3,7 +3,7 @@ precision highp float; precision highp int; -#define NUM_GBUFFERS 4 +#define NUM_GBUFFERS 2 void main() { for (int i = 0; i < NUM_GBUFFERS; i++) { diff --git a/glsl/copy.frag.glsl b/glsl/copy.frag.glsl index 823ebcd..eea059e 100644 --- a/glsl/copy.frag.glsl +++ b/glsl/copy.frag.glsl @@ -10,11 +10,43 @@ varying vec3 v_position; varying vec3 v_normal; varying vec2 v_uv; -void main() { - // TODO: copy values into gl_FragData[0], [1], etc. - // You can use the GLSL texture2D function to access the textures using - // the UV in v_uv. +vec3 applyNormalMap(vec3 geomnor, vec3 normap) +{ + normap = normap * 2.0 - 1.0; + vec3 up = normalize(vec3(0.001, 1, 0.001)); + vec3 surftan = normalize(cross(geomnor, up)); + vec3 surfbinor = cross(geomnor, surftan); + return normap.y * surftan + normap.x * surfbinor + normap.z * geomnor; +} + +void main() +{ + vec3 geomnor = v_normal; + vec3 normap = texture2D(u_normap, v_uv).xyz; + vec3 normal = applyNormalMap(geomnor, normap); + vec2 two_comp_normal; + + vec3 colmap = texture2D(u_colmap, v_uv).xyz; + + // BLACK MAGIC: use color map signs to represent which axis is seen as 1 in normal map + if (abs(normal.z) > 0.33) + { + two_comp_normal = normal.xy/normal.z; + colmap.z *= -1.0; + colmap.x *= sign(normal.z); // and use x to store if normal is inverted + } + else if (abs(normal.y) > 0.33) + { + two_comp_normal = normal.xz/normal.y; + colmap.y *= -1.0; + colmap.x *= sign(normal.y); + } + else + { + two_comp_normal = normal.yz/normal.x; + colmap.x *= sign(normal.x); + } - // this gives you the idea - // gl_FragData[0] = vec4( v_position, 1.0 ); + gl_FragData[0] = vec4(v_position, two_comp_normal.x); // world-space position + gl_FragData[1] = vec4(colmap, two_comp_normal.y); // Normals of the geometry as defined, without normal mapping } diff --git a/glsl/deferred/ambient.frag.glsl b/glsl/deferred/ambient.frag.glsl index 1fd4647..efe3dbf 100644 --- a/glsl/deferred/ambient.frag.glsl +++ b/glsl/deferred/ambient.frag.glsl @@ -1,27 +1,34 @@ - #version 100 +#extension GL_EXT_frag_depth : enable precision highp float; precision highp int; -#define NUM_GBUFFERS 4 +#define NUM_GBUFFERS 2 uniform sampler2D u_gbufs[NUM_GBUFFERS]; uniform sampler2D u_depth; varying vec2 v_uv; +const vec3 ambient_color = vec3(0.15,0.15,0.15); + +const vec4 SKY_COLOR = vec4(0.98, 0.98, 0.98, 1.0); +//const vec4 SKY_COLOR = vec4(0.01, 0.14, 0.42, 1.0); void main() { - vec4 gb0 = texture2D(u_gbufs[0], v_uv); + //vec4 gb0 = texture2D(u_gbufs[0], v_uv); vec4 gb1 = texture2D(u_gbufs[1], v_uv); - vec4 gb2 = texture2D(u_gbufs[2], v_uv); - vec4 gb3 = texture2D(u_gbufs[3], v_uv); float depth = texture2D(u_depth, v_uv).x; // TODO: Extract needed properties from the g-buffers into local variables + gl_FragDepthEXT = depth; // for use in light proxy + if (depth == 1.0) { - gl_FragColor = vec4(0, 0, 0, 0); // set alpha to 0 + //gl_FragColor = vec4(0, 0, 0, 0); // set alpha to 0 + gl_FragColor = SKY_COLOR; return; } - gl_FragColor = vec4(0.1, 0.1, 0.1, 1); // TODO: replace this + vec3 colmap = abs(gb1.rgb); + //gl_FragColor = vec4(vec3(depth),1.0); + gl_FragColor = vec4(colmap * ambient_color, 1); // DONE: replace this } diff --git a/glsl/deferred/blinnphong-pointlight.frag.glsl b/glsl/deferred/blinnphong-pointlight.frag.glsl index b24a54a..a05e6ef 100644 --- a/glsl/deferred/blinnphong-pointlight.frag.glsl +++ b/glsl/deferred/blinnphong-pointlight.frag.glsl @@ -2,38 +2,70 @@ precision highp float; precision highp int; -#define NUM_GBUFFERS 4 +#define NUM_GBUFFERS 2 uniform vec3 u_lightCol; uniform vec3 u_lightPos; uniform float u_lightRad; +uniform vec3 u_camPos; uniform sampler2D u_gbufs[NUM_GBUFFERS]; uniform sampler2D u_depth; -varying vec2 v_uv; +//varying vec2 v_uv; -vec3 applyNormalMap(vec3 geomnor, vec3 normap) { - normap = normap * 2.0 - 1.0; - vec3 up = normalize(vec3(0.001, 1, 0.001)); - vec3 surftan = normalize(cross(geomnor, up)); - vec3 surfbinor = cross(geomnor, surftan); - return normap.y * surftan + normap.x * surfbinor + normap.z * geomnor; +vec3 extractNormal(float nor_x, float nor_y, vec3 colmap) +{ + // Black magic: I colmap sign to prevent normal losing too much precision on a particular axis + if (colmap.z < 0.0) + { + return normalize(vec3(nor_x, nor_y, 1.0)) * sign(colmap.x); + } + else if(colmap.y < 0.0) + { + return normalize(vec3(nor_x, 1.0, nor_y)) * sign(colmap.x); + } + else + { + return normalize(vec3(1.0, nor_x, nor_y)) * sign(colmap.x); + } } void main() { - vec4 gb0 = texture2D(u_gbufs[0], v_uv); - vec4 gb1 = texture2D(u_gbufs[1], v_uv); - vec4 gb2 = texture2D(u_gbufs[2], v_uv); - vec4 gb3 = texture2D(u_gbufs[3], v_uv); - float depth = texture2D(u_depth, v_uv).x; - // TODO: Extract needed properties from the g-buffers into local variables - - // If nothing was rendered to this pixel, set alpha to 0 so that the - // postprocessing step can render the sky color. - if (depth == 1.0) { - gl_FragColor = vec4(0, 0, 0, 0); - return; + + vec2 uv = vec2(gl_FragCoord.x / 800.0 , gl_FragCoord.y / 600.0); + + vec4 gb0 = texture2D(u_gbufs[0], uv); + vec4 gb1 = texture2D(u_gbufs[1], uv); + float depth = texture2D(u_depth, uv).x; + // DONE: Extract needed properties from the g-buffers into local variables + vec3 pos = gb0.xyz; // World-space position + vec3 colmap = gb1.rgb; // The color map - unlit "albedo" (surface color) + vec3 nor = extractNormal (gb0.w, gb1.w, colmap); // gb1: geometry normal; gb3: raw normal map + colmap = abs(colmap); + + // // If nothing was rendered to this pixel, set alpha to 0 so that the + // // postprocessing step can render the sky color. + // if (depth == 1.0) { + // gl_FragColor = vec4(0, 0, 0, 0); + // return; + // } + + vec3 lightDir = normalize(u_lightPos - pos); + float lambertian = max(dot(lightDir, nor), 0.0); + float specular = 0.0; + if(lambertian > 0.0) + { + vec3 viewDir = normalize(u_camPos - pos); + vec3 halfDir = normalize(lightDir + viewDir); + float specAngle = max(dot(halfDir, nor), 0.0); + specular = pow(specAngle, 32.0); // TODO?: spec color & power in g-buffer? } - gl_FragColor = vec4(0, 0, 1, 1); // TODO: perform lighting calculations + // square falloff + float light_distance = distance(u_lightPos, pos); + float att = clamp(1.0 - light_distance * light_distance / (u_lightRad * u_lightRad), 0.0, 1.0); + + vec3 color = (lambertian * colmap + specular) * u_lightCol * att; + gl_FragColor = vec4(color, 1.0); // DONE: perform lighting calculations + //gl_FragColor = vec4(v_uv, 0.0, 1.0); } diff --git a/glsl/deferred/debug.frag.glsl b/glsl/deferred/debug.frag.glsl index 007466f..3348406 100644 --- a/glsl/deferred/debug.frag.glsl +++ b/glsl/deferred/debug.frag.glsl @@ -2,7 +2,7 @@ precision highp float; precision highp int; -#define NUM_GBUFFERS 4 +#define NUM_GBUFFERS 2 uniform int u_debug; uniform sampler2D u_gbufs[NUM_GBUFFERS]; @@ -12,41 +12,42 @@ varying vec2 v_uv; const vec4 SKY_COLOR = vec4(0.66, 0.73, 1.0, 1.0); -vec3 applyNormalMap(vec3 geomnor, vec3 normap) { - normap = normap * 2.0 - 1.0; - vec3 up = normalize(vec3(0.001, 1, 0.001)); - vec3 surftan = normalize(cross(geomnor, up)); - vec3 surfbinor = cross(geomnor, surftan); - return normap.y * surftan + normap.x * surfbinor + normap.z * geomnor; +vec3 extractNormal(float nor_x, float nor_y, vec3 colmap) +{ + // Black magic: I colmap sign to prevent normal losing too much precision on a particular axis + if (colmap.z < 0.0) + { + return normalize(vec3(nor_x, nor_y, 1.0)) * sign(colmap.x); + } + else if(colmap.y < 0.0) + { + return normalize(vec3(nor_x, 1.0, nor_y)) * sign(colmap.x); + } + else + { + return normalize(vec3(1.0, nor_x, nor_y)) * sign(colmap.x); + } } void main() { vec4 gb0 = texture2D(u_gbufs[0], v_uv); vec4 gb1 = texture2D(u_gbufs[1], v_uv); - vec4 gb2 = texture2D(u_gbufs[2], v_uv); - vec4 gb3 = texture2D(u_gbufs[3], v_uv); float depth = texture2D(u_depth, v_uv).x; - // TODO: Extract needed properties from the g-buffers into local variables - // These definitions are suggested for starting out, but you will probably want to change them. + vec3 pos = gb0.xyz; // World-space position - vec3 geomnor = gb1.xyz; // Normals of the geometry as defined, without normal mapping - vec3 colmap = gb2.rgb; // The color map - unlit "albedo" (surface color) - vec3 normap = gb3.xyz; // The raw normal map (normals relative to the surface they're on) - vec3 nor = applyNormalMap (geomnor, normap); // The true normals as we want to light them - with the normal map applied to the geometry normals (applyNormalMap above) + vec3 colmap = gb1.xyz; // Normals of the geometry as defined, without normal mapping + vec3 nor = extractNormal (gb0.w, gb1.w, colmap); // The true normals as we want to light them - with the normal map applied to the geometry normals (applyNormalMap above) + colmap = abs(colmap); - // TODO: uncomment + // DONE: uncomment if (u_debug == 0) { gl_FragColor = vec4(vec3(depth), 1.0); } else if (u_debug == 1) { - // gl_FragColor = vec4(abs(pos) * 0.1, 1.0); + gl_FragColor = vec4(abs(pos) * 0.1, 1.0); } else if (u_debug == 2) { - // gl_FragColor = vec4(abs(geomnor), 1.0); + gl_FragColor = vec4(colmap, 1.0); } else if (u_debug == 3) { - // gl_FragColor = vec4(colmap, 1.0); - } else if (u_debug == 4) { - // gl_FragColor = vec4(normap, 1.0); - } else if (u_debug == 5) { - // gl_FragColor = vec4(abs(nor), 1.0); + gl_FragColor = vec4(abs(nor), 1.0); } else { gl_FragColor = vec4(1, 0, 1, 1); } diff --git a/glsl/post/_bloom_fixed.frag.glsl b/glsl/post/_bloom_fixed.frag.glsl new file mode 100644 index 0000000..cd9f9f1 --- /dev/null +++ b/glsl/post/_bloom_fixed.frag.glsl @@ -0,0 +1,51 @@ +// UNUSED: changed to bloom.frag.glsl so I can use a variable to compute + + +#version 100 +precision highp float; +precision highp int; + +varying vec2 v_uv; + +uniform sampler2D u_color; +uniform bool u_horizontal; +uniform vec2 u_tex_offset; +// length of texel in uv coord +// hard to get texel size in glsl es 100 + +float weight[5]; + +void main() +{ + // since 100 does not support array constructor + weight[0] = 0.227027; + weight[1] = 0.1945946; + weight[2] = 0.1216216; + weight[3] = 0.054054; + weight[4] = 0.016216; + // TODO: make bloom radius a uniform variable + + // ref: http://learnopengl.com/#!Advanced-Lighting/Bloom + + //vec2 tex_offset = 1.0 / texel_size; // gets size of single texel + //vec3 result= vec3(1.0,1.0,1.0); + //vec3 result= texture2D(u_color, v_uv).rgb; + vec3 result = texture2D(u_color, v_uv).rgb * weight[0]; // current fragment's contribution + if(u_horizontal) + { + for(int i = 1; i < 5; ++i) + { + result += texture2D(u_color, v_uv + vec2(u_tex_offset.x * float(i), 0.0)).rgb * weight[i]; + result += texture2D(u_color, v_uv - vec2(u_tex_offset.x * float(i), 0.0)).rgb * weight[i]; + } + } + else + { + for(int i = 1; i < 5; ++i) + { + result += texture2D(u_color, v_uv + vec2(0.0, u_tex_offset.y * float(i))).rgb * weight[i]; + result += texture2D(u_color, v_uv - vec2(0.0, u_tex_offset.y * float(i))).rgb * weight[i]; + } + } + gl_FragColor = vec4(result, texture2D(u_color, v_uv).a); +} diff --git a/glsl/post/bloom.frag.glsl b/glsl/post/bloom.frag.glsl new file mode 100644 index 0000000..59aed9b --- /dev/null +++ b/glsl/post/bloom.frag.glsl @@ -0,0 +1,55 @@ +#version 100 +precision highp float; +precision highp int; + +varying vec2 v_uv; + +uniform sampler2D u_color; +uniform bool u_horizontal; +uniform float u_scale; +// length of texel in uv coord +// hard to get texel size in glsl es 100 +uniform vec2 u_tex_offset; + +// using a gaussian model with a sigma of 0.84089642 +//const float sigma = 0.84089642; +const float one_over_sigma_sqr = 1.414213; +const float cent_coeff = 0.22508352; + +void main() +{ + // ref: http://learnopengl.com/#!Advanced-Lighting/Bloom + // & https://en.wikipedia.org/wiki/Gaussian_blur + + float dist_scale_sqr = 1.0 / (u_scale * u_scale); + + vec3 result = texture2D(u_color, v_uv).rgb * cent_coeff; // current fragment's contribution + float offset; + float weight; + float weight_sum = cent_coeff; + if(u_horizontal) + { + for(int i = 1; i < 100; ++i) + { + offset = u_tex_offset.x * float(i); + weight = exp(-0.5 * offset * offset * dist_scale_sqr * one_over_sigma_sqr) * cent_coeff; + if (weight < 0.01) break; + result += texture2D(u_color, v_uv + vec2(offset, 0.0)).rgb * weight; + result += texture2D(u_color, v_uv - vec2(offset, 0.0)).rgb * weight; + weight_sum += weight; + } + } + else + { + for(int i = 1; i < 100; ++i) + { + offset = u_tex_offset.y * float(i); + weight = exp(-0.5 * offset * offset * dist_scale_sqr * one_over_sigma_sqr ) * cent_coeff; + if (weight < 0.01) break; + result += texture2D(u_color, v_uv + vec2(0.0, offset)).rgb * weight; + result += texture2D(u_color, v_uv - vec2(0.0, offset)).rgb * weight; + weight_sum += weight; + } + } + gl_FragColor = vec4(result, texture2D(u_color, v_uv).a) / weight_sum; +} diff --git a/glsl/post/bloom_combine.frag.glsl b/glsl/post/bloom_combine.frag.glsl new file mode 100644 index 0000000..2d152f8 --- /dev/null +++ b/glsl/post/bloom_combine.frag.glsl @@ -0,0 +1,23 @@ +#version 100 +precision highp float; +precision highp int; + +varying vec2 v_uv; + +uniform sampler2D u_scene; +uniform sampler2D u_bloom; + + +// Extract bright colors for bloom +void main() +{ + const float exposure = 0.8; + const float gamma = 1.1; + // ref: http://learnopengl.com/#!Advanced-Lighting/Bloom + vec3 color = texture2D(u_scene, v_uv).rgb; + color += texture2D(u_bloom, v_uv).rgb; + color = vec3(1.0) - exp(-color * exposure); + color = pow(color, vec3(1.0/gamma)); + // TODO + gl_FragColor = vec4(color, 0.5); +} diff --git a/glsl/post/bloom_extract.frag.glsl b/glsl/post/bloom_extract.frag.glsl new file mode 100644 index 0000000..fcf5a37 --- /dev/null +++ b/glsl/post/bloom_extract.frag.glsl @@ -0,0 +1,26 @@ +#version 100 +precision highp float; +precision highp int; + +varying vec2 v_uv; + +uniform sampler2D u_color; + +const float threshold = 0.95; // TODO: uniform + +// Extract bright colors for bloom +void main() +{ + // ref: http://learnopengl.com/#!Advanced-Lighting/Bloom + vec4 color = texture2D(u_color, v_uv); + // TODO: maybe combining this into a gathering pass after defered shading + float brightness = dot(color.rgb, vec3(0.2126, 0.7152, 0.0722)); + if (brightness > threshold) + { + gl_FragColor = color; + } + else + { + gl_FragColor = vec4(0.0); + } +} diff --git a/glsl/post/one.frag.glsl b/glsl/post/one.frag.glsl index 94191cd..e09408e 100644 --- a/glsl/post/one.frag.glsl +++ b/glsl/post/one.frag.glsl @@ -6,15 +6,15 @@ uniform sampler2D u_color; varying vec2 v_uv; -const vec4 SKY_COLOR = vec4(0.01, 0.14, 0.42, 1.0); +//const vec4 SKY_COLOR = vec4(0.01, 0.14, 0.42, 1.0); void main() { vec4 color = texture2D(u_color, v_uv); - if (color.a == 0.0) { - gl_FragColor = SKY_COLOR; - return; - } + // if (color.a == 0.0) { + // gl_FragColor = SKY_COLOR; + // return; + // } gl_FragColor = color; } diff --git a/glsl/red.frag.glsl b/glsl/red.frag.glsl index f8ef1ec..1c4ae5e 100644 --- a/glsl/red.frag.glsl +++ b/glsl/red.frag.glsl @@ -3,5 +3,6 @@ precision highp float; precision highp int; void main() { - gl_FragColor = vec4(1, 0, 0, 1); + //gl_FragColor = vec4(0.8, 0.2, 0.55, 1); + gl_FragColor = vec4(1, 0, 0, 0.1); } diff --git a/glsl/sphere.vert.glsl b/glsl/sphere.vert.glsl new file mode 100644 index 0000000..a878d3f --- /dev/null +++ b/glsl/sphere.vert.glsl @@ -0,0 +1,17 @@ +#version 100 +precision highp float; +precision highp int; + +uniform mat4 u_cameraMat; +uniform mat4 u_worldMat; + +attribute vec3 a_position; + +//varying vec2 v_uv; + +void main() +{ + vec4 vpos = u_cameraMat * u_worldMat * vec4(a_position, 1.0); + gl_Position = vpos; + //v_uv = vec2(vpos * 0.5)/vpos.w + vec2(0.5); //using gl_FragCoord directly +} diff --git a/img/chart_gbuffer.png b/img/chart_gbuffer.png new file mode 100644 index 0000000..3d0a71e Binary files /dev/null and b/img/chart_gbuffer.png differ diff --git a/img/chart_proxy.png b/img/chart_proxy.png new file mode 100644 index 0000000..0e2d005 Binary files /dev/null and b/img/chart_proxy.png differ diff --git a/img/charts.xlsx b/img/charts.xlsx new file mode 100644 index 0000000..cb46173 Binary files /dev/null and b/img/charts.xlsx differ diff --git a/img/thumb.png b/img/thumb.png index 9ec8ed0..0b17d35 100644 Binary files a/img/thumb.png and b/img/thumb.png differ diff --git a/img/thumb2.png b/img/thumb2.png new file mode 100644 index 0000000..4ce4d78 Binary files /dev/null and b/img/thumb2.png differ diff --git a/index.html b/index.html index 7ada197..798819e 100644 --- a/index.html +++ b/index.html @@ -101,7 +101,7 @@ DEBUG MODE! (Disable before measuring performance.)
- +
diff --git a/js/deferredRender.js b/js/deferredRender.js index bb3edd4..e0ce2e7 100644 --- a/js/deferredRender.js +++ b/js/deferredRender.js @@ -10,17 +10,23 @@ !R.prog_Ambient || !R.prog_BlinnPhong_PointLight || !R.prog_Debug || - !R.progPost1)) { + !R.progPost1 || + !R.prog_bloom|| + !R.prog_bloom_combine|| + !R.prog_bloom_extract)) { console.log('waiting for programs to load...'); return; } - // Move the R.lights - for (var i = 0; i < R.lights.length; i++) { - // OPTIONAL TODO: Edit if you want to change how lights move - var mn = R.light_min[1]; - var mx = R.light_max[1]; - R.lights[i].pos[1] = (R.lights[i].pos[1] + R.light_dt - mn + mx) % mx + mn; + if (!cfg.pause) + { + // Move the R.lights + for (var i = 0; i < R.lights.length; i++) { + // OPTIONAL TODO: Edit if you want to change how lights move + var mn = R.light_min[1]; + var mx = R.light_max[1]; + R.lights[i].pos[1] = (R.lights[i].pos[1] + R.light_dt - mn + mx) % mx + mn; + } } // Execute deferred shading pipeline @@ -28,24 +34,31 @@ // CHECKITOUT: START HERE! You can even uncomment this: //debugger; - { // TODO: this block should be removed after testing renderFullScreenQuad - gl.bindFramebuffer(gl.FRAMEBUFFER, null); - // TODO: Implement/test renderFullScreenQuad first - renderFullScreenQuad(R.progRed); - return; - } - R.pass_copy.render(state); - if (cfg && cfg.debugView >= 0) { + if (cfg && cfg.debugView >= 0) + { // Do a debug render instead of a regular render // Don't do any post-processing in debug mode - R.pass_debug.render(state); - } else { + if (cfg.debugView <= 3) + { + R.pass_debug.render(state); + } + else if (cfg.debugView == 4) // TODO: maybe use dicts instead of numbers + { + R.pass_debug.render_scissor(state); + } + } + else + { // * Deferred pass and postprocessing pass(es) - // TODO: uncomment these - // R.pass_deferred.render(state); - // R.pass_post1.render(state); + // DONE: uncomment these + R.pass_deferred.render(state); + if (cfg.enableBloom) + { + R.pass_bloom.render(state); + } + R.pass_post1.render(state); // OPTIONAL TODO: call more postprocessing passes, if any } @@ -56,34 +69,34 @@ */ R.pass_copy.render = function(state) { // * Bind the framebuffer R.pass_copy.fbo - // TODO: uncomment - // gl.bindFramebuffer(gl.FRAMEBUFFER,R.pass_copy.fbo); + // DONE: uncomment + gl.bindFramebuffer(gl.FRAMEBUFFER,R.pass_copy.fbo); // * Clear screen using R.progClear - // TODO: uncomment - // renderFullScreenQuad(R.progClear); + // DONE: uncomment + renderFullScreenQuad(R.progClear); // * Clear depth buffer to value 1.0 using gl.clearDepth and gl.clear - // TODO: uncomment - // gl.clearDepth(1.0); - // gl.clear(gl.DEPTH_BUFFER_BIT); + // DONE: uncomment + gl.clearDepth(1.0); + gl.clear(gl.DEPTH_BUFFER_BIT); // * "Use" the program R.progCopy.prog - // TODO: uncomment - // gl.useProgram(R.progCopy.prog); + // DONE: uncomment + gl.useProgram(R.progCopy.prog); - // TODO: Go write code in glsl/copy.frag.glsl + // DONE: Go write code in glsl/copy.frag.glsl var m = state.cameraMat.elements; // * Upload the camera matrix m to the uniform R.progCopy.u_cameraMat // using gl.uniformMatrix4fv - // TODO: uncomment - // gl.uniformMatrix4fv(R.progCopy.u_cameraMat, false, m); + // DONE: uncomment + gl.uniformMatrix4fv(R.progCopy.u_cameraMat, false, m); // * Draw the scene - // TODO: uncomment - // drawScene(state); + // DONE: uncomment + drawScene(state); }; var drawScene = function(state) { @@ -96,24 +109,78 @@ drawReadyModel(m); } + }; R.pass_debug.render = function(state) { // * Unbind any framebuffer, so we can write to the screen - // TODO: uncomment - // gl.bindFramebuffer(gl.FRAMEBUFFER, null); + // DONE: uncomment + gl.bindFramebuffer(gl.FRAMEBUFFER, null); // * Bind/setup the debug "lighting" pass // * Tell shader which debug view to use - // TODO: uncomment - // bindTexturesForLightPass(R.prog_Debug); - // gl.uniform1i(R.prog_Debug.u_debug, cfg.debugView); + // DONE: uncomment + bindTexturesForLightPass(R.prog_Debug); + gl.uniform1i(R.prog_Debug.u_debug, cfg.debugView); // * Render a fullscreen quad to perform shading on - // TODO: uncomment - // renderFullScreenQuad(R.prog_Debug); + // DONE: uncomment + renderFullScreenQuad(R.prog_Debug); }; + R.pass_debug.render_scissor = function(state) + { + // * Bind R.pass_deferred.fbo to write into for later postprocessing + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + gl.clearColor(0.0, 0.0, 0.0, 0.0); + gl.clearDepth(1.0); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + + gl.enable(gl.BLEND); + gl.blendEquation( gl.FUNC_ADD ); + gl.blendFunc(gl.SRC_ALPHA, gl.ONE); + + // * Bind/setup the Blinn-Phong pass, and render using fullscreen quad + //bindTexturesForLightPass(R.prog_BlinnPhong_PointLight); + + + if (cfg.useLightProxy) + { + + readyNonGltfModelForDraw(R.progRed_sphere, R.sphereModel); + for (let light of R.lights) + { + var m_scale = new THREE.Matrix4(); + m_scale.makeScale(light.rad * 1.1,light.rad * 1.1,light.rad * 1.1); // edg + var m_translation = new THREE.Matrix4(); + m_translation.makeTranslation(light.pos[0],light.pos[1],light.pos[2]); + var m_world = new THREE.Matrix4(); + m_world.multiplyMatrices(m_translation,m_scale); + + gl.uniformMatrix4fv(R.progRed_sphere.u_cameraMat,false,state.cameraMat.elements); + gl.uniformMatrix4fv(R.progRed_sphere.u_worldMat,false,m_world.elements); + + drawReadyNonGltfModel(R.sphereModel); + } + } + else + { + gl.enable(gl.SCISSOR_TEST); + for (let light of R.lights) + { + var sc = getScissorForLight(state.viewMat, state.projMat, light); + if (!sc){continue;} + gl.scissor(sc[0], sc[1], sc[2], sc[3]); + + renderFullScreenQuad(R.progRed); + } + gl.disable(gl.SCISSOR_TEST); + } + + gl.disable(gl.BLEND); + } + /** * 'deferred' pass: Add lighting results for each individual light */ @@ -130,25 +197,135 @@ // Enable blending and use gl.blendFunc to blend with: // color = 1 * src_color + 1 * dst_color - // Here is a wonderful demo of showing how blend function works: + // Here is a wonderful demo of showing how blend function works: // http://mrdoob.github.io/webgl-blendfunctions/blendfunc.html - // TODO: uncomment - // gl.enable(gl.BLEND); - // gl.blendEquation( gl.FUNC_ADD ); - // gl.blendFunc(gl.ONE,gl.ONE); - + // DONE: uncomment + gl.enable(gl.BLEND); + gl.blendEquation( gl.FUNC_ADD ); + gl.blendFunc(gl.ONE,gl.ONE); + gl.depthFunc(gl.ALWAYS); // * Bind/setup the ambient pass, and render using fullscreen quad bindTexturesForLightPass(R.prog_Ambient); renderFullScreenQuad(R.prog_Ambient); + gl.depthFunc(gl.LEQUAL); + + if (cfg.useLightProxy) + { + if (cfg.useInvertedDepthTestForLightProxy) + { + gl.cullFace(gl.FRONT); + gl.depthFunc(gl.GREATER); + gl.depthMask(false); + } + else + { + gl.disable(gl.DEPTH_TEST); + } - // * Bind/setup the Blinn-Phong pass, and render using fullscreen quad - bindTexturesForLightPass(R.prog_BlinnPhong_PointLight); + // * Bind/setup the Blinn-Phong pass, and render using fullscreen quad + bindTexturesForLightPass(R.prog_BlinnPhong_PointLight_sphere); + + // Bind camera position + gl.uniform3fv(R.prog_BlinnPhong_PointLight_sphere.u_camPos, state.cameraPos.toArray()); + + readyNonGltfModelForDraw(R.prog_BlinnPhong_PointLight_sphere, R.sphereModel); + for (let light of R.lights) + { + var m_scale = new THREE.Matrix4(); + m_scale.makeScale(light.rad * 1.1,light.rad * 1.1,light.rad * 1.1); // edg + var m_translation = new THREE.Matrix4(); + m_translation.makeTranslation(light.pos[0],light.pos[1],light.pos[2]); + var m_world = new THREE.Matrix4(); + m_world.multiplyMatrices(m_translation,m_scale); + + gl.uniformMatrix4fv(R.prog_BlinnPhong_PointLight_sphere.u_cameraMat,false,state.cameraMat.elements); + gl.uniformMatrix4fv(R.prog_BlinnPhong_PointLight_sphere.u_worldMat,false,m_world.elements); + + gl.uniform3fv(R.prog_BlinnPhong_PointLight_sphere.u_lightCol, light.col); + gl.uniform3fv(R.prog_BlinnPhong_PointLight_sphere.u_lightPos, light.pos); + gl.uniform1f(R.prog_BlinnPhong_PointLight_sphere.u_lightRad, light.rad); + drawReadyNonGltfModel(R.sphereModel); + } - // TODO: add a loop here, over the values in R.lights, which sets the - // uniforms R.prog_BlinnPhong_PointLight.u_lightPos/Col/Rad etc., - // then does renderFullScreenQuad(R.prog_BlinnPhong_PointLight). + if (cfg.debugScissor) + { + readyNonGltfModelForDraw(R.progRed_sphere, R.sphereModel); + gl.blendEquation( gl.FUNC_ADD ); + gl.blendFunc(gl.SRC_ALPHA, gl.ONE); + for (let light of R.lights) + { + var m_scale = new THREE.Matrix4(); + m_scale.makeScale(light.rad * 1.1,light.rad * 1.1,light.rad * 1.1); // edg + var m_translation = new THREE.Matrix4(); + m_translation.makeTranslation(light.pos[0],light.pos[1],light.pos[2]); + var m_world = new THREE.Matrix4(); + m_world.multiplyMatrices(m_translation,m_scale); + + gl.uniformMatrix4fv(R.progRed_sphere.u_cameraMat,false,state.cameraMat.elements); + gl.uniformMatrix4fv(R.progRed_sphere.u_worldMat,false,m_world.elements); + + drawReadyNonGltfModel(R.sphereModel); + } + } - // TODO: In the lighting loop, use the scissor test optimization + if (cfg.useInvertedDepthTestForLightProxy) + { + gl.cullFace(gl.BACK); + gl.depthFunc(gl.LEQUAL); + gl.depthMask(true); + } + else + { + gl.enable(gl.DEPTH_TEST); + } + } + else + { + // * Bind/setup the Blinn-Phong pass, and render using fullscreen quad + bindTexturesForLightPass(R.prog_BlinnPhong_PointLight); + + // Bind camera position + gl.uniform3fv(R.prog_BlinnPhong_PointLight.u_camPos, state.cameraPos.toArray()); + + + // use scissors + gl.disable(gl.DEPTH_TEST); + gl.enable(gl.SCISSOR_TEST); + + // DONE: add a loop here, over the values in R.lights, which sets the + // uniforms R.prog_BlinnPhong_PointLight.u_lightPos/Col/Rad etc., + // then does renderFullScreenQuad(R.prog_BlinnPhong_PointLight). + for (let light of R.lights) + { + var sc = getScissorForLight(state.viewMat, state.projMat, light); + if (!sc){continue;} + gl.scissor(sc[0], sc[1], sc[2], sc[3]); + + gl.uniform3fv(R.prog_BlinnPhong_PointLight.u_lightCol, light.col); + gl.uniform3fv(R.prog_BlinnPhong_PointLight.u_lightPos, light.pos); + gl.uniform1f(R.prog_BlinnPhong_PointLight.u_lightRad, light.rad); + renderFullScreenQuad(R.prog_BlinnPhong_PointLight); + } + + if (cfg.debugScissor) + { + gl.blendEquation( gl.FUNC_ADD ); + gl.blendFunc(gl.SRC_ALPHA, gl.ONE); + for (let light of R.lights) + { + var sc = getScissorForLight(state.viewMat, state.projMat, light); + if (!sc){continue;} + gl.scissor(sc[0], sc[1], sc[2], sc[3]); + + renderFullScreenQuad(R.progRed); + } + } + + gl.disable(gl.SCISSOR_TEST); + gl.enable(gl.DEPTH_TEST); + } + + // DONE: In the lighting loop, use the scissor test optimization // Enable gl.SCISSOR_TEST, render all lights, then disable it. // // getScissorForLight returns null if the scissor is off the screen. @@ -158,6 +335,8 @@ // Disable blending so that it doesn't affect other code gl.disable(gl.BLEND); + + R.previous_pass_tex_output = R.pass_deferred.colorTex; }; var bindTexturesForLightPass = function(prog) { @@ -175,6 +354,56 @@ gl.uniform1i(prog.u_depth, R.NUM_GBUFFERS); }; + /** + * 'bloom' pass: apply bloom to the scene + */ + R.pass_bloom.render = function(state) { + // Step1: extract bright area + gl.bindFramebuffer(gl.FRAMEBUFFER, R.pass_bloom.fbos[0]); + //gl.clearDepth(1.0); + gl.useProgram(R.prog_bloom_extract.prog) + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, R.previous_pass_tex_output); + gl.uniform1i(R.prog_bloom_extract.u_color, 0); + renderFullScreenQuad(R.prog_bloom_extract); + + // Step2: two-pass blur + gl.useProgram(R.prog_bloom.prog); + gl.uniform2fv(R.prog_bloom.u_tex_offset, [1.0 / width, 1.0/ height]); + gl.uniform1f(R.prog_bloom.u_scale, cfg.bloomSize); + for (let i = 1; i < 3; i++) + { + gl.bindFramebuffer(gl.FRAMEBUFFER, R.pass_bloom.fbos[i]); + //gl.clearDepth(1.0); + // gl.clear(gl.DEPTH_BUFFER_BIT); + gl.uniform1i(R.prog_bloom.u_horizontal, i - 1); + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, R.pass_bloom.colorTexs[i-1]); + gl.uniform1i(R.prog_bloom.u_color, 0); + + renderFullScreenQuad(R.prog_bloom); + } + + // Step3: combine + gl.bindFramebuffer(gl.FRAMEBUFFER, R.pass_bloom.fbos[3]); + //gl.clearDepth(1.0); + gl.useProgram(R.prog_bloom_combine.prog) + + // output of deferred pass + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, R.previous_pass_tex_output); + gl.uniform1i(R.prog_bloom_combine.u_scene, 0); + + // blur pass output + gl.activeTexture(gl.TEXTURE1); + gl.bindTexture(gl.TEXTURE_2D, R.pass_bloom.colorTexs[2]); + gl.uniform1i(R.prog_bloom_combine.u_bloom, 1); + + renderFullScreenQuad(R.prog_bloom_combine); + + R.previous_pass_tex_output = R.pass_bloom.colorTexs[3]; + }; + /** * 'post1' pass: Perform (first) pass of post-processing */ @@ -191,12 +420,13 @@ // * Bind the deferred pass's color output as a texture input // Set gl.TEXTURE0 as the gl.activeTexture unit - // TODO: uncomment - // gl.activeTexture(gl.TEXTURE0); + // DONE: uncomment + gl.activeTexture(gl.TEXTURE0); // Bind the TEXTURE_2D, R.pass_deferred.colorTex to the active texture unit - // TODO: uncomment - // gl.bindTexture(gl.TEXTURE_2D, R.pass_deferred.colorTex); + // DONE: uncomment + gl.bindTexture(gl.TEXTURE_2D, R.previous_pass_tex_output); + //gl.bindTexture(gl.TEXTURE_2D, R.pass_deferred.colorTex); // Configure the R.progPost1.u_color uniform to point at texture unit 0 gl.uniform1i(R.progPost1.u_color, 0); @@ -225,17 +455,17 @@ var init = function() { // Create a new buffer with gl.createBuffer, and save it as vbo. - // TODO: uncomment + // DONE: uncomment vbo = gl.createBuffer(); // Bind the VBO as the gl.ARRAY_BUFFER - // TODO: uncomment - // gl.bindBuffer(gl.ARRAY_BUFFER,vbo); + // DONE: uncomment + gl.bindBuffer(gl.ARRAY_BUFFER,vbo); // Upload the positions array to the currently-bound array buffer // using gl.bufferData in static draw mode. - // TODO: uncomment - // gl.bufferData(gl.ARRAY_BUFFER,positions,gl.STATIC_DRAW); + // DONE: uncomment + gl.bufferData(gl.ARRAY_BUFFER,positions,gl.STATIC_DRAW); }; return function(prog) { @@ -248,22 +478,22 @@ gl.useProgram(prog.prog); // Bind the VBO as the gl.ARRAY_BUFFER - // TODO: uncomment - // gl.bindBuffer(gl.ARRAY_BUFFER, vbo); + // DONE: uncomment + gl.bindBuffer(gl.ARRAY_BUFFER, vbo); // Enable the bound buffer as the vertex attrib array for // prog.a_position, using gl.enableVertexAttribArray - // TODO: uncomment - // gl.enableVertexAttribArray(prog.a_position); + // DONE: uncomment + gl.enableVertexAttribArray(prog.a_position); // Use gl.vertexAttribPointer to tell WebGL the type/layout for // prog.a_position's access pattern. - // TODO: uncomment - // gl.vertexAttribPointer(prog.a_position, 3, gl.FLOAT, gl.FALSE, 0, 0); + // DONE: uncomment + gl.vertexAttribPointer(prog.a_position, 3, gl.FLOAT, gl.FALSE, 0, 0); // Use gl.drawArrays (or gl.drawElements) to draw your quad. - // TODO: uncomment - // gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); + // DONE: uncomment + gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); // Unbind the array buffer. gl.bindBuffer(gl.ARRAY_BUFFER, null); diff --git a/js/deferredSetup.js b/js/deferredSetup.js index 65136e0..ffd5452 100644 --- a/js/deferredSetup.js +++ b/js/deferredSetup.js @@ -5,10 +5,11 @@ R.pass_copy = {}; R.pass_debug = {}; R.pass_deferred = {}; + R.pass_bloom = {}; R.pass_post1 = {}; R.lights = []; - R.NUM_GBUFFERS = 4; + R.NUM_GBUFFERS = 2; /** * Set up the deferred pipeline framebuffer objects and textures. @@ -18,6 +19,7 @@ loadAllShaderPrograms(); R.pass_copy.setup(); R.pass_deferred.setup(); + R.pass_bloom.setup(); }; // TODO: Edit if you want to change the light initial positions @@ -89,6 +91,8 @@ R.pass_deferred.colorTex = createAndBindColorTargetTexture( R.pass_deferred.fbo, gl_draw_buffers.COLOR_ATTACHMENT0_WEBGL); + R.pass_deferred.depthTex = createAndBindDepthTargetTexture(R.pass_deferred.fbo); + // * Check for framebuffer errors abortIfFramebufferIncomplete(R.pass_deferred.fbo); // * Tell the WEBGL_draw_buffers extension which FBO attachments are @@ -98,6 +102,33 @@ gl.bindFramebuffer(gl.FRAMEBUFFER, null); }; + /** + * Create/configure framebuffers on "bloom" stage + */ + R.pass_bloom.setup = function() { + // * Create the FBO + var fbos = [] + var color_texs = [] + for (var i = 0; i < 4; i++) + { + // 0 for extraction + // 1 and 2 for horizontal and vertical bloom pass + // 3 for combination + // TODO: use ping-pong to reduce textures needed + fbos.push(gl.createFramebuffer()); + color_texs.push(createAndBindColorTargetTexture( + fbos[i], gl_draw_buffers.COLOR_ATTACHMENT0_WEBGL)); + + // * Check for framebuffer errors + abortIfFramebufferIncomplete(fbos[i]); + + gl_draw_buffers.drawBuffersWEBGL([gl_draw_buffers.COLOR_ATTACHMENT0_WEBGL]); + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + } + R.pass_bloom.fbos = fbos; + R.pass_bloom.colorTexs = color_texs; + }; + /** * Loads all of the shader programs used in the pipeline. */ @@ -125,6 +156,20 @@ R.progRed = { prog: prog }; }); + loadShaderProgram(gl, 'glsl/sphere.vert.glsl', 'glsl/red.frag.glsl', + function(prog) { + // Create an object to hold info about this shader program + var p = { prog: prog }; + + // Retrieve the uniform and attribute locations + p.u_cameraMat = gl.getUniformLocation(p.prog, 'u_cameraMat'); + p.u_worldMat = gl.getUniformLocation(p.prog, 'u_worldMat'); + p.a_position = gl.getAttribLocation(prog, 'a_position'); + + // Save the object into this variable for access later + R.progRed_sphere = p; + }); + loadShaderProgram(gl, 'glsl/quad.vert.glsl', 'glsl/clear.frag.glsl', function(prog) { // Create an object to hold info about this shader program @@ -141,9 +186,36 @@ p.u_lightPos = gl.getUniformLocation(p.prog, 'u_lightPos'); p.u_lightCol = gl.getUniformLocation(p.prog, 'u_lightCol'); p.u_lightRad = gl.getUniformLocation(p.prog, 'u_lightRad'); + p.u_camPos = gl.getUniformLocation(p.prog, 'u_camPos'); R.prog_BlinnPhong_PointLight = p; }); + + + loadShaderProgram(gl, 'glsl/sphere.vert.glsl', 'glsl/deferred/blinnphong-pointlight.frag.glsl', + function(prog) { + // Create an object to hold info about this shader program + var p = { prog: prog }; + + // Retrieve the uniform and attribute locations + p.u_cameraMat = gl.getUniformLocation(p.prog, 'u_cameraMat'); + p.u_worldMat = gl.getUniformLocation(p.prog, 'u_worldMat'); + p.a_position = gl.getAttribLocation(prog, 'a_position'); + + p.u_gbufs = []; + for (var i = 0; i < R.NUM_GBUFFERS; i++) { + p.u_gbufs[i] = gl.getUniformLocation(prog, 'u_gbufs[' + i + ']'); + } + p.u_depth = gl.getUniformLocation(prog, 'u_depth'); + p.u_lightPos = gl.getUniformLocation(p.prog, 'u_lightPos'); + p.u_lightCol = gl.getUniformLocation(p.prog, 'u_lightCol'); + p.u_lightRad = gl.getUniformLocation(p.prog, 'u_lightRad'); + p.u_camPos = gl.getUniformLocation(p.prog, 'u_camPos'); + + // Save the object into this variable for access later + R.prog_BlinnPhong_PointLight_sphere = p; + }); + loadDeferredProgram('debug', function(p) { p.u_debug = gl.getUniformLocation(p.prog, 'u_debug'); // Save the object into this variable for access later @@ -157,6 +229,25 @@ }); // TODO: If you add more passes, load and set up their shader programs. + loadPostProgram('bloom', function(p) { + p.u_color = gl.getUniformLocation(p.prog, 'u_color'); + p.u_horizontal = gl.getUniformLocation(p.prog, 'u_horizontal'); + p.u_tex_offset = gl.getUniformLocation(p.prog, 'u_tex_offset'); + p.u_scale = gl.getUniformLocation(p.prog, 'u_scale'); + // Save the object into this variable for access later + R.prog_bloom = p; + }); + loadPostProgram('bloom_extract', function(p) { + p.u_color = gl.getUniformLocation(p.prog, 'u_color'); + // Save the object into this variable for access later + R.prog_bloom_extract = p; + }); + loadPostProgram('bloom_combine', function(p) { + p.u_scene = gl.getUniformLocation(p.prog, 'u_scene'); + p.u_bloom = gl.getUniformLocation(p.prog, 'u_bloom'); + // Save the object into this variable for access later + R.prog_bloom_combine = p; + }); }; var loadDeferredProgram = function(name, callback) { diff --git a/js/framework.js b/js/framework.js index 4f944ee..f426a4b 100644 --- a/js/framework.js +++ b/js/framework.js @@ -47,7 +47,8 @@ var width, height; 'OES_texture_float', 'OES_texture_float_linear', 'WEBGL_depth_texture', - 'WEBGL_draw_buffers' + 'WEBGL_draw_buffers', + 'EXT_frag_depth' ]; for (var i = 0; i < reqd.length; i++) { var e = reqd[i]; @@ -63,11 +64,13 @@ var width, height; gl_draw_buffers = gl.getExtension('WEBGL_draw_buffers'); var maxdb = gl.getParameter(gl_draw_buffers.MAX_DRAW_BUFFERS_WEBGL); console.log('MAX_DRAW_BUFFERS_WEBGL: ' + maxdb); + + gl.getExtension('EXT_frag_depth'); }; var init = function() { - // TODO: For performance measurements, disable debug mode! - var debugMode = true; + // DONE: For performance measurements, disable debug mode! + var debugMode = false; canvas = document.getElementById('canvas'); renderer = new THREE.WebGLRenderer({ @@ -187,8 +190,8 @@ var width, height; gl.texParameteri(target, gl.TEXTURE_MAG_FILTER, magFilter); gl.texParameteri(target, gl.TEXTURE_WRAP_S, wrapS); gl.texParameteri(target, gl.TEXTURE_WRAP_T, wrapT); - if (minFilter == gl.NEAREST_MIPMAP_NEAREST || - minFilter == gl.NEAREST_MIPMAP_LINEAR || + if (minFilter == gl.NEAREST_MIPMAP_NEAREST || + minFilter == gl.NEAREST_MIPMAP_LINEAR || minFilter == gl.LINEAR_MIPMAP_NEAREST || minFilter == gl.LINEAR_MIPMAP_LINEAR ) { gl.generateMipmap(target); @@ -245,7 +248,7 @@ var width, height; uvInfo: {size: uvInfo.size, type: uvInfo.type, stride: uvInfo.stride, offset: uvInfo.offset}, // specific textures temp test - colmap: webGLTextures[colorTextureName].texture, + colmap: webGLTextures[colorTextureName].texture, normap: webGLTextures[normalTextureName].texture }); @@ -254,7 +257,7 @@ var width, height; } - + }); diff --git a/js/ui.js b/js/ui.js index abd6119..48f78bd 100644 --- a/js/ui.js +++ b/js/ui.js @@ -5,31 +5,44 @@ var cfg; var Cfg = function() { // TODO: Define config fields and defaults here + this.pause = false; this.debugView = -1; this.debugScissor = false; - this.enableEffect0 = false; + this.enableBloom = false; + this.bloomSize = 0.01; + this.useLightProxy = true; + this.useInvertedDepthTestForLightProxy = true; }; var init = function() { cfg = new Cfg(); var gui = new dat.GUI(); + gui.add(cfg, 'pause'); // TODO: Define any other possible config values gui.add(cfg, 'debugView', { 'None': -1, '0 Depth': 0, '1 Position': 1, - '2 Geometry normal': 2, - '3 Color map': 3, - '4 Normal map': 4, - '5 Surface normal': 5 + '2 Color map': 2, + '3 Normal': 3, + '4 Light scissors': 4, }); gui.add(cfg, 'debugScissor'); + gui.add(cfg, 'useLightProxy'); + gui.add(cfg, 'useInvertedDepthTestForLightProxy') var eff0 = gui.addFolder('EFFECT NAME HERE'); eff0.open(); - eff0.add(cfg, 'enableEffect0'); + eff0.add(cfg, 'enableBloom'); // TODO: add more effects toggles and parameters here + eff0.add(cfg, 'bloomSize', { + '0.002': 0.002, + '0.003': 0.003, + '0.005': 0.005, + '0.01': 0.01, + '0.05': 0.05, + }); }; window.handle_load.push(init); diff --git a/js/util.js b/js/util.js index 8f43d38..3ea705b 100644 --- a/js/util.js +++ b/js/util.js @@ -93,7 +93,7 @@ window.readyModelForDraw = function(prog, m) { } gl.bindBuffer(gl.ARRAY_BUFFER, m.attributes); - + gl.enableVertexAttribArray(prog.a_position); gl.vertexAttribPointer(prog.a_position, m.posInfo.size, m.posInfo.type, false, m.posInfo.stride, m.posInfo.offset); @@ -106,6 +106,38 @@ window.readyModelForDraw = function(prog, m) { gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, m.idx); }; +window.readyNonGltfModelForDraw = function(prog, m) { + gl.useProgram(prog.prog); + + if (m.colmap) { + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, m.colmap); + gl.uniform1i(prog.u_colmap, 0); + } + if (m.normap) { + gl.activeTexture(gl.TEXTURE1); + gl.bindTexture(gl.TEXTURE_2D, m.normap); + gl.uniform1i(prog.u_normap, 1); + } + + gl.enableVertexAttribArray(prog.a_position); + gl.bindBuffer(gl.ARRAY_BUFFER, m.position); + gl.vertexAttribPointer(prog.a_position, 3, gl.FLOAT, false, 0, 0); + + if (prog.a_normal >= 0 && m.normal) { + gl.enableVertexAttribArray(prog.a_normal); + gl.bindBuffer(gl.ARRAY_BUFFER, m.normal); + gl.vertexAttribPointer(prog.a_normal, 3, gl.FLOAT, false, 0, 0); + } + if (prog.a_uv >= 0 && m.uv) { + gl.enableVertexAttribArray(prog.a_uv); + gl.bindBuffer(gl.ARRAY_BUFFER, m.uv); + gl.vertexAttribPointer(prog.a_uv, 2, gl.FLOAT, false, 0, 0); + } + + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, m.idx); +}; + window.drawReadyModel = function(m) { // TODO for TA in future: matrix transform for multiple hierachy gltf models // reference: https://github.com/CIS565-Fall-2016/Project5A-WebGL-Forward-Plus-Shading-with-glTF/blob/master/js/forwardPlusRenderer/forwardPlusRenderer.js#L201 @@ -113,6 +145,10 @@ window.drawReadyModel = function(m) { gl.drawElements(m.gltf.mode, m.gltf.indices.length, m.gltf.indicesComponentType, 0); }; +window.drawReadyNonGltfModel = function(m) { + gl.drawElements(gl.TRIANGLES, m.elemCount, gl.UNSIGNED_INT, 0); +}; + window.getScissorForLight = (function() { // Pre-allocate for performance - avoids additional allocation var a = new THREE.Vector4(0, 0, 0, 0); @@ -146,7 +182,9 @@ window.getScissorForLight = (function() { maxpt.set(Math.min( 1, b.x), Math.min( 1, b.y)); if (maxpt.x < -1 || 1 < minpt.x || - maxpt.y < -1 || 1 < minpt.y) { + maxpt.y < -1 || 1 < minpt.y || + maxpt.x < minpt.x|| + maxpt.y < minpt.y) { return null; } diff --git a/screenshots/11.02.2016_debugview_color.jpg b/screenshots/11.02.2016_debugview_color.jpg new file mode 100644 index 0000000..02ed60e Binary files /dev/null and b/screenshots/11.02.2016_debugview_color.jpg differ diff --git a/screenshots/11.02.2016_debugview_depth.jpg b/screenshots/11.02.2016_debugview_depth.jpg new file mode 100644 index 0000000..0ad3740 Binary files /dev/null and b/screenshots/11.02.2016_debugview_depth.jpg differ diff --git a/screenshots/11.02.2016_debugview_geom_normal.jpg b/screenshots/11.02.2016_debugview_geom_normal.jpg new file mode 100644 index 0000000..ad4c6f4 Binary files /dev/null and b/screenshots/11.02.2016_debugview_geom_normal.jpg differ diff --git a/screenshots/11.02.2016_debugview_normal.jpg b/screenshots/11.02.2016_debugview_normal.jpg new file mode 100644 index 0000000..303ef1f Binary files /dev/null and b/screenshots/11.02.2016_debugview_normal.jpg differ diff --git a/screenshots/11.02.2016_debugview_normal_map.jpg b/screenshots/11.02.2016_debugview_normal_map.jpg new file mode 100644 index 0000000..38f9313 Binary files /dev/null and b/screenshots/11.02.2016_debugview_normal_map.jpg differ diff --git a/screenshots/11.02.2016_debugview_position.jpg b/screenshots/11.02.2016_debugview_position.jpg new file mode 100644 index 0000000..d2f9f3d Binary files /dev/null and b/screenshots/11.02.2016_debugview_position.jpg differ diff --git a/screenshots/11.02.2016_depth_works.jpg b/screenshots/11.02.2016_depth_works.jpg new file mode 100644 index 0000000..225692d Binary files /dev/null and b/screenshots/11.02.2016_depth_works.jpg differ diff --git a/screenshots/11.02.2016_position.jpg b/screenshots/11.02.2016_position.jpg new file mode 100644 index 0000000..4494de2 Binary files /dev/null and b/screenshots/11.02.2016_position.jpg differ diff --git a/screenshots/11.02.2016_prog.jpg b/screenshots/11.02.2016_prog.jpg new file mode 100644 index 0000000..184426e Binary files /dev/null and b/screenshots/11.02.2016_prog.jpg differ diff --git a/screenshots/11.03.2016_blinn_phong.jpg b/screenshots/11.03.2016_blinn_phong.jpg new file mode 100644 index 0000000..b53c3bc Binary files /dev/null and b/screenshots/11.03.2016_blinn_phong.jpg differ diff --git a/screenshots/11.03.2016_blinn_phong_2.jpg b/screenshots/11.03.2016_blinn_phong_2.jpg new file mode 100644 index 0000000..ff25453 Binary files /dev/null and b/screenshots/11.03.2016_blinn_phong_2.jpg differ diff --git a/screenshots/11.03.2016_lambert_with_attnuation.jpg b/screenshots/11.03.2016_lambert_with_attnuation.jpg new file mode 100644 index 0000000..09dd938 Binary files /dev/null and b/screenshots/11.03.2016_lambert_with_attnuation.jpg differ diff --git a/screenshots/11.03.2016_prog_lambert.jpg b/screenshots/11.03.2016_prog_lambert.jpg new file mode 100644 index 0000000..896cc7e Binary files /dev/null and b/screenshots/11.03.2016_prog_lambert.jpg differ diff --git a/screenshots/11.04.2016_bloom_progress_blur.jpg b/screenshots/11.04.2016_bloom_progress_blur.jpg new file mode 100644 index 0000000..60cd9b2 Binary files /dev/null and b/screenshots/11.04.2016_bloom_progress_blur.jpg differ diff --git a/screenshots/11.04.2016_bloom_progress_blur.png b/screenshots/11.04.2016_bloom_progress_blur.png new file mode 100644 index 0000000..34a4f23 Binary files /dev/null and b/screenshots/11.04.2016_bloom_progress_blur.png differ diff --git a/screenshots/11.04.2016_blooper_wrong_bloom.png b/screenshots/11.04.2016_blooper_wrong_bloom.png new file mode 100644 index 0000000..832221e Binary files /dev/null and b/screenshots/11.04.2016_blooper_wrong_bloom.png differ diff --git a/screenshots/11.05.2016_bloom.jpg b/screenshots/11.05.2016_bloom.jpg new file mode 100644 index 0000000..6f03661 Binary files /dev/null and b/screenshots/11.05.2016_bloom.jpg differ diff --git a/screenshots/11.05.2016_bloom_off.png b/screenshots/11.05.2016_bloom_off.png new file mode 100644 index 0000000..4829c17 Binary files /dev/null and b/screenshots/11.05.2016_bloom_off.png differ diff --git a/screenshots/11.05.2016_bloom_on.png b/screenshots/11.05.2016_bloom_on.png new file mode 100644 index 0000000..4197d7f Binary files /dev/null and b/screenshots/11.05.2016_bloom_on.png differ diff --git a/screenshots/11.05.2016_bright_area_extration_for_bloom.png b/screenshots/11.05.2016_bright_area_extration_for_bloom.png new file mode 100644 index 0000000..5a7fa62 Binary files /dev/null and b/screenshots/11.05.2016_bright_area_extration_for_bloom.png differ diff --git a/screenshots/11.05.2016_scissor1.jpg b/screenshots/11.05.2016_scissor1.jpg new file mode 100644 index 0000000..a044f95 Binary files /dev/null and b/screenshots/11.05.2016_scissor1.jpg differ diff --git a/screenshots/11.05.2016_scissor1.png b/screenshots/11.05.2016_scissor1.png new file mode 100644 index 0000000..a4f898d Binary files /dev/null and b/screenshots/11.05.2016_scissor1.png differ diff --git a/screenshots/11.06.2016_debug_scissor.jpg b/screenshots/11.06.2016_debug_scissor.jpg new file mode 100644 index 0000000..84a6294 Binary files /dev/null and b/screenshots/11.06.2016_debug_scissor.jpg differ diff --git a/screenshots/11.06.2016_debug_scissor.png b/screenshots/11.06.2016_debug_scissor.png new file mode 100644 index 0000000..e19a695 Binary files /dev/null and b/screenshots/11.06.2016_debug_scissor.png differ diff --git a/screenshots/11.06.2016_light_scissor_only.png b/screenshots/11.06.2016_light_scissor_only.png new file mode 100644 index 0000000..633f610 Binary files /dev/null and b/screenshots/11.06.2016_light_scissor_only.png differ diff --git a/screenshots/11.06.2016_show_scissor_only.jpg b/screenshots/11.06.2016_show_scissor_only.jpg new file mode 100644 index 0000000..31b26a2 Binary files /dev/null and b/screenshots/11.06.2016_show_scissor_only.jpg differ diff --git a/screenshots/11.08.2016_completed.png b/screenshots/11.08.2016_completed.png new file mode 100644 index 0000000..0b17d35 Binary files /dev/null and b/screenshots/11.08.2016_completed.png differ diff --git a/screenshots/11.08.2016_light_proxy_with_inverted_depth_test_and_front_face_culling.jpg b/screenshots/11.08.2016_light_proxy_with_inverted_depth_test_and_front_face_culling.jpg new file mode 100644 index 0000000..de15086 Binary files /dev/null and b/screenshots/11.08.2016_light_proxy_with_inverted_depth_test_and_front_face_culling.jpg differ diff --git a/screenshots/11.08.2016_light_proxy_with_inverted_depth_test_and_front_face_culling.png b/screenshots/11.08.2016_light_proxy_with_inverted_depth_test_and_front_face_culling.png new file mode 100644 index 0000000..4ce4d78 Binary files /dev/null and b/screenshots/11.08.2016_light_proxy_with_inverted_depth_test_and_front_face_culling.png differ diff --git a/screenshots/bloom1.png b/screenshots/bloom1.png new file mode 100644 index 0000000..8c129c3 Binary files /dev/null and b/screenshots/bloom1.png differ diff --git a/screenshots/bloom2.png b/screenshots/bloom2.png new file mode 100644 index 0000000..a2cc5a7 Binary files /dev/null and b/screenshots/bloom2.png differ diff --git a/screenshots/bloom3.png b/screenshots/bloom3.png new file mode 100644 index 0000000..2f37fcc Binary files /dev/null and b/screenshots/bloom3.png differ diff --git a/screenshots/bloopers/11.02.2016_blooper1.jpg b/screenshots/bloopers/11.02.2016_blooper1.jpg new file mode 100644 index 0000000..1bb2ce3 Binary files /dev/null and b/screenshots/bloopers/11.02.2016_blooper1.jpg differ diff --git a/screenshots/bloopers/11.04.2016_blooper.jpg b/screenshots/bloopers/11.04.2016_blooper.jpg new file mode 100644 index 0000000..6c9810e Binary files /dev/null and b/screenshots/bloopers/11.04.2016_blooper.jpg differ diff --git a/screenshots/bloopers/11.04.2016_blooper2.png b/screenshots/bloopers/11.04.2016_blooper2.png new file mode 100644 index 0000000..7585d96 Binary files /dev/null and b/screenshots/bloopers/11.04.2016_blooper2.png differ diff --git a/screenshots/bloopers/11.04.2016_blooper_you_call_it_bloom.jpg b/screenshots/bloopers/11.04.2016_blooper_you_call_it_bloom.jpg new file mode 100644 index 0000000..a82eb84 Binary files /dev/null and b/screenshots/bloopers/11.04.2016_blooper_you_call_it_bloom.jpg differ diff --git a/screenshots/bloopers/11.04.2016_blooper_you_call_it_bloom.png b/screenshots/bloopers/11.04.2016_blooper_you_call_it_bloom.png new file mode 100644 index 0000000..832221e Binary files /dev/null and b/screenshots/bloopers/11.04.2016_blooper_you_call_it_bloom.png differ diff --git a/screenshots/bloopers/11.06.2016_another_blur_approach.jpg b/screenshots/bloopers/11.06.2016_another_blur_approach.jpg new file mode 100644 index 0000000..879e437 Binary files /dev/null and b/screenshots/bloopers/11.06.2016_another_blur_approach.jpg differ diff --git a/screenshots/bloopers/11.06.2016_another_blur_approach.png b/screenshots/bloopers/11.06.2016_another_blur_approach.png new file mode 100644 index 0000000..fe44116 Binary files /dev/null and b/screenshots/bloopers/11.06.2016_another_blur_approach.png differ diff --git a/screenshots/bloopers/11.07.2016_hall_of_illusion_light_proxy_wrong.gif b/screenshots/bloopers/11.07.2016_hall_of_illusion_light_proxy_wrong.gif new file mode 100644 index 0000000..5875e3f Binary files /dev/null and b/screenshots/bloopers/11.07.2016_hall_of_illusion_light_proxy_wrong.gif differ diff --git a/screenshots/bloopers/11.08.2016_supercooltwistbloomper.gif b/screenshots/bloopers/11.08.2016_supercooltwistbloomper.gif new file mode 100644 index 0000000..0e37595 Binary files /dev/null and b/screenshots/bloopers/11.08.2016_supercooltwistbloomper.gif differ diff --git a/screenshots/bloopers/deferred-1478574081008.png b/screenshots/bloopers/deferred-1478574081008.png new file mode 100644 index 0000000..b4c9592 Binary files /dev/null and b/screenshots/bloopers/deferred-1478574081008.png differ diff --git a/screenshots/bloopers/deferred-1478574116376.png b/screenshots/bloopers/deferred-1478574116376.png new file mode 100644 index 0000000..b058c9e Binary files /dev/null and b/screenshots/bloopers/deferred-1478574116376.png differ diff --git a/screenshots/bloopers/deferred-1478620244130.png b/screenshots/bloopers/deferred-1478620244130.png new file mode 100644 index 0000000..3c2d75d Binary files /dev/null and b/screenshots/bloopers/deferred-1478620244130.png differ diff --git a/screenshots/nobloom.png b/screenshots/nobloom.png new file mode 100644 index 0000000..9ddb151 Binary files /dev/null and b/screenshots/nobloom.png differ diff --git a/screenshots/preview.gif b/screenshots/preview.gif new file mode 100644 index 0000000..4d2c2b5 Binary files /dev/null and b/screenshots/preview.gif differ diff --git a/screenshots/proxy1.png b/screenshots/proxy1.png new file mode 100644 index 0000000..7ca43a9 Binary files /dev/null and b/screenshots/proxy1.png differ diff --git a/screenshots/proxy2.png b/screenshots/proxy2.png new file mode 100644 index 0000000..19b0af2 Binary files /dev/null and b/screenshots/proxy2.png differ diff --git a/screenshots/proxy3.png b/screenshots/proxy3.png new file mode 100644 index 0000000..427aaf4 Binary files /dev/null and b/screenshots/proxy3.png differ diff --git a/screenshots/proxy4.png b/screenshots/proxy4.png new file mode 100644 index 0000000..783a8ea Binary files /dev/null and b/screenshots/proxy4.png differ