Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules
README.html
64 changes: 51 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,66 @@ WebGL Clustered 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)
* Klayton Wittler
* [LinkedIn](https://www.linkedin.com/in/klayton-wittler/)
* Tested on: **Google Chrome 78.0.3904.97** on
Windows 10 Pro, i7-7700K @ 4.20GHz 16.0GB, GTX 1070 8.192GB (my PC)

### Live Online
![demo](images/disco.gif)

[![](img/thumb.png)](http://TODO.github.io/Project5B-WebGL-Deferred-Shading)
## Sections

### Demo Video/GIF
* [Introduction](#introduction)
* [Live Online](#live-online)
* [Implementation](#implemenatation)
* [Forward plus](#forward-plus)
* [Clustered](#clustered)
* [Blinn-Phong](#blinn-phong)
* [Performance Analaysis](#performance-analysis)
* [Credits](#credits)

[![](img/video.png)](TODO)
## Live Online

### (TODO: Your README)
[![](images/clustered1.png)](http://klaywittler.github.io/Project6-WebGL-Clustered-Deferred-Forward-Plus)
*Note: click the image to go to online demo.*

*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!
## Implementation

This project shows how to efficiently implement a virtual disco party scene. Check out the live demo above.

### Credits
![explanation](images/explanation.png)

### Forward Plus

Foward plus divides the environment into tiles in screen space where the minimum and maximum depth within each tile is determined. Lights are culled by determining if they have any effect on a given tile. After this process, rendering is similar to regular forward rendering.

### Clustered

Deferred clusting works by splitting the process into buffers. Visibility and material is given its own shader and stores its results in a buffer and light and shading is given another shader which subsequently stores its results in another buffer. These buffers can be combined at the end to give the final image. This method is the most computationally efficient in scenes with enormous amounts of lights.

### Blinn-Phong

![blinn-phong](images/blinn-phong.png)

Inorder to capture specular lighting, the [Blinn-Phong](https://en.wikipedia.org/wiki/Blinn%E2%80%93Phong_reflection_model) shading can be used. This requires the calculation of the half vector, H, and view vector, V, to do a dot product with the surface normal in order to figure out how to add on specular components of lighting.

There seems to be some bug in the implementation of forward plus as the specular component creates a weird artifact.

![artifact](images/artifact.gif)

However the implemenation in clustered gives a nice disco feeling

![disco](images/disco.gif)

## Performance Analysis

![performance](images/Performance.png)

Clustered is able to best handle increases in number of lights with hardly and performance decrease as the lights increase. Forward+ sees some performance hits as the number of lights increase but seems to have platued at around 60 FPS. Foward rendering sees an exponential performance decrease as the number of lights increase. At lower numbers the results are inconclusive since the frame rate is capped at 120 FPS.


## Credits

* [Three.js](https://github.com/mrdoob/three.js) by [@mrdoob](https://github.com/mrdoob) and contributors
* [stats.js](https://github.com/mrdoob/stats.js) by [@mrdoob](https://github.com/mrdoob) and contributors
Expand Down
Binary file added images/Performance.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/artifact.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/atrifact.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.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/clustered1.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/disco.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/disco.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/explanation.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/forwardplus.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
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
110 changes: 88 additions & 22 deletions src/renderers/base.js
Original file line number Diff line number Diff line change
@@ -1,30 +1,96 @@
import TextureBuffer from './textureBuffer';
import {Sphere, Box3, Vector3} from 'three';
import { mat4, vec4, vec3, vec2 } from 'gl-matrix';

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
this._clusterTexture = new TextureBuffer(xSlices * ySlices * zSlices, MAX_LIGHTS_PER_CLUSTER + 1);
this._xSlices = xSlices;
this._ySlices = ySlices;
this._zSlices = zSlices;
}

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) {
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;
}
}
constructor(xSlices, ySlices, zSlices) {
// Create a texture to store cluster data. Each cluster stores the number of lights followed by the light indices
this._clusterTexture = new TextureBuffer(xSlices * ySlices * zSlices, MAX_LIGHTS_PER_CLUSTER + 1);
this._xSlices = xSlices;
this._ySlices = ySlices;
this._zSlices = zSlices;
}

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) {
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;
}
}
}

let depthLength = camera.far - camera.near;
let viewHeight = 2.0 * Math.tan(camera.fov*Math.PI/360.0);

let nearHeight = viewHeight * camera.near;
let nearWidth = viewHeight * camera.aspect * camera.near;

let farHeight = viewHeight * camera.far;
let farWidth = viewHeight * camera.aspect * camera.far;

let dz = depthLength / this._zSlices;

for(let light = 0; light < scene.lights.length; light++){
let radius = scene.lights[light].radius;
let position = scene.lights[light].position;
var screenPos = vec4.fromValues(position[0], position[1], position[2], 1.0);
vec4.transformMat4(screenPos, screenPos, viewMatrix);
screenPos[2] *= -1.0;

let lambda = (screenPos[2] - camera.near)/depthLength;

let width = nearWidth * (1-lambda) + farWidth * lambda;
let height = nearHeight * (1-lambda) + farHeight * lambda;
let dx = width / this._xSlices;
let dy = height / this._ySlices;

let xStart = Math.floor((screenPos[0] - radius + width/2) / dx);
let xEnd = Math.floor((screenPos[0] + radius + width/2) / dx);

let yStart = Math.floor((screenPos[1] - radius + height/2) / dy);
let yEnd = Math.floor((screenPos[1] + radius + height/2) / dy);

let zStart = Math.floor((Math.abs(screenPos[2]) - radius - camera.near) / dz);
let zEnd = Math.floor((Math.abs(screenPos[2]) + radius - camera.near) / dz);

xStart = Math.max(0, Math.min(this._xSlices - 1, xStart));
xEnd = Math.max(0, Math.min(this._xSlices - 1, xEnd));

yStart = Math.max(0, Math.min(this._ySlices - 1, yStart));
yEnd = Math.max(0, Math.min(this._ySlices - 1, yEnd));

zStart = Math.max(0, Math.min(this._zSlices - 1, zStart));
zEnd = Math.max(0, Math.min(this._zSlices - 1, zEnd));

for (let z = zStart; z <= zEnd; ++z) {
for (let y = yStart; y <= yEnd; ++y) {
for (let x = xStart; x <= xEnd; ++x) {
let i = x + y * this._xSlices + z * this._xSlices * this._ySlices;
let lightIdx = this._clusterTexture.bufferIndex(i, 0);
let num_lights = this._clusterTexture.buffer[lightIdx] + 1;

if(num_lights <= MAX_LIGHTS_PER_CLUSTER){
this._clusterTexture.buffer[lightIdx] = num_lights;

let t = Math.floor(num_lights/4.0);
let nextIdx = this._clusterTexture.bufferIndex(i, t) + num_lights - t*4;;

this._clusterTexture.buffer[nextIdx] = light;
}
}
}
}

}

this._clusterTexture.update();
}
}
31 changes: 27 additions & 4 deletions src/renderers/clustered.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ import QuadVertSource from '../shaders/quad.vert.glsl';
import fsSource from '../shaders/deferred.frag.glsl.js';
import TextureBuffer from './textureBuffer';
import BaseRenderer from './base';
import { MAX_LIGHTS_PER_CLUSTER } from './base.js'

export const NUM_GBUFFERS = 4;
export const NUM_GBUFFERS = 2;

export default class ClusteredRenderer extends BaseRenderer {
constructor(xSlices, ySlices, zSlices) {
Expand All @@ -28,8 +29,13 @@ export default class ClusteredRenderer extends BaseRenderer {
this._progShade = loadShaderProgram(QuadVertSource, fsSource({
numLights: NUM_LIGHTS,
numGBuffers: NUM_GBUFFERS,
maxLightsPerCluster: MAX_LIGHTS_PER_CLUSTER,
slicesX: xSlices,
slicesY: ySlices,
slicesZ: zSlices,
}), {
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_near', 'u_far', 'u_screenW', 'u_screenH'],
attribs: ['a_uv'],
});

Expand All @@ -41,7 +47,7 @@ export default class ClusteredRenderer extends BaseRenderer {
setupDrawBuffers(width, height) {
this._width = width;
this._height = height;

this._fbo = gl.createFramebuffer();

//Create, bind, and store a depth target texture for the FBO
Expand Down Expand Up @@ -153,7 +159,24 @@ export default class ClusteredRenderer extends BaseRenderer {
// Use this shader program
gl.useProgram(this._progShade.glShaderProgram);

// TODO: Bind any other shader inputs
// Set the light texture as a uniform input to the shader
gl.activeTexture(gl.TEXTURE2);
gl.bindTexture(gl.TEXTURE_2D, this._lightTexture.glTexture);
gl.uniform1i(this._progShade.u_lightbuffer, 2);

// Set the cluster texture as a uniform input to the shader
gl.activeTexture(gl.TEXTURE3);
gl.bindTexture(gl.TEXTURE_2D, this._clusterTexture.glTexture);
gl.uniform1i(this._progShade.u_clusterbuffer, 3);

// TODO: Bind any other shader inputs
gl.uniformMatrix4fv(this._progCopy.u_viewMatrix, false, this._viewMatrix);

gl.uniformMatrix4fv(this._progShade.u_viewMatrix, false, this._viewMatrix);
gl.uniform1f(this._progShade.u_near, camera.near);
gl.uniform1f(this._progShade.u_far, camera.far);
gl.uniform1f(this._progShade.u_screenW, canvas.width);
gl.uniform1f(this._progShade.u_screenH, canvas.height);

// Bind g-buffers
const firstGBufferBinding = 0; // You may have to change this if you use other texture slots
Expand Down
21 changes: 16 additions & 5 deletions src/renderers/forwardPlus.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,24 @@ import vsSource from '../shaders/forwardPlus.vert.glsl';
import fsSource from '../shaders/forwardPlus.frag.glsl.js';
import TextureBuffer from './textureBuffer';
import BaseRenderer from './base';
import { MAX_LIGHTS_PER_CLUSTER } from './base.js'

export default class ForwardPlusRenderer extends BaseRenderer {
constructor(xSlices, ySlices, zSlices) {
super(xSlices, ySlices, zSlices);
constructor(xSlices, ySlices, zSlices) {
super(xSlices, ySlices, zSlices);

// Create a texture to store light data
this._lightTexture = new TextureBuffer(NUM_LIGHTS, 8);

this._shaderProgram = loadShaderProgram(vsSource, fsSource({
numLights: NUM_LIGHTS,
numLights: NUM_LIGHTS,
maxLightsPerCluster: MAX_LIGHTS_PER_CLUSTER,
slicesX: xSlices,
slicesY: ySlices,
slicesZ: zSlices,
}), {
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_near', 'u_far', 'u_screenW', 'u_screenH'],
attribs: ['a_position', 'a_normal', 'a_uv'],
});

Expand Down Expand Up @@ -75,7 +81,12 @@ export default class ForwardPlusRenderer extends BaseRenderer {
gl.bindTexture(gl.TEXTURE_2D, this._clusterTexture.glTexture);
gl.uniform1i(this._shaderProgram.u_clusterbuffer, 3);

// TODO: Bind any other shader inputs
// TODO: Bind any other shader inputs
gl.uniformMatrix4fv(this._shaderProgram.u_viewMatrix, false, this._viewMatrix);
gl.uniform1f(this._shaderProgram.u_near, camera.near);
gl.uniform1f(this._shaderProgram.u_far, camera.far);
gl.uniform1f(this._shaderProgram.u_screenW, canvas.width);
gl.uniform1f(this._shaderProgram.u_screenH, canvas.height);

// 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);
Expand Down
Loading