diff --git a/README.md b/README.md index 94de884..4ff613b 100755 --- a/README.md +++ b/README.md @@ -3,26 +3,22 @@ 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) +* Matt Elser +* Tested on: **Google Chrome 95.0.4638** on 2019 MacBook Pro -### Live Online +[![](img/demo.gif)](https://mattelser.github.io/Project5-WebGL-Forward-Plus-and-Clustered-Deferred/index.html) -[![](img/thumb.png)](http://TODO.github.io/Project5-WebGL-Forward-Plus-and-Clustered-Deferred) +### What's implemented +The cluster frustums are successfully created, as can be seen when turning wireframe on: +![](img/frustum_side.png) +![](img/frustum_lookthrough.png) -### Demo Video/GIF - -[![](img/video.png)](TODO) - -### (TODO: Your README) - -*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 assignment has a considerable amount of performance analysis compared -to implementation work. Complete the implementation early to leave time! +lights are correctly associated to the intersection frustum(s), and stored in the light cluster texture. +### Hypothetical performance comparison +Based on what was shown in class, Forward+ should be significantly faster than forward shading for non-trivial numbers of lights. Forward+ can be made slower by larger light radii (a larger radius means lights will intersect more frustums, counteracting the optimization), but it should still generally be notably faster than forward. +Clustered shading would further optimize forward+, particularly for scenes with significant distances between objects in the foreground and background, by splitting the clusters up along the camera's z-axis. +Both of these algorithms trade a more complicated shading calculation for more efficient scene processing. ### Credits diff --git a/img/demo.gif b/img/demo.gif new file mode 100644 index 0000000..699ce4e Binary files /dev/null and b/img/demo.gif differ diff --git a/img/frustum_lookthrough.png b/img/frustum_lookthrough.png new file mode 100644 index 0000000..db50401 Binary files /dev/null and b/img/frustum_lookthrough.png differ diff --git a/img/frustum_side.png b/img/frustum_side.png new file mode 100644 index 0000000..55ecea2 Binary files /dev/null and b/img/frustum_side.png differ diff --git a/src/main.js b/src/main.js index d688fde..19e4179 100755 --- a/src/main.js +++ b/src/main.js @@ -22,7 +22,7 @@ function setRenderer(renderer) { params._renderer = new ForwardRenderer(); break; case FORWARD_PLUS: - params._renderer = new ForwardPlusRenderer(15, 15, 15); + params._renderer = new ForwardPlusRenderer(3, 3, 3); break; case CLUSTERED: params._renderer = new ClusteredDeferredRenderer(15, 15, 15); @@ -41,15 +41,18 @@ scene.loadGLTF('models/sponza/sponza.gltf'); // 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 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]); +//console.log(LINES.length); +//console.table(LINES); camera.position.set(-10, 8, 0); cameraControls.target.set(0, 2, 0); gl.enable(gl.DEPTH_TEST); +var haveDrawnLines = false; function render() { scene.update(); @@ -59,6 +62,15 @@ function render() { // 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. + var lines = params._renderer.lines; + var segmentColor = [1.0, 0.0, 1.0]; + //console.log(lines); + if (!haveDrawnLines){ + for (let i = 0; i < lines.length; i++){ + wireframe.addLineSegment(lines[i][0], lines[i][1], segmentColor); + } + haveDrawnLines = true; + } gl.disable(gl.DEPTH_TEST); wireframe.render(camera); gl.enable(gl.DEPTH_TEST); diff --git a/src/renderers/base.js b/src/renderers/base.js index 8a975b9..c13c3fa 100755 --- a/src/renderers/base.js +++ b/src/renderers/base.js @@ -1,7 +1,12 @@ +import { mat4 } from 'gl-matrix'; +import { Vector3 , Vector4, Matrix4, LineSegments, Quaternion, Plane, Frustum, Sphere} from 'three'; +import Wireframe from '../wireframe'; import TextureBuffer from './textureBuffer'; export const MAX_LIGHTS_PER_CLUSTER = 100; + + export default class BaseRenderer { constructor(xSlices, ySlices, zSlices) { // Create a texture to store cluster data. Each cluster stores the number of lights followed by the light indices @@ -9,22 +14,137 @@ export default class BaseRenderer { this._xSlices = xSlices; this._ySlices = ySlices; this._zSlices = zSlices; + + this.lines = []; + } + + frustumPointsToWireframe(points){ + // store frustum edges in list of lines to + // be drawn in wireframe + for (let i = 1; i < 8; i++){ + let p1 = [points[i-1].x, points[i-1].y, points[i-1].z] + let p2 = [points[i].x, points[i].y, points[i].z] + this.lines.push([p1, p2]); + } + let p1 = [points[0].x, points[0].y, points[0].z] + let p2 = [points[3].x, points[3].y, points[3].z] + this.lines.push([p1, p2]); + + for (let i = 0; i < 4; i++){ + let p1 = [points[i].x, points[i].y, points[i].z] + let p2 = [points[i+4].x, points[i+4].y, points[i+4].z] + this.lines.push([p1, p2]); + } + //console.log(this.lines); + } + + getFrustumPoints(ivp, nearClipDist, farClipDist, xPos, yPos, zPos, xScale, yScale, zScale){ + + // set up the corners of our frustum + var frustumCornersRaw = [new Vector4(-1.0, 1.0, nearClipDist, 1), + new Vector4( 1.0, 1.0, nearClipDist, 1), + new Vector4( 1.0, -1.0, nearClipDist, 1), + new Vector4(-1.0, -1.0, nearClipDist, 1), + new Vector4(-1.0, 1.0, farClipDist, 1), + new Vector4( 1.0, 1.0, farClipDist, 1), + new Vector4( 1.0, -1.0, farClipDist, 1), + new Vector4(-1.0, -1.0, farClipDist, 1)]; + var frustumCorners = []; + + //let frustumCorners = []; + let transScale = new Matrix4; + transScale.compose(new Vector3(xPos, yPos, zPos), + new Quaternion(), + new Vector3(xScale, yScale, zScale)); - updateClusters(camera, viewMatrix, scene) { + // translate those corners into world space + //console.table(transScale); + for (let i = 0; i < 8; i++){ + frustumCornersRaw[i].applyMatrix4(transScale); + frustumCornersRaw[i].applyMatrix4(ivp); + //frustumCorners[i] = new Vector3(frustumCorners[i]/frustumCorners[i][3]); + //console.log(frustumCorners[i]); + frustumCornersRaw[i].divideScalar(frustumCornersRaw[i].w); + frustumCorners.push(new Vector3(frustumCornersRaw[i].x, + frustumCornersRaw[i].y, + frustumCornersRaw[i].z)); + } + + return frustumCorners; + } + + updateClusters(camera, viewMatrix, inverseViewProjection, scene) { // 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... + var nearClipDist = 0.0001; + var farClipDist = 1; + + var ivp = new Matrix4; + ivp.fromArray(inverseViewProjection); + //ivp.set(inverseViewProjection); + + //var foo = mat4.fromValues(1, 1, 1,1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4); + //console.table(ivp); + + this.lines.length = 0; + + //console.log(this._zSlices); + let xScale = 1.0 / this._xSlices; + let yScale = 1.0 / this._ySlices; + let zScale = 1.0 / this._zSlices; + for (let z = 0; z < this._zSlices; ++z) { + let zPos = (z * zScale); for (let y = 0; y < this._ySlices; ++y) { + let yPos = (2 * y * yScale) - (1 - yScale); for (let x = 0; x < this._xSlices; ++x) { + let xPos =(2 * x * xScale) - (1 - xScale); + //console.log("x: " + xPos + " y: " + yPos + " z: " + zPos); + let i = x + y * this._xSlices + z * this._xSlices * this._ySlices; // Reset the light count to 0 for every cluster this._clusterTexture.buffer[this._clusterTexture.bufferIndex(i, 0)] = 0; + + // build a subfrustum for this x,y,z combination + let fp = this.getFrustumPoints(ivp, nearClipDist, farClipDist, xPos, yPos, zPos, xScale, yScale, zScale); + // store the frustum edges in our wireframe buffer + this.frustumPointsToWireframe(fp); + + // build planes for three.js frustum + let p0 = new Plane; + let p1 = new Plane; + let p2 = new Plane; + let p3 = new Plane; + let p4 = new Plane; + let p5 = new Plane; + p0.setFromCoplanarPoints(fp[0], fp[1], fp[2]); + p1.setFromCoplanarPoints(fp[4], fp[0], fp[3]); + p2.setFromCoplanarPoints(fp[4], fp[5], fp[1]); + p3.setFromCoplanarPoints(fp[5], fp[6], fp[2]); + p4.setFromCoplanarPoints(fp[6], fp[2], fp[3]); + p5.setFromCoplanarPoints(fp[5], fp[4], fp[7]); + + let f = new Frustum(p0, p1, p2, p3, p4, p5); + + let li = 0; + let numLightsThisCell = 0; + scene.lights.forEach(l => { + // be sure to check each light, since they can intersect multiple frustums + let p = new Vector3(l.position[0], l.position[1], l.position[2]); + let s = new Sphere(p, l.radius); + if(f.intersectsSphere(s)){ + // append the light to this frustum's cluster texture cell + this._clusterTexture.buffer[this._clusterTexture.bufferIndex(i, 0) + numLightsThisCell] = li; + numLightsThisCell++; + } + li++; + }); + } } } - this._clusterTexture.update(); } } \ No newline at end of file diff --git a/src/renderers/forwardPlus.js b/src/renderers/forwardPlus.js index a02649c..4acfd03 100755 --- a/src/renderers/forwardPlus.js +++ b/src/renderers/forwardPlus.js @@ -24,6 +24,7 @@ export default class ForwardPlusRenderer extends BaseRenderer { this._projectionMatrix = mat4.create(); this._viewMatrix = mat4.create(); this._viewProjectionMatrix = mat4.create(); + this._inverseViewProjection = mat4.create(); } render(camera, scene) { @@ -32,9 +33,11 @@ export default class ForwardPlusRenderer extends BaseRenderer { mat4.invert(this._viewMatrix, camera.matrixWorld.elements); mat4.copy(this._projectionMatrix, camera.projectionMatrix.elements); mat4.multiply(this._viewProjectionMatrix, this._projectionMatrix, this._viewMatrix); + mat4.invert(this._inverseViewProjection, this._viewProjectionMatrix); + //console.table(this._viewMatrix); // Update cluster texture which maps from cluster index to light list - this.updateClusters(camera, this._viewMatrix, scene); + this.updateClusters(camera, this._viewMatrix, this._inverseViewProjection, scene); // Update the buffer used to populate the texture packed with light data for (let i = 0; i < NUM_LIGHTS; ++i) {