diff --git a/README.md b/README.md index 94de884..f5ed88a 100755 --- a/README.md +++ b/README.md @@ -3,26 +3,37 @@ WebGL Forward+ and Clustered 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) +* Ryan Tong +* Tested on: **Google Chrome 160.0** on + Windows 10, i7-8750H @ 2.20GHz 16GB, GeForce GTX 1060 6144MB (Personal Laptop) ### Live Online -[![](img/thumb.png)](http://TODO.github.io/Project5-WebGL-Forward-Plus-and-Clustered-Deferred) +[![](img/title.jpg)](http://ryanryantong.github.io/Project5-WebGL-Forward-Plus-and-Clustered-Deferred) ### Demo Video/GIF -[![](img/video.png)](TODO) +![vid](img/vid.gif) -### (TODO: Your README) +### Project Description +This project implements forward+ and clustered deferred rendering. Forward+ rendering optimizes a forward rendering pipeline by dividing the scene into frustums. The lights in the scene are treated as spheres and are then clustered into frustum buckets based on position. This allows the shader to only need to check the lights closeby that are within the frustum instead of looping through all lights which . Blinn-phong shading is also implemented. -*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. +### Performance Analysis +The key advantage of the forward+ and clustered deferred pipeline is the optimization to only check relevant light sources. Therefore, performance gains are especially noticeable in scenes with a large number of lights. Clustered deferred rendering is also implemented. Clustered deferred rendering further improves the forward+ pipeline by decoupling the calculation of vertex attributes and shading. This is especially good for scenes with complex or large numbers of objects. Note that the max number of lights allowed in a cluster are increased each time to match the total number of lights. Below is a plot of delay based on the number of lights which shows these performance gains. -This assignment has a considerable amount of performance analysis compared -to implementation work. Complete the implementation early to leave time! +### Performance +![performance](img/performance.png) +From these results we can clearly see that clustered deferred is fastest, followed by forward+, then regular forward rendering. This is expected because clustered deferred builds on the optimizations in forward+. +### Optimizations +Unfortunately, I did not have time to implement further optimization but instead provide some theoretical analysis. Specifically with GBuffers that each hold 4 floats to pass 3 vec3s (9 values). If we were able to condense one of the vec3s into 2 values, we would only have 8 values meaning we can save a Gbuffer. This would reduce the number of different global memory locations read meaning better performance. This could be reduced even further if the position vector is not passed and instead calculated using the camera and X/Y/depth. Although a comparison would need to be made to see if the computations outweigh the delay improvement. + +### Blinn-Phong Shading +I implemented blinn-phong shading to improve the visual quality of the render. This is a standard technique that has negligible impact. The results can be seen below. +### Regular +![regular](img/regular.jpg) +### Blinn-Phong +![blinn](img/blinn.jpg) ### Credits diff --git a/img/blinn.jpg b/img/blinn.jpg new file mode 100644 index 0000000..02e208a Binary files /dev/null and b/img/blinn.jpg differ diff --git a/img/performance.png b/img/performance.png new file mode 100644 index 0000000..276b485 Binary files /dev/null and b/img/performance.png differ diff --git a/img/regular.jpg b/img/regular.jpg new file mode 100644 index 0000000..03d5da9 Binary files /dev/null and b/img/regular.jpg differ diff --git a/img/title.jpg b/img/title.jpg new file mode 100644 index 0000000..0078f05 Binary files /dev/null and b/img/title.jpg differ diff --git a/img/vid.gif b/img/vid.gif new file mode 100644 index 0000000..7661ef6 Binary files /dev/null and b/img/vid.gif differ diff --git a/img/vid.mp4 b/img/vid.mp4 new file mode 100644 index 0000000..1e64708 Binary files /dev/null and b/img/vid.mp4 differ diff --git a/src/main.js b/src/main.js index d688fde..c2567e6 100755 --- a/src/main.js +++ b/src/main.js @@ -39,29 +39,29 @@ scene.loadGLTF('models/sponza/sponza.gltf'); // It lets you draw arbitrary lines in the scene. // This may be helpful for visualizing your frustum clusters so you can make // sure that they are in the right place. -const wireframe = new Wireframe(); var segmentStart = [-14.0, 0.0, -6.0]; var segmentEnd = [14.0, 20.0, 6.0]; var segmentColor = [1.0, 0.0, 0.0]; -wireframe.addLineSegment(segmentStart, segmentEnd, segmentColor); -wireframe.addLineSegment([-14.0, 1.0, -6.0], [14.0, 21.0, 6.0], [0.0, 1.0, 0.0]); +//wireframe.addLineSegment(segmentStart, segmentEnd, segmentColor); +//wireframe.addLineSegment([-14.0, 1.0, -6.0], [14.0, 21.0, 6.0], [0.0, 1.0, 0.0]); camera.position.set(-10, 8, 0); cameraControls.target.set(0, 2, 0); gl.enable(gl.DEPTH_TEST); function render() { + const wireframe = new Wireframe(); scene.update(); - params._renderer.render(camera, scene); + params._renderer.render(camera, scene, wireframe); // LOOK: Render wireframe "in front" of everything else. // If you would like the wireframe to render behind and in front // of objects based on relative depths in the scene, comment out / //the gl.disable(gl.DEPTH_TEST) and gl.enable(gl.DEPTH_TEST) lines. - gl.disable(gl.DEPTH_TEST); - wireframe.render(camera); - gl.enable(gl.DEPTH_TEST); + // gl.disable(gl.DEPTH_TEST); + // wireframe.render(camera); + // gl.enable(gl.DEPTH_TEST); } makeRenderLoop(render)(); \ No newline at end of file diff --git a/src/renderers/base.js b/src/renderers/base.js index 8a975b9..ed31a7b 100755 --- a/src/renderers/base.js +++ b/src/renderers/base.js @@ -1,3 +1,5 @@ +import { vec3, vec4 } from 'gl-matrix'; +import { NUM_LIGHTS } from '../scene'; import TextureBuffer from './textureBuffer'; export const MAX_LIGHTS_PER_CLUSTER = 100; @@ -10,11 +12,10 @@ export default class BaseRenderer { this._ySlices = ySlices; this._zSlices = zSlices; } - - updateClusters(camera, viewMatrix, scene) { + + updateClusters(camera, viewMatrix, scene, wireframe) { // TODO: Update the cluster texture with the count and indices of the lights in each cluster // This will take some time. The math is nontrivial... - for (let z = 0; z < this._zSlices; ++z) { for (let y = 0; y < this._ySlices; ++y) { for (let x = 0; x < this._xSlices; ++x) { @@ -25,6 +26,184 @@ export default class BaseRenderer { } } + // Project to camera space, z slices are centered at 0,0 + // For min, max point on sphere, calc min, max cluster indicies + // Get w,h of Z slice of view frustrum using: + // h = 2 * tan(fovRadians/ 2) * zDist; w = h * aspectRatio; + // Compute which x,y bucket/cluster + // Compute which z bucket/cluster + // Cache ratio to avoid repeated computations + let heightRatio = 2 * Math.tan(camera.fov / 2 * Math.PI / 180); + let widthRatio = heightRatio * camera.aspect; + + //Loop over all lights + for (let i = 0; i < NUM_LIGHTS; ++i) { + let light = scene.lights[i]; + let lightPos = vec4.fromValues(light.position[0], light.position[1], light.position[2], 1); + // Do radius and camera.near offset before projection since they are in world position? + // Not as efficent bc more lights in range now :/ but not sure why flickering was occuring + // 7 was found experimentally, removes ~most flickering + let radius = light.radius + 7; //This offset increases the reach of the light to reduce flickering + let lightMin = vec4.fromValues(light.position[0] - radius, light.position[1] - radius, light.position[2] - camera.near - radius, 1); + let lightMax = vec4.fromValues(light.position[0] + radius, light.position[1] + radius, light.position[2] - camera.near + radius, 1); + //let cameraPos = vec4.fromValues(camera.position.x, camera.position.y, camera.position.z, 1); + + //Project to camera space + vec4.transformMat4(lightMin, lightMin, viewMatrix); + vec4.transformMat4(lightMax, lightMax, viewMatrix); + vec4.transformMat4(lightPos, lightPos, viewMatrix); + + + // NOTE: sign of z becomes flipped after viewMatrix transform + // clamp light min so that zmin is not neg? + var tempMin = [lightMin[0], lightMin[1], lightMin[2], 1]; + var tempMax = [lightMax[0], lightMax[1], lightMax[2], 1]; + // if (lightMin[2] > 0) { + // lightMin[2] = 0; + // } + // if (lightMax[2] > 0) { + // lightMax[2] = 0; + // } + lightMin[2] *= -1; + lightMax[2] *= -1; + lightPos[2] *= -1; + // if (lightMin[2] < 0 || lightMax[2] < 0 || lightMin[2] > lightMax[2]) { + // // console.log(tempMin); + // // console.log(tempMax); + // // debugger; + // continue; + // } + // if (lightMin[0] > lightMax[0] || lightMin[1] > lightMax[1]) { + // debugger; + // } + + // Calc h, w based on Z + let totalHeightNear = heightRatio * lightMin[2]; + let totalWidthNear = widthRatio * lightMin[2]; + let totalHeightFar = heightRatio * lightMax[2]; + let totalWidthFar = widthRatio * lightMax[2]; + + //Half height + let halfHeightNear = totalHeightNear / 2.0; + let halfWidthNear = totalWidthNear / 2.0; + let halfHeightFar = totalHeightFar / 2.0; + let halfWidthFar = totalWidthFar / 2.0; + + //Strides + let yStrideNear = totalHeightNear / this._ySlices; + let xStrideNear = totalWidthNear / this._xSlices; + let yStrideFar = totalHeightFar / this._ySlices; + let xStrideFar = totalWidthFar / this._xSlices; + let zStride = (camera.far - camera.near) / this._zSlices; + + // Add h,w / 2 to be relative to [0, h,w] instead of [-h/2,-w/2, h/2,w/2] to avoid negative flooring issues + //Camera space pos after being shifted + let yMinPos = (lightMin[1] + halfHeightNear); + let yMaxPos = (lightMax[1] + halfHeightFar); + let xMinPos = (lightMin[0] + halfWidthNear); + let xMaxPos = (lightMax[0] + halfWidthFar); + + // if (yMinPos < 0 || yMaxPos < 0 || xMinPos < 0 || xMaxPos < 0) { + // debugger; + // } + // if (yStrideNear < 0 || xStrideNear < 0 || yStrideFar < 0 || xStrideFar < 0) { + // debugger; + // } + + let totalWidth = lightPos[2] * widthRatio; + let totalHeight = lightPos[2] * heightRatio; + let yStride = totalHeight / this._ySlices; + let xStride = totalWidth / this._xSlices; + + // x, y bucketing + // clamp out of bounds + let yMax = Math.min(Math.max(Math.floor(yMaxPos / yStrideFar), 0), this._ySlices - 1); + let yMin = Math.min(Math.max(Math.floor(yMinPos / yStrideNear), 0), this._ySlices - 1); + let xMin = Math.min(Math.max(Math.floor(xMinPos / xStrideNear), 0), this._xSlices - 1); + let xMax = Math.min(Math.max(Math.floor(xMaxPos / xStrideFar), 0), this._xSlices - 1); + + // z bucketing, z ranges from camera.near -> camera.far + // Subtract camera.near to get [0, camera.far-camera.near] + //Something is wrong here maybe? Z is never > 0 bc zStride is so big + let zMin = Math.min(Math.max(Math.floor(lightMin[2] / zStride), 0), this._zSlices - 1); + let zMax = Math.min(Math.max(Math.floor(lightMax[2] / zStride), 0), this._zSlices - 1); + //console.log("(%d, %d), (%d, %d), (%d,%d)", xMin, xMax, yMin, yMax, zMin, zMax); + + // Loop through all canidate frustrums + for (let z = zMin; z <= zMax; ++z) { + for (let y = yMin; y <= yMax; ++y) { + for (let x = xMin; x <= xMax; ++x) { + let idx = x + y * this._xSlices + z * this._xSlices * this._ySlices; + let numLights = this._clusterTexture.buffer[this._clusterTexture.bufferIndex(idx, 0)]; + if (numLights > MAX_LIGHTS_PER_CLUSTER) { + continue; + } + ++this._clusterTexture.buffer[this._clusterTexture.bufferIndex(idx, 0)]; + ++numLights; + + let debug = 1; + + if (debug == 0) { + // DEBUG CODE + // NOTE: Flip zs back around to be negative for transformation + let startZ = z * zStride + camera.near; + let frontBL = vec4.fromValues(x * xStrideNear - halfWidthNear, y * yStrideNear - halfHeightNear, -startZ, 1); + let frontBR = vec4.fromValues(frontBL[0] + xStrideNear, frontBL[1], frontBL[2], 1); + let frontTL = vec4.fromValues(frontBL[0], frontBL[1] + yStrideNear, frontBL[2], 1); + let frontTR = vec4.fromValues(frontBL[0] + xStrideNear, frontBL[1] + yStrideNear, frontBL[2], 1); + + let backBL = vec4.fromValues(x * xStrideFar - halfWidthFar, y * yStrideFar - halfHeightFar, -(startZ + zStride), 1); + let backBR = vec4.fromValues(backBL[0] + xStrideFar, backBL[1], backBL[2], 1); + let backTL = vec4.fromValues(backBL[0], backBL[1] + yStrideFar, backBL[2], 1); + let backTR = vec4.fromValues(backBL[0] + xStrideFar, backBL[1] + yStrideFar, backBL[2], 1); + + // Transform back to world space + let tempFrontBL = vec4.create(); + let tempFrontBR = vec4.create(); + let tempFrontTL = vec4.create(); + let tempFrontTR = vec4.create(); + + vec4.transformMat4(tempFrontBL, frontBL, camera.matrixWorld.toArray()); + vec4.transformMat4(tempFrontBR, frontBR, camera.matrixWorld.toArray()); + vec4.transformMat4(tempFrontTL, frontTL, camera.matrixWorld.toArray()); + vec4.transformMat4(tempFrontTR, frontTR, camera.matrixWorld.toArray()); + + vec4.transformMat4(backBL, backBL, camera.matrixWorld.toArray()); + vec4.transformMat4(backBR, backBR, camera.matrixWorld.toArray()); + vec4.transformMat4(backTL, backTL, camera.matrixWorld.toArray()); + vec4.transformMat4(backTR, backTR, camera.matrixWorld.toArray()); + + // Draw Lines + var red = [1.0, 0.0, 0.0]; + var green = [0.0, 1.0, 0.0]; + var blue = [0.0, 0.0, 1.0]; + // Front face + wireframe.addLineSegment([frontBL[0], frontBL[1], frontBL[2]], [frontTL[0], frontTL[1], frontTL[2]], red); + wireframe.addLineSegment([frontBL[0], frontBL[1], frontBL[2]], [frontBR[0], frontBR[1], frontBR[2]], red); + wireframe.addLineSegment([frontBR[0], frontBR[1], frontBR[2]], [frontTR[0], frontTR[1], frontTR[2]], red); + wireframe.addLineSegment([frontTR[0], frontTR[1], frontTR[2]], [frontTL[0], frontTL[1], frontTL[2]], red); + + // Back face + wireframe.addLineSegment([backBL[0], backBL[1], backBL[2]], [backTL[0], backTL[1], backTL[2]], blue); + wireframe.addLineSegment([backBL[0], backBL[1], backBL[2]], [backBR[0], backBR[1], backBR[2]], blue); + wireframe.addLineSegment([backBR[0], backBR[1], backBR[2]], [backTR[0], backTR[1], backTR[2]], blue); + wireframe.addLineSegment([backTR[0], backTR[1], backTR[2]], [backTL[0], backTL[1], backTL[2]], blue); + + //Connecting Lines + wireframe.addLineSegment([backBL[0], backBL[1], backBL[2]], [frontBL[0], frontBL[1], frontBL[2]], green); + wireframe.addLineSegment([backBR[0], backBR[1], backBR[2]], [frontBR[0], frontBR[1], frontBR[2]], green); + wireframe.addLineSegment([backTR[0], backTR[1], backTR[2]], [frontTR[0], frontTR[1], frontTR[2]], green); + wireframe.addLineSegment([backTL[0], backTL[1], backTL[2]], [frontTL[0], frontTL[1], frontTL[2]], green); + } + + // Set light index + this._clusterTexture.buffer[this._clusterTexture.bufferIndex(idx, Math.floor(numLights / 4)) + Math.floor(numLights % 4)] = i; + } + } + } + } + this._clusterTexture.update(); } + } \ No newline at end of file diff --git a/src/renderers/clusteredDeferred.js b/src/renderers/clusteredDeferred.js index f9ae494..d525542 100644 --- a/src/renderers/clusteredDeferred.js +++ b/src/renderers/clusteredDeferred.js @@ -29,7 +29,9 @@ export default class ClusteredDeferredRenderer extends BaseRenderer { numLights: NUM_LIGHTS, numGBuffers: NUM_GBUFFERS, }), { - uniforms: ['u_gbuffers[0]', 'u_gbuffers[1]', 'u_gbuffers[2]', 'u_gbuffers[3]'], + uniforms: ['u_gbuffers[0]', 'u_gbuffers[1]', 'u_gbuffers[2]', 'u_gbuffers[3]', + 'u_lightbuffer', 'u_clusterbuffer', 'u_viewMatrix', 'u_slices', 'u_height', 'u_width', + 'u_near', 'u_far', 'u_clipDist'], attribs: ['a_uv'], }); @@ -98,7 +100,7 @@ export default class ClusteredDeferredRenderer extends BaseRenderer { gl.bindTexture(gl.TEXTURE_2D, null); } - render(camera, scene) { + render(camera, scene, wireframe) { if (canvas.width != this._width || canvas.height != this._height) { this.resize(canvas.width, canvas.height); } @@ -142,7 +144,7 @@ export default class ClusteredDeferredRenderer extends BaseRenderer { this._lightTexture.update(); // Update the clusters for the frame - this.updateClusters(camera, this._viewMatrix, scene); + this.updateClusters(camera, this._viewMatrix, scene, wireframe); // Bind the default null framebuffer which is the screen gl.bindFramebuffer(gl.FRAMEBUFFER, null); @@ -153,8 +155,6 @@ export default class ClusteredDeferredRenderer extends BaseRenderer { // Use this shader program gl.useProgram(this._progShade.glShaderProgram); - // TODO: Bind any other shader inputs - // Bind g-buffers const firstGBufferBinding = 0; // You may have to change this if you use other texture slots for (let i = 0; i < NUM_GBUFFERS; i++) { @@ -163,6 +163,24 @@ export default class ClusteredDeferredRenderer extends BaseRenderer { gl.uniform1i(this._progShade[`u_gbuffers[${i}]`], i + firstGBufferBinding); } + // TODO: Bind any other shader inputs + // Set the light texture as a uniform input to the shader + gl.activeTexture(gl[`TEXTURE${NUM_GBUFFERS}`]); + gl.bindTexture(gl.TEXTURE_2D, this._lightTexture.glTexture); + gl.uniform1i(this._progShade.u_lightbuffer, NUM_GBUFFERS); + + // Set the cluster texture as a uniform input to the shader + gl.activeTexture(gl[`TEXTURE${NUM_GBUFFERS + 1}`]); + gl.bindTexture(gl.TEXTURE_2D, this._clusterTexture.glTexture); + gl.uniform1i(this._progShade.u_clusterbuffer, NUM_GBUFFERS+1); + + gl.uniformMatrix4fv(this._progShade.u_viewMatrix, false, this._viewMatrix); + gl.uniform3f(this._progShade.u_slices, this._xSlices, this._ySlices, this._zSlices); + gl.uniform1f(this._progShade.u_height, canvas.height); + gl.uniform1f(this._progShade.u_width, canvas.width); + gl.uniform1f(this._progShade.u_near, camera.near); + gl.uniform1f(this._progShade.u_far, camera.far); + renderFullscreenQuad(this._progShade); } }; diff --git a/src/renderers/forward.js b/src/renderers/forward.js index ac044f9..cf87bde 100644 --- a/src/renderers/forward.js +++ b/src/renderers/forward.js @@ -24,7 +24,7 @@ export default class ForwardRenderer { this._viewProjectionMatrix = mat4.create(); } - render(camera, scene) { + render(camera, scene, wireframe) { // Update the camera matrices camera.updateMatrixWorld(); mat4.invert(this._viewMatrix, camera.matrixWorld.elements); diff --git a/src/renderers/forwardPlus.js b/src/renderers/forwardPlus.js index a02649c..37a0148 100755 --- a/src/renderers/forwardPlus.js +++ b/src/renderers/forwardPlus.js @@ -16,8 +16,10 @@ export default class ForwardPlusRenderer extends BaseRenderer { this._shaderProgram = loadShaderProgram(vsSource, fsSource({ numLights: NUM_LIGHTS, - }), { - uniforms: ['u_viewProjectionMatrix', 'u_colmap', 'u_normap', 'u_lightbuffer', 'u_clusterbuffer'], + }), + { + uniforms: ['u_viewProjectionMatrix', 'u_colmap', 'u_normap', 'u_lightbuffer', 'u_clusterbuffer', + 'u_viewMatrix', 'u_slices', 'u_height', 'u_width', 'u_near', 'u_far', 'u_clipDist'], attribs: ['a_position', 'a_normal', 'a_uv'], }); @@ -26,7 +28,7 @@ export default class ForwardPlusRenderer extends BaseRenderer { this._viewProjectionMatrix = mat4.create(); } - render(camera, scene) { + render(camera, scene, wireframe) { // Update the camera matrices camera.updateMatrixWorld(); mat4.invert(this._viewMatrix, camera.matrixWorld.elements); @@ -34,7 +36,7 @@ export default class ForwardPlusRenderer extends BaseRenderer { mat4.multiply(this._viewProjectionMatrix, this._projectionMatrix, this._viewMatrix); // Update cluster texture which maps from cluster index to light list - this.updateClusters(camera, this._viewMatrix, scene); + this.updateClusters(camera, this._viewMatrix, scene, wireframe); // Update the buffer used to populate the texture packed with light data for (let i = 0; i < NUM_LIGHTS; ++i) { @@ -76,6 +78,12 @@ export default class ForwardPlusRenderer extends BaseRenderer { gl.uniform1i(this._shaderProgram.u_clusterbuffer, 3); // TODO: Bind any other shader inputs + gl.uniformMatrix4fv(this._shaderProgram.u_viewMatrix, false, this._viewMatrix); + gl.uniform3f(this._shaderProgram.u_slices, this._xSlices, this._ySlices, this._zSlices); + gl.uniform1f(this._shaderProgram.u_height, canvas.height); + gl.uniform1f(this._shaderProgram.u_width, canvas.width); + gl.uniform1f(this._shaderProgram.u_near, camera.near); + gl.uniform1f(this._shaderProgram.u_far, camera.far); // Draw the scene. This function takes the shader program so that the model's textures can be bound to the right inputs scene.draw(this._shaderProgram); diff --git a/src/scene.js b/src/scene.js index 35f6700..862f90d 100644 --- a/src/scene.js +++ b/src/scene.js @@ -8,7 +8,8 @@ export const LIGHT_RADIUS = 5.0; export const LIGHT_DT = -0.03; // TODO: This controls the number of lights -export const NUM_LIGHTS = 100; +export const NUM_LIGHTS = 100 +; class Scene { constructor() { diff --git a/src/shaders/deferred.frag.glsl.js b/src/shaders/deferred.frag.glsl.js index 50f1e75..f16c02a 100644 --- a/src/shaders/deferred.frag.glsl.js +++ b/src/shaders/deferred.frag.glsl.js @@ -4,17 +4,137 @@ export default function(params) { precision highp float; uniform sampler2D u_gbuffers[${params.numGBuffers}]; + uniform sampler2D u_lightbuffer; + uniform sampler2D u_clusterbuffer; + uniform mat4 u_viewMatrix; + uniform vec3 u_slices; //x,y,z slices + uniform float u_height; // Image height, width + uniform float u_width; + uniform float u_near; // Camera near, far + uniform float u_far; + varying vec2 v_uv; - + + struct Light { + vec3 position; + float radius; + vec3 color; + }; + + float ExtractFloat(sampler2D texture, int textureWidth, int textureHeight, int index, int component) { + float u = float(index + 1) / float(textureWidth + 1); + int pixel = component / 4; + float v = float(pixel + 1) / float(textureHeight + 1); + vec4 texel = texture2D(texture, vec2(u, v)); + int pixelComponent = component - pixel * 4; + if (pixelComponent == 0) { + return texel[0]; + } else if (pixelComponent == 1) { + return texel[1]; + } else if (pixelComponent == 2) { + return texel[2]; + } else if (pixelComponent == 3) { + return texel[3]; + } + } + + Light UnpackLight(int index) { + Light light; + float u = float(index + 1) / float(${params.numLights + 1}); + vec4 v1 = texture2D(u_lightbuffer, vec2(u, 0.3)); + vec4 v2 = texture2D(u_lightbuffer, vec2(u, 0.6)); + light.position = v1.xyz; + + // LOOK: This extracts the 4th float (radius) of the (index)th light in the buffer + // Note that this is just an example implementation to extract one float. + // There are more efficient ways if you need adjacent values + light.radius = ExtractFloat(u_lightbuffer, ${params.numLights}, 2, index, 3); + + light.color = v2.rgb; + return light; + } + + // Cubic approximation of gaussian curve so we falloff to exactly 0 at the light radius + float cubicGaussian(float h) { + if (h < 1.0) { + return 0.25 * pow(2.0 - h, 3.0) - pow(1.0 - h, 3.0); + } else if (h < 2.0) { + return 0.25 * pow(2.0 - h, 3.0); + } else { + return 0.0; + } + } + void main() { - // TODO: extract data from g buffers and do lighting - // vec4 gb0 = texture2D(u_gbuffers[0], v_uv); - // vec4 gb1 = texture2D(u_gbuffers[1], v_uv); - // vec4 gb2 = texture2D(u_gbuffers[2], v_uv); - // vec4 gb3 = texture2D(u_gbuffers[3], v_uv); + //Unpack Gbuf + // vec4 buf0 = texture2D(u_gbuffers[0], v_uv); + // vec4 buf1 = texture2D(u_gbuffers[1], v_uv);; + // //Can be unsed if we optimize + // vec4 buf2 = texture2D(u_gbuffers[2], v_uv);; + + // vec3 v_position = buf0.xyz; + // vec3 albedo = vec3(buf0.w, buf1.xy); + // vec3 normal = vec3(buf1.zw, buf2.x); + + vec3 v_position = texture2D(u_gbuffers[0], v_uv).xyz; + vec3 albedo = texture2D(u_gbuffers[1], v_uv).rgb; + vec3 normal = texture2D(u_gbuffers[2], v_uv).xyz; + + vec3 fragColor = vec3(0.0); + + // gl_FragCoord already in camera space + // We can use it directly to bucket x,y if we know image size + int x = int(float(gl_FragCoord.x / u_width) * u_slices.x); + int y = int(float(gl_FragCoord.y / u_height) * u_slices.y); + + // Convert to camera space + vec4 cameraSpacePos = u_viewMatrix * vec4(v_position, 1); + + // Need to convert from [near, far] to [0, far-near] + int z = int((cameraSpacePos.z - u_near) / (u_far - u_near) * u_slices.z); + + //Index into cluster texture to get num lights (0th element) + int clusterWidth = int(u_slices.x * u_slices.y * u_slices.z); + int clusterHeight = int(ceil(float(${params.numLights} + 1) / 4.0)); + int index = x + y * int(u_slices.x) + z * int(u_slices.x) * int(u_slices.y); + int numLights = int(ExtractFloat(u_clusterbuffer, clusterWidth, clusterHeight, index, 0)); + + // Still loop to numLights bc cant have dynamic for loops :( + for (int i = 0; i < ${params.numLights}; ++i) { + if (i > numLights) { + break; + } + // i+1 element since first element is number of lights + int lightIndex = int(ExtractFloat(u_clusterbuffer, clusterWidth, clusterHeight, index, i+1)); + + Light light = UnpackLight(lightIndex); + + float lightDistance = distance(light.position, v_position); + vec3 L = (light.position - v_position) / lightDistance; //Light dir + + float lightIntensity = cubicGaussian(2.0 * lightDistance / light.radius); + float lambertTerm = max(dot(L, normal), 0.0); + + // Blinn Phong from https://en.wikipedia.org/wiki/Blinn%E2%80%93Phong_reflection_model + vec3 specularColor = vec3(1.0, 1.0, 1.0); + float shininess = 16.0; + float specular = 0.0; + if (lambertTerm > 0.0) { + vec3 viewDir = vec3(-cameraSpacePos); + vec3 halfDir = normalize(L + viewDir); + float specAngle = max(dot(halfDir, normal), 0.0); + specular = pow(specAngle, shininess); + } + + fragColor += albedo * lambertTerm * light.color * vec3(lightIntensity) + + specularColor * specular * light.color * vec3(lightIntensity); + } + + const vec3 ambientLight = vec3(0.025); + fragColor += albedo * ambientLight; - gl_FragColor = vec4(v_uv, 0.0, 1.0); + gl_FragColor = vec4(fragColor, 1.0); } `; } \ No newline at end of file diff --git a/src/shaders/deferredToTexture.frag.glsl b/src/shaders/deferredToTexture.frag.glsl index bafc086..725e49f 100644 --- a/src/shaders/deferredToTexture.frag.glsl +++ b/src/shaders/deferredToTexture.frag.glsl @@ -22,8 +22,11 @@ void main() { vec3 col = vec3(texture2D(u_colmap, v_uv)); // TODO: populate your g buffer - // gl_FragData[0] = ?? - // gl_FragData[1] = ?? - // gl_FragData[2] = ?? + // gl_FragData[0] = vec4(v_position, col.r); + // gl_FragData[1] = vec4(col.gb, norm.xy); + // gl_FragData[2] = vec4(norm.z); + gl_FragData[0] = vec4(v_position, 0); + gl_FragData[1] = vec4(col, 0); + gl_FragData[2] = vec4(norm, 0); // gl_FragData[3] = ?? } \ No newline at end of file diff --git a/src/shaders/forwardPlus.frag.glsl.js b/src/shaders/forwardPlus.frag.glsl.js index 022fda7..4b7bfbe 100644 --- a/src/shaders/forwardPlus.frag.glsl.js +++ b/src/shaders/forwardPlus.frag.glsl.js @@ -11,6 +11,13 @@ export default function(params) { // TODO: Read this buffer to determine the lights influencing a cluster uniform sampler2D u_clusterbuffer; + //Additional uniforms to figure out which cluster index + uniform mat4 u_viewMatrix; + uniform vec3 u_slices; //x,y,z slices + uniform float u_height; // Image height, width + uniform float u_width; + uniform float u_near; // Camera near, far + uniform float u_far; varying vec3 v_position; varying vec3 v_normal; @@ -73,7 +80,7 @@ export default function(params) { return 0.0; } } - + void main() { vec3 albedo = texture2D(u_colmap, v_uv).rgb; vec3 normap = texture2D(u_normap, v_uv).xyz; @@ -81,8 +88,33 @@ export default function(params) { vec3 fragColor = vec3(0.0); + // gl_FragCoord already in camera space + // We can use it directly to bucket x,y if we know image size + int x = int(float(gl_FragCoord.x / u_width) * u_slices.x); + int y = int(float(gl_FragCoord.y / u_height) * u_slices.y); + + // Convert to camera space + vec4 cameraSpacePos = u_viewMatrix * vec4(v_position, 1); + + // Need to convert from [near, far] to [0, far-near] + int z = int((cameraSpacePos.z - u_near) / (u_far - u_near) * u_slices.z); + + //Index into cluster texture to get num lights (0th element) + int clusterWidth = int(u_slices.x * u_slices.y * u_slices.z); + int clusterHeight = int(ceil(float(${params.numLights} + 1) / 4.0)); + int index = x + y * int(u_slices.x) + z * int(u_slices.x) * int(u_slices.y); + int numLights = int(ExtractFloat(u_clusterbuffer, clusterWidth, clusterHeight, index, 0)); + + // Still loop to numLights bc cant have dynamic for loops :( for (int i = 0; i < ${params.numLights}; ++i) { - Light light = UnpackLight(i); + if (i > numLights) { + break; + } + // i+1 element since first element is number of lights + int lightIndex = int(ExtractFloat(u_clusterbuffer, clusterWidth, clusterHeight, index, i+1)); + + Light light = UnpackLight(lightIndex); + float lightDistance = distance(light.position, v_position); vec3 L = (light.position - v_position) / lightDistance;