diff --git a/README.md b/README.md index 25002db..b36ccf0 100644 --- a/README.md +++ b/README.md @@ -3,26 +3,63 @@ 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) +* Richard Lee +* Tested on: Windows 7, i7-3720QM @ 2.60GHz 8GB, GT 650M 4GB (Personal Computer) -### Live Online +[![](img/thumb.png)](https://leerichard42.github.io/Project5-WebGL-Deferred-Shading-with-glTF/) -[![](img/thumb.png)](http://TODO.github.io/Project5B-WebGL-Deferred-Shading) +## Features -### Demo Video/GIF +* Basic Deferred Shading Pipeline with Lambert and Blinn-Phong shading +* Lighting Scissor Test Optimization +* Toon Shading +* Compact Normal Buffer Optimization +* Screen-Space Motion Blur +* Variable Material Properties -[![](img/video.png)](TODO) + -### (TODO: Your README) +#### Basic Pipeline -*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. +This deferred shading renderer implements a deferred shader which lights a scene based on geometry buffers calculated in an initial pass. It uses the Lambert and Blinn-Phong reflection models with point lights to calculate the lighting for each light in the scene. -This assignment has a considerable amount of performance analysis compared -to implementation work. Complete the implementation early to leave time! +#### Toon Shading + + +An alternate toon shading view was added, which clamped the lighting at fixed intervals to give a cartoon look. This effect had a negligible effect on performance, as it only consisted of a few additional lines in the deferred shading stage. + +#### Scissor Test + + + + + +The scissor test optimization calculates the screen space bounding box and creates a scissor for each light before performing the lighting calculations, which is very effective because the majority of lights only take up a small portion of the scene when rendered. As seen when observing the average time per frame with and without the scissor test, the scissor test provided a substantial improvement in performance as the number of lights increased, and also decreased in performance at a slower rate than without the optimization. + +When using the Firefox JavaScript profiler, the timing results did not take into account the WebGL draw calls, and only looked at the runtime on the CPU. This showed the scissor test as taking much longer than without the optimization, which was due to the fact that the screen space bounding box calculations were being performed for each light on each frame. + +In addition, there was a visual difference when enabling the optimization, as lights that were behind the camera did not register a bounding box on the screen, even if they had initially cast light past the camera. + +#### G-Buffer Optimization + + + +The number of g-buffers was reduced by packing the geometry and texture normals into a single buffer. This was done by only storing the x and y components for the normals, as the z component could be recalculated from these components when performing the shading calculations. Performance wise, there was not much difference in the time taken per render call on the CPU, as seen in the chart above - the time per frame also remained about the same with and without the optimization. However, since we were able to remove the use of an entire buffer, this optimization would definitely be effective in terms of memory usage. + +One caveat was that the recalculated normals seemed to have more contrast than without the optimization, giving a slightly different look to the model. This could be due to numerical precision issues, or a problem with the normal recalculation. + +#### Motion Blur + + + +Screen-space motion blur was also implemented as a post-process shader, by calculating how far each visible point on the screen had moved relative to the camera using the camera matrix stored from the previous frame and the position g-buffer. This gave a screen-space velocity for each point, which was then used to interpolate the result from the deferred shading pass. This post-process did not have a noticeable effect on performance of the renderer. + +#### Material Properties + + + +Additional variability in materials was included with the addition of a specular exponent value in the g-buffers, which allowed for multiple objects with different amounts of reflectivity to be rendered at the same time. ### Credits diff --git a/glsl/copy.frag.glsl b/glsl/copy.frag.glsl old mode 100644 new mode 100755 index 823ebcd..ceefcd9 --- a/glsl/copy.frag.glsl +++ b/glsl/copy.frag.glsl @@ -3,18 +3,26 @@ precision highp float; precision highp int; +uniform bool u_packNormals; uniform sampler2D u_colmap; uniform sampler2D u_normap; +uniform float u_specmap; varying vec3 v_position; varying vec3 v_normal; varying vec2 v_uv; void main() { - // TODO: copy values into gl_FragData[0], [1], etc. // You can use the GLSL texture2D function to access the textures using // the UV in v_uv. - // this gives you the idea - // gl_FragData[0] = vec4( v_position, 1.0 ); + gl_FragData[0] = vec4( v_position, u_specmap ); + gl_FragData[2] = texture2D(u_colmap, v_uv); + if (u_packNormals) { + gl_FragData[1] = vec4( v_normal.xy, texture2D(u_normap, v_uv).xy ); + } + else { + gl_FragData[1] = vec4( v_normal, 1.0 ); + gl_FragData[3] = texture2D(u_normap, v_uv); + } } diff --git a/glsl/deferred/ambient.frag.glsl b/glsl/deferred/ambient.frag.glsl old mode 100644 new mode 100755 diff --git a/glsl/deferred/blinnphong-pointlight.frag.glsl b/glsl/deferred/blinnphong-pointlight.frag.glsl old mode 100644 new mode 100755 index b24a54a..c88466a --- a/glsl/deferred/blinnphong-pointlight.frag.glsl +++ b/glsl/deferred/blinnphong-pointlight.frag.glsl @@ -4,6 +4,9 @@ precision highp int; #define NUM_GBUFFERS 4 +uniform bool u_toon; +uniform bool u_packNormals; +uniform vec3 u_eyePos; uniform vec3 u_lightCol; uniform vec3 u_lightPos; uniform float u_lightRad; @@ -12,6 +15,10 @@ uniform sampler2D u_depth; varying vec2 v_uv; +vec3 reconstructNormal(vec2 xy) { + return vec3(xy, sqrt(1.0 - dot(xy, xy))); +} + vec3 applyNormalMap(vec3 geomnor, vec3 normap) { normap = normap * 2.0 - 1.0; vec3 up = normalize(vec3(0.001, 1, 0.001)); @@ -20,14 +27,32 @@ vec3 applyNormalMap(vec3 geomnor, vec3 normap) { return normap.y * surftan + normap.x * surfbinor + normap.z * geomnor; } +const float levels = 4.0; +const float invLevels = 1.0 / levels; +float applyToonFilter(float value) { + return floor(value * levels) * invLevels; +} + 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 + vec3 pos = gb0.xyz; // World-space position + float specExp = gb0.w; + vec3 colmap = gb2.rgb; // The color map - unlit "albedo" (surface color) + vec3 geomnor, normap; + if (u_packNormals) { + geomnor = reconstructNormal(gb1.xy); // Normals of the geometry as defined, without normal mapping + normap = reconstructNormal(gb1.zw); // The raw normal map (normals relative to the surface they're on) + } + else { + geomnor = gb1.xyz; // Normals of the geometry as defined, without normal mapping + normap = gb3.xyz; // The raw normal map (normals relative to the surface they're on) + } + vec3 nor = normalize(applyNormalMap (geomnor, normap)); // The true normals as we want to light them - with the normal map applied to the geometry normals (applyNormalMap above) // 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) { @@ -35,5 +60,22 @@ void main() { return; } - gl_FragColor = vec4(0, 0, 1, 1); // TODO: perform lighting calculations + vec3 posToLight = u_lightPos - pos; + vec3 L = normalize(posToLight); + vec3 R = normalize(-reflect(L, nor)); + vec3 E = normalize(u_eyePos - pos); + + float diffuse = clamp(max(dot(nor, L), 0.0), 0.0, 1.0); + float specular = clamp(pow(max(dot(R, E), 0.0), specExp), 0.0, 1.0); + + if (u_toon) { + diffuse = applyToonFilter(diffuse); + specular = applyToonFilter(specular); + } + + vec3 diffuseCol = u_lightCol * colmap * diffuse; + vec3 specularCol = u_lightCol * vec3(1.0) * specular; + + float attenuation = pow(max(0.0, u_lightRad - length(posToLight)) / u_lightRad, 0.5); + gl_FragColor = vec4(attenuation * (0.3 * diffuseCol + 0.7 * specularCol), 1.0); } diff --git a/glsl/deferred/debug.frag.glsl b/glsl/deferred/debug.frag.glsl old mode 100644 new mode 100755 index 007466f..137a8e9 --- a/glsl/deferred/debug.frag.glsl +++ b/glsl/deferred/debug.frag.glsl @@ -5,6 +5,7 @@ precision highp int; #define NUM_GBUFFERS 4 uniform int u_debug; +uniform bool u_packNormals; uniform sampler2D u_gbufs[NUM_GBUFFERS]; uniform sampler2D u_depth; @@ -12,6 +13,10 @@ varying vec2 v_uv; const vec4 SKY_COLOR = vec4(0.66, 0.73, 1.0, 1.0); +vec3 reconstructNormal(vec2 xy) { + return normalize(vec3(xy, sqrt(1.0 - dot(xy, xy)))); + } + vec3 applyNormalMap(vec3 geomnor, vec3 normap) { normap = normap * 2.0 - 1.0; vec3 up = normalize(vec3(0.001, 1, 0.001)); @@ -26,27 +31,33 @@ 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 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 geomnor, normap; + if (u_packNormals) { + geomnor = reconstructNormal(gb1.xy); // Normals of the geometry as defined, without normal mapping + normap = reconstructNormal(gb1.zw); // The raw normal map (normals relative to the surface they're on) + } + else { + geomnor = gb1.xyz; // Normals of the geometry as defined, without normal mapping + normap = gb3.xyz; // The raw normal map (normals relative to the surface they're on) + } + vec3 nor = normalize(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/one.frag.glsl b/glsl/post/one.frag.glsl old mode 100644 new mode 100755 index 94191cd..af5f56f --- a/glsl/post/one.frag.glsl +++ b/glsl/post/one.frag.glsl @@ -2,7 +2,10 @@ precision highp float; precision highp int; +uniform bool u_motion; uniform sampler2D u_color; +uniform mat4 u_prevCameraMat; +uniform sampler2D u_gbuf0; varying vec2 v_uv; @@ -16,5 +19,24 @@ void main() { return; } - gl_FragColor = color; + if (u_motion) { + vec3 worldPos = texture2D(u_gbuf0, v_uv).xyz; + vec4 prevPos = u_prevCameraMat * vec4(worldPos, 1.0); + prevPos /= prevPos.w; + vec2 prev_uv = prevPos.xy * 0.5 + 0.5; + vec2 velocity = (v_uv - prev_uv) / 2.0; + + float numSamples = 6.0; + vec2 uv = v_uv + velocity; + for (int i = 1; i < 6; ++i) { + color += texture2D(u_color, uv); + uv += velocity; + } + + gl_FragColor = color / numSamples; + } + else { + gl_FragColor = color; + } + } diff --git a/glsl/red.frag.glsl b/glsl/red.frag.glsl old mode 100644 new mode 100755 index f8ef1ec..e41993b --- 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/blur.gif b/img/blur.gif new file mode 100755 index 0000000..b54d13b Binary files /dev/null and b/img/blur.gif differ diff --git a/img/gbuffer.png b/img/gbuffer.png new file mode 100755 index 0000000..f201dff Binary files /dev/null and b/img/gbuffer.png differ diff --git a/img/materials.png b/img/materials.png new file mode 100755 index 0000000..9902682 Binary files /dev/null and b/img/materials.png differ diff --git a/img/noblur.gif b/img/noblur.gif new file mode 100755 index 0000000..7e5bbad Binary files /dev/null and b/img/noblur.gif differ diff --git a/img/preview.gif b/img/preview.gif new file mode 100755 index 0000000..06e6537 Binary files /dev/null and b/img/preview.gif differ diff --git a/img/scissor.gif b/img/scissor.gif new file mode 100755 index 0000000..4642a76 Binary files /dev/null and b/img/scissor.gif differ diff --git a/img/scissor_cpu.png b/img/scissor_cpu.png new file mode 100755 index 0000000..1eeee9c Binary files /dev/null and b/img/scissor_cpu.png differ diff --git a/img/scissor_performance.png b/img/scissor_performance.png new file mode 100755 index 0000000..d6d03c4 Binary files /dev/null and b/img/scissor_performance.png differ diff --git a/img/thumb.png b/img/thumb.png index 9ec8ed0..ae7a97d 100644 Binary files a/img/thumb.png and b/img/thumb.png differ diff --git a/img/toon.gif b/img/toon.gif new file mode 100755 index 0000000..a5bb3c6 Binary files /dev/null and b/img/toon.gif differ diff --git a/js/deferredRender.js b/js/deferredRender.js old mode 100644 new mode 100755 index bb3edd4..1605d6c --- a/js/deferredRender.js +++ b/js/deferredRender.js @@ -17,24 +17,12 @@ // Move the R.lights for (var i = 0; i < R.lights.length; i++) { - // OPTIONAL TODO: Edit if you want to change how lights move var mn = R.light_min[1]; var mx = R.light_max[1]; R.lights[i].pos[1] = (R.lights[i].pos[1] + R.light_dt - mn + mx) % mx + mn; } // Execute deferred shading pipeline - - // CHECKITOUT: START HERE! You can even uncomment this: - //debugger; - - { // TODO: this block should be removed after testing renderFullScreenQuad - gl.bindFramebuffer(gl.FRAMEBUFFER, null); - // TODO: Implement/test renderFullScreenQuad first - renderFullScreenQuad(R.progRed); - return; - } - R.pass_copy.render(state); if (cfg && cfg.debugView >= 0) { @@ -43,11 +31,8 @@ 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); + R.pass_post1.render(state); } }; @@ -56,34 +41,31 @@ */ 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 + gl.uniform1i(R.progCopy.u_packNormals, cfg.packNormals === true ? 1 : 0); var m = state.cameraMat.elements; + if (!R.prevCameraMat) { + R.prevCameraMat = new Float32Array(m); + } + // * 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); }; var drawScene = function(state) { @@ -100,18 +82,16 @@ 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_packNormals, cfg.packNormals === true ? 1 : 0); + 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 +112,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,19 +123,48 @@ // * 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). + //Enable scissor test for lights + if (cfg.enableScissor) { + gl.enable(gl.SCISSOR_TEST); + } + + gl.uniform1i(R.prog_BlinnPhong_PointLight.u_packNormals, cfg.packNormals === true ? 1 : 0); + gl.uniform1i(R.prog_BlinnPhong_PointLight.u_toon, cfg.toonShading === true ? 1 : 0); + gl.uniform3fv(R.prog_BlinnPhong_PointLight.u_eyePos, + [state.cameraPos.x, state.cameraPos.y, state.cameraPos.z]); - // 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); + for (var i = 0; i < R.lights.length; i++) { + var light = R.lights[i]; + gl.useProgram(R.prog_BlinnPhong_PointLight.prog); + 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); + + if (cfg.enableScissor) { + var sc = getScissorForLight(state.viewMat, state.projMat, light); + if (sc && sc[2] > 0 && sc[3] > 0) { + gl.scissor(sc[0], sc[1], sc[2], sc[3]); + + gl.blendFunc(gl.ONE, gl.ONE); + renderFullScreenQuad(R.prog_BlinnPhong_PointLight); + + if (cfg.debugScissor) { + gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); + gl.useProgram(R.progRed.prog); + renderFullScreenQuad(R.progRed); + } + } + } + else { + gl.blendFunc(gl.ONE, gl.ONE); + renderFullScreenQuad(R.prog_BlinnPhong_PointLight); + } + } - // Disable blending so that it doesn't affect other code + // Disable blending and scissor test so that they don't affect other code + if (cfg.enableScissor) { + gl.disable(gl.SCISSOR_TEST); + } gl.disable(gl.BLEND); }; @@ -191,18 +199,27 @@ // * 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, 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.uniform1i(R.progPost1.u_motion, cfg.motionBlur === true ? 1 : 0); + + gl.activeTexture(gl.TEXTURE1); + gl.bindTexture(gl.TEXTURE_2D, R.pass_copy.gbufs[0]); + gl.uniform1i(R.progPost1.u_gbuf0, 1); + + gl.uniformMatrix4fv(R.progPost1.u_prevCameraMat, false, R.prevCameraMat); + // * Render a fullscreen quad to perform shading on renderFullScreenQuad(R.progPost1); + + //store the camera matrix for motion blur + R.prevCameraMat = new Float32Array(state.cameraMat.elements); }; var renderFullScreenQuad = (function() { @@ -225,17 +242,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 +262,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 old mode 100644 new mode 100755 index 65136e0..312e448 --- a/js/deferredSetup.js +++ b/js/deferredSetup.js @@ -24,8 +24,8 @@ 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 = 6.0; + R.NUM_LIGHTS = 60; // TODO: test with MORE lights! var setupLights = function() { Math.seedrandom(0); @@ -108,9 +108,11 @@ var p = { prog: prog }; // Retrieve the uniform and attribute locations + p.u_packNormals = gl.getUniformLocation(prog, 'u_packNormals'); p.u_cameraMat = gl.getUniformLocation(prog, 'u_cameraMat'); p.u_colmap = gl.getUniformLocation(prog, 'u_colmap'); p.u_normap = gl.getUniformLocation(prog, 'u_normap'); + p.u_specmap = gl.getUniformLocation(prog, 'u_specmap'); p.a_position = gl.getAttribLocation(prog, 'a_position'); p.a_normal = gl.getAttribLocation(prog, 'a_normal'); p.a_uv = gl.getAttribLocation(prog, 'a_uv'); @@ -138,6 +140,9 @@ loadDeferredProgram('blinnphong-pointlight', function(p) { // Save the object into this variable for access later + p.u_packNormals = gl.getUniformLocation(p.prog, 'u_packNormals'); + p.u_toon = gl.getUniformLocation(p.prog, 'u_toon'); + p.u_eyePos = gl.getUniformLocation(p.prog, 'u_eyePos'); 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,12 +150,16 @@ }); loadDeferredProgram('debug', function(p) { + p.u_packNormals = gl.getUniformLocation(p.prog, 'u_packNormals'); 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) { + p.u_prevCameraMat = gl.getUniformLocation(p.prog, 'u_prevCameraMat'); + p.u_gbuf0 = gl.getUniformLocation(p.prog, 'u_gbuf0'); + p.u_motion = gl.getUniformLocation(p.prog, 'u_motion'); p.u_color = gl.getUniformLocation(p.prog, 'u_color'); // Save the object into this variable for access later R.progPost1 = p; diff --git a/js/framework.js b/js/framework.js old mode 100644 new mode 100755 index 4f944ee..b9fdb73 --- 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({ @@ -122,10 +122,10 @@ var width, height; R.sphereModel = m; }); - // var glTFURL = 'models/glTF-duck/duck.gltf'; - var glTFURL = 'models/glTF-sponza-kai-fix/sponza.gltf'; + var duckURL = 'models/glTF-duck/duck.gltf'; + var sponzaURL = 'models/gltf-sponza-kai-fix/sponza.gltf'; var glTFLoader = new MinimalGLTFLoader.glTFLoader(gl); - glTFLoader.loadGLTF(glTFURL, function (glTF) { + glTFLoader.loadGLTF(sponzaURL, function (glTF) { var curScene = glTF.scenes[glTF.defaultScene]; var webGLTextures = {}; @@ -154,6 +154,7 @@ var width, height; var colorTextureName = 'texture_color'; var normalTextureName = 'texture_normal'; + var specExpInfo = glTF.json.materials['material_0'].values.shininess; // textures for (var tid in glTF.json.textures) { @@ -187,8 +188,8 @@ var width, height; gl.texParameteri(target, gl.TEXTURE_MAG_FILTER, magFilter); gl.texParameteri(target, gl.TEXTURE_WRAP_S, wrapS); gl.texParameteri(target, gl.TEXTURE_WRAP_T, wrapT); - if (minFilter == gl.NEAREST_MIPMAP_NEAREST || - minFilter == gl.NEAREST_MIPMAP_LINEAR || + if (minFilter == gl.NEAREST_MIPMAP_NEAREST || + minFilter == gl.NEAREST_MIPMAP_LINEAR || minFilter == gl.LINEAR_MIPMAP_NEAREST || minFilter == gl.LINEAR_MIPMAP_LINEAR ) { gl.generateMipmap(target); @@ -245,18 +246,143 @@ var width, height; uvInfo: {size: uvInfo.size, type: uvInfo.type, stride: uvInfo.stride, offset: uvInfo.offset}, // specific textures temp test - colmap: webGLTextures[colorTextureName].texture, - normap: webGLTextures[normalTextureName].texture + colmap: webGLTextures[colorTextureName].texture, + normap: webGLTextures[normalTextureName].texture, + specExp: specExpInfo }); + } + } + + glTFLoader.loadGLTF(duckURL, function (glTF) { + var curScene = glTF.scenes[glTF.defaultScene]; + + var webGLTextures = {}; + + // temp var + var i,len; + var primitiveOrderID; + + var mesh; + var primitive; + var vertexBuffer; + var indicesBuffer; + + + // textures setting + var textureID = 0; + var textureInfo; + var samplerInfo; + var target, format, internalFormat, type; // texture info + var magFilter, minFilter, wrapS, wrapT; + var image; + var texture; + + + // temp for sponza + var colorTextureName = 'texture_color'; + var normalTextureName = 'texture_normal'; + + var specExpInfo = glTF.json.materials['material_0'].values.shininess; + + // textures + for (var tid in glTF.json.textures) { + + textureInfo = glTF.json.textures[tid]; + target = textureInfo.target || gl.TEXTURE_2D; + format = textureInfo.format || gl.RGBA; + internalFormat = textureInfo.format || gl.RGBA; + type = textureInfo.type || gl.UNSIGNED_BYTE; + + image = glTF.images[textureInfo.source]; + + texture = gl.createTexture(); + gl.activeTexture(gl.TEXTURE0 + textureID); + gl.bindTexture(target, texture); + + switch(target) { + case 3553: // gl.TEXTURE_2D + gl.texImage2D(target, 0, internalFormat, format, type, image); + break; + // TODO for TA + } + + // !! Sampler + // raw WebGL 1, no sampler object, set magfilter, wrapS, etc + samplerInfo = glTF.json.samplers[textureInfo.sampler]; + minFilter = samplerInfo.minFilter || gl.NEAREST_MIPMAP_LINEAR; + magFilter = samplerInfo.magFilter || gl.LINEAR; + wrapS = samplerInfo.wrapS || gl.REPEAT; + wrapT = samplerInfo.wrapT || gl.REPEAT; + gl.texParameteri(target, gl.TEXTURE_MIN_FILTER, minFilter); + gl.texParameteri(target, gl.TEXTURE_MAG_FILTER, magFilter); + gl.texParameteri(target, gl.TEXTURE_WRAP_S, wrapS); + gl.texParameteri(target, gl.TEXTURE_WRAP_T, wrapT); + if (minFilter == gl.NEAREST_MIPMAP_NEAREST || + minFilter == gl.NEAREST_MIPMAP_LINEAR || + minFilter == gl.LINEAR_MIPMAP_NEAREST || + minFilter == gl.LINEAR_MIPMAP_LINEAR ) { + gl.generateMipmap(target); + } + + gl.bindTexture(target, null); + + webGLTextures[tid] = { + texture: texture, + target: target, + id: textureID + }; + + textureID++; } - } + // vertex attributes + for (var mid in curScene.meshes) { + mesh = curScene.meshes[mid]; + + for (i = 0, len = mesh.primitives.length; i < len; ++i) { + primitive = mesh.primitives[i]; - - }); + vertexBuffer = gl.createBuffer(); + indicesBuffer = gl.createBuffer(); + + // initialize buffer + var vertices = primitive.vertexBuffer; + gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer); + gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW); + gl.bindBuffer(gl.ARRAY_BUFFER, null); + + var indices = primitive.indices; + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indicesBuffer); + gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW); + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null); + + + var posInfo = primitive.attributes[primitive.technique.parameters['position'].semantic]; + var norInfo = primitive.attributes[primitive.technique.parameters['normal'].semantic]; + var uvInfo = primitive.attributes[primitive.technique.parameters['texcoord_0'].semantic]; + + models.push({ + gltf: primitive, + + idx: indicesBuffer, + + attributes: vertexBuffer, + posInfo: {size: posInfo.size, type: posInfo.type, stride: posInfo.stride, offset: posInfo.offset}, + norInfo: {size: norInfo.size, type: norInfo.type, stride: norInfo.stride, offset: norInfo.offset}, + uvInfo: {size: uvInfo.size, type: uvInfo.type, stride: uvInfo.stride, offset: uvInfo.offset}, + + // specific textures temp test + colmap: webGLTextures[colorTextureName].texture, + normap: webGLTextures[normalTextureName].texture, + specExp: specExpInfo + }); + } + } + }); + }); resize(); // renderer.render(scene, camera); diff --git a/js/ui.js b/js/ui.js old mode 100644 new mode 100755 index abd6119..714d791 --- 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.enableScissor = false; this.debugScissor = false; - this.enableEffect0 = false; + this.toonShading = false; + this.packNormals = false; + this.motionBlur = false; }; var init = function() { @@ -24,12 +27,16 @@ var cfg; '4 Normal map': 4, '5 Surface normal': 5 }); - gui.add(cfg, 'debugScissor'); + var scissor = gui.addFolder('Scissor Test'); + scissor.open(); + scissor.add(cfg, 'enableScissor'); + scissor.add(cfg, 'debugScissor'); + + gui.add(cfg, 'toonShading'); + gui.add(cfg, 'packNormals'); + gui.add(cfg, 'motionBlur'); + - var eff0 = gui.addFolder('EFFECT NAME HERE'); - eff0.open(); - eff0.add(cfg, 'enableEffect0'); - // TODO: add more effects toggles and parameters here }; window.handle_load.push(init); diff --git a/js/util.js b/js/util.js old mode 100644 new mode 100755 index 8f43d38..c75ed37 --- a/js/util.js +++ b/js/util.js @@ -92,6 +92,8 @@ window.readyModelForDraw = function(prog, m) { gl.uniform1i(prog.u_normap, 1); } + gl.uniform1f(prog.u_specmap, m.specExp); + gl.bindBuffer(gl.ARRAY_BUFFER, m.attributes); gl.enableVertexAttribArray(prog.a_position); diff --git a/models/glTF-duck/Duck.gltf b/models/glTF-duck/Duck.gltf index 051777f..698b7df 100644 --- a/models/glTF-duck/Duck.gltf +++ b/models/glTF-duck/Duck.gltf @@ -108,10 +108,13 @@ "file2": { "name": "file2", "uri": "DuckCM.png" + }, + "normals": { + "uri": "normal.png" } }, "materials": { - "blinn3-fx": { + "material_0": { "name": "blinn3", "technique": "technique0", "values": { @@ -121,14 +124,15 @@ 0, 1 ], - "diffuse": "texture_file2", + "diffuse": "texture_color", + "normalMap": "texture_normal", "emission": [ 0, 0, 0, 1 ], - "shininess": 38.4, + "shininess": 32.0, "specular": [ 0, 0, @@ -149,7 +153,7 @@ "TEXCOORD_0": "accessor_27" }, "indices": "accessor_21", - "material": "blinn3-fx", + "material": "material_0", "mode": 4 } ] @@ -323,7 +327,7 @@ "specular": { "type": 35666 }, - "texcoord0": { + "texcoord_0": { "semantic": "TEXCOORD_0", "type": 35664 } @@ -350,13 +354,21 @@ } }, "textures": { - "texture_file2": { + "texture_color": { "format": 6408, "internalFormat": 6408, "sampler": "sampler_0", "source": "file2", "target": 3553, "type": 5121 + }, + "texture_normal": { + "format": 6407, + "internalFormat": 6407, + "sampler": "sampler_0", + "source": "normals", + "target": 3553, + "type": 5121 } } } \ No newline at end of file diff --git a/models/glTF-duck/normal.png b/models/glTF-duck/normal.png new file mode 100755 index 0000000..600d922 Binary files /dev/null and b/models/glTF-duck/normal.png differ diff --git a/models/gltf-sponza-kai-fix/sponza.gltf b/models/gltf-sponza-kai-fix/sponza.gltf index e21c7eb..aa46e63 100644 --- a/models/gltf-sponza-kai-fix/sponza.gltf +++ b/models/gltf-sponza-kai-fix/sponza.gltf @@ -106,7 +106,7 @@ } }, "materials": { - "material_lambert2SG": { + "material_0": { "name": "lambert2SG", "extensions": {}, "values": { @@ -130,7 +130,7 @@ 0, 1 ], - "shininess": 0, + "shininess": 16.0, "transparency": 1 }, "technique": "technique0" @@ -147,7 +147,7 @@ "TEXCOORD_0": "accessor_uv" }, "indices": "accessor_index_0", - "material": "material_lambert2SG", + "material": "material_0", "mode": 4 } ]