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: Jonathan Lee #19

Open
wants to merge 15 commits into
base: master
Choose a base branch
from
63 changes: 51 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,58 @@ 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)
* Jonathan Lee
* Tested on: **Google Chrome 61.0.3163.100** on
Windows 10, Xeon ES-1630 @ 3.70GHz 32GB, GTX 1070 24GB (SigLab)

### Live Online
# Overview

[![](img/thumb.png)](http://TODO.github.io/Project5B-WebGL-Deferred-Shading)
In this project, I was able to explore **Clustered Forward+ and Deferred Shading**.

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

[![](img/video.png)](TODO)
[![](images/shading.gif)](http://agentlee.github.io/Project5-WebGL-Clustered-Deferred-Forward-Plus)

### (TODO: Your README)
# Clustering
Both of these implementations of Forward+ and Deferred Shading were done using clustering. All of the operations are computed in camera space and the camera frustum gets split into clusters.

*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.
# Clustered Forward+

This assignment has a considerable amount of performance analysis compared
to implementation work. Complete the implementation early to leave time!
![](images/forward+.gif)

Forward+ is an extension of Forward Shading except we use the light's z position determines how to find the cluster it's in. Once we find the cluster that the light's in, the shader only computes for the lights that lie within the cluster.

# Clustered Deferred

![](images/deferred.gif)

Deferred Shading utilizes the same logic of dividing the camera frustum based on the light's position. The shading, however, requires two passes. Fragments get written the the g-buffer and store position, color, and normal values.

### Blinn Shading

| 100 Shininess | 200 Shininess | 500 Shininess |
|---------------------------|----------------------------|--------------------------|
| ![](images/100shine.gif) | ![](images/200shine.gif) | ![](images/500shine.gif) |

| Without Blinn | With Blinn |
|---------------------------|----------------------------------|
| ![](images/500lights.gif) | ![](images/500lightsblinn.gif) |

# Analysis

## Forward vs. Forward+ vs. Deferred Shading

![](images/shadingchart.png)
![](images/shadingfps.PNG)

Clearly, Deferred Shading performs much better than Forward+ and Forward Shading. The divide between the clustered shaders and Foward begins rapidly around 200 lights. Deferred becomes nearly impossible to see at around 15000 lights.

## Normal Compression in Deferred Shading

![](images/deferredchart.png)
![](images/deferredfps.PNG)

To do compression, I stored `normal.x` and `normal.y` at the `w` position of the `vec4` that also stores position and color. Rather than reading in the value from a third g-buffer that only stores the normal, `normal.z` can be calculated in the shader. Surpsingly, I found a huge dropoff between uncompressed and compressed normals after 500 lights.

### Credits

Expand All @@ -31,3 +63,10 @@ to implementation work. Complete the implementation early to leave time!
* [webgl-debug](https://github.com/KhronosGroup/WebGLDeveloperTools) by Khronos Group Inc.
* [glMatrix](https://github.com/toji/gl-matrix) by [@toji](https://github.com/toji) and contributors
* [minimal-gltf-loader](https://github.com/shrekshao/minimal-gltf-loader) by [@shrekshao](https://github.com/shrekshao)

### References
* [Blinn Shading](https://en.wikipedia.org/wiki/Blinn%E2%80%93Phong_shading_model)
* [Various Camera Stuff](http://www.txutxi.com/)
* [OpenGL-Tutorail](http://www.opengl-tutorial.org)
* [Foward+ vs. Deferred](https://www.3dgep.com/forward-plus_)
* CIS560 and CIS565 Slides
Binary file added images/100shine.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/200shine.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/500lights.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/500lightsblinn.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/500shine.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/blinn.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/deferred.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/deferredchart.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/deferredfps.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/forward+.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/shading.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/shadingFPS.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/shadingchart.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 src/.DS_Store
Binary file not shown.
15 changes: 15 additions & 0 deletions src/aabb.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { mat4, vec4, vec3 } from 'gl-matrix';

export default class AABB
{
constructor(pos, radius)
{
this.min = vec3.fromValues( pos[0] - radius,
pos[1] - radius,
pos[2] - radius);

this.max = vec3.fromValues( pos[0] + radius,
pos[1] + radius,
pos[2] + radius);
}
}
2 changes: 1 addition & 1 deletion 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
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
178 changes: 178 additions & 0 deletions src/renderers/clustered.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,46 @@ import TextureBuffer from './textureBuffer';

export const MAX_LIGHTS_PER_CLUSTER = 100;

const DEBUG_SHADER = false;

function clamp(num, min, max)
{
let flooredNum = Math.floor(num);

if(flooredNum < min) {
return min;
}
else if(flooredNum > max) {
return max;
}
else {
return flooredNum;
}
}

function printClusterBounds(minX, maxX, minY, maxY, minZ, maxZ)
{
console.log("xMin: " + minX);
console.log("xMax: " + maxX);
// if(minX > maxX) {
// console.log("FUHX");
// }

console.log("yMin: " + minY);
console.log("yMax: " + maxY);
// if(minY > maxY) {
// console.log("FUHX");
// }

console.log("zMin: " + minZ);
console.log("zMax: " + maxZ);
// if(minZ > maxZ) {
// console.log("FUHX");
// }

console.log("--------------");
}

export default class ClusteredRenderer {
constructor(xSlices, ySlices, zSlices) {
// Create a texture to store cluster data. Each cluster stores the number of lights followed by the light indices
Expand All @@ -13,6 +53,24 @@ export default class ClusteredRenderer {
this._zSlices = zSlices;
}

// Check to see if the cluster is within the camera
lightInCluster(minX, maxX, minY, maxY, minZ, maxZ)
{
if(minX < 0 && maxX < 0 || minX > this.xSlices && maxX > this.xSlices) {
return false;
}

if(minY < 0 && maxY < 0 || minY > this.ySlices && maxY > this.ySlices) {
return false;
}

if(minZ < 0 && maxZ < 0 || minZ > this.zSlices && maxZ > this.zSlices) {
return false;
}

return true;
}

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...
Expand All @@ -27,6 +85,126 @@ export default class ClusteredRenderer {
}
}

// Convert fov from degrees to radians
var fov = camera.fov * (Math.PI / 180.0);
var screenHeight = 2.0 * Math.tan(fov / 2);
var screenWidth = camera.aspect * screenHeight;

// Z logistics
var near = camera.near;
var far = camera.far;
var depth = (far - near);
var zStride = depth / this._zSlices;

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.0);
let lightRad = light.radius;

// Take the lightPos from world to camera space
vec4.transformMat4(lightPos, lightPos, viewMatrix);
lightPos[2] *= -1;

// Get height and width based on the light's position
let height = screenHeight * lightPos[2];
let width = screenWidth * lightPos[2];

// Strides
let yStride = height / this._ySlices;
let xStride = width / this._xSlices;

// Min and Max bounds for the cluster
let minX, maxX;
let minY, maxY;
let minZ, maxZ;

if(DEBUG_SHADER) {
minX = 0;
maxX = 14;

minY = 0;
maxY = 14;

minZ = 0;
maxZ = 14;
}
else {
let lightMin = vec3.fromValues( lightPos[0] - lightRad,
lightPos[1] - lightRad,
lightPos[2] - lightRad);
let lightMax = vec3.fromValues( lightPos[0] + lightRad,
lightPos[1] + lightRad,
lightPos[2] + lightRad);

minX = Math.floor((lightMin[0] + (width / 2)) / xStride);
maxX = Math.floor((lightMax[0] + (width / 2)) / xStride);

minY = Math.floor((lightMin[1] + (height / 2)) / yStride);
maxY = Math.floor((lightMax[1] + (height / 2)) / yStride);

minZ = Math.floor(lightMin[2] / zStride);
maxZ = Math.floor(lightMax[2] / zStride);

// Offset the min/max values by the slices since the clusters will be indexed [0, slices - 1]
minX = clamp(minX, 0, this._xSlices - 1);
maxX = clamp(maxX, 0, this._xSlices - 1);

minY = clamp(minY, 0, this._ySlices - 1);
maxY = clamp(maxY, 0, this._ySlices - 1);

minZ = clamp(minZ, 0, this._zSlices - 1);
maxZ = clamp(maxZ, 0, this._zSlices - 1);

// Check if the light is outside the cluster frustum
if(!this.lightInCluster(minX, maxX, minY, maxY, minZ, maxZ)) {
continue;
}
}

// Update the buffer
for(let z = minZ; z <= maxZ; z++) {
for(let y = minY; y <= maxY; y++) {
for(let x = minX; x <= maxX; x++) {
// ------------------cluster0-----------------cluster1---------------cluster2---------------(u)-
// component0 |count|lid0|lid1|lid2 || count|lid0|lid1|lid2 || count|lid0|lid1|lid2
// component1 |lid3 |lid4|lid5|lid6 || lid3 |lid4|lid5|lid6 || lid3 |lid4|lid5|lid6
// component2 |lid7 |lid8|lid9|lid10|| lid7 |lid8|lid9|lid10|| lid7 |lid8|lid9|lid10
// (v)

// Cluster Index (u)
// ------------------cluster0-----------------cluster1---------------cluster2---------------(u)-
let clusterIndex = x + (y * this._xSlices) + (z * this._xSlices * this._ySlices);

// Index of where the count lies in each cluster
let lightCountIndex = this._clusterTexture.bufferIndex(clusterIndex, 0);
// Get the number of lights
let lightCount = this._clusterTexture.buffer[lightCountIndex];
// Increment the number of lights
lightCount++;

// Safety check to make sure we don't exceed the amount of lights allowed
if(lightCount <= MAX_LIGHTS_PER_CLUSTER) {
// Update the light count
this._clusterTexture.buffer[lightCountIndex] = lightCount;

// Find the component (v)
// component0
let component = Math.floor((lightCount) / 4);

// Get the light id in the cluster (lid0, lid1, etc.)
// |count|lid0|lid1|lid2
let lightClusterID = (lightCount) % 4;

// Update the pixel to include the current light
let texelIndex = this._clusterTexture.bufferIndex(clusterIndex, component);
let componentIndex = (lightCount) - (component * 4);
this._clusterTexture.buffer[texelIndex + componentIndex] = i;
}
}
}
}
}

this._clusterTexture.update();
}
}
Loading