diff --git a/README.md b/README.md index 25002db..3c2773b 100644 --- a/README.md +++ b/README.md @@ -3,26 +3,67 @@ 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) +* Jian Ru +* Tested on: **Google Chrome 54.0.2840.71 m (64-bit)** + Windows 10, i7-4850 @ 2.3GHz 16GB, GT 750M 2GB (Personal) ### Live Online -[![](img/thumb.png)](http://TODO.github.io/Project5B-WebGL-Deferred-Shading) +[![](img/thumb.png)](https://jian-ru.github.io/Project5-WebGL-Deferred-Shading-with-glTF) ### Demo Video/GIF -[![](img/video.png)](TODO) +![](img/demo.gif) -### (TODO: Your README) +### Analysis -*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. +* Features + * Basic deferred shading pipeline + * Bloom with 2-pass Gaussian filtering + * SS Motion Blur + * G-buffer optimization + * Spherical light proxies with depth test and front face culling optimization -This assignment has a considerable amount of performance analysis compared -to implementation work. Complete the implementation early to leave time! +* Bloom + * Extracting bright regions + * Convert RGB color to illuminance using `I = 0.2126 * R + 0.7152 * G + 0.0722 * B` and copy the pixels whose brightness pass a user defined threshold. + * Blur the resulting brightness map + * I used two 1D Gaussian kernels with size 1x9 and 9x1 respectively and convolve them with the brightness map 5 times each. + + ![](img/bloom_perf.png) +* SS Motion Blur + * Implemented by following the idea from GPU Gems 3, Ch. 27. + * Reconstruct point world position using pixel texture coordinates and depth + * Transform the point from world to the NDC space in the previous frame by pre-multiplying it with the inverse of view-projection matrix from last frame + * Velocity vector is defined as the difference between the point's positions in the current and last frame + + ![](img/motionblur_perf.png) + +* G-Buffer Optimization + * G-buffer number reduced to 1 from 4 by + * Applying normal map in the copy pass instead of the light pass + * Using only two-component normal by taking advantage of the fact that normals have lengths of 1 + * Reconstructing eye space position from screen coordinates and depth of pixels + * Can easily reduce further to 1 by packing albedo with normal but choose not to for the sake of performance because bit shift and bit-wise operations are not available in GLSL 1.0. Doing packing hence requires many floating point divisions and multiplications. + * Further testing shows that using only 1 G-buffer gives better performance. I think the reason is that enabling multiple rendering targets and the extra global memory writes have greater cost than the extra floating point computations. + + | G-buffers | R | G | B | A | + | --- | --- | --- | --- | --- | + | G-buffer 1 | nrm.x | nrm.y | packedAlbedo | unused | + + ![](img/gbuffer_perf.png) + +* Spherical Light Proxies + * Render spheres instead of a full screen quad with scissor test to trigger light computation + * Each sphere is move to center at each point light source and scaled to the light's radius of influence + * The benefits is increased accuracy of light influence estimation, which further reduce the chance of shading unlit pixels. As a result, it further improves performance. + + | Scissors | Spherical Proxies | + | --- | --- | + | ![](img/scissor.PNG) | ![](img/sphere_proxy.PNG) | + + ![](img/scissor_vs_proxy.png) ### Credits diff --git a/glsl/copy.frag.glsl b/glsl/copy.frag.glsl index 823ebcd..1f4b43c 100644 --- a/glsl/copy.frag.glsl +++ b/glsl/copy.frag.glsl @@ -1,20 +1,41 @@ #version 100 -#extension GL_EXT_draw_buffers: enable +// #extension GL_EXT_draw_buffers: enable precision highp float; precision highp int; uniform sampler2D u_colmap; uniform sampler2D u_normap; -varying vec3 v_position; +// varying vec3 v_position; varying vec3 v_normal; varying vec2 v_uv; -void main() { +float packRGBA(vec4 color) +{ + color = floor(color * 255.0 + 0.5); + return color.r * 16777216.0 + color.g * 65536.0 + color.b * 256.0 + color.a; +} + +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() +{ // 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. - + vec4 albedo = texture2D(u_colmap, v_uv); + vec3 mapnrm = texture2D(u_normap, v_uv).xyz; + + vec3 nrm = applyNormalMap(v_normal, mapnrm); + // this gives you the idea - // gl_FragData[0] = vec4( v_position, 1.0 ); + // gl_FragData[0] = vec4(nrm.xy, packRGBA(albedo), 1.0); + gl_FragColor = vec4(nrm.xy, packRGBA(albedo), 1.0); } diff --git a/glsl/copy.vert.glsl b/glsl/copy.vert.glsl index ec14e69..c3e0975 100644 --- a/glsl/copy.vert.glsl +++ b/glsl/copy.vert.glsl @@ -3,19 +3,20 @@ precision highp float; precision highp int; -uniform mat4 u_cameraMat; +uniform mat4 u_cameraMat; // proj * view +uniform mat4 u_viewMat; -attribute vec3 a_position; -attribute vec3 a_normal; +attribute vec3 a_position; // in world space +attribute vec3 a_normal; // in world space attribute vec2 a_uv; -varying vec3 v_position; -varying vec3 v_normal; +// varying vec3 v_position; // in eye space +varying vec3 v_normal; // in eye space varying vec2 v_uv; void main() { gl_Position = u_cameraMat * vec4(a_position, 1.0); - v_position = a_position; - v_normal = a_normal; + // v_position = vec3(u_viewMat * vec4(a_position, 1.0)); + v_normal = vec3(u_viewMat * vec4(a_normal, 0.0)); v_uv = a_uv; } diff --git a/glsl/copydepth.frag.glsl b/glsl/copydepth.frag.glsl new file mode 100644 index 0000000..c71b157 --- /dev/null +++ b/glsl/copydepth.frag.glsl @@ -0,0 +1,12 @@ +#version 100 +precision highp float; +precision highp int; + +uniform sampler2D u_depth; + +varying vec2 v_uv; + +void main() +{ + gl_FragColor = texture2D(u_depth, v_uv); +} diff --git a/glsl/deferred/ambient.frag.glsl b/glsl/deferred/ambient.frag.glsl index 1fd4647..4e2e236 100644 --- a/glsl/deferred/ambient.frag.glsl +++ b/glsl/deferred/ambient.frag.glsl @@ -3,18 +3,31 @@ precision highp float; precision highp int; -#define NUM_GBUFFERS 4 +#define NUM_GBUFFERS 1 uniform sampler2D u_gbufs[NUM_GBUFFERS]; uniform sampler2D u_depth; varying vec2 v_uv; -void main() { +vec4 unpackRGBA(float packedColor) +{ + float tmp1 = packedColor; + float tmp2 = floor(tmp1 * 0.00390625); + float a = tmp1 - tmp2 * 256.0; + tmp1 = floor(tmp2 * 0.00390625); + float b = tmp2 - tmp1 * 256.0; + tmp2 = floor(tmp1 * 0.00390625); + float g = tmp1 - tmp2 * 256.0; + tmp1 = floor(tmp2 * 0.00390625); + float r = tmp2 - tmp1 * 256.0; + + return vec4(r, g, b, a) * 0.0039215686274509803921568627451; +} + +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 @@ -23,5 +36,5 @@ void main() { return; } - gl_FragColor = vec4(0.1, 0.1, 0.1, 1); // TODO: replace this + gl_FragColor = vec4(0.1 * unpackRGBA(gb0.z).rgb, 1); // TODO: replace this } diff --git a/glsl/deferred/blinnphong-pointlight.frag.glsl b/glsl/deferred/blinnphong-pointlight.frag.glsl index b24a54a..0b53c4e 100644 --- a/glsl/deferred/blinnphong-pointlight.frag.glsl +++ b/glsl/deferred/blinnphong-pointlight.frag.glsl @@ -2,38 +2,78 @@ precision highp float; precision highp int; -#define NUM_GBUFFERS 4 +#define NUM_GBUFFERS 1 +uniform vec3 u_viewportInfo; // (2_times_tan_halfFovy, width, height) uniform vec3 u_lightCol; -uniform vec3 u_lightPos; +uniform vec3 u_lightPos; // in eye space uniform float u_lightRad; 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 recoverEyePos(float depth) +{ + float oneOverHeight = 1.0 / u_viewportInfo.z; + float aspect = u_viewportInfo.y * oneOverHeight; + float ph = u_viewportInfo.x * oneOverHeight; + float pw = ph * aspect; + vec3 viewVec = vec3(vec2(pw, ph) * (gl_FragCoord.xy - u_viewportInfo.yz * 0.5), -1.0); + depth = depth * 2.0 - 1.0; + float eyeDepth = (depth - (101.0 / 99.0)) * (99.0 / 200.0); + eyeDepth = -1.0 / eyeDepth; + return viewVec * eyeDepth; } -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); +vec4 unpackRGBA(float packedColor) +{ + float tmp1 = packedColor; + float tmp2 = floor(tmp1 * 0.00390625); + float a = tmp1 - tmp2 * 256.0; + tmp1 = floor(tmp2 * 0.00390625); + float b = tmp2 - tmp1 * 256.0; + tmp2 = floor(tmp1 * 0.00390625); + float g = tmp1 - tmp2 * 256.0; + tmp1 = floor(tmp2 * 0.00390625); + float r = tmp2 - tmp1 * 256.0; + + return vec4(r, g, b, a) * 0.0039215686274509803921568627451; +} + +void main() +{ + vec2 v_uv = gl_FragCoord.xy / u_viewportInfo.yz; + vec4 gb0 = texture2D(u_gbufs[0], v_uv); // (eye_normal_x, eye_normal_y, packedAlbedo) 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) { + if (depth == 1.0) + { gl_FragColor = vec4(0, 0, 0, 0); return; } - gl_FragColor = vec4(0, 0, 1, 1); // TODO: perform lighting calculations + // TODO: perform lighting calculations + vec3 pos = recoverEyePos(depth); + vec3 nrm = vec3(gb0.xy, sqrt(max(0.0, 1.0 - dot(gb0.xy, gb0.xy)))); + vec3 albedo = unpackRGBA(gb0.z).rgb; + float dist2Light = distance(pos, u_lightPos); + + if (dist2Light < u_lightRad) + { + vec3 view = normalize(-pos); + vec3 light = normalize(u_lightPos - pos); + vec3 h = normalize(view + light); + vec3 finalColor = 0.5 * albedo * max(0.0, dot(nrm, light)) + + 0.5 * u_lightCol * pow(max(0.0, dot(nrm, h)), 20.0); + float attenuation = (u_lightRad - dist2Light) / u_lightRad; + gl_FragColor = vec4(attenuation * finalColor, 1.0); + } + else + { + gl_FragColor = vec4(0.0); + } } diff --git a/glsl/deferred/debug.frag.glsl b/glsl/deferred/debug.frag.glsl index 007466f..dfc1e28 100644 --- a/glsl/deferred/debug.frag.glsl +++ b/glsl/deferred/debug.frag.glsl @@ -2,8 +2,9 @@ precision highp float; precision highp int; -#define NUM_GBUFFERS 4 +#define NUM_GBUFFERS 1 +uniform vec3 u_viewportInfo; // (2_times_tan_halfFovy, width, height) uniform int u_debug; uniform sampler2D u_gbufs[NUM_GBUFFERS]; uniform sampler2D u_depth; @@ -12,7 +13,8 @@ varying vec2 v_uv; const vec4 SKY_COLOR = vec4(0.66, 0.73, 1.0, 1.0); -vec3 applyNormalMap(vec3 geomnor, vec3 normap) { +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)); @@ -20,34 +22,64 @@ vec3 applyNormalMap(vec3 geomnor, vec3 normap) { return normap.y * surftan + normap.x * surfbinor + normap.z * geomnor; } -void main() { +vec3 recoverEyePos(float depth) +{ + float oneOverHeight = 1.0 / u_viewportInfo.z; + float aspect = u_viewportInfo.y * oneOverHeight; + float ph = u_viewportInfo.x * oneOverHeight; + float pw = ph * aspect; + vec3 viewVec = vec3(vec2(pw, ph) * (gl_FragCoord.xy - u_viewportInfo.yz * 0.5), -1.0); + depth = depth * 2.0 - 1.0; + float eyeDepth = (depth - (101.0 / 99.0)) * (99.0 / 200.0); + eyeDepth = -1.0 / eyeDepth; + return viewVec * eyeDepth; +} + +vec4 unpackRGBA(float packedColor) +{ + float tmp1 = packedColor; + float tmp2 = floor(tmp1 * 0.00390625); + float a = tmp1 - tmp2 * 256.0; + tmp1 = floor(tmp2 * 0.00390625); + float b = tmp2 - tmp1 * 256.0; + tmp2 = floor(tmp1 * 0.00390625); + float g = tmp1 - tmp2 * 256.0; + tmp1 = floor(tmp2 * 0.00390625); + float r = tmp2 - tmp1 * 256.0; + + return vec4(r, g, b, a) * 0.0039215686274509803921568627451; +} + +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 + + // 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 pos = recoverEyePos(depth); // World-space position + vec3 nrm = vec3(gb0.xy, sqrt(max(0.0, 1.0 - dot(gb0.xy, gb0.xy)))); // Normals of the geometry as defined, without normal mapping + vec3 colmap = unpackRGBA(gb0.z).rgb; + // TODO: uncomment - if (u_debug == 0) { + 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); - } else if (u_debug == 2) { - // gl_FragColor = vec4(abs(geomnor), 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); - } else { + } + else if (u_debug == 1) + { + gl_FragColor = vec4(abs(pos) * 0.1, 1.0); + } + else if (u_debug == 2) + { + gl_FragColor = vec4(abs(nrm), 1.0); + } + else if (u_debug == 3) + { + gl_FragColor = vec4(colmap, 1.0); + } + else + { gl_FragColor = vec4(1, 0, 1, 1); } } diff --git a/glsl/post/one.frag.glsl b/glsl/post/bloomgather.frag.glsl similarity index 59% rename from glsl/post/one.frag.glsl rename to glsl/post/bloomgather.frag.glsl index 94191cd..583f7fa 100644 --- a/glsl/post/one.frag.glsl +++ b/glsl/post/bloomgather.frag.glsl @@ -3,18 +3,22 @@ precision highp float; precision highp int; uniform sampler2D u_color; +uniform sampler2D u_brightness; varying vec2 v_uv; const vec4 SKY_COLOR = vec4(0.01, 0.14, 0.42, 1.0); -void main() { +void main() +{ vec4 color = texture2D(u_color, v_uv); - if (color.a == 0.0) { + if (color.a == 0.0) + { gl_FragColor = SKY_COLOR; return; } - gl_FragColor = color; + vec3 brightness = texture2D(u_brightness, v_uv).rgb; + gl_FragColor = vec4(color.rgb + brightness, 1.0); } diff --git a/glsl/post/gaussianblur.frag.glsl b/glsl/post/gaussianblur.frag.glsl new file mode 100644 index 0000000..5b07e4f --- /dev/null +++ b/glsl/post/gaussianblur.frag.glsl @@ -0,0 +1,26 @@ +#version 100 +precision highp float; +precision highp int; + +uniform vec2 u_imgDim; +uniform sampler2D u_color; // brightness texture (may be blurred before) +uniform bool u_isHorizontal; + +varying vec2 v_uv; + +void main() +{ + vec2 offset = 1.0 / u_imgDim * vec2(u_isHorizontal, !u_isHorizontal); + vec3 result = texture2D(u_color, v_uv).rgb * 0.227027; + + result += texture2D(u_color, v_uv + offset).rgb * 0.1945946; + result += texture2D(u_color, v_uv - offset).rgb * 0.1945946; + result += texture2D(u_color, v_uv + 2.0 * offset).rgb * 0.1216216; + result += texture2D(u_color, v_uv - 2.0 * offset).rgb * 0.1216216; + result += texture2D(u_color, v_uv + 3.0 * offset).rgb * 0.054054; + result += texture2D(u_color, v_uv - 3.0 * offset).rgb * 0.054054; + result += texture2D(u_color, v_uv + 4.0 * offset).rgb * 0.016216; + result += texture2D(u_color, v_uv - 4.0 * offset).rgb * 0.016216; + + gl_FragColor = vec4(result, 1.0); +} diff --git a/glsl/post/motionblur.frag.glsl b/glsl/post/motionblur.frag.glsl new file mode 100644 index 0000000..43ce82e --- /dev/null +++ b/glsl/post/motionblur.frag.glsl @@ -0,0 +1,37 @@ +#version 100 +precision highp float; +precision highp int; + +#define NUM_SAMPLES 9 + +uniform float u_scale; +uniform sampler2D u_color; +uniform sampler2D u_depth; +uniform mat4 u_preViewProj; +uniform mat4 u_viewProjInverse; + +varying vec2 v_uv; + + +void main() +{ + vec2 texCoord = v_uv; + float zOverW = texture2D(u_depth, texCoord).x * 2.0 - 1.0; + vec4 ndcCoord = vec4(texCoord * 2.0 - 1.0, zOverW, 1.0); + vec4 worldPos = u_viewProjInverse * ndcCoord; + worldPos /= worldPos.w; + vec4 preNdcPos = u_preViewProj * worldPos; + preNdcPos /= preNdcPos.w; + + vec2 vel = (ndcCoord.xy - preNdcPos.xy) * (u_scale / float(NUM_SAMPLES - 1)); + + vec4 color = texture2D(u_color, texCoord); + + for (int i = 1; i < NUM_SAMPLES; ++i) + { + texCoord += vel; + color += texture2D(u_color, texCoord); + } + + gl_FragColor = color / float(NUM_SAMPLES); +} diff --git a/glsl/post/thresholdbrightness.frag.glsl b/glsl/post/thresholdbrightness.frag.glsl new file mode 100644 index 0000000..eff0a54 --- /dev/null +++ b/glsl/post/thresholdbrightness.frag.glsl @@ -0,0 +1,20 @@ +#version 100 +precision highp float; +precision highp int; + +uniform float u_threshold; +uniform sampler2D u_color; + +varying vec2 v_uv; + + +void main() +{ + vec4 color = texture2D(u_color, v_uv); + float brightness = dot(color.rgb, vec3(0.2126, 0.7152, 0.0722)); + + if (brightness > u_threshold) + { + gl_FragColor = color; + } +} diff --git a/glsl/red.frag.glsl b/glsl/red.frag.glsl index f8ef1ec..e41993b 100644 --- a/glsl/red.frag.glsl +++ b/glsl/red.frag.glsl @@ -3,5 +3,5 @@ precision highp float; precision highp int; void main() { - gl_FragColor = vec4(1, 0, 0, 1); + gl_FragColor = vec4(1, 0, 0, 0.1); } diff --git a/glsl/sphereproxy.vert.glsl b/glsl/sphereproxy.vert.glsl new file mode 100644 index 0000000..fa3ae37 --- /dev/null +++ b/glsl/sphereproxy.vert.glsl @@ -0,0 +1,17 @@ +#version 100 +precision highp float; +precision highp int; + +uniform vec3 u_lightPos; // in eye space +uniform float u_lightRad; +uniform mat4 u_proj; + +attribute vec3 a_position; + + +void main() +{ + vec3 pos = a_position * 1.1 * u_lightRad + u_lightPos; + gl_Position = u_proj * vec4(pos, 1.0); + // gl_Position = vec4(a_position, 1.0); +} diff --git a/img/analysis.xlsx b/img/analysis.xlsx new file mode 100644 index 0000000..6d67321 Binary files /dev/null and b/img/analysis.xlsx differ diff --git a/img/bloom_perf.png b/img/bloom_perf.png new file mode 100644 index 0000000..8a6dffe Binary files /dev/null and b/img/bloom_perf.png differ diff --git a/img/demo.gif b/img/demo.gif new file mode 100644 index 0000000..46ca720 Binary files /dev/null and b/img/demo.gif differ diff --git a/img/gbuffer_perf.png b/img/gbuffer_perf.png new file mode 100644 index 0000000..ac4c2d1 Binary files /dev/null and b/img/gbuffer_perf.png differ diff --git a/img/motionblur_perf.png b/img/motionblur_perf.png new file mode 100644 index 0000000..157509f Binary files /dev/null and b/img/motionblur_perf.png differ diff --git a/img/scissor.PNG b/img/scissor.PNG new file mode 100644 index 0000000..b5fdbf4 Binary files /dev/null and b/img/scissor.PNG differ diff --git a/img/scissor_vs_proxy.png b/img/scissor_vs_proxy.png new file mode 100644 index 0000000..6f0258a Binary files /dev/null and b/img/scissor_vs_proxy.png differ diff --git a/img/sphere_proxy.PNG b/img/sphere_proxy.PNG new file mode 100644 index 0000000..f79da18 Binary files /dev/null and b/img/sphere_proxy.PNG differ diff --git a/img/thumb.png b/img/thumb.png index 9ec8ed0..fa03bca 100644 Binary files a/img/thumb.png and b/img/thumb.png differ diff --git a/img/video.png b/img/video.png deleted file mode 100644 index 9ec8ed0..0000000 Binary files a/img/video.png and /dev/null differ diff --git a/js/deferredRender.js b/js/deferredRender.js index bb3edd4..71f5f9d 100644 --- a/js/deferredRender.js +++ b/js/deferredRender.js @@ -1,3 +1,5 @@ +var preViewProjMat = null; + (function() { 'use strict'; // deferredSetup.js must be loaded first @@ -5,12 +7,17 @@ R.deferredRender = function(state) { if (!aborted && ( !R.progCopy || + !R.progCopyDepth || !R.progRed || + !R.progSphereRed || !R.progClear || !R.prog_Ambient || !R.prog_BlinnPhong_PointLight || !R.prog_Debug || - !R.progPost1)) { + !R.progPostMotionBlur || + !R.progPostGaussianBlur || + !R.progPostBloomGather || + !R.progPostThresholdBrightness)) { console.log('waiting for programs to load...'); return; } @@ -28,62 +35,102 @@ // CHECKITOUT: START HERE! You can even uncomment this: //debugger; - { // TODO: this block should be removed after testing renderFullScreenQuad +/* { // 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) { + R.pass_copyDepth.render(state); + + if (cfg && cfg.debugScissor) + { + if (cfg.sphereProxy) + { + R.pass_sphereProxyDebug.render(state); + } + else + { + R.pass_scissorTestDebug.render(state); + } + } + else 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 { // * Deferred pass and postprocessing pass(es) // TODO: uncomment these - // R.pass_deferred.render(state); - // R.pass_post1.render(state); + var isFirstPost; + var isLastStage = !cfg.enableBloom && !cfg.enableMotionBlur; + R.pass_deferred.render = cfg.sphereProxy ? sphereProxyLighting : scissorTestLighting; + R.pass_deferred.render(state, isLastStage); // OPTIONAL TODO: call more postprocessing passes, if any + if (cfg.enableBloom) + { + isFirstPost = true; + isLastStage = !cfg.enableMotionBlur; + R.pass_bloom.render(state, isFirstPost, isLastStage); + } + if (cfg.enableMotionBlur) + { + isFirstPost = !cfg.enableBloom; + isLastStage = true; + R.pass_motionblur.render(state, isFirstPost, isLastStage); + } } }; + R.pass_copyDepth.render = function(state) + { + gl.bindFramebuffer(gl.FRAMEBUFFER, R.pass_copyDepth.fbo); + gl.depthFunc(gl.ALWAYS); + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, R.pass_copy.depthTex); + gl.useProgram(R.progCopyDepth.prog); + gl.uniform1i(R.progCopyDepth.u_depth, 0); + renderFullScreenQuad(R.progCopyDepth); + gl.depthFunc(gl.LEQUAL); + }; + /** * 'copy' pass: Render into g-buffers */ R.pass_copy.render = function(state) { // * Bind the framebuffer R.pass_copy.fbo // TODO: uncomment - // gl.bindFramebuffer(gl.FRAMEBUFFER,R.pass_copy.fbo); + gl.bindFramebuffer(gl.FRAMEBUFFER,R.pass_copy.fbo); // * Clear screen using R.progClear // TODO: uncomment - // renderFullScreenQuad(R.progClear); + 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); + gl.clearDepth(1.0); + gl.clear(gl.DEPTH_BUFFER_BIT); // * "Use" the program R.progCopy.prog // TODO: uncomment - // gl.useProgram(R.progCopy.prog); + gl.useProgram(R.progCopy.prog); // TODO: Go write code in glsl/copy.frag.glsl var m = state.cameraMat.elements; + var viewMat = state.viewMat.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); + gl.uniformMatrix4fv(R.progCopy.u_cameraMat, false, m); + gl.uniformMatrix4fv(R.progCopy.u_viewMat, false, viewMat); // * Draw the scene // TODO: uncomment - // drawScene(state); + drawScene(state); }; var drawScene = function(state) { @@ -98,33 +145,141 @@ } }; - R.pass_debug.render = function(state) { + R.pass_debug.render = function(state) + { + gl.disable(gl.DEPTH_TEST); // * Unbind any framebuffer, so we can write to the screen // TODO: uncomment - // gl.bindFramebuffer(gl.FRAMEBUFFER, null); + 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); + bindTexturesForLightPass(R.prog_Debug); + gl.uniform3f(R.prog_Debug.u_viewportInfo, 2.0 * Math.tan(22.5 * Math.PI / 180.0), width, height); + gl.uniform1i(R.prog_Debug.u_debug, cfg.debugView); // * Render a fullscreen quad to perform shading on // TODO: uncomment - // renderFullScreenQuad(R.prog_Debug); + renderFullScreenQuad(R.prog_Debug); + gl.enable(gl.DEPTH_TEST); }; + R.pass_sphereProxyDebug.render = function(state) + { + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + gl.clearColor(0.0, 0.0, 0.0, 0.0); + gl.clear(gl.COLOR_BUFFER_BIT); + + gl.disable(gl.DEPTH_TEST); + gl.enable(gl.BLEND); + gl.blendEquation(gl.FUNC_ADD); + gl.blendFunc(gl.SRC_ALPHA, gl.ONE); + + readySphereProxy(R.progSphereRed, R.sphereModel); + for (var i = 0; i < R.lights.length; ++i) + { + var light = R.lights[i]; + var lightPos = new THREE.Vector3(light.pos[0], light.pos[1], light.pos[2]); + lightPos.applyMatrix4(state.viewMat); + gl.uniform3fv(R.progSphereRed.u_lightPos, lightPos.toArray()); + gl.uniform1f(R.progSphereRed.u_lightRad, light.rad); + gl.uniformMatrix4fv(R.progSphereRed.u_proj, false, state.projMat.elements); + drawSphereProxy(R.sphereModel); + } + + gl.disable(gl.BLEND); + gl.enable(gl.DEPTH_TEST); + }; + + R.pass_scissorTestDebug.render = function(state) + { + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + gl.useProgram(R.progRed.prog); + + // * Clear depth to 1.0 and color to black + gl.clearColor(0.0, 0.0, 0.0, 0.0); + gl.clear(gl.COLOR_BUFFER_BIT); + + gl.disable(gl.DEPTH_TEST); + gl.enable(gl.BLEND); + gl.blendEquation(gl.FUNC_ADD); + gl.blendFunc(gl.SRC_ALPHA, gl.ONE); + gl.enable(gl.SCISSOR_TEST); + + for (var i = 0; i < R.lights.length; ++i) + { + var sc = getScissorForLight(state.viewMat, state.projMat, R.lights[i]); + if (sc != null) + { + gl.scissor(sc[0], sc[1], sc[2], sc[3]); + renderFullScreenQuad(R.progRed); + } + } + + gl.disable(gl.SCISSOR_TEST); + gl.disable(gl.BLEND); + gl.enable(gl.DEPTH_TEST); + }; + /** * 'deferred' pass: Add lighting results for each individual light */ - R.pass_deferred.render = function(state) { + var sphereProxyLighting = function(state, isLastStage = false) + { + var renderTarget = isLastStage ? null : R.pass_deferred.fbo; + gl.bindFramebuffer(gl.FRAMEBUFFER, renderTarget); + + gl.clearColor(0.0, 0.0, 0.0, 0.0); + gl.clear(gl.COLOR_BUFFER_BIT); + + gl.enable(gl.BLEND); + gl.blendEquation(gl.FUNC_ADD); + gl.blendFunc(gl.ONE,gl.ONE); + + // Ambient light + gl.disable(gl.DEPTH_TEST); + bindTexturesForLightPass(R.prog_Ambient); + renderFullScreenQuad(R.prog_Ambient); + gl.enable(gl.DEPTH_TEST); + + // Blinn-Phong point light + bindTexturesForLightPass(R.prog_BlinnPhong_PointLight); + readySphereProxy(R.prog_BlinnPhong_PointLight, R.sphereModel); + gl.uniform3f(R.prog_BlinnPhong_PointLight.u_viewportInfo, 2.0 * Math.tan(22.5 * Math.PI / 180.0), width, height); + gl.uniformMatrix4fv(R.prog_BlinnPhong_PointLight.u_proj, false, state.projMat.elements); + + var depthFunc = isLastStage ? gl.ALWAYS : gl.GREATER; + gl.depthFunc(depthFunc); + gl.depthMask(false); + gl.cullFace(gl.FRONT); + + for (var i = 0; i < R.lights.length; ++i) + { + var light = R.lights[i]; + var lightPos = new THREE.Vector3(light.pos[0], light.pos[1], light.pos[2]); + lightPos.applyMatrix4(state.viewMat); + gl.uniform3fv(R.prog_BlinnPhong_PointLight.u_lightPos, lightPos.toArray()); + gl.uniform3fv(R.prog_BlinnPhong_PointLight.u_lightCol, light.col); + gl.uniform1f(R.prog_BlinnPhong_PointLight.u_lightRad, light.rad); + drawSphereProxy(R.sphereModel); + } + + gl.cullFace(gl.BACK); + gl.depthMask(true); + gl.depthFunc(gl.LEQUAL); + + gl.disable(gl.BLEND); + }; + + var scissorTestLighting = function(state, isLastStage = false) { // * Bind R.pass_deferred.fbo to write into for later postprocessing - gl.bindFramebuffer(gl.FRAMEBUFFER, R.pass_deferred.fbo); + var renderTarget = isLastStage ? null : R.pass_deferred.fbo; + gl.bindFramebuffer(gl.FRAMEBUFFER, renderTarget); - // * Clear depth to 1.0 and color to black 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.clear(gl.COLOR_BUFFER_BIT); // * _ADD_ together the result of each lighting pass @@ -133,31 +288,50 @@ // 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); - - // * Bind/setup the ambient pass, and render using fullscreen quad - bindTexturesForLightPass(R.prog_Ambient); - renderFullScreenQuad(R.prog_Ambient); - - // * Bind/setup the Blinn-Phong pass, and render using fullscreen quad - bindTexturesForLightPass(R.prog_BlinnPhong_PointLight); - - // 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). - - // TODO: 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. - // Otherwise, it returns an array [xmin, ymin, width, height]. - // - // var sc = getScissorForLight(state.viewMat, state.projMat, light); - - // Disable blending so that it doesn't affect other code + gl.disable(gl.DEPTH_TEST); + gl.enable(gl.BLEND); + gl.blendEquation(gl.FUNC_ADD); + gl.blendFunc(gl.ONE,gl.ONE); + + // * Bind/setup the ambient pass, and render using fullscreen quad + bindTexturesForLightPass(R.prog_Ambient); + renderFullScreenQuad(R.prog_Ambient); + + // * Bind/setup the Blinn-Phong pass, and render using fullscreen quad + bindTexturesForLightPass(R.prog_BlinnPhong_PointLight); + + // 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). + + // TODO: 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. + // Otherwise, it returns an array [xmin, ymin, width, height]. + + gl.enable(gl.SCISSOR_TEST); + for (var i = 0; i < R.lights.length; ++i) + { + var light = R.lights[i]; + var sc = getScissorForLight(state.viewMat, state.projMat, light); + + if (sc != null) + { + gl.scissor(sc[0], sc[1], sc[2], sc[3]); + var lightPos = new THREE.Vector3(light.pos[0], light.pos[1], light.pos[2]); + lightPos.applyMatrix4(state.viewMat); + gl.uniform3fv(R.prog_BlinnPhong_PointLight.u_lightPos, lightPos.toArray()); + gl.uniform3fv(R.prog_BlinnPhong_PointLight.u_lightCol, light.col); + gl.uniform1f(R.prog_BlinnPhong_PointLight.u_lightRad, light.rad); + renderFullScreenQuad(R.prog_BlinnPhong_PointLight); + } + } + gl.disable(gl.SCISSOR_TEST); + + // Disable blending so that it doesn't affect other code gl.disable(gl.BLEND); + gl.enable(gl.DEPTH_TEST); }; var bindTexturesForLightPass = function(prog) { @@ -171,39 +345,110 @@ gl.uniform1i(prog.u_gbufs[i], i); } gl.activeTexture(gl['TEXTURE' + R.NUM_GBUFFERS]); - gl.bindTexture(gl.TEXTURE_2D, R.pass_copy.depthTex); + gl.bindTexture(gl.TEXTURE_2D, R.pass_copyDepth.colorTex); gl.uniform1i(prog.u_depth, R.NUM_GBUFFERS); }; + + R.pass_bloom.render = function(state, isFirstPost = false, isLastStage = false) + { + gl.disable(gl.DEPTH_TEST); + + // Generate brightness map + gl.bindFramebuffer(gl.FRAMEBUFFER, R.pass_bloom.fbo1); - /** - * 'post1' pass: Perform (first) pass of post-processing - */ - R.pass_post1.render = function(state) { - // * Unbind any existing framebuffer (if there are no more passes) - gl.bindFramebuffer(gl.FRAMEBUFFER, null); - - // * Clear the framebuffer depth to 1.0 - gl.clearDepth(1.0); - gl.clear(gl.DEPTH_BUFFER_BIT); - - // * Bind the postprocessing shader program - gl.useProgram(R.progPost1.prog); - - // * 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); - - // Bind the TEXTURE_2D, R.pass_deferred.colorTex to the active texture unit - // TODO: uncomment - // 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); + gl.clearColor(0.0, 0.0, 0.0, 0.0); + gl.clear(gl.COLOR_BUFFER_BIT); + + gl.useProgram(R.progPostThresholdBrightness.prog); + + gl.activeTexture(gl.TEXTURE0); + var srcTex = isFirstPost ? R.pass_deferred.colorTex : R.pass_bloom.colorTex0; + gl.bindTexture(gl.TEXTURE_2D, srcTex); + + gl.uniform1i(R.progPostThresholdBrightness.u_color, 0); + gl.uniform1f(R.progPostThresholdBrightness.u_threshold, cfg.bloomThreshold); + + renderFullScreenQuad(R.progPostThresholdBrightness); + + // 2-pass Gaussian blur + gl.useProgram(R.progPostGaussianBlur.prog); + gl.activeTexture(gl.TEXTURE0); + + gl.uniform2f(R.progPostGaussianBlur.u_imgDim, width, height); + gl.uniform1i(R.progPostGaussianBlur.u_color, 0); + + for (var i = 0; i < 5; ++i) + { + gl.bindFramebuffer(gl.FRAMEBUFFER, R.pass_bloom.fbo0); + gl.bindTexture(gl.TEXTURE_2D, R.pass_bloom.colorTex1); + gl.uniform1i(R.progPostGaussianBlur.u_isHorizontal, true); + renderFullScreenQuad(R.progPostGaussianBlur); + + gl.bindFramebuffer(gl.FRAMEBUFFER, R.pass_bloom.fbo1); + gl.bindTexture(gl.TEXTURE_2D, R.pass_bloom.colorTex0); + gl.uniform1i(R.progPostGaussianBlur.u_isHorizontal, false); + renderFullScreenQuad(R.progPostGaussianBlur); + } + + // Add blurred brightness back to original lighting result + var renderTarget = isLastStage ? null : R.pass_bloom.fbo0; + gl.bindFramebuffer(gl.FRAMEBUFFER, renderTarget); + + gl.useProgram(R.progPostBloomGather.prog); + + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, R.pass_deferred.colorTex); + gl.activeTexture(gl.TEXTURE1); + gl.bindTexture(gl.TEXTURE_2D, R.pass_bloom.colorTex1) + + gl.uniform1i(R.progPostBloomGather.u_color, 0); + gl.uniform1i(R.progPostBloomGather.u_brightness, 1); + + renderFullScreenQuad(R.progPostBloomGather); + + gl.enable(gl.DEPTH_TEST); + } + + R.pass_motionblur.render = function(state, isFirstPost = false, isLastStage = false) + { + gl.disable(gl.DEPTH_TEST); + + var renderTarget = isLastStage ? null : R.pass_bloom.fbo1; + gl.bindFramebuffer(gl.FRAMEBUFFER, renderTarget); - // * Render a fullscreen quad to perform shading on - renderFullScreenQuad(R.progPost1); - }; + gl.clearColor(0.0, 0.0, 0.0, 0.0); + gl.clear(gl.COLOR_BUFFER_BIT); + + gl.useProgram(R.progPostMotionBlur.prog); + + gl.activeTexture(gl.TEXTURE0); + var srcColorTex = isFirstPost ? R.pass_deferred.colorTex : R.pass_bloom.colorTex0; + gl.bindTexture(gl.TEXTURE_2D, srcColorTex); + gl.activeTexture(gl.TEXTURE1); + gl.bindTexture(gl.TEXTURE_2D, R.pass_copyDepth.colorTex); + + gl.uniform1f(R.progPostMotionBlur.u_scale, cfg.motionBlurScale); + gl.uniform1i(R.progPostMotionBlur.u_color, 0); + gl.uniform1i(R.progPostMotionBlur.u_depth, 1); + if (preViewProjMat == null) + { + preViewProjMat = new THREE.Matrix4(); + preViewProjMat.copy(state.cameraMat); + } + gl.uniformMatrix4fv(R.progPostMotionBlur.u_preViewProj, false, preViewProjMat.elements); + var viewProjInverse = new THREE.Matrix4(); + viewProjInverse.getInverse(state.cameraMat); + gl.uniformMatrix4fv(R.progPostMotionBlur.u_viewProjInverse, false, viewProjInverse.elements); + + renderFullScreenQuad(R.progPostMotionBlur); + + preViewProjMat.copy(state.cameraMat); + gl.enable(gl.DEPTH_TEST); + + // R.pass_bloom.colorTex0 will always be the input to next stage + R.pass_bloom.fbo0 = [R.pass_bloom.fbo1, R.pass_bloom.fbo1 = R.pass_bloom.fbo0][0]; + R.pass_bloom.colorTex0 = [R.pass_bloom.colorTex1, R.pass_bloom.colorTex1 = R.pass_bloom.colorTex0][0]; + }; var renderFullScreenQuad = (function() { // The variables in this function are private to the implementation of @@ -230,12 +475,12 @@ // Bind the VBO as the gl.ARRAY_BUFFER // TODO: uncomment - // gl.bindBuffer(gl.ARRAY_BUFFER,vbo); + 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); + gl.bufferData(gl.ARRAY_BUFFER,positions,gl.STATIC_DRAW); }; return function(prog) { @@ -249,21 +494,21 @@ // Bind the VBO as the gl.ARRAY_BUFFER // TODO: uncomment - // gl.bindBuffer(gl.ARRAY_BUFFER, vbo); + 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); + 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); + 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); + 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..7869e5c 100644 --- a/js/deferredSetup.js +++ b/js/deferredSetup.js @@ -3,12 +3,16 @@ window.R = {}; R.pass_copy = {}; + R.pass_copyDepth = {}; R.pass_debug = {}; R.pass_deferred = {}; - R.pass_post1 = {}; + R.pass_bloom = {}; // bloom + R.pass_motionblur = {}; + R.pass_scissorTestDebug = {}; + R.pass_sphereProxyDebug = {}; R.lights = []; - R.NUM_GBUFFERS = 4; + R.NUM_GBUFFERS = 1; /** * Set up the deferred pipeline framebuffer objects and textures. @@ -17,7 +21,9 @@ setupLights(); loadAllShaderPrograms(); R.pass_copy.setup(); + R.pass_copyDepth.setup(); R.pass_deferred.setup(); + R.pass_bloom.setup(); }; // TODO: Edit if you want to change the light initial positions @@ -25,7 +31,7 @@ R.light_max = [14, 18, 6]; R.light_dt = -0.03; R.LIGHT_RADIUS = 4.0; - R.NUM_LIGHTS = 20; // TODO: test with MORE lights! + R.NUM_LIGHTS = 100; // TODO: test with MORE lights! var setupLights = function() { Math.seedrandom(0); @@ -43,9 +49,9 @@ R.lights.push({ pos: posfn(), col: [ - 1 + Math.random(), - 1 + Math.random(), - 1 + Math.random()], + 0 + Math.random(), + 0 + Math.random(), + 0 + Math.random()], rad: R.LIGHT_RADIUS }); } @@ -63,9 +69,12 @@ // * Create, bind, and store "color" target textures for the FBO R.pass_copy.gbufs = []; var attachments = []; + var formats = [gl.RGBA, gl.RGBA]; + var types = [gl.FLOAT, gl.FLOAT]; + for (var i = 0; i < R.NUM_GBUFFERS; i++) { var attachment = gl_draw_buffers['COLOR_ATTACHMENT' + i + '_WEBGL']; - var tex = createAndBindColorTargetTexture(R.pass_copy.fbo, attachment); + var tex = createAndBindColorTargetTexture(R.pass_copy.fbo, attachment, formats[i], types[i]); R.pass_copy.gbufs.push(tex); attachments.push(attachment); } @@ -78,6 +87,16 @@ gl.bindFramebuffer(gl.FRAMEBUFFER, null); }; + + R.pass_copyDepth.setup = function() + { + R.pass_copyDepth.fbo = gl.createFramebuffer(); + R.pass_copyDepth.colorTex = createAndBindColorTargetTexture(R.pass_copyDepth.fbo, + gl_draw_buffers.COLOR_ATTACHMENT0_WEBGL); + abortIfFramebufferIncomplete(R.pass_copyDepth.fbo); + gl_draw_buffers.drawBuffersWEBGL([gl_draw_buffers.COLOR_ATTACHMENT0_WEBGL]); + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + }; /** * Create/configure framebuffer between "deferred" and "post1" stages @@ -88,6 +107,7 @@ // * Create, bind, and store a single color target texture for the FBO R.pass_deferred.colorTex = createAndBindColorTargetTexture( R.pass_deferred.fbo, gl_draw_buffers.COLOR_ATTACHMENT0_WEBGL); + gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.TEXTURE_2D, R.pass_copy.depthTex, 0); // * Check for framebuffer errors abortIfFramebufferIncomplete(R.pass_deferred.fbo); @@ -97,6 +117,24 @@ gl.bindFramebuffer(gl.FRAMEBUFFER, null); }; + + R.pass_bloom.setup = function() + { + // ping-pong buffers for blur filerting + R.pass_bloom.fbo0 = gl.createFramebuffer(); + R.pass_bloom.colorTex0 = createAndBindColorTargetTexture( + R.pass_bloom.fbo0, gl_draw_buffers.COLOR_ATTACHMENT0_WEBGL); + abortIfFramebufferIncomplete(R.pass_bloom.fbo0); + gl_draw_buffers.drawBuffersWEBGL([gl_draw_buffers.COLOR_ATTACHMENT0_WEBGL]); + + R.pass_bloom.fbo1 = gl.createFramebuffer(); + R.pass_bloom.colorTex1 = createAndBindColorTargetTexture( + R.pass_bloom.fbo1, gl_draw_buffers.COLOR_ATTACHMENT0_WEBGL); + abortIfFramebufferIncomplete(R.pass_bloom.fbo1); + gl_draw_buffers.drawBuffersWEBGL([gl_draw_buffers.COLOR_ATTACHMENT0_WEBGL]); + + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + }; /** * Loads all of the shader programs used in the pipeline. @@ -109,6 +147,7 @@ // Retrieve the uniform and attribute locations p.u_cameraMat = gl.getUniformLocation(prog, 'u_cameraMat'); + p.u_viewMat = gl.getUniformLocation(prog, 'u_viewMat'); p.u_colmap = gl.getUniformLocation(prog, 'u_colmap'); p.u_normap = gl.getUniformLocation(prog, 'u_normap'); p.a_position = gl.getAttribLocation(prog, 'a_position'); @@ -118,12 +157,34 @@ // Save the object into this variable for access later R.progCopy = p; }); + + loadShaderProgram(gl, 'glsl/quad.vert.glsl', 'glsl/copydepth.frag.glsl', + function(prog) { + // Create an object to hold info about this shader program + var p = { prog: prog }; + + p.u_depth = gl.getUniformLocation(prog, 'u_depth'); + + R.progCopyDepth = p; + }); loadShaderProgram(gl, 'glsl/quad.vert.glsl', 'glsl/red.frag.glsl', function(prog) { // Create an object to hold info about this shader program R.progRed = { prog: prog }; }); + + loadShaderProgram(gl, 'glsl/sphereproxy.vert.glsl', 'glsl/red.frag.glsl', + function(prog) { + // Create an object to hold info about this shader program + var p = { prog: prog }; + + p.u_lightPos = gl.getUniformLocation(prog, 'u_lightPos'); + p.u_lightRad = gl.getUniformLocation(prog, 'u_lightRad'); + p.u_proj = gl.getUniformLocation(prog, 'u_proj'); + + R.progSphereRed = p; + }); loadShaderProgram(gl, 'glsl/quad.vert.glsl', 'glsl/clear.frag.glsl', function(prog) { @@ -136,8 +197,19 @@ R.prog_Ambient = p; }); - loadDeferredProgram('blinnphong-pointlight', function(p) { +/* loadDeferredProgram('blinnphong-pointlight', function(p) { + // Save the object into this variable for access later + p.u_viewportInfo = gl.getUniformLocation(p.prog, 'u_viewportInfo'); + 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'); + R.prog_BlinnPhong_PointLight = p; + }); */ + + loadDeferredProgram_SphereProxy('blinnphong-pointlight', function(p) { // Save the object into this variable for access later + p.u_proj = gl.getUniformLocation(p.prog, 'u_proj'); + p.u_viewportInfo = gl.getUniformLocation(p.prog, 'u_viewportInfo'); 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'); @@ -145,18 +217,45 @@ }); loadDeferredProgram('debug', function(p) { + p.u_viewportInfo = gl.getUniformLocation(p.prog, 'u_viewportInfo'); p.u_debug = gl.getUniformLocation(p.prog, 'u_debug'); // Save the object into this variable for access later R.prog_Debug = p; }); - - loadPostProgram('one', function(p) { + + loadPostProgram('thresholdbrightness', function(p) { + p.u_threshold = gl.getUniformLocation(p.prog, 'u_threshold'); p.u_color = gl.getUniformLocation(p.prog, 'u_color'); // Save the object into this variable for access later - R.progPost1 = p; + R.progPostThresholdBrightness = p; }); // TODO: If you add more passes, load and set up their shader programs. + loadPostProgram('gaussianblur', function(p) { + p.u_imgDim = gl.getUniformLocation(p.prog, 'u_imgDim'); + p.u_isHorizontal = gl.getUniformLocation(p.prog, 'u_isHorizontal'); + p.u_color = gl.getUniformLocation(p.prog, 'u_color'); + // Save the object into this variable for access later + R.progPostGaussianBlur = p; + }); + + loadPostProgram('bloomgather', function(p) { + p.u_color = gl.getUniformLocation(p.prog, 'u_color'); + p.u_bloomEnabled = gl.getUniformLocation(p.prog, 'u_bloomEnabled'); + p.u_brightness = gl.getUniformLocation(p.prog, 'u_brightness'); + // Save the object into this variable for access later + R.progPostBloomGather = p; + }); + + loadPostProgram('motionblur', function(p) { + p.u_scale = gl.getUniformLocation(p.prog, 'u_scale'); + p.u_color = gl.getUniformLocation(p.prog, 'u_color'); + p.u_depth = gl.getUniformLocation(p.prog, 'u_depth'); + p.u_preViewProj = gl.getUniformLocation(p.prog, 'u_preViewProj'); + p.u_viewProjInverse = gl.getUniformLocation(p.prog, 'u_viewProjInverse'); + // Save the object into this variable for access later + R.progPostMotionBlur = p; + }); }; var loadDeferredProgram = function(name, callback) { @@ -177,6 +276,25 @@ callback(p); }); }; + + var loadDeferredProgram_SphereProxy = function(name, callback) { + loadShaderProgram(gl, 'glsl/sphereproxy.vert.glsl', + 'glsl/deferred/' + name + '.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_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.a_position = gl.getAttribLocation(prog, 'a_position'); + + callback(p); + }); + }; var loadPostProgram = function(name, callback) { loadShaderProgram(gl, 'glsl/quad.vert.glsl', @@ -211,14 +329,18 @@ return depthTex; }; - var createAndBindColorTargetTexture = function(fbo, attachment) { + var createAndBindColorTargetTexture = function(fbo, attachment, + componentFormat = gl.RGBA, + componentType = gl.FLOAT) + { var tex = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, tex); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); 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.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.FLOAT, null); + gl.texImage2D(gl.TEXTURE_2D, 0, componentFormat, width, height, 0, + componentFormat, componentType, null); gl.bindTexture(gl.TEXTURE_2D, null); gl.bindFramebuffer(gl.FRAMEBUFFER, fbo); diff --git a/js/framework.js b/js/framework.js index 4f944ee..633f1b2 100644 --- a/js/framework.js +++ b/js/framework.js @@ -67,7 +67,7 @@ var width, height; var init = function() { // TODO: For performance measurements, disable debug mode! - var debugMode = true; + var debugMode = false; canvas = document.getElementById('canvas'); renderer = new THREE.WebGLRenderer({ @@ -115,7 +115,7 @@ var width, height; controls.panSpeed = 2.0; // Add sphere geometry to the scene so it gets initialized - var sph = new THREE.Mesh(new THREE.SphereGeometry(1, 8, 6)); + var sph = new THREE.Mesh(new THREE.SphereGeometry(1, 16, 12)); scene.add(sph); renderer.render(scene, camera); uploadModel(sph, function(m) { @@ -123,7 +123,7 @@ var width, height; }); // var glTFURL = 'models/glTF-duck/duck.gltf'; - var glTFURL = 'models/glTF-sponza-kai-fix/sponza.gltf'; + var glTFURL = 'models/gltf-sponza-kai-fix/sponza.gltf'; var glTFLoader = new MinimalGLTFLoader.glTFLoader(gl); glTFLoader.loadGLTF(glTFURL, function (glTF) { var curScene = glTF.scenes[glTF.defaultScene]; diff --git a/js/ui.js b/js/ui.js index abd6119..61c6cdc 100644 --- a/js/ui.js +++ b/js/ui.js @@ -7,7 +7,11 @@ var cfg; // TODO: Define config fields and defaults here this.debugView = -1; this.debugScissor = false; - this.enableEffect0 = false; + this.sphereProxy = true; + this.enableBloom = true; + this.bloomThreshold = 0.2; + this.enableMotionBlur = true; + this.motionBlurScale = 0.5; }; var init = function() { @@ -19,17 +23,26 @@ var cfg; 'None': -1, '0 Depth': 0, '1 Position': 1, - '2 Geometry normal': 2, + '2 Surface normal': 2, '3 Color map': 3, - '4 Normal map': 4, - '5 Surface normal': 5 }); gui.add(cfg, 'debugScissor'); + + gui.add(cfg, 'sphereProxy'); - var eff0 = gui.addFolder('EFFECT NAME HERE'); - eff0.open(); - eff0.add(cfg, 'enableEffect0'); + var eff = gui.addFolder('EFFECT NAME HERE'); + eff.open(); + + var eff0 = eff.addFolder('Bloom'); + eff0.add(cfg, 'enableBloom'); // TODO: add more effects toggles and parameters here + eff0.add(cfg, 'bloomThreshold', 0.0, 2.0); + eff0.open(); + + var eff1 = eff.addFolder('Motion Blur'); + eff1.add(cfg, 'enableMotionBlur'); + eff1.add(cfg, 'motionBlurScale', 0.1, 2.0); + eff1.open(); }; window.handle_load.push(init); diff --git a/js/util.js b/js/util.js index 8f43d38..d68c051 100644 --- a/js/util.js +++ b/js/util.js @@ -113,6 +113,23 @@ window.drawReadyModel = function(m) { gl.drawElements(m.gltf.mode, m.gltf.indices.length, m.gltf.indicesComponentType, 0); }; +window.readySphereProxy = function(prog, m) +{ + gl.useProgram(prog.prog); + + gl.bindBuffer(gl.ARRAY_BUFFER, m.position); + + gl.enableVertexAttribArray(prog.a_position); + gl.vertexAttribPointer(prog.a_position, 3, gl.FLOAT, false, 0, 0); + + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, m.idx); +}; + +window.drawSphereProxy = 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); @@ -155,8 +172,8 @@ window.getScissorForLight = (function() { ret[0] = Math.round(width * minpt.x); ret[1] = Math.round(height * minpt.y); - ret[2] = Math.round(width * (maxpt.x - minpt.x)); - ret[3] = Math.round(height * (maxpt.y - minpt.y)); + ret[2] = Math.max(0, Math.round(width * (maxpt.x - minpt.x))); + ret[3] = Math.max(0, Math.round(height * (maxpt.y - minpt.y))); return ret; }; })();