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: Yash Vardhan #23

Open
wants to merge 20 commits into
base: master
Choose a base branch
from
Open
46 changes: 33 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,28 +1,48 @@
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)
* Yash Vardhan
* Tested on: Windows 10 Pro, Intel i5-4200U (4) @ 2.600GHz 4GB, NVIDIA GeForce 840M 2048MB

### Live Online
In this project, I have implemented Clustered Forward+ shading and Clustered Deferred shading and added Blinn-Phong shading effect.

[![](img/thumb.png)](http://TODO.github.io/Project5B-WebGL-Deferred-Shading)
# Live Online
[![](images/ss.jpg)](https://yashv28.github.io/Project5-WebGL-Clustered-Deferred-Forward-Plus/)

### Demo Video/GIF
# Demo GIF
![](images/y.gif)

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

### (TODO: Your README)
### Effects:
* Deferred Blinn-Phong shading (diffuse + specular) for point lights.

*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.
### Optimizations:
* Optimized g-buffer format - reduces the number and size of g-buffers:
- Used 2-component normals
- Reduced number of properties passed via g-buffer by reconstructing world space position using camera matrices and X/Y/depth

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

All analysis done on 1366x768 with default cluster size of 15x15x15.

![](images/table.jpg)

![](images/graph.jpg)

### Bloopers

Different views to check correctness:

| Albedo | Normal |
| ----------- | ----------- |
| ![](images/albedo.jpg) | ![](images/normal.jpg) |

| Depth | Z-Slices |
| ----------- | ----------- |
| ![](images/depth.jpg) | ![](images/z.jpg) |

### Credits

Expand Down
1 change: 1 addition & 0 deletions _config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
theme: jekyll-theme-slate
2 changes: 2 additions & 0 deletions build/bundle.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions build/bundle.js.map

Large diffs are not rendered by default.

Binary file added images/albedo.jpg
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/depth.jpg
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/graph.jpg
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/normal.jpg
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/ss.jpg
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/table.jpg
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/y.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/z.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
129 changes: 123 additions & 6 deletions src/renderers/clustered.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { mat4, vec4, vec3 } 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 = 500;

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

updateClusters(camera, viewMatrix, scene) {
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;
}
}
}

var pos = vec4.create();

var ta = Math.tan(camera.fov * 0.5 * (Math.PI / 180.0));
var tb = camera.aspect * ta;

var stepy = (2.0 * ta) / parseFloat(this._ySlices);
var stepx = (2.0 * tb) / parseFloat(this._xSlices);

for(let li = 0; li < NUM_LIGHTS; li++)
{
pos[0] = scene.lights[li].position[0];
pos[1] = scene.lights[li].position[1];
pos[2] = scene.lights[li].position[2];
pos[3] = 1.0;
vec4.transformMat4(pos, pos, viewMatrix);

pos[2] *= -1.0;
let r = scene.lights[li].radius;
let lx; let ly; let lz;
let ux; let uy; let uz;

for(lx = 0; lx <= this._xSlices; lx++)
{
let w = calx(pos, stepx * (lx + 1 - this._xSlices * 0.5));
if(w <= r)
{
break;
}
}
for(ux = this._xSlices; ux >= lx; ux--)
{
let w = calx(pos, stepx * (ux - 1 - this._xSlices * 0.5))
if(-w <= r)
{
ux--;
break;
}
}

for(ly = 0; ly <= this._ySlices; ly++)
{
let h = caly(pos, stepy * (ly + 1 - this._ySlices * 0.5));
if(h <= r)
{
break;
}
}
for(uy = this._ySlices; uy >= ly; uy--)
{
let h = caly(pos, stepy * (uy - 1 - this._ySlices * 0.5));
if(-h <= r)
{
uy--;
break;
}
}

for(lz = 0; lz <= this._zSlices; lz++)
{
let ow = calz(lz + 1, this._zSlices, camera);
if(ow > (pos[2] - r))
{
break;
}
}
for(uz = this._zSlices; uz >= lz; uz--)
{
let ow = calz(uz - 1, this._zSlices, camera);
if(ow <= (pos[2] + r))
{
uz += 2;
break;
}
}

for(let x = lx; x <= ux; x++) {
for(let y = ly; y <= uy; y++) {
for(let z = lz; z <= uz; z++) {
let i = x + y * this._xSlices + z * this._xSlices * this._ySlices;
let idx = this._clusterTexture.bufferIndex(i, 0);
let c = this._clusterTexture.buffer[idx] + 1;
if (c < MAX_LIGHTS_PER_CLUSTER)
{
this._clusterTexture.buffer[idx] = c;
let ft = Math.floor(c / 4.0);
let tidx = this._clusterTexture.bufferIndex(i, ft);
this._clusterTexture.buffer[c + tidx - (ft * 4.0)] = li;
}
}
}
}
}
this._clusterTexture.update();
}
}
}


