Skip to content

Commit

Permalink
Update filenames, instructions, and variables with clearer language
Browse files Browse the repository at this point in the history
  • Loading branch information
ottaviohartman committed Oct 17, 2018
1 parent e0e02e2 commit 59445f2
Show file tree
Hide file tree
Showing 12 changed files with 218 additions and 219 deletions.
3 changes: 2 additions & 1 deletion .gitignore
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
node_modules
node_modules
.idea
22 changes: 11 additions & 11 deletions INSTRUCTION.md
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
WebGL Clustered Deferred and Forward+ Shading - Instructions
WebGL Clustered and Forward+ Shading - Instructions
==========================================================

**This is due Thursday 10/26**
Expand Down Expand Up @@ -27,19 +27,19 @@ In this project, you are given code for:
- Loading glTF models
- Camera control
- Simple forward renderer
- Partial implementation and setup for Clustered Deferred and Forward+ shading
- Partial implementation and setup for Clustered and Forward+ shading
- Many helpful helpers

## Required Tasks

**Before doing performance analysis**, you must disable debug mode by changing `DEBUG` to false in `src/init.js`. Keep it enabled when developing - it helps find WebGL errors *much* more easily.

**Clustered Forward+**
**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 Deferred**
- Reuse clustering logic from Clustered Forward+
**Clustered**
- Reuse clustering logic from Forward+
- Store vertex attributes in g-buffer
- Read g-buffer in a shader to produce final output

Expand All @@ -63,7 +63,7 @@ In this project, you are given code for:

## Performance & Analysis

Compare your implementations of Clustered Forward+ and Clustered Deferred shading and analyze their differences.
Compare your implementations of Forward+ and Clustered shading and analyze their differences.
- Is one of them faster?
- Is one of them better at certain types of workloads?
- What are the benefits and tradeoffs of using one over the other?
Expand Down Expand Up @@ -95,7 +95,7 @@ For each performance feature (required or extra), please provide:

Initialization happens in `src/init.js`. You don't need to worry about this; it is mostly initializing the gl context, debug modes, extensions, etc.

`src/main.js` is configuration for the renderers. It sets up the gui for switching renderers and initializes the scene and render loop. The only important thing here are the arguments for `ClusteredForwardPlusRenderer` and `ClusteredDeferredRenderer`. These constructors take the number of x, y, and z slices to split the frustum into.
`src/main.js` is configuration for the renderers. It sets up the gui for switching renderers and initializes the scene and render loop. The only important thing here are the arguments for `ForwardPlusRenderer` and `ClusteredRenderer`. These constructors take the number of x, y, and z slices to split the frustum into.

`src/scene.js` handles loading a .gltf scene and initializes the lights. Here, you can modify the number of lights, their positions, and how they move around. Also, take a look at the `draw` function. This handles binding the vertex attributes, which are hardcoded to `a_position`, `a_normal`, and `a_uv`, as well as the color and normal maps to targets `gl.TEXTURE0` and `gl.TEXTURE1`.

Expand All @@ -115,17 +115,17 @@ Now go look inside `src/shaders/forward.frag.glsl.js`. Here, there is a simple l
**Getting Started**
Here's a few tips to get you started.

1. Complete `updateClusters` in `src/renderers/clustered.js`. This should update the cluster `TextureBuffer` with a mapping from cluster index to light count and light list (indices).
1. Complete `updateClusters` in `src/renderers/base.js`. This should update the cluster `TextureBuffer` with a mapping from cluster index to light count and light list (indices).

2. Update `src/shaders/clusteredForward.frag.glsl.js` to
2. Update `src/shaders/forwardPlus.frag.glsl.js` to
- Determine the cluster for a fragment
- Read in the lights in that cluster from the populated data
- Do shading for just those lights
- You may find it necessary to bind additional uniforms in `src/renderers/clusteredForwardPlus.js`
- You may find it necessary to bind additional uniforms in `src/renderers/forwardPlus.js`

