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
117 changes: 104 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,28 +1,119 @@
WebGL Clustered and Forward+ Shading
======================

**University of Pennsylvania, CIS 565: GPU Programming and Architecture, Project 5**
## [Live Online](https://lanlou123.github.io/WebGL-Clustered-Deferred-Forward-Plus-Rendering/)

* (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)
note : I only wrote the shading selection dropdown menu for clustered rendering

### Live Online
## Click this gif for video link

[![](img/thumb.png)](http://TODO.github.io/Project5B-WebGL-Deferred-Shading)
[![click this gif for video link](img/cluster.gif)](https://www.youtube.com/watch?v=FbcmS2m7nNE)

### Demo Video/GIF
due to gif capture sofware's limitation, the above gif which represent blinn-phong shading appear to have some ramp effects from toon.

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

### (TODO: Your README)
blinn-phong|lambert|toon
----|----|----
![](img/cluster.gif)|![](img/lambert.gif)|![](img/toon.gif)

*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.
# Introduction:

This assignment has a considerable amount of performance analysis compared
to implementation work. Complete the implementation early to leave time!
### forward rendering
forward shading is the most straight forward shading method, basically, it first loops through each geomery in the scene, then, inside one sigle loop for the geometry, it will do another loop of all the lights in the scene to apply light influence on the current geometry, it is esay to tell that once the number of lights is really huge, this method can suffer from terrible performance issue, which makes this rendering technique impossible to be applied to current day games as most of them possess large amount use of lighting.

### forward plus(clustered) rendering
a better solution towards the issue brought by forward rendering is to use clustered structure for lighting, in forward plus, we will have the simmilar first step:loop all the geometry, but different second step, this time, we will catergorize lights into different clusters, and inside the fragment shader for each geometry loop, we will not check every light anymore, instead, we only accumulate light influence from lights that inside the cluster the geometry point is in. as a result, we will have a big improvement on performance, especially when there are a lot of lights.

### clustered deferred rendering
we can further improve the performance by introducing an extra buffer:g-buffer which contains certain amounts of 2d textures(specified by user according to need), inside the textures, we will only store the geometry displacement "on the top" or in other words have the smaller depth value compared with other fragments at same NDC coordinate, apart from that, we can also store normal, depth, albedo, as long as it is required for our rendering.

a graph for typical deferred rendering.

![](img/deferred-v2.png)

according to the image above, the reason this method is better is that the lighting loop will only check the 2d texture instead of checking every geometry, and the texture only store the toppest geometry, which ensures that we won't check the lighting inluence on those geometry "behind the walls" since we are not seeing it.

### cluster structure

![](img/clu.png)

an example for the cluster structure would look like the above image, althogh it is simply drawed as a 2d frustum, it actually should be a 3d one, and this one can be taken as a simplified version of the 3d one looking along y axis direction, so, the orange sphere represents the light influence area, those clusters colored as red and orange are the ones we will consider storing into the buffer of this specific light, we will store the clusters in the form of cluster start index in x,y,z directions and end index respectively.


# Project Features:

### Forward+
- Build a data structure to keep track of how many lights are in each cluster and what their indices are
- Render the scene using only the lights that overlap a given cluster


### Clustered
- Reuse clustering logic from Forward+
- Store vertex attributes in g-buffer
- Read g-buffer in a shader to produce final output

### Effects
- Implemented deferred Blinn-Phong shading (diffuse + specular) for point lights
- for the blinn-phong effect, I simply used the half angle method and an extra inverse viewmatrix to compute the view point
position.
- Implemented toon shading (with ramp shading + simple depth-edge detection for outlines)
- toon is simple, just do an interpolation between the ramped value of lambert term and the original lambert term, the same goes for blinn-phong term ~

![](img/blinn.JPG)

blinn-phong shading, you can notice the reflection on the floor and pillar quite clearly.

![](img/toon.JPG)

toon shading, the color is ramped based on the distance to the light center.


### Optimizations
- Optimized g-buffer format - reduce the number and size of g-buffers:
- Pack values together into vec4s
- I stored position and normal data in vec4s
- Use 2-component normals
- since the length of a normal will always be 1, we don't need all three of them, I choosed to pack the normal's x and y component into the former 2 vec4s, one thing to notice is I have to multiply viewmatrix before packing , and use inverse viemat in shader do unpacking...




# Performance & Analysis

### Three methods comparision:

![](img/comp1.JPG)

this test is done with light radius being 4, cluster size = 15X15X15, and light number gradually increasing
as the diagram explicitly shown, when the light count increases, pure forward suffers from drastic performance drop while forward+ and clustered deferred shows better score, actually, the last one: deferred is really efficient , it's performance didn't change much even if there are up to 4k lights.

### Cluster size influence:
![](img/clus.JPG)

this test is done with 3500 light sources, light radius 4, renderer is clustered deferred.
from this image, we know that cluster size of 15X15X15 strikes the best performance, I guess this can only be aquired by fine tunning the renderer.

### compressed g buffer vs not compressed:

(the axis label should be compressed and uncompressed in the bottom of the image, just ignore it)

![](img/pa.JPG)
this test is done with 1000 light sources, renderer is clustered deferred
this apparently shows the benefit of packing 3 floats g-buffer into 2 floats g-buffer, as we reduced the number of g buffers, therefore reduced the times we read and write to g buffers.

### debug images:

xslice view|yslice view
----|-----
![](img/xslice.JPG)|![](img/yslice.JPG)

depth buffer| albedo buffer| surface normal buffer
---|---|---
![](img/depth.JPG)|![](img/albedo.JPG)|![](img/surfacenor.JPG)

pure light buffer
![](img/purecol.JPG)

### Credits

Expand Down
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 img/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 img/blinn.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 img/clu.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/clus.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 img/cluster.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/comp1.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 img/deferred-v2.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/depth.JPG
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions img/init
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

Binary file added img/lambert.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/pa.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 img/purecol.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 img/sc1.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 img/surfacenor.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 img/toon.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 img/toon.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/xslice.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 img/yslice.JPG
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
24 changes: 22 additions & 2 deletions src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,33 @@ import Scene from './scene';
const FORWARD = 'Forward';
const FORWARD_PLUS = 'Forward+';
const CLUSTERED = 'Clustered';
const lambert = 'lambert';
const blinnphong = 'blinnphong';
const toon = 'toon';

var shadingtype = 0;

const params = {
renderer: FORWARD_PLUS,
renderer: FORWARD,
shadingmode:lambert,
_renderer: null,
};

setRenderer(params.renderer);

function setRenderer(renderer) {
if(params.shadingmode == 'lambert')
{
shadingtype = 0;
}
else if(params.shadingmode == 'blinnphong')
{
shadingtype = 1;
}
else if(params.shadingmode == 'toon')
{
shadingtype = 2;
}
switch(renderer) {
case FORWARD:
params._renderer = new ForwardRenderer();
Expand All @@ -24,13 +42,15 @@ function setRenderer(renderer) {
params._renderer = new ForwardPlusRenderer(15, 15, 15);
break;
case CLUSTERED:
params._renderer = new ClusteredRenderer(15, 15, 15);
params._renderer = new ClusteredRenderer(15, 15, 15,shadingtype);
break;
}
}

gui.add(params, 'renderer', [FORWARD, FORWARD_PLUS, CLUSTERED]).onChange(setRenderer);

gui.add(params, 'shadingmode', [blinnphong, lambert,toon]).onChange(function (x) {setRenderer(params.renderer);});

const scene = new Scene();
scene.loadGLTF('models/sponza/sponza.gltf');

Expand Down
119 changes: 117 additions & 2 deletions src/renderers/base.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,32 @@
import TextureBuffer from './textureBuffer';
import { NUM_LIGHTS } from '../scene';
import { mat4, vec4, vec3, vec2 } from 'gl-matrix';

export const MAX_LIGHTS_PER_CLUSTER = 100;
export const MAX_LIGHTS_PER_CLUSTER = 700;

function getNormalComponents(angle) {

let bigHypot = Math.sqrt(1 + angle*angle);
let normSide1 = 1 / bigHypot;
let normSide2 = -angle*normSide1;
return vec2.fromValues(normSide1, normSide2);
}

function findPlanePointDis(planePos, Pt, XY) {
let interval = Math.sqrt(planePos*planePos+1);
let lightp = vec3.fromValues(Pt[0],Pt[1],Pt[2]);
let res = vec3.fromValues(0,0,0);
if(XY == 1) {
let planenor = vec3.fromValues(1.0/interval,0.0,-planePos/interval);
res = vec3.dot(lightp,planenor);
}
if(XY==2)
{
let planenor = vec3.fromValues(0.0,1.0/interval,-planePos/interval);
res = vec3.dot(lightp,planenor);
}
return res;
}

export default class BaseRenderer {
constructor(xSlices, ySlices, zSlices) {
Expand All @@ -25,6 +51,95 @@ export default class BaseRenderer {
}
}

this._clusterTexture.update();
const halfY = Math.tan((camera.fov*0.5) * (Math.PI/180.0));
const ylengthPerCluster = (halfY * 2.0 / this._ySlices);
const xlengthPerCluster = (halfY * 2.0 / this._xSlices) * camera.aspect;
const zlengthPerCluster = (camera.far - camera.near) / this._zSlices;
const ystart = -halfY;
const xstart = -halfY * camera.aspect;

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


let xminidx = this._xSlices;
let xmaxidx = this._xSlices;
let yminidx = this._ySlices;
let ymaxidx = this._ySlices;
let minposz = lightPos[2] - camera.near - lightRadius;
let maxposz = lightPos[2] - camera.near + lightRadius;
let zminidx = Math.floor(minposz / zlengthPerCluster);
let zmaxidx = Math.floor(maxposz / zlengthPerCluster)+1;
if(zminidx > this._zSlices-1 || zmaxidx < 0) { continue; }
zminidx = Math.max(0, zminidx);
zmaxidx = Math.min(this._zSlices, zmaxidx);

for(let j = 0; j <= this._xSlices; ++j) {
let norm2 = vec2.clone(getNormalComponents(xstart+xlengthPerCluster*j));
let norm3 = vec3.fromValues(norm2[0], 0, norm2[1]);
if(vec3.dot(lightPos, norm3) < lightRadius) {
xminidx = Math.max(0, j-1);
break;
}
}


for(let j = xminidx+1; j<=this.xSlices; ++j) {
let norm2 = vec2.clone(getNormalComponents(xstart+xlengthPerCluster*j));
let norm3 = vec3.fromValues(norm2[0], 0, norm2[1]);
if(vec3.dot(lightPos, norm3) < -lightRadius) {
xmaxidx = Math.max(0, j-1);
break;
}
}


for(let j = 0; j <= this._ySlices; ++j) {
let norm2 = vec2.clone(getNormalComponents(ystart+ylengthPerCluster*j));
let norm3 = vec3.fromValues(0, norm2[0], norm2[1]);
if(vec3.dot(lightPos, norm3) < lightRadius) {
yminidx = Math.max(0, j-1);
break;
}
}


for(let j = yminidx+1; j<=this.ySlices; ++j) {
let norm2 = vec2.clone(getNormalComponents(ystart+ylengthPerCluster*j));
let norm3 = vec3.fromValues(0, norm2[0], norm2[1]);
if(vec3.dot(lightPos, norm3) < -lightRadius) {
ymaxidx = Math.max(0, j-1);
break;
}
}



for(let z = zminidx; z < zmaxidx; ++z) {
for(let y = yminidx; y < ymaxidx; ++y) {
for(let x = xminidx; x < xmaxidx; ++x) {
let clusterIdx = x + y*this._xSlices + z*this._xSlices*this._ySlices;
let lightCountIdx = this._clusterTexture.bufferIndex(clusterIdx, 0);
let lightCount = 1 + this._clusterTexture.buffer[lightCountIdx];

if(lightCount <= MAX_LIGHTS_PER_CLUSTER) {
this._clusterTexture.buffer[lightCountIdx] = lightCount;
let texel = Math.floor(lightCount*0.25);
let texelIdx = this._clusterTexture.bufferIndex(clusterIdx, texel);
let componentIdx = lightCount - texel*4;
this._clusterTexture.buffer[texelIdx+componentIdx] = i;
}
}
}
}



}//end light loop

this._clusterTexture.update();
}
}
Loading