Skip to content

Project 6: Srinath Rajagopalan #25

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

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
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
.DS_Store
51 changes: 38 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,27 +1,52 @@
WebGL Clustered and Forward+ Shading
======================

**University of Pennsylvania, CIS 565: GPU Programming and Architecture, Project 5**
**University of Pennsylvania, CIS 565: GPU Programming and Architecture, Project 6**

* (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)
* Srinath Rajagopalan
* Tested on: Google Chrome (v78), Macbook Pro, Intel-Core-i5 8th Gen, Intel Iris Graphics

### Live Online
<p align='center'>
<img src="data/demo.gif" width=500>
</p>

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

### Demo Video/GIF
### WebGL Shading

[![](img/video.png)](TODO)
In this project, we use the web browser using WebGL to do real-time rendering. We explore three rendering techniques and analyze their performance. At at 1000-ft view, for forward rendering, we loop through all the light sources, and for each of them we compute the interaction with all the geometries present in the scene and determine projection of a 3D vertex onto to a 2D canvas and figure out how to color it. This is accompolished by the graphics pipeline consisting of a vertex shader followed by the fragment shader (where most of the heavy-lifiting happens).

### (TODO: Your README)
## Forward Plus Rendering

*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.
The naive approach described above searches through ALL light source - geometry pair. But we can do better. We tile the 3D scene into discrete cells. For each light source we compute the tiles which will be affected by the light. Since not all lights will affect all the objects, we can reduce our search space significantly. This is similar to the uniform-grid cell construction we employed in the Boids-Flocking project. However, not all the tiles are of equal volume. We can imaging a frustum extending from camera plane a max-reach depth sliced into `(xSlice, ySLice, zSlice)` tiles.

This assignment has a considerable amount of performance analysis compared
to implementation work. Complete the implementation early to leave time!
<p align='center'>
<img src="data/move1.gif" width=500>
</p>

## Deferred Rendering
In forward we compute the geometry intersection each time we loop through a light source. This can be expensive so it is better to pre-compute the intersections and store it in a geometry buffer. This way the logic to calculate lighting is decoupled from the complexity of the scene. After this, the logic for deferred rendering is pretty much the same as forward rendering.

<p align='center'>
<img src="data/move2.gif" width=500>
</p>


## Blinn–Phong Illumination
To improve lighting, we implement lighting claculations as described in [Blinn-Phong Reflection Model](https://en.wikipedia.org/wiki/Blinn%E2%80%93Phong_reflection_model)

We can observed the effects of the illumination below,

Without Blinn-Phong | With Blinn-Phong |
:-------------------------:|:-------------------------:
![](data/without.png)| ![](data/with.png)

## Performance Analysis

<p align='center'>
<img src="data/performance.png" width=500>
</p>

Unsurprisingly, forward+ is faster than forward because we don't loop over all the lights for each vertex. Deferred rendering is faster because pre-computing the geometry intersections avoids them being calculated multiple times in the shader.


### Credits
Expand Down
Binary file added data/demo.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 data/move1.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 data/move2.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 data/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 data/with.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 data/without.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
118 changes: 99 additions & 19 deletions src/renderers/base.js
Original file line number Diff line number Diff line change
@@ -1,30 +1,110 @@
import TextureBuffer from './textureBuffer';
import { NUM_LIGHTS } from '../scene';
import { mat4, vec4, vec3 } 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;
// 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.nearWidth = 0.0;
this.nearHeight = 0.0;
this.farWidth = 0.0;
this.farHeight = 0.0;
}

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

this._clusterTexture.update();
// 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 nearHeight = 2 * camera.near * Math.tan(camera.fov * 0.5 * Math.PI/180.0);
var nearWidth = camera.aspect * nearHeight;

this.nearHeight = nearHeight;
this.nearWidth = nearWidth;

var farHeight = 2 * camera.far * Math.tan(camera.fov * 0.5 * Math.PI/180.0);
var farWidth = camera.aspect * farHeight;

this.farWidth = farWidth;
this.farHeight = farHeight;

var depth = camera.far - camera.near;

