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
248 changes: 238 additions & 10 deletions README.md

Large diffs are not rendered by default.

Binary file added img/figures/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/figures/cluster_slices.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/figures/lambertian.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/figures/lambertian_1.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/figures/lambertian_1000.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/figures/lambertian_3.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/figures/light_radius.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/figures/metal_rough.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/figures/metal_spec.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/figures/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/figures/num_lights.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/figures/optimizations.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/figures/plastic_rough.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/figures/plastic_spec.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/figures/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/figures/shading_model.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/project_gif.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10,120 changes: 10,120 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"build": "webpack --mode production"
},
"dependencies": {
"dat.gui": "0.7.7",
"dat.gui": "^0.7.9",
"gl-matrix": "3.4.3",
"html-webpack-plugin": "^5.4.0",
"spectorjs": "0.9.27",
Expand All @@ -22,7 +22,7 @@
"babel-loader": "8.2.3",
"webpack": "5.59.1",
"webpack-cli": "^4.9.1",
"webpack-dev-server": "4.3.1",
"webpack-dev-server": "^4.11.1",
"webpack-glsl-loader": "1.0.1"
}
}
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
86 changes: 75 additions & 11 deletions src/main.js
Original file line number Diff line number Diff line change
@@ -1,36 +1,99 @@
import { makeRenderLoop, camera, cameraControls, gui, gl } from './init';
import ForwardRenderer from './renderers/forward';
import ForwardPlusRenderer from './renderers/forwardPlus';
import ClusteredDeferredRenderer from './renderers/clusteredDeferred';
import ForwardPlusRenderer_GGX from './renderers/forwardPlus_GGX';
import ClusteredDeferredRenderer_O0 from './renderers/clusteredDeferred_O0';
import ClusteredDeferredRenderer_O1 from './renderers/clusteredDeferred_O1';
import ClusteredDeferredRenderer_O2 from './renderers/clusteredDeferred_O2';
import ClusteredDeferredRenderer_O2_GGX from './renderers/clusteredDeferred_O2_GGX';
import Scene from './scene';
import Wireframe from './wireframe';

const FORWARD = 'Forward';
const FORWARD_PLUS = 'Forward+';
const CLUSTERED = 'Clustered Deferred';
const FORWARD_PLUS_GGX = 'Forward+ GGX';
const CLUSTERED_O0 = 'Clustered Deferred O0';
const CLUSTERED_O1 = 'Clustered Deferred O1';
const CLUSTERED_O2 = 'Clustered Deferred O2';
const CLUSTERED_O2_GGX = 'Clustered Deferred O2 GGX';

const params = {
renderer: FORWARD_PLUS,
renderer: CLUSTERED_O0,
_renderer: null,
xSlices: 20,
ySlices: 20,
zSlices: 20,
roughness: 0.25,
metallic: 0.0
};

setRenderer(params.renderer);

function setRenderer(renderer) {
switch(renderer) {
case FORWARD:
params._renderer = new ForwardRenderer();
params._renderer = new ForwardRenderer(params.roughness, params.metallic);
break;
case FORWARD_PLUS:
params._renderer = new ForwardPlusRenderer(15, 15, 15);
params._renderer = new ForwardPlusRenderer(params.xSlices, params.ySlices, params.zSlices);
break;
case CLUSTERED:
params._renderer = new ClusteredDeferredRenderer(15, 15, 15);
case FORWARD_PLUS_GGX:
params._renderer = new ForwardPlusRenderer_GGX(params.xSlices, params.ySlices, params.zSlices, params.roughness, params.metallic);
break;
case CLUSTERED_O0:
params._renderer = new ClusteredDeferredRenderer_O0(params.xSlices, params.ySlices, params.zSlices);
break;
case CLUSTERED_O1:
params._renderer = new ClusteredDeferredRenderer_O1(params.xSlices, params.ySlices, params.zSlices);
break;
case CLUSTERED_O2:
params._renderer = new ClusteredDeferredRenderer_O2(params.xSlices, params.ySlices, params.zSlices);
break;
case CLUSTERED_O2_GGX:
params._renderer = new ClusteredDeferredRenderer_O2_GGX(params.xSlices, params.ySlices, params.zSlices, params.roughness, params.metallic);
break;
}
}

