Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Project 5: Rishabh Shah #25

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
58 changes: 47 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,61 @@ WebGL Clustered Deferred and Forward+ 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)
* Rishabh Shah
* Tested on: **Version 62.0.3202.75 (Official Build) (64-bit)** on
Windows 10, i7-6700HQ @ 2.6GHz 16GB, GTX 960M 4096MB (Laptop)

### Live Online

[![](img/thumb.png)](http://TODO.github.io/Project5B-WebGL-Deferred-Shading)
[![](images/Capture.png)](https://rms13.github.io/Project5-WebGL-Clustered-Deferred-Forward-Plus/)

### Demo Video/GIF
### Demo GIF

[![](img/video.png)](TODO)
![](images/video2.gif)

### (TODO: Your README)
### Overview
In this project, I worked on implementing Clustered Forward+ and Clustered Deferred renderers. Clustered Forward+ renderer works in a similar way as a Forward rendered, but with one optimization. Here, we divide the view frustum in slices in three axes and bin the lights into the clusters. So in the fragment, only the lights in the cluster of the fragment need to be iterated through. Clustered Deferred takes this one step further by changing the way the scene is rendered. A Deferred shader postpones shading until the end. So we do per-pixel shading and not per-fragment. When shading is the heaviest stage, this is highly efficient than forward methods.

*DO NOT* leave the README to the last minute! It is a crucial part of the
project, and we will not be able to grade you without a good README.
#### Features
* Clustered Forward+
* Clustered Deferred
* Blinn-Phong shading (diffuse + specular) for point lights (in Clustered Deferred)
* Simple Toon shading (in Clustered Forward+)
* Optimizations
* Pack values together into vec4s
* Use 2-component normals

### Performance Analysis

#### Forward vs. Forward+ vs Deferred

![](images/chart.png)

As expected, Deferred shading is the fastest followed by Forward+ followed by Forward. The tests were performed using 100 lights in the sponza model.

#### 3-Buffers vs 2-Buffers

![](images/chart1.png)

Deferred shading requires passing all the data between shaders using g-buffers. For this implementation, I tried reducing the number of g-buffers by packing position, color and normals in 2 vec4s. This can be done by storing the x and y components of the normal in the 4th position of the 2 g-buffers, and computing the z in the fragment shader. This is known as screen-space normals. But the method gives low performance advantage, and causes artefacts as seen below. This happens because the sign of recomputed Z is not retained. So the deployed version does not contain that code. But it can be found in the files in comments.

**Screen-space normal artefacts**

![](images/2cn_artefacts.gif)


#### Lambertian Shading

![](images/diffuse.gif)

#### Blinn-Phong Shading

![](images/blinn_phong.gif)

#### Toon Shading

![](images/toon.gif)

This assignment has a considerable amount of performance analysis compared
to implementation work. Complete the implementation early to leave time!


### Credits
Expand Down
Binary file added images/2cn_artefacts.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/Capture.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/blinn_phong.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/chart.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/chart1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/diffuse.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/toon.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/video2.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions src/init.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// TODO: Change this to enable / disable debug mode
export const DEBUG = true && process.env.NODE_ENV === 'development';
export const DEBUG = false && process.env.NODE_ENV === 'development';

import DAT from 'dat-gui';
import WebGLDebug from 'webgl-debug';
Expand Down Expand Up @@ -60,7 +60,7 @@ stats.domElement.style.top = '0px';
document.body.appendChild(stats.domElement);

// Initialize camera
export const camera = new PerspectiveCamera(75, canvas.clientWidth / canvas.clientHeight, 0.1, 1000);
export const camera = new PerspectiveCamera(75, canvas.clientWidth / canvas.clientHeight, 0.1, 50);

// Initialize camera controls
export const cameraControls = new OrbitControls(camera, canvas);
Expand Down
2 changes: 1 addition & 1 deletion src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const CLUSTERED_FORWARD_PLUS = 'Clustered Forward+';
const CLUSTERED_DEFFERED = 'Clustered Deferred';

const params = {
renderer: CLUSTERED_FORWARD_PLUS,
renderer: CLUSTERED_DEFFERED,
_renderer: null,
};

Expand Down
258 changes: 255 additions & 3 deletions src/renderers/clustered.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,165 @@ export default class ClusteredRenderer {
this._zSlices = zSlices;
}

createPlane(v0, v1, v2) {
let norm = vec3.create();
norm = vec3.cross(norm, v1 - v0, v2 - v0);
norm = vec3.normalize(norm, norm);
return norm;
}

distanceFromPlane(n, p0, v0) {
return vec3.dot(n, p0 - v0) / vec3.length(n);
}

// function for computing adjacent and opposite side lengths for a RIGHT triangle
computeComponents(dist) {
// adj = 1; opp = d;
let hyp = Math.sqrt(1 / (1 + dist*dist));
return [hyp, dist*hyp]; // [cos,sin] // explaination in notes... todo: put an image in readme..
}

updateClustersOptimized(camera, viewMatrix, scene) {
//console.log("hi");
for (let z = 0; z < this._zSlices; ++z) {
for (let y = 0; y < this._ySlices; ++y) {
for (let x = 0; x < this._xSlices; ++x) {
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;
}
}
}

// LOOP OVER THE LIGHTS
// FIND THE NDC COORDS
// DIRECTLY GET THE X AND Y - USE VIEWPROJ MATRIX ??
// GET Z BASED ON THE DEPTH VALUE OR FROM AN ARRAY FOR EXPONENTIAL - USE VIEW MATRIX ONLY ??
// LOOP AROUND AND EXPAND THE RANGE BASED ON THE RADIUS

let rad = Math.PI / 180;
let halfY = Math.tan((camera.fov / 2) * rad);
let halfX = camera.aspect * halfY;

let stepY = 2 * halfY / this._ySlices;
let stepX = 2 * halfX / this._xSlices;
let stepZ = (camera.far-camera.near) / this._zSlices; // has nothing to do with FOV...


let lRad, lPos, lViewPos = vec4.create();

// RUN THREE SEPERATE LOOPS FOR X,Y,Z INSTEAD OF NESTED...
for(let l=0; l<MAX_LIGHTS_PER_CLUSTER; l++) {

lRad = scene.lights[l].radius;
lPos = vec4.fromValues(scene.lights[l].position[0],
scene.lights[l].position[1],
scene.lights[l].position[2], 1.0);
lViewPos = vec4.transformMat4(lViewPos, lPos, viewMatrix);
lViewPos[2] = -lViewPos[2]; // flip z because it is inverted..

let xmin = this._xSlices;
let ymin = this._ySlices;
let zmin = this._zSlices;
let xmax = this._xSlices;
let ymax = this._ySlices;
let zmax = this._zSlices;

// Z
for(let i = 0; i < this._zSlices; i++) {
if (camera.near + i * stepZ > lViewPos[2] - lRad) { // search starts at NCP not origin...
zmin = i-1;
break;
}
}
if(zmin >= this._zSlices) {
continue;
}

for(let i = zmin + 1; i < this._zSlices; i++) {
if (camera.near + i * stepZ > lViewPos[2] + lRad) {
zmax = i;
break;
}
}
if(zmax < 0) {
continue;
}

// Y
for(let i = 0; i < this._ySlices; i++) {
let nor = this.computeComponents(i * stepY - halfY);
if (vec3.dot(lViewPos, vec3.fromValues(0, nor[0], -nor[1])) < lRad) {
ymin = i-1;
break;
}
}
if(ymin >= this._ySlices) {
continue;
}

// X
for(let i = ymin + 1; i < this._ySlices; i++) {
let nor = this.computeComponents(i * stepY - halfY);
if (vec3.dot(lViewPos, vec3.fromValues(0, nor[0], -nor[1])) > lRad) {
ymax = i+1;
break;
}
}
if(ymax < 0) {
continue;
}

for(let i = 0; i < this._xSlices; i++) {
let nor = this.computeComponents(i * stepX - halfX);
if (vec3.dot(lViewPos, vec3.fromValues(nor[0], 0, -nor[1])) < lRad) {
xmin = i-1;
break;
}
}
if(xmin >= this._xSlices) {
continue;
}

for(let i = xmin + 1; i < this._xSlices; i++) {
let nor = this.computeComponents(i * stepX - halfX);
if (vec3.dot(lViewPos, vec3.fromValues(nor[0], 0, -nor[1])) > lRad) {
xmax = i+1;
break;
}
}
if(xmax < 0) {
continue;
}

xmin = Math.max(0, xmin);
ymin = Math.max(0, ymin);
zmin = Math.max(0, zmin);
xmax = Math.min(this._xSlices, xmax);
ymax = Math.min(this._ySlices, ymax);
zmax = Math.min(this._zSlices, zmax);

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) {
this._clusterTexture.buffer[this._clusterTexture.bufferIndex(idx, 0)]--;
break;
}
let texIdx = Math.floor(numLights / 4);
let offset = numLights - texIdx * 4.0;
this._clusterTexture.buffer[this._clusterTexture.bufferIndex(idx, texIdx) + offset] = l;
}
}
}
}