function calx(pos, width)
{
var x = pos[0];
var z = pos[2];
return (x - width * z) / Math.sqrt(width * width + 1.0);
}

function caly(pos, height)
{
var y = pos[1];
var z = pos[2];
return (y - height * z) / Math.sqrt(height * height + 1.0);
}

function calz(z, slices, camera)
{
if (z <= 1) {
return camera.near;
}
else
{
var n = (parseFloat(z) - 1.0) / (parseFloat(slices) - 1.0);
return Math.exp(n * Math.log(camera.far - camera.near + 1.0)) + camera.near - 1.0;
}
}
38 changes: 31 additions & 7 deletions src/renderers/clusteredDeferred.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 ClusteredRenderer from './clustered';
import { MAX_LIGHTS_PER_CLUSTER } from './clustered';

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

export default class ClusteredDeferredRenderer extends ClusteredRenderer {
constructor(xSlices, ySlices, zSlices) {
Expand All @@ -21,21 +22,27 @@ 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_xSlices: xSlices,
num_ySlices: ySlices,
num_zSlices: zSlices,
num_maxLightsPerCluster: MAX_LIGHTS_PER_CLUSTER,
}), {
uniforms: ['u_gbuffers[0]', 'u_gbuffers[1]', 'u_gbuffers[2]', 'u_gbuffers[3]'],
uniforms: ['u_viewProjectionMatrix', 'u_viewMatrix', 'u_invProjectionMatrix', 'u_invViewProjectionMatrix', 'u_depthBuffer', 'u_gbuffers[0]', 'u_gbuffers[1]', 'u_lightbuffer', 'u_clusterbuffer', 'u_screenbuffer'],
attribs: ['a_uv'],
});

this._projectionMatrix = mat4.create();
this._viewMatrix = mat4.create();
this._viewProjectionMatrix = mat4.create();
this._invProjectionMatrix = mat4.create();
this._invViewProjectionMatrix = mat4.create();
}