function setRendererParams() {
switch(params.renderer) {
case FORWARD:
params._renderer = new ForwardRenderer(params.roughness, params.metallic);
break;
case FORWARD_PLUS:
params._renderer = new ForwardPlusRenderer(params.xSlices, params.ySlices, params.zSlices);
break;
case FORWARD_PLUS_GGX:
params._renderer = new ForwardPlusRenderer_GGX(params.xSlices, params.ySlices, params.zSlices, params.roughness, params.metallic);
break;
case CLUSTERED_O0:
params._renderer = new ClusteredDeferredRenderer_O0(params.xSlices, params.ySlices, params.zSlices);
break;
case CLUSTERED_O1:
params._renderer = new ClusteredDeferredRenderer_O1(params.xSlices, params.ySlices, params.zSlices);
break;
case CLUSTERED_O2:
params._renderer = new ClusteredDeferredRenderer_O2(params.xSlices, params.ySlices, params.zSlices);
break;
case CLUSTERED_O2_GGX:
params._renderer = new ClusteredDeferredRenderer_O2_GGX(params.xSlices, params.ySlices, params.zSlices, params.roughness, params.metallic);
break;
}
}

gui.add(params, 'renderer', [FORWARD, FORWARD_PLUS, CLUSTERED]).onChange(setRenderer);
function setMaterialParams() {
params._renderer._roughness = params.roughness;
params._renderer._metallic = params.metallic;

}

gui.add(params, 'renderer', [FORWARD, FORWARD_PLUS, FORWARD_PLUS_GGX, CLUSTERED_O0, CLUSTERED_O1, CLUSTERED_O2, CLUSTERED_O2_GGX]).onChange(setRenderer);
gui.add(params, 'xSlices', 1, 50).step(1).onChange(setRendererParams);
gui.add(params, 'ySlices', 1, 50).step(1).onChange(setRendererParams);
gui.add(params, 'zSlices', 1, 50).step(1).onChange(setRendererParams);
gui.add(params, 'roughness', 0.0, 1.0).step(0.01).onChange(setMaterialParams);
gui.add(params, 'metallic', 0.0, 1.0).step(0.01).onChange(setMaterialParams);


const scene = new Scene();
scene.loadGLTF('models/sponza/sponza.gltf');
Expand All @@ -46,6 +109,7 @@ 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([1.0, 1.0, 1.0], [1.0, 2.0, 1.0], [0.0, 0.0, 1.0]);

camera.position.set(-10, 8, 0);
cameraControls.target.set(0, 2, 0);
Expand All @@ -59,9 +123,9 @@ function render() {
// If you would like the wireframe to render behind and in front
// of objects based on relative depths in the scene, comment out /
//the gl.disable(gl.DEPTH_TEST) and gl.enable(gl.DEPTH_TEST) lines.
gl.disable(gl.DEPTH_TEST);
wireframe.render(camera);
gl.enable(gl.DEPTH_TEST);
//gl.disable(gl.DEPTH_TEST);
//wireframe.render(camera);
//gl.enable(gl.DEPTH_TEST);
}

makeRenderLoop(render)();
123 changes: 116 additions & 7 deletions src/renderers/base.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { vec4 } from 'gl-matrix';
import TextureBuffer from './textureBuffer';
import { cameraControls } from './../init';
import { NUM_LIGHTS } from './../scene';

export const MAX_LIGHTS_PER_CLUSTER = 100;
export const MAX_LIGHTS_PER_CLUSTER = NUM_LIGHTS;

export default class BaseRenderer {
constructor(xSlices, ySlices, zSlices) {
Expand All @@ -9,22 +12,128 @@ export default class BaseRenderer {
this._xSlices = xSlices;
this._ySlices = ySlices;
this._zSlices = zSlices;
this._frame = 0;
this._maxLightsPerCluster = MAX_LIGHTS_PER_CLUSTER;
}

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...

// clear out old light counts
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;
let idx = x + y * this._xSlices + z * this._xSlices * this._ySlices;

this._clusterTexture.buffer[this._clusterTexture.bufferIndex(idx, 0)] = 0;
}
}
}