this._clusterTexture.update();
}

updateClusters(camera, viewMatrix, 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...

for (let z = 0; z < this._zSlices; ++z) {
for (let y = 0; y < this._ySlices; ++y) {
Expand All @@ -27,6 +183,102 @@ export default class ClusteredRenderer {
}
}

//let expZ = [0.1, 5.0, 6.8, 9.2, 12.6, 17.1, 23.2, 31.5, 42.9, 58.3, 79.2, 108, 146, 199, 271, 368, 500];
let h = canvas.height;
let w = canvas.width;

let v0, norm_1, norm_2, norm_3, norm_4; // variables representing planes..
let v1_1, v1_2, v1_3, v1_4, v2_1, v2_2, v2_3, v2_4, yScaled, xScaled; // helper vars..
v0 = camera.position;

let zScale = 1000/this._zSlices;
for (let z = 0; z < this._zSlices; ++z) {
let z1 = z * zScale; //expZ[z];
let z2 = z1 + zScale; //expZ[z + 1];
for (let y = 0; y < this._ySlices; ++y) {
// LOWER PLANE
if (y === 0) {
yScaled = 0;
v1_1 = vec3.fromValues(0, yScaled, 1000);
v2_1 = vec3.fromValues(10, yScaled, 1000);
norm_1 = this.createPlane(v0, v1_1, v2_1);
}
else {
norm_1 = norm_2; // use from last iteration..
}

// UPPER PLANE
yScaled += h/this._ySlices;
v1_2 = vec3.fromValues(0, yScaled, 1000);
v2_2 = vec3.fromValues(10, yScaled, 1000);
norm_2 = this.createPlane(v0, v1_2, v2_2);

for (let x = 0; x < this._xSlices; ++x) {
let i = x + y * this._xSlices + z * this._xSlices * this._ySlices;

// LEFT PLANE
if (x === 0) {
xScaled = 0;
v1_3 = vec3.fromValues(xScaled, 0, 1000);
v2_3 = vec3.fromValues(xScaled, 10, 1000);
norm_3 = this.createPlane(v0, v1_3, v2_3);
}
else {
norm_3 = norm_4;
}

// RIGHT PLANE
xScaled += w/this._xSlices;
v1_4 = vec3.fromValues(xScaled, 0, 1000);
v2_4 = vec3.fromValues(xScaled, 10, 1000);
norm_4 = this.createPlane(v0, v1_4, v2_4);

// create 2 X planes
// loop and assign lights
for(let l=0; l<MAX_LIGHTS_PER_CLUSTER; l++) {

let p0 = vec4.fromValues(scene.lights[l].position[0], scene.lights[l].position[1], scene.lights[l].position[2], 1.0);
vec4.transformMat4(p0, p0, viewMatrix);

// Z planes check
if (p0[2] + scene.LIGHT_RADIUS < z1 || p0[2] - scene.LIGHT_RADIUS > z2) {
continue;
}

// LOWER PLANE
let dist = this.distanceFromPlane(norm_1, p0, v0);
if (dist > scene.LIGHT_RADIUS) {
continue;
}

// UPPER PLANE
dist = this.distanceFromPlane(norm_2, p0, v0);
if (dist > scene.LIGHT_RADIUS) {
continue;
}

// lEFT PLANE
dist = this.distanceFromPlane(norm_3, p0, v0);
if (dist > scene.LIGHT_RADIUS) {
continue;
}

// RIGHT PLANE
dist = this.distanceFromPlane(norm_4, p0, v0);
if (dist > scene.LIGHT_RADIUS) {
continue;
}

let numLights = ++this._clusterTexture.buffer[this._clusterTexture.bufferIndex(i, 0)];
let texIdx = Math.floor(numLights / 4.0);
let offset = numLights - texIdx * 4.0;
this._clusterTexture.buffer[this._clusterTexture.bufferIndex(i, texIdx) + offset] = l;
}
}
}
}

this._clusterTexture.update();
}
}

}
Loading