3. Update `src/shaders/deferredToTexture.frag.glsl` to write desired data to the g-buffer
4. Update `src/deferred.frag.glsl` to read values from the g-buffer and perform simple forward rendering. (Right now it just outputs the screen xy coordinate)
5. Update it to use clustered shading. You should be able to reuse lots of stuff from Clustered Forward+ for this. You will also likely need to update shader inputs in `src/renderers/clusteredDeferred.js`
5. Update it to use clustered shading. You should be able to reuse lots of stuff from Forward+ for this. You will also likely need to update shader inputs in `src/renderers/clustered.js`

## README

Expand Down
2 changes: 1 addition & 1 deletion README.md
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
WebGL Clustered Deferred and Forward+ Shading
WebGL Clustered and Forward+ Shading
======================

**University of Pennsylvania, CIS 565: GPU Programming and Architecture, Project 5**
Expand Down
2 changes: 1 addition & 1 deletion package.json
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"build": "webpack --env.production"
},
"dependencies": {
"dat-gui": "^0.5.0",
"dat.gui": "^0.7.3",
"gl-matrix": "^2.4.0",
"spectorjs": "^0.9.0",
"stats-js": "^1.0.0-alpha1",
Expand Down
2 changes: 1 addition & 1 deletion src/init.js
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// TODO: Change this to enable / disable debug mode
export const DEBUG = true && process.env.NODE_ENV === 'development';

import DAT from 'dat-gui';
import DAT from 'dat.gui';
import WebGLDebug from 'webgl-debug';
import Stats from 'stats-js';
import { PerspectiveCamera } from 'three';
Expand Down
20 changes: 10 additions & 10 deletions src/main.js
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { makeRenderLoop, camera, cameraControls, gui, gl } from './init';
import ForwardRenderer from './renderers/forward';
import ClusteredForwardPlusRenderer from './renderers/clusteredForwardPlus';
import ClusteredDeferredRenderer from './renderers/clusteredDeferred';
import ForwardPlusRenderer from './renderers/forwardPlus';
import ClusteredRenderer from './renderers/clustered';
import Scene from './scene';

const FORWARD = 'Forward';
const CLUSTERED_FORWARD_PLUS = 'Clustered Forward+';
const CLUSTERED_DEFFERED = 'Clustered Deferred';
const FORWARD_PLUS = 'Forward+';
const CLUSTERED = 'Clustered';

const params = {
renderer: CLUSTERED_FORWARD_PLUS,
renderer: FORWARD_PLUS,
_renderer: null,
};

Expand All @@ -20,16 +20,16 @@ function setRenderer(renderer) {
case FORWARD:
params._renderer = new ForwardRenderer();
break;
case CLUSTERED_FORWARD_PLUS:
params._renderer = new ClusteredForwardPlusRenderer(15, 15, 15);
case FORWARD_PLUS:
params._renderer = new ForwardPlusRenderer(15, 15, 15);
break;
case CLUSTERED_DEFFERED:
params._renderer = new ClusteredDeferredRenderer(15, 15, 15);
case CLUSTERED:
params._renderer = new ClusteredRenderer(15, 15, 15);
break;
}
}

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

const scene = new Scene();
scene.loadGLTF('models/sponza/sponza.gltf');
Expand Down
30 changes: 30 additions & 0 deletions src/renderers/base.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import TextureBuffer from './textureBuffer';

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

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();
}
}
180 changes: 158 additions & 22 deletions src/renderers/clustered.js
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,32 +1,168 @@
import { mat4, vec4, vec3 } from 'gl-matrix';
import { gl, WEBGL_draw_buffers, canvas } from '../init';
import { mat4, 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';

export const MAX_LIGHTS_PER_CLUSTER = 100;
export const NUM_GBUFFERS = 4;

export default class ClusteredRenderer {
export default class ClusteredRenderer extends 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;
super(xSlices, ySlices, zSlices);

this.setupDrawBuffers(canvas.width, canvas.height);

// Create a texture to store light data
this._lightTexture = new TextureBuffer(NUM_LIGHTS, 8);

this._progCopy = loadShaderProgram(toTextureVert, toTextureFrag, {
uniforms: ['u_viewProjectionMatrix', 'u_colmap', 'u_normap'],
attribs: ['a_position', 'a_normal', 'a_uv'],
});

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'],
});

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