this._clusterTexture.update();
// half height calculation (independent of z depth)
let frustum_half_height = Math.tan(camera.fov * 0.5 * Math.PI / 180.0);

// dividing total depth of frustum by number of z slices
let frustum_z_stride = (camera.far - camera.near) / this._zSlices;

for (let i = 0; i < scene.lights.length; i++) {


let light_position = scene.lights[i].position;
let light_radius = scene.lights[i].radius;

// get bounding box of sphere representing light with position and radius
let sphere_AABB_min = vec4.fromValues(light_position[0] - light_radius, light_position[1] - light_radius, light_position[2] - light_radius, 1);
let sphere_AABB_max = vec4.fromValues(light_position[0] + light_radius, light_position[1] + light_radius, light_position[2] + light_radius, 1);

// transform from world space to view space
vec4.transformMat4(sphere_AABB_min, sphere_AABB_min, viewMatrix);
vec4.transformMat4(sphere_AABB_max, sphere_AABB_max, viewMatrix);

// multiply z coordinate by -1 to make z positive in front of camera
sphere_AABB_min[2] *= -1.0;
sphere_AABB_max[2] *= -1.0;

// calculate the frustum half height at depth where sphere bounding box are in view space
let half_height_at_depth_min = frustum_half_height * sphere_AABB_min[2];
let half_height_at_depth_max = frustum_half_height * sphere_AABB_max[2];

// calculate frustum half width from half height using aspect ratio
let half_width_at_depth_min = half_height_at_depth_min * camera.aspect;
let half_width_at_depth_max = half_height_at_depth_max * camera.aspect;

// calculate x and y cluster index values by shifting negative x and y to positive
let frustum_grid_index_min_x = sphere_AABB_min[0] + (this._xSlices * half_width_at_depth_min) / (2.0 * half_width_at_depth_min);
let frustum_grid_index_max_x = sphere_AABB_max[0] + (this._xSlices * half_width_at_depth_max) / (2.0 * half_width_at_depth_max);

let frustum_grid_index_min_y = sphere_AABB_min[1] + (this._ySlices * half_height_at_depth_min) / (2.0 * half_height_at_depth_min);
let frustum_grid_index_max_y = sphere_AABB_max[1] + (this._ySlices * half_height_at_depth_max) / (2.0 * half_height_at_depth_max);

// divided view z coordinate by length of each cluster in z direction to get z cluster
let frustum_grid_index_min_z = sphere_AABB_min[2] / frustum_z_stride;
let frustum_grid_index_max_z = sphere_AABB_max[2] / frustum_z_stride;


// flip directions if necessary
if (frustum_grid_index_min_x > frustum_grid_index_max_x) {
let temp = frustum_grid_index_min_x;
frustum_grid_index_min_x = frustum_grid_index_max_x;
frustum_grid_index_max_x = temp;
}

if (frustum_grid_index_min_y > frustum_grid_index_max_y) {
let temp = frustum_grid_index_min_y;
frustum_grid_index_min_y = frustum_grid_index_max_y;
frustum_grid_index_max_y = temp;
}

if (frustum_grid_index_min_z > frustum_grid_index_max_z) {
let temp = frustum_grid_index_min_z;
frustum_grid_index_min_z = frustum_grid_index_max_z;
frustum_grid_index_max_z = temp;
}

// cull lights outside view
if ((frustum_grid_index_min_x < 0.0 && frustum_grid_index_max_x < 0.0) || (frustum_grid_index_min_x > this._xSlices && frustum_grid_index_max_x > this._xSlices)) {
continue;
}
if ((frustum_grid_index_min_y < 0.0 && frustum_grid_index_max_y < 0.0) || (frustum_grid_index_min_y > this._ySlices && frustum_grid_index_max_y > this._ySlices)) {
continue;
}
if ((frustum_grid_index_min_z < 0.0 && frustum_grid_index_max_z < 0.0) || (frustum_grid_index_min_z > this._zSlices && frustum_grid_index_max_z > this._zSlices)) {
continue;
}

// clamp and floor mins and ceil maxes to get final cluster bounds
frustum_grid_index_min_x = Math.floor(Math.min(Math.max(frustum_grid_index_min_x, 0.0), this._xSlices - 1));
frustum_grid_index_max_x = Math.ceil(Math.min(Math.max(frustum_grid_index_max_x, 0.0), this._xSlices));

frustum_grid_index_min_y = Math.floor(Math.min(Math.max(frustum_grid_index_min_y, 0.0), this._ySlices - 1));
frustum_grid_index_max_y = Math.ceil(Math.min(Math.max(frustum_grid_index_max_y, 0.0), this._ySlices));

frustum_grid_index_min_z = Math.floor(Math.min(Math.max(frustum_grid_index_min_z, 0.0), this._zSlices - 1));
frustum_grid_index_max_z = Math.ceil(Math.min(Math.max(frustum_grid_index_max_z, 0.0), this._zSlices));

// add light to each cluster it is in
for (let z = frustum_grid_index_min_z; z < frustum_grid_index_max_z; ++z) {
for (let y = frustum_grid_index_min_y; y < frustum_grid_index_max_y; ++y) {
for (let x = frustum_grid_index_min_x; x < frustum_grid_index_max_x; ++x) {
let idx = x + y * this._xSlices + z * this._xSlices * this._ySlices;

if (this._clusterTexture.buffer[this._clusterTexture.bufferIndex(idx, 0)] < MAX_LIGHTS_PER_CLUSTER) {
this._clusterTexture.buffer[this._clusterTexture.bufferIndex(idx, 0)]++;

let pixel_to_put_light_into = Math.floor(this._clusterTexture.buffer[this._clusterTexture.bufferIndex(idx, 0)] / 4);
let component_to_put_light_into = Math.floor(this._clusterTexture.buffer[this._clusterTexture.bufferIndex(idx, 0)] % 4);
this._clusterTexture.buffer[this._clusterTexture.bufferIndex(idx, pixel_to_put_light_into) + component_to_put_light_into] = i;
}
}
}
}
}