for(let lightId = 0; lightId < NUM_LIGHTS; lightId++) {

var lightPos = vec4.fromValues(
scene.lights[lightId].position[0],
scene.lights[lightId].position[1],
scene.lights[lightId].position[2],
1.0,
);

var radius = scene.lights[lightId].radius;

vec4.transformMat4(lightPos, lightPos, viewMatrix);
lightPos[2] = -1.0 * lightPos[2];

var depthAtPos = lightPos[2];
var lambda = (Math.abs(depthAtPos) - camera.near)/(1.0*camera.far - 1.0*camera.near);
var widthAtLightPos = nearWidth + (farWidth-nearWidth)*lambda;
var heightAtLightPos = nearHeight + (farHeight-nearHeight)*lambda;

var xstep = widthAtLightPos/(1.0*this._xSlices);
var ystep = heightAtLightPos/(1.0*this._ySlices);
var zstep = (camera.far-camera.near)/(1.0*this._zSlices);

var xMin = parseInt(Math.floor((lightPos[0] + 0.5*widthAtLightPos - radius) / xstep));
var xMax = parseInt(Math.floor((lightPos[0] + 0.5*widthAtLightPos + radius) / xstep));
xMin = Math.min(Math.max(xMin, 0), this._xSlices-1);
xMax = Math.min(Math.max(xMax, 0), this._xSlices-1);


var yMin = parseInt(Math.floor((lightPos[1] + 0.5*heightAtLightPos - radius) / ystep));
var yMax = parseInt(Math.floor((lightPos[1] + 0.5*heightAtLightPos + radius) / ystep));;
yMin = Math.min(Math.max(yMin, 0), this._ySlices-1);
yMax = Math.min(Math.max(yMax, 0), this._ySlices-1);


var zMin = parseInt(Math.floor((lightPos[2] - radius - camera.near)/zstep));
var zMax = parseInt(Math.floor((lightPos[2] + radius - camera.near)/zstep));
zMin = Math.min(Math.max(zMin, 0), this._zSlices-1);
zMax = Math.min(Math.max(zMax, 0), this._zSlices-1);

//console.log(zMin, zMax, xMin, xMax);
for (let z = zMin; z <= zMax; ++z) {
for (let y = yMin; y <= yMax; ++y) {
for (let x = xMin; x <= xMax; ++x) {
let clusterIdx = x + y *this._xSlices + z *this._xSlices*this._ySlices;

let numLights = this._clusterTexture.buffer[this._clusterTexture.bufferIndex(clusterIdx, 0)];
if(numLights < MAX_LIGHTS_PER_CLUSTER) {
numLights = numLights + 1;
this._clusterTexture.buffer[this._clusterTexture.bufferIndex(clusterIdx, 0)] = numLights;
let row = Math.floor(numLights/4.0);
let col = numLights % 4.0;
this._clusterTexture.buffer[this._clusterTexture.bufferIndex(clusterIdx, row)+col] = lightId;
}
}
}
}
}

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

export const NUM_GBUFFERS = 4;

Expand All @@ -28,10 +28,20 @@ export default class ClusteredRenderer extends BaseRenderer {
this._progShade = loadShaderProgram(QuadVertSource, fsSource({
numLights: NUM_LIGHTS,
numGBuffers: NUM_GBUFFERS,
}), {
uniforms: ['u_gbuffers[0]', 'u_gbuffers[1]', 'u_gbuffers[2]', 'u_gbuffers[3]'],
attribs: ['a_uv'],
});
textureWidth: this._clusterTexture._elementCount,
textureHeight: this._clusterTexture._pixelsPerElement
}), {
uniforms: ['u_gbuffers[0]', 'u_gbuffers[1]', 'u_gbuffers[2]', 'u_gbuffers[3]',
'u_lightbuffer',
'u_clusterbuffer',
'u_cameraPos',
'u_nearWidth', 'u_nearHeight',
'u_farWidth', 'u_farHeight',
'u_near', 'u_far',
'u_xSlices', 'u_ySlices', 'u_zSlices',
'u_viewMatrix'],
attribs: ['a_uv'],
});

this._projectionMatrix = mat4.create();
this._viewMatrix = mat4.create();
Expand Down Expand Up @@ -153,10 +163,32 @@ export default class ClusteredRenderer extends BaseRenderer {
// Use this shader program
gl.useProgram(this._progShade.glShaderProgram);

// 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.uniform1f(this._progShade.u_nearWidth, this.nearWidth);
gl.uniform1f(this._progShade.u_nearHeight, this.nearHeight);
gl.uniform1f(this._progShade.u_farWidth, this.farWidth);
gl.uniform1f(this._progShade.u_farHeight, this.farHeight);
gl.uniform1f(this._progShade.u_near, camera.near);
gl.uniform1f(this._progShade.u_far, camera.far);
gl.uniform1i(this._progShade.u_xSlices, this._xSlices);
gl.uniform1i(this._progShade.u_ySlices, this._ySlices);
gl.uniform1i(this._progShade.u_zSlices, this._zSlices);
gl.uniformMatrix4fv(this._progShade.u_viewMatrix, false, this._viewMatrix);
var cameraPos = vec3.fromValues(camera.position.x,camera.position.y,camera.position.z);
gl.uniform3fv(this._progShade.u_cameraPos, cameraPos);

// Bind g-buffers
const firstGBufferBinding = 0; // You may have to change this if you use other texture slots
const firstGBufferBinding = NUM_GBUFFERS; // 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
Loading