diff --git a/README.md b/README.md index 25002db..0176611 100644 --- a/README.md +++ b/README.md @@ -3,26 +3,78 @@ 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) +* Name: Zhan Xiong Chin +* Tested on: **Google Chrome (Version 54.0.2840.87 m (64-bit))** on + Windows 7 Professional, i7-4700 @ 3.40GHz 3.40GHz 16GB, Quadro K600 1GB (SIG Lab) ### Live Online -[![](img/thumb.png)](http://TODO.github.io/Project5B-WebGL-Deferred-Shading) +[![](img/thumb.png)](https://czxcjx.github.io/Project5-WebGL-Deferred-Shading-with-glTF/) -### Demo Video/GIF +### Demo GIF -[![](img/video.png)](TODO) +![](img/video.gif) -### (TODO: Your README) +### Features -*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. +* WebGL deferred Blinn-Phong shading +* Scissor test optimization +* G-buffer packing +* Bloom shader using gaussian blur with both 2d kernel and convolved 1d kernels +* Screen space motion blur -This assignment has a considerable amount of performance analysis compared -to implementation work. Complete the implementation early to leave time! +### Deferred shading introduction +A deferred shader packs information about each fragment to be rendered into g-buffers, then each light loops over the fragments rendered to screen to compute the final color of each pixel. This is an optimization over normal forward rendering, where lighting calculations will have to be done for each geometry fragment, even those which will not be rendered to screen due to the depth test. +### Scissor test optimization +Each light only has a maximum radius that it would affect. Thus, a simple screen space AABB was used to cull away pixels outside of the maximum radius, to ensure that each light did not have to loop over every pixel in the screen when doing lighting computations. + +![](img/scissor_aabb.png) + +Scissor test bounding boxes + +The bounding box computation is slightly inaccurate at the edges of screen space, causing unnatural-looking "edges" on lights near the edge of the screen. To remedy this problem, an option was added to scale the bounding boxes up, trading performance for a better visual effect. A further improvement that might be made beyond the current implementation is to use better AABB calculations, or to compute the scale factor according to how close to the center of the screen the bounding box is. + +Adding the scissor test for lights improved performance significantly, enabling scenes with hundreds of lights to be rendered with little degradation to render times. The follow graphs show the performance of the scene for different numbers of lights (radius 4.0) and different sizes of bounding box vs no bounding box used. + +![](img/scissor_test.png) + +### G-buffer packing +G-buffers are used to store fragment information that the material shaders subsequently use for lighting calculations. The basic "un-packed" data requires 4 g-buffers, storing the world space position, normals, texture color, and normal map direction for each fragment. To reduce the number of g-buffers needed and thus reduce the amount of memory that needs to be moved around to/from the GPU by the shaders, the following improvements were made, reducing the number of required g-buffers to two: + +* The normal map was applied by the fragment shader, rather than by the lighting shader. This removed the need to include the normal map direction as part of a g-buffer. +* Only the x and y positions of the normals were stored, the final position can be calculated by z = sqrt(1 - x^2 - y^2). These two were stored into the first two components of the second g-buffer. +* The components of the color vector were converted from floating point values to 8-bit integers, then packed into the last two components of the second g-buffer. + +While these optimizations did not make a noticeable difference for the standard 800x600 screen, since each individual g-buffer is not very big, with increased screen resolution and size, the packed g-buffers began to perform better (25 lights): + +![](img/gbuffer_test.png) + +### Bloom shader +A bloom shader was used for light glow effects. In this, a filter is applied to detect bright parts of the image. Then, a gaussian blur (using a 5x5 kernel) was applied over these bright parts, and they are added back to the original image. This creates a light glow effect, as shown below. + + + + + + +From left to right: Original image, brightness filter, Gaussian blurred, Image with glow + +This was further optimized by using two separate convolutions (horizontal and vertical) using a 1D Gaussian kernel, rather than using a 2D kernel. However, there was no observable difference in rendering time between the two. Also, the extra overhead of the Bloom shader only became apparent at 1600x1200 screen resolution, and even then it only slowed the rendering time from 40 to 45 ms. This suggests that the main performance bottleneck of the deferred renderer is not in the post-processing steps. + +### Screen space motion blur + +Screen space motion blur was achieved by copying the computed world space positions of the previous frame for each fragment, and projecting it using the current camera matrix instead. From these, we can obtain the new screen space position of the old fragment, and blur the rendered image between those two points. The debug view for motion vectors is shown below; the R and G values are determined by the X and Y difference between the previous position and current position in screen space. + +![](img/motion_vectors.gif) + +Debug view for motion vectors, note the slight color changes indicating the motion vectors as the image moves + +![](img/motion_blur.gif) + +Motion blur + +Motion blur causes a similar slowdown in performance as Bloom glow; it does not cause slowdown unless the screen resolution is high, as it is a post-processing step that is implemented in a similar fashion. ### Credits diff --git a/glsl/clone.frag.glsl b/glsl/clone.frag.glsl new file mode 100644 index 0000000..904f203 --- /dev/null +++ b/glsl/clone.frag.glsl @@ -0,0 +1,17 @@ +#version 100 +precision highp float; +precision highp int; + +uniform sampler2D u_in; +uniform sampler2D u_depth; + +varying vec2 v_uv; + +void main() { + float depth = texture2D(u_depth, v_uv).x; + if (depth >= 1.0) { + gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0); + } else { + gl_FragColor = texture2D(u_in, v_uv); + } +} diff --git a/glsl/copy-pack.frag.glsl b/glsl/copy-pack.frag.glsl new file mode 100644 index 0000000..4bb6f77 --- /dev/null +++ b/glsl/copy-pack.frag.glsl @@ -0,0 +1,33 @@ +#version 100 +#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_normal; +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; +} + +vec2 packVec(vec4 v) { + v = floor(v * 255.0); + return vec2( v.r * 256.0 + v.g, v.b * 256.0 + v.a ); +} + +void main() { + gl_FragData[0] = vec4( v_position, 1.0 ); + vec3 geomnor = normalize(v_normal.xyz); + vec3 normap = normalize(texture2D(u_normap, v_uv).xyz); + vec3 nor = normalize(applyNormalMap(geomnor, normap)); + vec2 col = packVec(texture2D(u_colmap, v_uv)); + gl_FragData[1] = vec4( nor.x, nor.y, col.x, col.y ); +} diff --git a/glsl/copy.frag.glsl b/glsl/copy.frag.glsl index 823ebcd..57a660e 100644 --- a/glsl/copy.frag.glsl +++ b/glsl/copy.frag.glsl @@ -11,10 +11,8 @@ 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. - - // this gives you the idea - // gl_FragData[0] = vec4( v_position, 1.0 ); + gl_FragData[0] = vec4( v_position, 1.0 ); + gl_FragData[1] = vec4( v_normal, 0.0 ); + gl_FragData[2] = texture2D(u_colmap, v_uv); + gl_FragData[3] = texture2D(u_normap, v_uv); } diff --git a/glsl/deferred-pack/ambient.frag.glsl b/glsl/deferred-pack/ambient.frag.glsl new file mode 100644 index 0000000..640ce49 --- /dev/null +++ b/glsl/deferred-pack/ambient.frag.glsl @@ -0,0 +1,27 @@ + +#version 100 +precision highp float; +precision highp int; + +#define NUM_GBUFFERS 4 + +uniform sampler2D u_gbufs[NUM_GBUFFERS]; +uniform sampler2D u_depth; + +varying vec2 v_uv; + +void main() { + vec4 gb0 = texture2D(u_gbufs[0], v_uv); + vec4 gb1 = texture2D(u_gbufs[1], v_uv); + float depth = texture2D(u_depth, v_uv).x; + ivec2 packedCol = ivec2(int(gb1.z), int(gb1.w)); + vec3 colmap = vec3(packedCol.x / 256, packedCol.x - (packedCol.x / 256) * 256, packedCol.y / 256) / 255.0; + + + if (depth == 1.0) { + gl_FragColor = vec4(0, 0, 0, 0); // set alpha to 0 + return; + } + + gl_FragColor = vec4(0.1 * colmap, 1.0); +} diff --git a/glsl/deferred-pack/blinnphong-pointlight.frag.glsl b/glsl/deferred-pack/blinnphong-pointlight.frag.glsl new file mode 100644 index 0000000..cc9d13a --- /dev/null +++ b/glsl/deferred-pack/blinnphong-pointlight.frag.glsl @@ -0,0 +1,50 @@ +#version 100 +precision highp float; +precision highp int; + +#define NUM_GBUFFERS 4 + +uniform vec3 u_lightCol; +uniform vec3 u_lightPos; +uniform vec3 u_cameraPos; +uniform float u_lightRad; +uniform sampler2D u_gbufs[NUM_GBUFFERS]; +uniform sampler2D u_depth; + +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; +} + +void main() { + vec4 gb0 = texture2D(u_gbufs[0], v_uv); + vec4 gb1 = texture2D(u_gbufs[1], v_uv); + float depth = texture2D(u_depth, v_uv).x; + vec3 pos = gb0.xyz; // World-space position + vec3 nor = vec3(gb1.x, gb1.y, sqrt(1.0 - gb1.x * gb1.x - gb1.y * gb1.y)); // Normal map already applied + ivec2 packedCol = ivec2(int(gb1.z), int(gb1.w)); + vec3 colmap = vec3(packedCol.x / 256, packedCol.x - (packedCol.x / 256) * 256, packedCol.y / 256) / 255.0; + + float dist = distance(pos, u_lightPos); + + // 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 || u_lightRad < dist) { + gl_FragColor = vec4(0, 0, 0, 0); + return; + } + + vec3 L = normalize(u_lightPos - pos); + vec3 V = normalize(- pos); + vec3 H = normalize(L + V); + float attenuation = max(0.0, u_lightRad - dist); + float lambert = max(0.0, dot(L, nor)); + float specular = max(0.0, dot(H, nor)); + vec3 col = colmap * u_lightCol * lambert + specular * u_lightCol; + gl_FragColor = vec4(col * attenuation, 1.0); +} diff --git a/glsl/deferred-pack/debug.frag.glsl b/glsl/deferred-pack/debug.frag.glsl new file mode 100644 index 0000000..e70154d --- /dev/null +++ b/glsl/deferred-pack/debug.frag.glsl @@ -0,0 +1,48 @@ +#version 100 +precision highp float; +precision highp int; + +#define NUM_GBUFFERS 4 + +uniform int u_debug; +uniform sampler2D u_gbufs[NUM_GBUFFERS]; +uniform sampler2D u_depth; + +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; +} + +void main() { + vec4 gb0 = texture2D(u_gbufs[0], v_uv); + vec4 gb1 = texture2D(u_gbufs[1], v_uv); + float depth = texture2D(u_depth, v_uv).x; + // These definitions are suggested for starting out, but you will probably want to change them. + vec3 pos = gb0.xyz; // World-space position + vec3 nor = vec3(gb1.x, gb1.y, sqrt(1.0 - gb1.x * gb1.x - gb1.y * gb1.y)); // Normal map already applied + ivec2 packedCol = ivec2(int(gb1.z), int(gb1.w)); + vec3 colmap = vec3(packedCol.x / 256, packedCol.x - (packedCol.x / 256) * 256, packedCol.y / 256) / 255.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(nor, 1.0); + } else if (u_debug == 3) { + gl_FragColor = vec4(colmap, 1.0); + } else if (u_debug == 4) { + gl_FragColor = vec4(nor, 1.0); + } else if (u_debug == 5) { + gl_FragColor = vec4(abs(nor), 1.0); + } else { + gl_FragColor = vec4(1, 0, 1, 1); + } +} diff --git a/glsl/deferred/ambient.frag.glsl b/glsl/deferred/ambient.frag.glsl index 1fd4647..d6eeafd 100644 --- a/glsl/deferred/ambient.frag.glsl +++ b/glsl/deferred/ambient.frag.glsl @@ -16,12 +16,13 @@ void main() { 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 + vec3 colmap = gb2.rgb; + if (depth == 1.0) { gl_FragColor = vec4(0, 0, 0, 0); // set alpha to 0 return; } - gl_FragColor = vec4(0.1, 0.1, 0.1, 1); // TODO: replace this + gl_FragColor = vec4(0.1 * colmap, 1.0); } diff --git a/glsl/deferred/blinnphong-pointlight.frag.glsl b/glsl/deferred/blinnphong-pointlight.frag.glsl index b24a54a..388fafe 100644 --- a/glsl/deferred/blinnphong-pointlight.frag.glsl +++ b/glsl/deferred/blinnphong-pointlight.frag.glsl @@ -6,6 +6,7 @@ precision highp int; uniform vec3 u_lightCol; uniform vec3 u_lightPos; +uniform vec3 u_cameraPos; uniform float u_lightRad; uniform sampler2D u_gbufs[NUM_GBUFFERS]; uniform sampler2D u_depth; @@ -26,14 +27,27 @@ void main() { 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 + vec3 pos = gb0.xyz; + vec3 geomnor = gb1.xyz; + vec3 colmap = gb2.rgb; + vec3 normap = gb3.xyz; + vec3 nor = applyNormalMap(geomnor, normap); + + float dist = distance(pos, u_lightPos); // 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 || u_lightRad < dist) { gl_FragColor = vec4(0, 0, 0, 0); return; } - gl_FragColor = vec4(0, 0, 1, 1); // TODO: perform lighting calculations + vec3 L = normalize(u_lightPos - pos); + vec3 V = normalize(- pos); + vec3 H = normalize(L + V); + float attenuation = max(0.0, u_lightRad - dist); + float lambert = max(0.0, dot(L, nor)); + float specular = max(0.0, dot(H, nor)); + vec3 col = colmap * u_lightCol * lambert + specular * u_lightCol; + gl_FragColor = vec4(col * attenuation, 1.0); } diff --git a/glsl/deferred/debug.frag.glsl b/glsl/deferred/debug.frag.glsl index 007466f..157a230 100644 --- a/glsl/deferred/debug.frag.glsl +++ b/glsl/deferred/debug.frag.glsl @@ -26,27 +26,25 @@ void main() { 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 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 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) - // TODO: 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(abs(geomnor), 1.0); } else if (u_debug == 3) { - // gl_FragColor = vec4(colmap, 1.0); + gl_FragColor = vec4(colmap, 1.0); } else if (u_debug == 4) { - // gl_FragColor = vec4(normap, 1.0); + 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.frag.glsl b/glsl/post/bloom.frag.glsl new file mode 100644 index 0000000..5b64517 --- /dev/null +++ b/glsl/post/bloom.frag.glsl @@ -0,0 +1,14 @@ +#version 100 +precision highp float; +precision highp int; + +uniform sampler2D u_color; +uniform sampler2D u_glow; + +varying vec2 v_uv; + +void main() { + vec4 color = texture2D(u_color, v_uv); + vec4 glow = texture2D(u_glow, v_uv); + gl_FragColor = vec4((color + glow).rgb, 1.0); +} diff --git a/glsl/post/brightness.frag.glsl b/glsl/post/brightness.frag.glsl new file mode 100644 index 0000000..18634d9 --- /dev/null +++ b/glsl/post/brightness.frag.glsl @@ -0,0 +1,17 @@ +#version 100 +precision highp float; +precision highp int; + +uniform sampler2D u_color; + +varying vec2 v_uv; + +void main() { + vec4 color = texture2D(u_color, v_uv); + + if (dot(color.rgb, vec3(0.2126, 0.7152, 0.0722)) > 1.0) { + gl_FragColor = color; + } else { + gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0); + } +} diff --git a/glsl/post/gaussian1dconv.frag.glsl b/glsl/post/gaussian1dconv.frag.glsl new file mode 100644 index 0000000..7cf027d --- /dev/null +++ b/glsl/post/gaussian1dconv.frag.glsl @@ -0,0 +1,19 @@ +#version 100 +precision highp float; +precision highp int; + +uniform sampler2D u_color; +uniform vec2 u_pixWidthHeight; +uniform vec2 u_direction; + +varying vec2 v_uv; + +void main() { + vec4 color = vec4(0.0, 0.0, 0.0, 1.0); + color += texture2D(u_color, v_uv + u_pixWidthHeight * 2.0 * -u_direction) * 0.06136; + color += texture2D(u_color, v_uv + u_pixWidthHeight * -u_direction) * 0.24477; + color += texture2D(u_color, v_uv) * 0.38774; + color += texture2D(u_color, v_uv + u_pixWidthHeight * u_direction) * 0.224477; + color += texture2D(u_color, v_uv + u_pixWidthHeight * 2.0 * u_direction) * 0.06136; + gl_FragColor = color; +} diff --git a/glsl/post/gaussian2d.frag.glsl b/glsl/post/gaussian2d.frag.glsl new file mode 100644 index 0000000..4c095ff --- /dev/null +++ b/glsl/post/gaussian2d.frag.glsl @@ -0,0 +1,48 @@ +#version 100 +precision highp float; +precision highp int; + +uniform sampler2D u_color; +uniform vec2 u_pixWidthHeight; + +varying vec2 v_uv; + +vec4 col(int x, int y, float f) { + vec4 c = texture2D(u_color, v_uv + u_pixWidthHeight * vec2(x, y)) * f; + return vec4(c.rgb, 0.0); +} + +void main() { + vec4 color = vec4(0.0, 0.0, 0.0, 1.0); + color += col(-2, -2, 0.003765); + color += col(-2, -1, 0.015019); + color += col(-2, 0, 0.023792); + color += col(-2, 1, 0.015019); + color += col(-2, 2, 0.003765); + + color += col(-1, -2, 0.015019); + color += col(-1, -1, 0.059912); + color += col(-1, 0, 0.094907); + color += col(-1, 1, 0.059912); + color += col(-1, 2, 0.015019); + + color += col(0, -2, 0.023792); + color += col(0, -1, 0.094907); + color += col(0, 0, 0.150342); + color += col(0, 1, 0.094907); + color += col(0, 2, 0.023792); + + color += col(1, -2, 0.015019); + color += col(1, -1, 0.059912); + color += col(1, 0, 0.094907); + color += col(1, 1, 0.059912); + color += col(1, 2, 0.015019); + + color += col(2, -2, 0.003765); + color += col(2, -1, 0.015019); + color += col(2, 0, 0.023792); + color += col(2, 1, 0.015019); + color += col(2, 2, 0.003765); + + gl_FragColor = color; +} diff --git a/glsl/post/motionblur.frag.glsl b/glsl/post/motionblur.frag.glsl new file mode 100644 index 0000000..95e9cf1 --- /dev/null +++ b/glsl/post/motionblur.frag.glsl @@ -0,0 +1,36 @@ +#version 100 +precision highp float; +precision highp int; + +uniform sampler2D u_color; +uniform sampler2D u_oldpos; +uniform int u_debug; +uniform mat4 u_projMat; + +varying vec2 v_uv; + +const float INV_SAMPLES = 0.2; + +void main() { + vec4 oldPos = texture2D(u_oldpos, v_uv); + if (abs(oldPos.w) < 1e-9) { + if (u_debug == 0) { + gl_FragColor = texture2D(u_color, v_uv); + } else { + gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0); + } + return; + } + vec4 oldScreenSpacePos = u_projMat * oldPos; + vec2 oldPos_uv = 0.5 * (1.0 + (oldScreenSpacePos.xy / oldScreenSpacePos.w)); + vec4 color = vec4(0.0, 0.0, 0.0, 1.0); + vec2 diff = oldPos_uv - v_uv; + for (float i = 0.0; i < 1.0; i += INV_SAMPLES) { + color += texture2D(u_color, v_uv + i * diff); + } + if (u_debug == 0) { + gl_FragColor = color * INV_SAMPLES; + } else if (u_debug == 1) { + gl_FragColor = vec4(0.5 + diff, 0.0, 1.0); + } +} 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/img/bloom.png b/img/bloom.png new file mode 100644 index 0000000..5948a77 Binary files /dev/null and b/img/bloom.png differ diff --git a/img/bloom_blur.png b/img/bloom_blur.png new file mode 100644 index 0000000..3cab25c Binary files /dev/null and b/img/bloom_blur.png differ diff --git a/img/bloom_brightness.png b/img/bloom_brightness.png new file mode 100644 index 0000000..2c48a1f Binary files /dev/null and b/img/bloom_brightness.png differ diff --git a/img/gbuffer_test.png b/img/gbuffer_test.png new file mode 100644 index 0000000..bf655c3 Binary files /dev/null and b/img/gbuffer_test.png differ diff --git a/img/motion_blur.gif b/img/motion_blur.gif new file mode 100644 index 0000000..80bad13 Binary files /dev/null and b/img/motion_blur.gif differ diff --git a/img/motion_vectors.gif b/img/motion_vectors.gif new file mode 100644 index 0000000..1ccd983 Binary files /dev/null and b/img/motion_vectors.gif differ diff --git a/img/nobloom.png b/img/nobloom.png new file mode 100644 index 0000000..85c2359 Binary files /dev/null and b/img/nobloom.png differ diff --git a/img/scissor_aabb.png b/img/scissor_aabb.png new file mode 100644 index 0000000..7a933d6 Binary files /dev/null and b/img/scissor_aabb.png differ diff --git a/img/scissor_test.png b/img/scissor_test.png new file mode 100644 index 0000000..9bef6dc Binary files /dev/null and b/img/scissor_test.png differ diff --git a/img/thumb.png b/img/thumb.png index 9ec8ed0..4c60d80 100644 Binary files a/img/thumb.png and b/img/thumb.png differ diff --git a/img/video.gif b/img/video.gif new file mode 100644 index 0000000..7ed44c7 Binary files /dev/null and b/img/video.gif differ diff --git a/js/deferredRender.js b/js/deferredRender.js index bb3edd4..83476ac 100644 --- a/js/deferredRender.js +++ b/js/deferredRender.js @@ -7,9 +7,15 @@ !R.progCopy || !R.progRed || !R.progClear || + !R.progClone || !R.prog_Ambient || !R.prog_BlinnPhong_PointLight || !R.prog_Debug || + !R.progMotionBlur || + !R.progBloom || + !R.progBrightness || + !R.progBlur2d || + !R.progBlur1dconv || !R.progPost1)) { console.log('waiting for programs to load...'); return; @@ -17,73 +23,91 @@ // 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; + var dt = (cfg) ? -cfg.light_dt : -0.03; + R.lights[i].pos[1] = (R.lights[i].pos[1] + dt - mn + mx) % mx + mn; } // Execute deferred shading pipeline // 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; - } + // debugger; R.pass_copy.render(state); - + var doPostPasses = function(passes) { + for (var i = 0; i < passes.length; i++) { + var tex = (i == 0) ? R.pass_deferred.colorTex : R.tex[1 - R.curFbo]; + var fbo = (i == passes.length - 1) ? null : R.fbo[R.curFbo]; + passes[i].render(state, fbo, tex); + R.curFbo = 1 - R.curFbo; + } + }; 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); + if (cfg.debugView == 6) { + doPostPasses([R.pass_post1, R.pass_motionBlur]); + } else if (cfg.debugView == 7 || cfg.debugView == 8) { + doPostPasses([R.pass_post1, R.pass_bloom]); + } else { + 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); - - // OPTIONAL TODO: call more postprocessing passes, if any + R.pass_deferred.render(state); + + var passes = [R.pass_post1]; + if (cfg.enableMotionBlur) { + passes.push(R.pass_motionBlur); + } + if (cfg.bloom > 0) { + passes.push(R.pass_bloom); + } + doPostPasses(passes); } - }; + } /** * '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); - - // TODO: Go write code in glsl/copy.frag.glsl + gl.useProgram(R.progCopy.prog); 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); + gl.uniformMatrix4fv(R.progCopy.u_cameraMat, false, m); // * Draw the scene - // TODO: uncomment - // drawScene(state); + drawScene(state); + + // Clone position into curPosIdx + gl.useProgram(R.progClone.prog); + gl.bindFramebuffer(gl.FRAMEBUFFER, R.fbo[2]); + gl.framebufferTexture2D(gl.FRAMEBUFFER, gl_draw_buffers.COLOR_ATTACHMENT0_WEBGL, + gl.TEXTURE_2D, R.prevPos[R.curPosIdx], 0); + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, R.pass_copy.gbufs[0]); + gl.activeTexture(gl.TEXTURE1); + gl.bindTexture(gl.TEXTURE_2D, R.pass_copy.depthTex); + gl.uniform1i(R.progClone.u_in, 0); + gl.uniform1i(R.progClone.u_depth, 1); + + renderFullScreenQuad(R.progClone); + R.curPosIdx = 1 - R.curPosIdx; }; var drawScene = function(state) { @@ -100,18 +124,15 @@ R.pass_debug.render = function(state) { // * 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.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); }; /** @@ -132,10 +153,9 @@ // color = 1 * src_color + 1 * dst_color // 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); + 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); @@ -144,17 +164,40 @@ // * 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); + // Process color for each light + if (cfg && cfg.enableScissor > 0) { + gl.enable(gl.SCISSOR_TEST); + } + for (var i = 0; i < R.lights.length; i++) { + var light = R.lights[i]; + if (cfg && cfg.enableScissor > 0) { + var sc = getScissorForLight(state.viewMat, state.projMat, light, cfg.boundingBoxScale); + if (sc == null) { + continue; + } + gl.scissor(sc[0], sc[1], sc[2], sc[3]); + } + gl.uniform3fv(R.prog_BlinnPhong_PointLight.u_lightPos, light.pos); + 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); + + if (cfg && cfg.enableScissor == 2) { + gl.enable(gl.SCISSOR_TEST); + gl.blendFunc(gl.SRC_ALPHA,gl.ONE); + for (var i = 0; i < R.lights.length; i++) { + var light = R.lights[i]; + var sc = getScissorForLight(state.viewMat, state.projMat, light, cfg.boundingBoxScale); + if (sc == null) { + continue; + } + gl.scissor(sc[0], sc[1], sc[2], sc[3]); + renderFullScreenQuad(R.progRed); + } + gl.disable(gl.SCISSOR_TEST); + } // Disable blending so that it doesn't affect other code gl.disable(gl.BLEND); @@ -178,9 +221,9 @@ /** * 'post1' pass: Perform (first) pass of post-processing */ - R.pass_post1.render = function(state) { + R.pass_post1.render = function(state, fbo, tex) { // * Unbind any existing framebuffer (if there are no more passes) - gl.bindFramebuffer(gl.FRAMEBUFFER, null); + gl.bindFramebuffer(gl.FRAMEBUFFER, fbo); // * Clear the framebuffer depth to 1.0 gl.clearDepth(1.0); @@ -191,12 +234,10 @@ // * 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); + 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); + gl.bindTexture(gl.TEXTURE_2D, tex); // Configure the R.progPost1.u_color uniform to point at texture unit 0 gl.uniform1i(R.progPost1.u_color, 0); @@ -205,6 +246,107 @@ renderFullScreenQuad(R.progPost1); }; + /** + * 'motionBlur' pass: Perform motion blur pass of post-processing + */ + R.pass_motionBlur.render = function(state, fbo, tex) { + gl.bindFramebuffer(gl.FRAMEBUFFER, fbo); + gl.useProgram(R.progMotionBlur.prog); + + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, tex); + gl.activeTexture(gl.TEXTURE1); + gl.bindTexture(gl.TEXTURE_2D, R.prevPos[R.curPosIdx]); + + gl.uniform1i(R.progMotionBlur.u_color, 0); + gl.uniform1i(R.progMotionBlur.u_oldpos, 1); + if (cfg.debugView == 6) { + gl.uniform1i(R.progMotionBlur.u_debug, 1); + } else { + gl.uniform1i(R.progMotionBlur.u_debug, 0); + } + gl.uniformMatrix4fv(R.progMotionBlur.u_projMat, false, state.cameraMat.elements); + renderFullScreenQuad(R.progMotionBlur); + }; + + R.pass_bloom.render = function(state, fbo, tex) { + if (cfg.debugView == 7) { + R.pass_brightness.render(state, null, tex); + return; + } + R.pass_brightness.render(state, R.pass_brightness.fbo, tex); + if (cfg.debugView == 8) { + R.pass_postBlur2d.render(state, null, R.pass_brightness.colorTex); + return; + } + if (cfg.bloom == 1) { + R.pass_postBlur2d.render(state, R.pass_bloom.fbo, R.pass_brightness.colorTex); + } else { + R.pass_postBlur1d.render(state, R.pass_bloom.fbo, R.pass_brightness.colorTex); + } + + gl.bindFramebuffer(gl.FRAMEBUFFER, fbo); + gl.useProgram(R.progBloom.prog); + + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, tex); + gl.activeTexture(gl.TEXTURE1); + gl.bindTexture(gl.TEXTURE_2D, R.pass_bloom.colorTex); + + gl.uniform1i(R.progBloom.u_color, 0); + gl.uniform1i(R.progBloom.u_glow, 1); + + renderFullScreenQuad(R.progBloom); + } + + R.pass_brightness.render = function(state, fbo, tex) { + gl.bindFramebuffer(gl.FRAMEBUFFER, fbo); + gl.useProgram(R.progBrightness.prog); + + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, tex); + + gl.uniform1i(R.progBrightness.u_color, 0); + + renderFullScreenQuad(R.progBrightness); + } + + R.pass_postBlur2d.render = function(state, fbo, tex) { + gl.bindFramebuffer(gl.FRAMEBUFFER, fbo); + gl.useProgram(R.progBlur2d.prog); + + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, tex); + + gl.uniform1i(R.progBlur2d.u_color, 0); + gl.uniform2fv(R.progBlur2d.u_pixWidthHeight, [1.0 / width, 1.0 / height]); + + renderFullScreenQuad(R.progBlur2d); + } + + R.pass_postBlur1d.render = function(state, fbo, tex) { + gl.bindFramebuffer(gl.FRAMEBUFFER, R.pass_postBlur1d.fbo); + gl.useProgram(R.progBlur1dconv.prog); + + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, tex); + + gl.uniform1i(R.progBlur1dconv.u_color, 0); + gl.uniform2fv(R.progBlur1dconv.u_pixWidthHeight, [1.0 / width, 1.0 / height]); + gl.uniform2fv(R.progBlur1dconv.u_direction, [1.0, 0.0]); + + renderFullScreenQuad(R.progBlur1dconv); + + gl.bindFramebuffer(gl.FRAMEBUFFER, fbo); + + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, R.pass_postBlur1d.colorTex); + + gl.uniform2fv(R.progBlur1dconv.u_direction, [0.0, 1.0]); + + renderFullScreenQuad(R.progBlur1dconv); + } + var renderFullScreenQuad = (function() { // The variables in this function are private to the implementation of // renderFullScreenQuad. They work like static local variables in C++. @@ -225,17 +367,14 @@ var init = function() { // Create a new buffer with gl.createBuffer, and save it as vbo. - // TODO: uncomment vbo = gl.createBuffer(); // 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) { @@ -248,22 +387,18 @@ gl.useProgram(prog.prog); // 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..50a534d 100644 --- a/js/deferredSetup.js +++ b/js/deferredSetup.js @@ -6,9 +6,15 @@ R.pass_debug = {}; R.pass_deferred = {}; R.pass_post1 = {}; + R.pass_motionBlur = {}; + R.pass_brightness = {}; + R.pass_postBlur2d = {}; + R.pass_postBlur1d = {}; + R.pass_bloom = {}; R.lights = []; - R.NUM_GBUFFERS = 4; + R.USE_PACKED_GBUFFERS = true; + R.NUM_GBUFFERS = (R.USE_PACKED_GBUFFERS) ? 2 : 4; /** * Set up the deferred pipeline framebuffer objects and textures. @@ -18,14 +24,29 @@ loadAllShaderPrograms(); R.pass_copy.setup(); R.pass_deferred.setup(); + R.pass_post1.setup(); + R.pass_postBlur1d.setup(); + R.pass_brightness.setup(); + R.pass_bloom.setup(); + + R.prevPos = []; + R.curPosIdx = 0; + R.fbo = [gl.createFramebuffer(), gl.createFramebuffer(), gl.createFramebuffer()]; + R.tex = [ + createAndBindColorTargetTexture(R.fbo[0], gl_draw_buffers.COLOR_ATTACHMENT0_WEBGL), + createAndBindColorTargetTexture(R.fbo[1], gl_draw_buffers.COLOR_ATTACHMENT0_WEBGL), + ]; + R.curFbo = 0; + R.prevPos[0] = gl.createTexture(); + configTexture(R.prevPos[0]); + R.prevPos[1] = gl.createTexture(); + configTexture(R.prevPos[1]); }; - // TODO: Edit if you want to change the light initial positions R.light_min = [-14, 0, -6]; 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.LIGHT_RADIUS = 2.0; + R.NUM_LIGHTS = 100; var setupLights = function() { Math.seedrandom(0); @@ -98,11 +119,51 @@ gl.bindFramebuffer(gl.FRAMEBUFFER, null); }; + R.pass_post1.setup = function() { + R.pass_post1.fbo = gl.createFramebuffer(); + R.pass_post1.colorTex = createAndBindColorTargetTexture( + R.pass_post1.fbo, gl_draw_buffers.COLOR_ATTACHMENT0_WEBGL); + abortIfFramebufferIncomplete(R.pass_post1.fbo); + gl_draw_buffers.drawBuffersWEBGL([gl_draw_buffers.COLOR_ATTACHMENT0_WEBGL]); + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + } + + R.pass_motionBlur.setup = function() { + R.pass_motionBlur.fbo = gl.createFramebuffer(); + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + } + + R.pass_brightness.setup = function() { + R.pass_brightness.fbo = gl.createFramebuffer(); + R.pass_brightness.colorTex = createAndBindColorTargetTexture( + R.pass_brightness.fbo, gl_draw_buffers.COLOR_ATTACHMENT0_WEBGL); + abortIfFramebufferIncomplete(R.pass_brightness.fbo); + gl_draw_buffers.drawBuffersWEBGL([gl_draw_buffers.COLOR_ATTACHMENT0_WEBGL]); + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + } + + R.pass_bloom.setup = function() { + R.pass_bloom.fbo = gl.createFramebuffer(); + R.pass_bloom.colorTex = createAndBindColorTargetTexture( + R.pass_bloom.fbo, gl_draw_buffers.COLOR_ATTACHMENT0_WEBGL); + abortIfFramebufferIncomplete(R.pass_bloom.fbo); + gl_draw_buffers.drawBuffersWEBGL([gl_draw_buffers.COLOR_ATTACHMENT0_WEBGL]); + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + } + R.pass_postBlur1d.setup = function() { + R.pass_postBlur1d.fbo = gl.createFramebuffer(); + R.pass_postBlur1d.colorTex = createAndBindColorTargetTexture( + R.pass_postBlur1d.fbo, gl_draw_buffers.COLOR_ATTACHMENT0_WEBGL); + abortIfFramebufferIncomplete(R.pass_postBlur1d.fbo); + 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. */ var loadAllShaderPrograms = function() { - loadShaderProgram(gl, 'glsl/copy.vert.glsl', 'glsl/copy.frag.glsl', + loadShaderProgram(gl, 'glsl/copy.vert.glsl', (R.USE_PACKED_GBUFFERS) ? 'glsl/copy-pack.frag.glsl' : 'glsl/copy.frag.glsl', function(prog) { // Create an object to hold info about this shader program var p = { prog: prog }; @@ -130,6 +191,15 @@ // Create an object to hold info about this shader program R.progClear = { prog: prog }; }); + + loadShaderProgram(gl, 'glsl/quad.vert.glsl', 'glsl/clone.frag.glsl', + function(prog) { + // Create an object to hold info about this shader program + var p = { prog: prog }; + p.u_in = gl.getUniformLocation(p.prog, 'u_in'); + p.u_depth = gl.getUniformLocation(p.prog, 'u_depth'); + R.progClone = p; + }); loadDeferredProgram('ambient', function(p) { // Save the object into this variable for access later @@ -156,12 +226,45 @@ R.progPost1 = p; }); - // TODO: If you add more passes, load and set up their shader programs. + loadPostProgram('motionblur', function(p) { + p.u_color = gl.getUniformLocation(p.prog, 'u_color'); + p.u_oldpos = gl.getUniformLocation(p.prog, 'u_oldpos'); + p.u_projMat = gl.getUniformLocation(p.prog, 'u_projMat'); + p.u_debug = gl.getUniformLocation(p.prog, 'u_debug'); + R.progMotionBlur = p; + }); + + loadPostProgram('brightness', function(p) { + p.u_color = gl.getUniformLocation(p.prog, 'u_color'); + // Save the object into this variable for access later + R.progBrightness = p; + }); + + loadPostProgram('bloom', function(p) { + p.u_color = gl.getUniformLocation(p.prog, 'u_color'); + p.u_glow = gl.getUniformLocation(p.prog, 'u_glow'); + // Save the object into this variable for access later + R.progBloom = p; + }); + + loadPostProgram('gaussian2d', function(p) { + p.u_color = gl.getUniformLocation(p.prog, 'u_color'); + p.u_pixWidthHeight = gl.getUniformLocation(p.prog, 'u_pixWidthHeight'); + R.progBlur2d = p; + }); + + loadPostProgram('gaussian1dconv', function(p) { + p.u_color = gl.getUniformLocation(p.prog, 'u_color'); + p.u_pixWidthHeight = gl.getUniformLocation(p.prog, 'u_pixWidthHeight'); + p.u_direction = gl.getUniformLocation(p.prog, 'u_direction'); + R.progBlur1dconv = p; + }); }; var loadDeferredProgram = function(name, callback) { + var deferredFolder = (R.USE_PACKED_GBUFFERS) ? 'deferred-pack' : 'deferred'; loadShaderProgram(gl, 'glsl/quad.vert.glsl', - 'glsl/deferred/' + name + '.frag.glsl', + 'glsl/' + deferredFolder + '/' + name + '.frag.glsl', function(prog) { // Create an object to hold info about this shader program var p = { prog: prog }; @@ -213,6 +316,15 @@ var createAndBindColorTargetTexture = function(fbo, attachment) { var tex = gl.createTexture(); + configTexture(tex); + + gl.bindFramebuffer(gl.FRAMEBUFFER, fbo); + gl.framebufferTexture2D(gl.FRAMEBUFFER, attachment, gl.TEXTURE_2D, tex, 0); + + return tex; + }; + + var configTexture = function(tex) { 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); @@ -220,10 +332,5 @@ 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.bindTexture(gl.TEXTURE_2D, null); - - gl.bindFramebuffer(gl.FRAMEBUFFER, fbo); - gl.framebufferTexture2D(gl.FRAMEBUFFER, attachment, gl.TEXTURE_2D, tex, 0); - - return tex; }; })(); diff --git a/js/ui.js b/js/ui.js index abd6119..503b717 100644 --- a/js/ui.js +++ b/js/ui.js @@ -6,8 +6,11 @@ var cfg; var Cfg = function() { // TODO: Define config fields and defaults here this.debugView = -1; - this.debugScissor = false; - this.enableEffect0 = false; + this.enableScissor = 1; + this.bloom = 1; + this.enableMotionBlur = false; + this.boundingBoxScale = 1.0; + this.light_dt = 0.03; }; var init = function() { @@ -22,14 +25,25 @@ var cfg; '2 Geometry normal': 2, '3 Color map': 3, '4 Normal map': 4, - '5 Surface normal': 5 + '5 Surface normal': 5, + '6 Motion blur vectors': 6, + '7 Brightness filter': 7, + '8 Blurred brightness': 8, }); - gui.add(cfg, 'debugScissor'); + gui.add(cfg, 'light_dt', 0.0, 0.10); + gui.add(cfg, 'enableScissor', { + 'No': 0, + 'Yes': 1, + 'Yes (with debug view)': 2, + }); + gui.add(cfg, 'boundingBoxScale', 0.0, 2.0); - var eff0 = gui.addFolder('EFFECT NAME HERE'); - eff0.open(); - eff0.add(cfg, 'enableEffect0'); - // TODO: add more effects toggles and parameters here + gui.add(cfg, 'bloom', { + 'None': 0, + '2d Gaussian kernel bloom': 1, + '1d Gaussian kernel bloom': 2, + }); + gui.add(cfg, 'enableMotionBlur'); }; window.handle_load.push(init); diff --git a/js/util.js b/js/util.js index 8f43d38..0dbcbb3 100644 --- a/js/util.js +++ b/js/util.js @@ -121,14 +121,14 @@ window.getScissorForLight = (function() { var maxpt = new THREE.Vector2(0, 0); var ret = [0, 0, 0, 0]; - return function(view, proj, l) { + return function(view, proj, l, scale) { // front bottom-left corner of sphere's bounding cube a.fromArray(l.pos); a.w = 1; a.applyMatrix4(view); a.x -= l.rad; a.y -= l.rad; - a.z += l.rad; + a.z -= l.rad; a.applyMatrix4(proj); a.divideScalar(a.w); @@ -153,10 +153,18 @@ window.getScissorForLight = (function() { minpt.addScalar(1.0); minpt.multiplyScalar(0.5); maxpt.addScalar(1.0); maxpt.multiplyScalar(0.5); - 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)); + if (scale === undefined) { + scale = 1.0; + } + var mx = 0.5 * (minpt.x + maxpt.x); + var my = 0.5 * (minpt.y + maxpt.y); + var w = 0.5 * (maxpt.x - minpt.x) * scale; + var h = 0.5 * (maxpt.y - minpt.y) * scale; + + ret[0] = Math.round(width * (mx - w)); + ret[1] = Math.round(height * (my - h)); + ret[2] = Math.round(width * w * 2.0); + ret[3] = Math.round(height * h * 2.0); return ret; }; })(); diff --git a/models/gltf-sponza-kai-fix/buffer_0.bin b/models/glTF-sponza-kai-fix/buffer_0.bin similarity index 100% rename from models/gltf-sponza-kai-fix/buffer_0.bin rename to models/glTF-sponza-kai-fix/buffer_0.bin diff --git a/models/gltf-sponza-kai-fix/color.jpeg b/models/glTF-sponza-kai-fix/color.jpeg similarity index 100% rename from models/gltf-sponza-kai-fix/color.jpeg rename to models/glTF-sponza-kai-fix/color.jpeg diff --git a/models/gltf-sponza-kai-fix/fragmentShader0.glsl b/models/glTF-sponza-kai-fix/fragmentShader0.glsl similarity index 100% rename from models/gltf-sponza-kai-fix/fragmentShader0.glsl rename to models/glTF-sponza-kai-fix/fragmentShader0.glsl diff --git a/models/gltf-sponza-kai-fix/normal.png b/models/glTF-sponza-kai-fix/normal.png similarity index 100% rename from models/gltf-sponza-kai-fix/normal.png rename to models/glTF-sponza-kai-fix/normal.png diff --git a/models/gltf-sponza-kai-fix/sponza.gltf b/models/glTF-sponza-kai-fix/sponza.gltf similarity index 100% rename from models/gltf-sponza-kai-fix/sponza.gltf rename to models/glTF-sponza-kai-fix/sponza.gltf diff --git a/models/gltf-sponza-kai-fix/vertexShader0.glsl b/models/glTF-sponza-kai-fix/vertexShader0.glsl similarity index 100% rename from models/gltf-sponza-kai-fix/vertexShader0.glsl rename to models/glTF-sponza-kai-fix/vertexShader0.glsl diff --git a/server.py b/server.py index 17ff1a6..d6313a1 100755 --- a/server.py +++ b/server.py @@ -8,7 +8,10 @@ from http.server import SimpleHTTPRequestHandler from socketserver import TCPServer -PORT = 10565 +if len(sys.argv) < 2: + PORT = 10565 +else: + PORT = int(sys.argv[1]) Handler = SimpleHTTPRequestHandler