this._frame++;

this._clusterTexture.update();
}
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import { gl, WEBGL_draw_buffers, canvas } from '../init';
import { mat4, vec4 } from 'gl-matrix';
import { mat4, vec4, vec3, vec2 } 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 toTextureFrag from '../shaders/deferred_O0_ToTexture.frag.glsl';
import QuadVertSource from '../shaders/quad.vert.glsl';
import fsSource from '../shaders/deferred.frag.glsl.js';
import fsSource from '../shaders/deferred_O0.frag.glsl.js';
import TextureBuffer from './textureBuffer';
import BaseRenderer from './base';
import BaseRenderer, { MAX_LIGHTS_PER_CLUSTER } from './base';

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

export default class ClusteredDeferredRenderer extends BaseRenderer {
constructor(xSlices, ySlices, zSlices) {
export default class ClusteredDeferredRenderer_O0 extends BaseRenderer {
constructor(xSlices, ySlices, zSlices, roughness, metallic) {
super(xSlices, ySlices, zSlices);

this.setupDrawBuffers(canvas.width, canvas.height);
Expand All @@ -28,8 +28,10 @@ export default class ClusteredDeferredRenderer extends BaseRenderer {
this._progShade = loadShaderProgram(QuadVertSource, fsSource({
numLights: NUM_LIGHTS,
numGBuffers: NUM_GBUFFERS,
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_lightbuffer', 'u_clusterbuffer', 'u_canvasresolution', 'u_clusterslices', 'u_viewMatrix'],
attribs: ['a_uv'],
});

Expand Down Expand Up @@ -155,14 +157,28 @@ export default class ClusteredDeferredRenderer extends BaseRenderer {

// TODO: Bind any other shader inputs

// 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);

// Bind g-buffers
const firstGBufferBinding = 0; // You may have to change this if you use other texture slots
const firstGBufferBinding = 4; // 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]);
gl.uniform1i(this._progShade[`u_gbuffers[${i}]`], i + firstGBufferBinding);
}

gl.uniformMatrix4fv(this._progShade.u_viewMatrix, false, this._viewMatrix);
gl.uniform2fv(this._progShade.u_canvasresolution, vec2.fromValues(canvas.width, canvas.height));
gl.uniform3fv(this._progShade.u_clusterslices, vec3.fromValues(this._xSlices, this._ySlices, this._zSlices));

renderFullscreenQuad(this._progShade);
}
};
Loading