setupDrawBuffers(width, height) {
this._width = width;
this._height = height;

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);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.DEPTH_COMPONENT, width, height, 0, gl.DEPTH_COMPONENT, gl.UNSIGNED_SHORT, null);
gl.bindTexture(gl.TEXTURE_2D, null);

gl.bindFramebuffer(gl.FRAMEBUFFER, this._fbo);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.TEXTURE_2D, this._depthTex, 0);

// Create, bind, and store "color" target textures for the FBO
this._gbuffers = new Array(NUM_GBUFFERS);
let attachments = new Array(NUM_GBUFFERS);
for (let i = 0; i < NUM_GBUFFERS; i++) {
attachments[i] = WEBGL_draw_buffers[`COLOR_ATTACHMENT${i}_WEBGL`];
this._gbuffers[i] = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, this._gbuffers[i]);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.FLOAT, null);
gl.bindTexture(gl.TEXTURE_2D, null);

gl.framebufferTexture2D(gl.FRAMEBUFFER, attachments[i], gl.TEXTURE_2D, this._gbuffers[i], 0);
}

if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE) {
throw "Framebuffer incomplete";
}

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

resize(width, height) {
this._width = width;
this._height = height;

gl.bindTexture(gl.TEXTURE_2D, this._depthTex);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.DEPTH_COMPONENT, width, height, 0, gl.DEPTH_COMPONENT, gl.UNSIGNED_SHORT, null);
for (let i = 0; i < NUM_GBUFFERS; i++) {
gl.bindTexture(gl.TEXTURE_2D, this._gbuffers[i]);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.FLOAT, null);
}
gl.bindTexture(gl.TEXTURE_2D, null);
}

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;
}
}
render(camera, scene) {
if (canvas.width != this._width || canvas.height != this._height) {
this.resize(canvas.width, canvas.height);
}

// Update the camera matrices
camera.updateMatrixWorld();
mat4.invert(this._viewMatrix, camera.matrixWorld.elements);
mat4.copy(this._projectionMatrix, camera.projectionMatrix.elements);
mat4.multiply(this._viewProjectionMatrix, this._projectionMatrix, this._viewMatrix);

// Render to the whole screen
gl.viewport(0, 0, canvas.width, canvas.height);

// Bind the framebuffer
gl.bindFramebuffer(gl.FRAMEBUFFER, this._fbo);

// Clear the frame
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

// Use the shader program to copy to the draw buffers
gl.useProgram(this._progCopy.glShaderProgram);

// Upload the camera matrix
gl.uniformMatrix4fv(this._progCopy.u_viewProjectionMatrix, false, this._viewProjectionMatrix);

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

// 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];
this._lightTexture.buffer[this._lightTexture.bufferIndex(i, 0) + 1] = scene.lights[i].position[1];
this._lightTexture.buffer[this._lightTexture.bufferIndex(i, 0) + 2] = scene.lights[i].position[2];
this._lightTexture.buffer[this._lightTexture.bufferIndex(i, 0) + 3] = scene.lights[i].radius;

this._lightTexture.buffer[this._lightTexture.bufferIndex(i, 1) + 0] = scene.lights[i].color[0];
this._lightTexture.buffer[this._lightTexture.bufferIndex(i, 1) + 1] = scene.lights[i].color[1];
this._lightTexture.buffer[this._lightTexture.bufferIndex(i, 1) + 2] = scene.lights[i].color[2];
}
// Update the light texture
this._lightTexture.update();

// Update the clusters for the frame
this.updateClusters(camera, this._viewMatrix, scene);

// Bind the default null framebuffer which is the screen
gl.bindFramebuffer(gl.FRAMEBUFFER, null);

// Clear the frame
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

// Use this shader program
gl.useProgram(this._progShade.glShaderProgram);

// TODO: Bind any other shader inputs

// Bind g-buffers
const firstGBufferBinding = 0; // 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);
}

this._clusterTexture.update();
renderFullscreenQuad(this._progShade);
}
}
};
Loading

0 comments on commit 59445f2

Please sign in to comment.