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
39 changes: 27 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,40 @@ 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)
* Fengkai Wu
* Tested on: **Google Chrome 61.0.3163.100 (64bit)** on
Windows 10, i7-4700HQ @ 2.40GHz 4GB, GT 745M 2048MB (Personal)

### Live Online
### Demo GIF

[![](img/thumb.png)](http://TODO.github.io/Project5B-WebGL-Deferred-Shading)
[![](https://github.com/wufk/Project5-WebGL-Clustered-Deferred-Forward-Plus/blob/master/img/mygif.gif)]()

### Demo Video/GIF
The gif shows when clustered foward+ mode is switched to the clustered deferred mode.

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

### (TODO: Your README)
#### Clustered Forward+
* Divides the space in clusters (or small blocks) and only render when lights overlapped on a cluster.
* Keeps track of the indices of lights based on a data structure to enable fast look up.

*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 Deferred
* Using clusteres to keep track of lights. (Same as above)
* Stores attributes in g-buffers and renders on the last pass.
* Uses three g buffers to store the attributes.
* Adds Bling-Phong shading model. The effect is shown in the above gif when the reflectivity of lights are changed.

This assignment has a considerable amount of performance analysis compared
to implementation work. Complete the implementation early to leave time!
This figure shows the data g-buffers are storing, which corresponds to albedo, normal and position.
![g-buffers](https://github.com/wufk/Project5-WebGL-Clustered-Deferred-Forward-Plus/blob/master/img/gbuffers.png)

Below shows the performance using different rendering strategies.

![](https://github.com/wufk/Project5-WebGL-Clustered-Deferred-Forward-Plus/blob/master/img/numLights.PNG)

As the graph shows, the rendering time varies significantly with different strategies. Naive forward is the simplest way, which iterates all the lights and does computation on all of them, which is very slow, and the running time increasing very fast when we have more lights to render.

Clustered forward+ is a good technique, which only computes the lights are overlapped in clusteres, which greately reduces the amount of work. When the number of lights is relatively small. the speed is considerably reduced.

Clustered deferred stores the vertex attributes in g-buffers and only do the computation at the last pass. This technique is much more faster than the previous two especially when number of lights is large.


### Credits
Expand Down
Binary file added img/albedo.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 img/gbuffers.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 img/mygif.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 img/normal.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 img/numLights.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 img/position.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 img/snapshot.PNG
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 @@ -43,4 +43,4 @@ function render() {
params._renderer.render(camera, scene);
}

makeRenderLoop(render)();
makeRenderLoop(render)();
74 changes: 70 additions & 4 deletions src/renderers/clustered.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { mat4, vec4, vec3 } from 'gl-matrix';
import { mat4, vec4, vec3, vec2 } from 'gl-matrix';
import { NUM_LIGHTS } from '../scene';
import TextureBuffer from './textureBuffer';

export const MAX_LIGHTS_PER_CLUSTER = 100;
export const MAX_LIGHTS_PER_CLUSTER = 1000;

export default class ClusteredRenderer {
constructor(xSlices, ySlices, zSlices) {
Expand All @@ -13,7 +13,7 @@ export default class ClusteredRenderer {
this._zSlices = zSlices;
}

updateClusters(camera, viewMatrix, scene) {
updateClusters(camera, viewMatrix, projectionMatrix, 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 +27,72 @@ export default class ClusteredRenderer {
}
}

const radian = Math.PI / 180.0;
const y_len = Math.tan(camera.fov * 0.5 * radian);
const x_len = y_len * camera.aspect;
const z_step = (camera.far - camera.near) / this._zSlices;

for(let i = 0; i < NUM_LIGHTS; i++) {
let radius = scene.lights[i].radius;
let light_pos = vec4.fromValues(scene.lights[i].position[0],
scene.lights[i].position[1],
scene.lights[i].position[2], 1.0);
vec4.transformMat4(light_pos, light_pos, viewMatrix);
light_pos[2] *= -1.0;

let light_x_len = y_len*light_pos[2];
let light_y_len = x_len*light_pos[2];
let y_step = 2 * light_x_len / this._ySlices;
let x_step = 2 * light_y_len / this._xSlices;
let lightPosNewY = light_x_len + light_pos[1];
let lightPosNewX = light_y_len + light_pos[0];


let lightStartY = lightPosNewY - radius;
let lightStopY = lightPosNewY + radius;
let lightStartX = lightPosNewX - radius;
let lightStopX = lightPosNewX + radius;

let y_lo = Math.floor(lightStartY / y_step);
let y_hi = Math.floor(lightStopY / y_step);
let x_lo = Math.floor(lightStartX / x_step);
let x_hi = Math.floor(lightStopX / x_step);

if(y_lo > this._ySlices-1 || y_hi < 0) continue;
if(x_lo > this._xSlices-1 || x_hi < 0) { continue; }
y_lo = Math.max(0, y_lo);
x_lo = Math.max(0, x_lo);
y_hi = Math.min(this._ySlices, y_hi);
x_hi = Math.min(this._xSlices, x_hi);

let lightPosNewZ = light_pos[2] - camera.near;
let lightStartZ = lightPosNewZ - radius;
let lightStopZ = lightPosNewZ + radius;
let z_lo = Math.floor(lightStartZ / z_step);
let z_hi = Math.floor(lightStopZ / z_step)+1;
if(z_lo > this._zSlices-1 || z_hi < 0) { continue; }
z_lo = Math.max(0, z_lo);
z_hi = Math.min(this._zSlices, z_hi);

for(let x = x_lo; x <= x_hi; ++x) {
for(let y = y_lo; y < y_hi; ++y) {
for(let z = z_lo; z < z_hi; ++z) {
let cluster_idx = x + y*this._xSlices + z*this._xSlices*this._ySlices;
let light_num_idx = this._clusterTexture.bufferIndex(cluster_idx, 0);
let light_nums = 1 + this._clusterTexture.buffer[light_num_idx];

if(light_nums < MAX_LIGHTS_PER_CLUSTER) {
this._clusterTexture.buffer[light_num_idx] = light_nums;
let light_texel = Math.floor(light_nums*0.25);
let texel_idx = this._clusterTexture.bufferIndex(cluster_idx, light_texel);
let comp_idx = light_nums - light_texel*4;
this._clusterTexture.buffer[texel_idx+comp_idx] = i;
}
}
}
}
}

this._clusterTexture.update();
}
}
}
40 changes: 33 additions & 7 deletions src/renderers/clusteredDeferred.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { gl, WEBGL_draw_buffers, canvas } from '../init';
import { mat4, vec4 } from 'gl-matrix';
import { mat4, vec4, vec3 } from 'gl-matrix';
import { loadShaderProgram, renderFullscreenQuad } from '../utils';
import { NUM_LIGHTS } from '../scene';
import toTextureVert from '../shaders/deferredToTexture.vert.glsl';
Expand All @@ -8,8 +8,9 @@ import QuadVertSource from '../shaders/quad.vert.glsl';
import fsSource from '../shaders/deferred.frag.glsl.js';
import TextureBuffer from './textureBuffer';
import ClusteredRenderer from './clustered';
import {MAX_LIGHTS_PER_CLUSTER} from './clustered';

export const NUM_GBUFFERS = 4;
export const NUM_GBUFFERS = 3;

export default class ClusteredDeferredRenderer extends ClusteredRenderer {
constructor(xSlices, ySlices, zSlices) {
Expand All @@ -21,15 +22,23 @@ export default class ClusteredDeferredRenderer extends ClusteredRenderer {
this._lightTexture = new TextureBuffer(NUM_LIGHTS, 8);

this._progCopy = loadShaderProgram(toTextureVert, toTextureFrag, {
uniforms: ['u_viewProjectionMatrix', 'u_colmap', 'u_normap'],
uniforms: ['u_viewProjectionMatrix', 'u_viewMatrix', 'u_colmap', 'u_normap'],
attribs: ['a_position', 'a_normal', 'a_uv'],
});

this._progShade = loadShaderProgram(QuadVertSource, fsSource({
numLights: NUM_LIGHTS,
numGBuffers: NUM_GBUFFERS,
num_lights: NUM_LIGHTS,
num_gbuffers: NUM_GBUFFERS,
xSlices: xSlices,
ySlices: ySlices,
zSlices: zSlices,
maxLightsPerCluster: MAX_LIGHTS_PER_CLUSTER,
}), {
uniforms: ['u_gbuffers[0]', 'u_gbuffers[1]', 'u_gbuffers[2]', 'u_gbuffers[3]'],
uniforms: [
'u_gbuffers[0]', 'u_gbuffers[1]', 'u_gbuffers[2]',
'u_viewMatrix', 'u_invViewMatrix',
'u_clusterbuffer', 'u_lightbuffer',
'u_width', 'u_height','u_near','u_far'],
attribs: ['a_uv'],
});

Expand Down Expand Up @@ -123,6 +132,7 @@ export default class ClusteredDeferredRenderer extends ClusteredRenderer {

// Upload the camera matrix
gl.uniformMatrix4fv(this._progCopy.u_viewProjectionMatrix, false, this._viewProjectionMatrix);
gl.uniformMatrix4fv(this._progCopy.u_viewMatrix, false, this._viewMatrix);

// 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._progCopy);
Expand All @@ -142,7 +152,7 @@ export default class ClusteredDeferredRenderer extends ClusteredRenderer {
this._lightTexture.update();

// Update the clusters for the frame
this.updateClusters(camera, this._viewMatrix, scene);
this.updateClusters(camera, this._viewMatrix, this._projectionMatrix, scene);

// Bind the default null framebuffer which is the screen
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
Expand All @@ -154,6 +164,22 @@ export default class ClusteredDeferredRenderer extends ClusteredRenderer {
gl.useProgram(this._progShade.glShaderProgram);

// TODO: Bind any other shader inputs
gl.uniformMatrix4fv(this._progShade.u_viewMatrix, false, this._viewMatrix);
gl.uniformMatrix4fv(this._progShade.u_invViewMatrix, false, camera.matrixWorld.elements);

gl.activeTexture(gl.TEXTURE3);
gl.bindTexture(gl.TEXTURE_2D, this._clusterTexture.glTexture);
gl.uniform1i(this._progShade.u_clusterbuffer, 3);

gl.activeTexture(gl.TEXTURE5);
gl.bindTexture(gl.TEXTURE_2D, this._lightTexture.glTexture);
gl.uniform1i(this._progShade.u_lightbuffer, 5);

gl.uniform1f(this._progShade.u_width, canvas.width);
gl.uniform1f(this._progShade.u_height, canvas.height);

gl.uniform1f(this._progShade.u_near, camera.near);
gl.uniform1f(this._progShade.u_far, camera.far);

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

export default class ClusteredForwardPlusRenderer extends ClusteredRenderer {
constructor(xSlices, ySlices, zSlices) {
Expand All @@ -14,13 +15,24 @@ export default class ClusteredForwardPlusRenderer extends ClusteredRenderer {
// Create a texture to store light data
this._lightTexture = new TextureBuffer(NUM_LIGHTS, 8);

//NOTE: add more params and unifs here:
this._shaderProgram = loadShaderProgram(vsSource, fsSource({
numLights: NUM_LIGHTS,
}), {
uniforms: ['u_viewProjectionMatrix', 'u_colmap', 'u_normap', 'u_lightbuffer', 'u_clusterbuffer'],
num_lights: NUM_LIGHTS,
xSlices: xSlices,
ySlices: ySlices,
zSlices: zSlices,
maxLightsPerCluster: MAX_LIGHTS_PER_CLUSTER,
}), {
uniforms: [
'u_viewProjectionMatrix',
'u_viewMatrix',
'u_colmap', 'u_normap',
'u_lightbuffer', 'u_clusterbuffer',
'u_width', 'u_height', 'u_near', 'u_far'],
attribs: ['a_position', 'a_normal', 'a_uv'],
});


this._projectionMatrix = mat4.create();
this._viewMatrix = mat4.create();
this._viewProjectionMatrix = mat4.create();
Expand All @@ -34,7 +46,7 @@ export default class ClusteredForwardPlusRenderer extends ClusteredRenderer {
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, this._projectionMatrix, scene);

// Update the buffer used to populate the texture packed with light data
for (let i = 0; i < NUM_LIGHTS; ++i) {
Expand Down Expand Up @@ -76,8 +88,13 @@ export default class ClusteredForwardPlusRenderer extends ClusteredRenderer {
gl.uniform1i(this._shaderProgram.u_clusterbuffer, 3);

// TODO: Bind any other shader inputs
gl.uniformMatrix4fv(this._shaderProgram.u_viewMatrix, false, this._viewMatrix);
gl.uniform1f(this._shaderProgram.u_width, canvas.width);
gl.uniform1f(this._shaderProgram.u_height, canvas.height);
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);
}
};
};
2 changes: 1 addition & 1 deletion src/scene.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ 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 = 500;

class Scene {
constructor() {
Expand Down
Loading