setupDrawBuffers(width, height) {
Expand All @@ -44,7 +51,6 @@ export default class ClusteredDeferredRenderer extends ClusteredRenderer {

this._fbo = gl.createFramebuffer();

//Create, bind, and store a depth target texture for the FBO
this._depthTex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, this._depthTex);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
Expand Down Expand Up @@ -81,7 +87,6 @@ export default class ClusteredDeferredRenderer extends ClusteredRenderer {
// Tell the WEBGL_draw_buffers extension which FBO attachments are
// being used. (This extension allows for multiple render targets.)
WEBGL_draw_buffers.drawBuffersWEBGL(attachments);

gl.bindFramebuffer(gl.FRAMEBUFFER, null);
}

Expand All @@ -108,6 +113,8 @@ export default class ClusteredDeferredRenderer extends ClusteredRenderer {
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._invProjectionMatrix, this._projectionMatrix);
mat4.invert(this._invViewProjectionMatrix, this._viewProjectionMatrix);

// Render to the whole screen
gl.viewport(0, 0, canvas.width, canvas.height);
Expand All @@ -123,6 +130,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 Down Expand Up @@ -154,9 +162,25 @@ export default class ClusteredDeferredRenderer extends ClusteredRenderer {
gl.useProgram(this._progShade.glShaderProgram);

// TODO: Bind any other shader inputs
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, this._lightTexture.glTexture);
gl.uniform1i(this._progShade.u_lightbuffer, 0);

gl.activeTexture(gl.TEXTURE1);
gl.bindTexture(gl.TEXTURE_2D, this._clusterTexture.glTexture);
gl.uniform1i(this._progShade.u_clusterbuffer, 1);

// Bind g-buffers
const firstGBufferBinding = 0; // You may have to change this if you use other texture slots
gl.activeTexture(gl.TEXTURE2);
gl.bindTexture(gl.TEXTURE_2D, this._depthTex);
gl.uniform1i(this._progShade.u_depthBuffer, 2);

gl.uniformMatrix4fv(this._progShade.u_viewProjectionMatrix, false, this._viewProjectionMatrix);
gl.uniformMatrix4fv(this._progShade.u_viewMatrix, false, this._viewMatrix);
gl.uniformMatrix4fv(this._progShade.u_invProjectionMatrix, false, this._invProjectionMatrix);
gl.uniformMatrix4fv(this._progShade.u_invViewProjectionMatrix, false, this._invViewProjectionMatrix);
gl.uniform4f(this._progShade.u_screenbuffer, canvas.width, canvas.height, camera.near, camera.far);

const firstGBufferBinding = 3; // You may have to change this if you use other texture slots
for (let i = 0; i < NUM_GBUFFERS; i++) {
gl.activeTexture(gl[`TEXTURE${i + firstGBufferBinding}`]);
gl.bindTexture(gl.TEXTURE_2D, this._gbuffers[i]);
Expand Down
17 changes: 15 additions & 2 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 @@ -16,14 +17,20 @@ export default class ClusteredForwardPlusRenderer extends ClusteredRenderer {

this._shaderProgram = loadShaderProgram(vsSource, fsSource({
numLights: NUM_LIGHTS,
num_xSlices: xSlices,
num_ySlices: ySlices,
num_zSlices: zSlices,
num_maxLightsPerCluster: MAX_LIGHTS_PER_CLUSTER,
}), {
uniforms: ['u_viewProjectionMatrix', 'u_colmap', 'u_normap', 'u_lightbuffer', 'u_clusterbuffer'],
uniforms: ['u_viewProjectionMatrix', 'u_invProjectionMatrix', 'u_invViewMatrix', 'u_colmap', 'u_normap', 'u_lightbuffer', 'u_clusterbuffer', 'u_screenbuffer'],
attribs: ['a_position', 'a_normal', 'a_uv'],
});

this._projectionMatrix = mat4.create();
this._viewMatrix = mat4.create();
this._viewProjectionMatrix = mat4.create();
this._invprojectionMatrix = mat4.create();
this._invViewMatrix = mat4.create();
}

render(camera, scene) {
Expand All @@ -32,10 +39,12 @@ export default class ClusteredForwardPlusRenderer extends ClusteredRenderer {
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._invprojectionMatrix, this._projectionMatrix);
mat4.copy(this._invViewMatrix, camera.matrixWorld.elements);

// Update cluster texture which maps from cluster index to light list
this.updateClusters(camera, this._viewMatrix, scene);

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

// TODO: Bind any other shader inputs
gl.uniformMatrix4fv(this._shaderProgram.u_invProjectionMatrix, false, this._invprojectionMatrix);
gl.uniformMatrix4fv(this._shaderProgram.u_invViewMatrix, false, this._invViewMatrix);
gl.uniform4f(this._shaderProgram.u_screenbuffer, canvas.width, canvas.height, camera.near, 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);
}
};
Loading