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
32 changes: 21 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,35 @@ WebGL Forward+ and Clustered Deferred 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)
* Haoyu Sui
* [LinkedIn](http://linkedin.com/in/haoyu-sui-721284192)
* Tested on: Windows 10, i5-9600K @ 3.70GHz 16GB, RTX 2070 SUPER 8GB
* SM:7.5

| Forward+ | Clustered Deferred and Blinn-Phong |
| ------------------------ | ----------------------- |
| ![](img/forwardplus.png) | ![](img/deferred.png) |


### Live Online

[![](img/thumb.png)](http://TODO.github.io/Project5-WebGL-Forward-Plus-and-Clustered-Deferred)
[Live Demo](https://haoyusui.github.io/Project5-WebGL-Forward-Plus-and-Clustered-Deferred/)

### Demo Video

[Demo Video on Youtube](https://youtu.be/e2mTMiIlSO8)

### Performance Analysis

### Demo Video/GIF
![](img/A1.jpg)

[![](img/video.png)](TODO)
From the figure we can see that forward+ and clustered deferred can improve performance, and as the total number of lights increases, the performance improvement becomes more obvious

### (TODO: Your README)
**2-component normals**

*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.
![](img/A2.jpg)

This assignment has a considerable amount of performance analysis compared
to implementation work. Complete the implementation early to leave time!
According to the chart, 2-component normal does not improve performance to a large extent. I think the reason may be that although 2-component normal optimizes the use of G-Buffer and reduces its space, it also adds additional computational overhead.


### Credits
Expand Down
Binary file added img/A1.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/A2.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.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/forwardplus.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/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ const wireframe = new Wireframe();
var segmentStart = [-14.0, 0.0, -6.0];
var segmentEnd = [14.0, 20.0, 6.0];
var segmentColor = [1.0, 0.0, 0.0];
wireframe.addLineSegment(segmentStart, segmentEnd, segmentColor);
wireframe.addLineSegment([-14.0, 1.0, -6.0], [14.0, 21.0, 6.0], [0.0, 1.0, 0.0]);
// wireframe.addLineSegment(segmentStart, segmentEnd, segmentColor);
// wireframe.addLineSegment([-14.0, 1.0, -6.0], [14.0, 21.0, 6.0], [0.0, 1.0, 0.0]);

camera.position.set(-10, 8, 0);
cameraControls.target.set(0, 2, 0);
Expand Down
82 changes: 80 additions & 2 deletions src/renderers/base.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import TextureBuffer from './textureBuffer';
import { NUM_LIGHTS } from '../scene.js';
import { vec3, vec4, mat4 } from 'gl-matrix';

export const MAX_LIGHTS_PER_CLUSTER = 100;
export const MAX_LIGHTS_PER_CLUSTER = 200;

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

// compute height, width, depth of frustum
let frustumDepth = camera.far - camera.near;
let frustumHeight = 2 * Math.tan(camera.fov * 0.5 * Math.PI / 180);
let frustumWidth = frustumHeight * camera.aspect;

// compute height, width, depth of each cell
let xStride = frustumWidth / this._xSlices;
let yStride = frustumHeight / this._ySlices;
let zStride = frustumDepth / this._zSlices;

// for each light, get the position and radius,
for (let i = 0; i < NUM_LIGHTS; ++i) {
let light = scene.lights[i];
let lightPosTmp = vec4.fromValues(light.position[0], light.position[1], light.position[2], 1.0);
let radius = light.radius;

let lightPos = vec4.create();
vec4.transformMat4(lightPos, lightPosTmp, viewMatrix);

let offset = radius * 0.25;

let xMax = lightPos[0] + radius + offset;
let xMin = lightPos[0] - radius - offset;
let yMax = lightPos[1] + radius + offset;
let yMin = lightPos[1] - radius - offset;
let zMax = lightPos[2] + radius + offset;
let zMin = lightPos[2] - radius - offset;

let depth = Math.max(zMin, camera.near);
let height = 2 * depth * Math.tan(camera.fov * 0.5 * Math.PI / 180);
let width = height * camera.aspect;

// for Z
let zMinIdx = Math.floor((zMin - camera.near) / zStride);
zMinIdx = Math.max(zMinIdx, 0);
zMinIdx = Math.min(zMinIdx, this._zSlices - 1);

let zMaxIdx = Math.floor((zMax - camera.near) / zStride);
zMaxIdx = Math.max(zMaxIdx, 0);
zMaxIdx = Math.min(zMaxIdx, this._zSlices - 1);

// for y
let yMinIdx = Math.floor((yMin - (-1 * 0.5 * height)) / yStride);
yMinIdx = Math.max(yMinIdx, 0);
yMinIdx = Math.min(yMinIdx, this._ySlices - 1);

let yMaxIdx = Math.floor((yMax - (-1 * 0.5 * height)) / yStride);
yMaxIdx = Math.max(yMaxIdx, 0);
yMaxIdx = Math.min(yMaxIdx, this._ySlices - 1);

// for x
let xMinIdx = Math.floor((xMin - (-1 * 0.5 * width)) / xStride);
xMinIdx = Math.max(xMinIdx, 0);
xMinIdx = Math.min(xMinIdx, this._xSlices - 1);

let xMaxIdx = Math.floor((xMax - (-1 * 0.5 * width)) / xStride);
xMaxIdx = Math.max(xMaxIdx, 0);
xMaxIdx = Math.min(xMaxIdx, this._xSlices - 1);


for (let z = zMinIdx; z <= zMaxIdx; ++z) {
for (let y = yMinIdx; y <= yMaxIdx; ++y) {
for (let x = xMinIdx; x <= xMaxIdx; ++x) {
let idx = x + y * this._xSlices + z * this._xSlices * this._ySlices;
let lightCount = this._clusterTexture.buffer[this._clusterTexture.bufferIndex(idx, 0)];

if(lightCount < MAX_LIGHTS_PER_CLUSTER) {
lightCount++;
let offset = lightCount % 4;
this._clusterTexture.buffer[this._clusterTexture.bufferIndex(idx, 0)] = lightCount;
this._clusterTexture.buffer[this._clusterTexture.bufferIndex(idx, Math.floor(lightCount / 4)) + offset] = i;
}
}
}
}
}
this._clusterTexture.update();
}
}
}
26 changes: 23 additions & 3 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 BaseRenderer from './base';
import { MAX_LIGHTS_PER_CLUSTER } from './base.js';

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

export default class ClusteredDeferredRenderer extends BaseRenderer {
constructor(xSlices, ySlices, zSlices) {
Expand All @@ -28,8 +29,13 @@ export default class ClusteredDeferredRenderer extends BaseRenderer {
this._progShade = loadShaderProgram(QuadVertSource, fsSource({
numLights: NUM_LIGHTS,
numGBuffers: NUM_GBUFFERS,
numLightsMax: MAX_LIGHTS_PER_CLUSTER,
xSlices: xSlices,
ySlices: ySlices,
zSlices: 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_nearFarPlane', 'u_canvasSize', 'u_cameraPosition'],
attribs: ['a_uv'],
});

Expand Down Expand Up @@ -154,9 +160,23 @@ export default class ClusteredDeferredRenderer extends BaseRenderer {
gl.useProgram(this._progShade.glShaderProgram);

// TODO: Bind any other shader inputs
gl.uniform2f(this._progShade.u_canvasSize, canvas.width, canvas.height);

gl.uniform2f(this._progShade.u_nearFarPlane, camera.near, camera.far);

gl.uniform3f(this._progShade.u_cameraPosition, camera.position.x, camera.position.y, camera.position.z);

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
const firstGBufferBinding = 2; // 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
13 changes: 11 additions & 2 deletions src/renderers/forwardPlus.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { gl } from '../init';
import { canvas, gl } from '../init';
import { mat4, vec4, vec3 } from 'gl-matrix';
import { loadShaderProgram } from '../utils';
import { NUM_LIGHTS } from '../scene';
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) {
Expand All @@ -16,8 +17,13 @@ export default class ForwardPlusRenderer extends BaseRenderer {

this._shaderProgram = loadShaderProgram(vsSource, fsSource({
numLights: NUM_LIGHTS,
numLightsMax: MAX_LIGHTS_PER_CLUSTER,
xSlices: xSlices,
ySlices: ySlices,
zSlices: zSlices,

}), {
uniforms: ['u_viewProjectionMatrix', 'u_colmap', 'u_normap', 'u_lightbuffer', 'u_clusterbuffer'],
uniforms: ['u_viewProjectionMatrix', 'u_colmap', 'u_normap', 'u_lightbuffer', 'u_clusterbuffer', 'u_nearFarPlane', 'u_canvasSize'],
attribs: ['a_position', 'a_normal', 'a_uv'],
});

Expand Down Expand Up @@ -76,6 +82,9 @@ export default class ForwardPlusRenderer extends BaseRenderer {
gl.uniform1i(this._shaderProgram.u_clusterbuffer, 3);

// TODO: Bind any other shader inputs
gl.uniform2f(this._shaderProgram.u_canvasSize, canvas.width, canvas.height);

gl.uniform2f(this._shaderProgram.u_nearFarPlane, 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);
Expand Down
117 changes: 114 additions & 3 deletions src/shaders/deferred.frag.glsl.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,128 @@ export default function(params) {
precision highp float;

uniform sampler2D u_gbuffers[${params.numGBuffers}];

uniform sampler2D u_lightbuffer;
uniform sampler2D u_clusterbuffer;

uniform vec2 u_nearFarPlane;
uniform vec2 u_canvasSize;
uniform vec3 u_cameraPosition;

varying vec2 v_uv;

#define TWO_COMPONENT_NORMAL 1

struct Light {
vec3 position;
float radius;
vec3 color;
};

float ExtractFloat(sampler2D texture, int textureWidth, int textureHeight, int index, int component) {
float u = float(index + 1) / float(textureWidth + 1);
int pixel = component / 4;
float v = float(pixel + 1) / float(textureHeight + 1);
vec4 texel = texture2D(texture, vec2(u, v));
int pixelComponent = component - pixel * 4;
if (pixelComponent == 0) {
return texel[0];
} else if (pixelComponent == 1) {
return texel[1];
} else if (pixelComponent == 2) {
return texel[2];
} else if (pixelComponent == 3) {
return texel[3];
}
}

Light UnpackLight(int index) {
Light light;
float u = float(index + 1) / float(${params.numLights + 1});
vec4 v1 = texture2D(u_lightbuffer, vec2(u, 0.3));
vec4 v2 = texture2D(u_lightbuffer, vec2(u, 0.6));
light.position = v1.xyz;

// LOOK: This extracts the 4th float (radius) of the (index)th light in the buffer
// Note that this is just an example implementation to extract one float.
// There are more efficient ways if you need adjacent values
light.radius = ExtractFloat(u_lightbuffer, ${params.numLights}, 2, index, 3);

light.color = v2.rgb;
return light;
}

// Cubic approximation of gaussian curve so we falloff to exactly 0 at the light radius
float cubicGaussian(float h) {
if (h < 1.0) {
return 0.25 * pow(2.0 - h, 3.0) - pow(1.0 - h, 3.0);
} else if (h < 2.0) {
return 0.25 * pow(2.0 - h, 3.0);
} else {
return 0.0;
}
}

void main() {
// TODO: extract data from g buffers and do lighting
// vec4 gb0 = texture2D(u_gbuffers[0], v_uv);
// vec4 gb1 = texture2D(u_gbuffers[1], v_uv);
vec4 gb0 = texture2D(u_gbuffers[0], v_uv);
vec4 gb1 = texture2D(u_gbuffers[1], v_uv);
// vec4 gb2 = texture2D(u_gbuffers[2], v_uv);
// vec4 gb3 = texture2D(u_gbuffers[3], v_uv);

gl_FragColor = vec4(v_uv, 0.0, 1.0);
vec3 albedo = gb0.xyz;
vec3 position = gb1.xyz;

#if TWO_COMPONENT_NORMAL == 1
vec2 tmp = vec2(gb0.w, gb1.w) * 2.0 - 1.0;
vec4 nn = vec4(tmp, 1.0, -1.0);
float l = dot(nn.xyz, -nn.xyw);
nn.z = l;
nn.xy *= sqrt(l);
vec3 normal = nn.xyz * 2.0 + vec3(0.0, 0.0, -1.0);
#else
vec4 gb2 = texture2D(u_gbuffers[2], v_uv);
vec3 normal = gb2.xyz;
#endif // TWO_COMPONENT_NORMAL

vec3 fragColor = vec3(0.0);

int clustersNum = ${params.xSlices} * ${params.ySlices} * ${params.zSlices};
int componentsNum = int(ceil(float(${params.numLightsMax} + 1) / 4.0));

int x = int(floor(gl_FragCoord.x / float(u_canvasSize.x ) * float(${params.xSlices})));
int y = int(floor(gl_FragCoord.y / float(u_canvasSize.y) * float(${params.ySlices})));
int z = int(floor((gl_FragCoord.z - u_nearFarPlane.x) / (u_nearFarPlane.y - u_nearFarPlane.x) * float(${params.zSlices})));
int clusterIdx = x + y * ${params.xSlices} + z * ${params.xSlices} * ${params.ySlices};
int lightCount = int(ExtractFloat(u_clusterbuffer, clustersNum, componentsNum, clusterIdx, 0));


for (int i = 1; i <= ${params.numLights}; ++i) {
if (i > lightCount) {
break;
}
int lightIdx = int(ExtractFloat(u_clusterbuffer, clustersNum, componentsNum, clusterIdx, i));
Light light = UnpackLight(lightIdx);
float lightDistance = distance(light.position, position);
vec3 L = (light.position - position) / lightDistance;

float lightIntensity = cubicGaussian(2.0 * lightDistance / light.radius);
float lambertTerm = max(dot(L, normal), 0.0);
fragColor += albedo * lambertTerm * light.color * vec3(lightIntensity);

// blinnphong
vec3 V = normalize(u_cameraPosition - position);
vec3 H = normalize(L + V);
float specularTerm = pow( max(dot(H, normal), 0.0), 100.0);
fragColor += specularTerm * light.color * vec3(lightIntensity);
}

const vec3 ambientLight = vec3(0.025);
fragColor += albedo * ambientLight;

fragColor = clamp(fragColor, vec3(0.0), vec3(1.0));

gl_FragColor = vec4(fragColor, 1.0);
}
`;
}
Loading