diff --git a/README.md b/README.md
index ec3c73b..79af845 100755
--- a/README.md
+++ b/README.md
@@ -3,25 +3,24 @@ WebGL Clustered and Forward+ 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)
+* Yan Wu
+* Tested on: **Google Chrome 70.0** on
+ Windows 10, i7-8750H @ 2.20GHz 16GB, GTX 1060 6GB (Personal Laptop)
### Live Online
-
-[![](img/thumb.png)](http://TODO.github.io/Project5B-WebGL-Deferred-Shading)
+* Still in development
+[](http://wuyan33.github.io/Project5-WebGL-Clustered-Deferred-Forward-Plus)
### Demo Video/GIF
+* Click the picture for video!
+[](https://drive.google.com/file/d/1Md_yvFBjy2JOdNT1rUiqcvvkOBn8lH1X/view?usp=sharing)
-[![](img/video.png)](TODO)
-
-### (TODO: Your README)
-
-*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.
+### Performance Analysis
+* FPS analysis with three methods
+
+ - We can see clearly that cluster method is better than the other two.
+* In development...
-This assignment has a considerable amount of performance analysis compared
-to implementation work. Complete the implementation early to leave time!
### Credits
diff --git a/Video/report.avi b/Video/report.avi
new file mode 100644
index 0000000..a51aca1
Binary files /dev/null and b/Video/report.avi differ
diff --git a/image/Capture.PNG b/image/Capture.PNG
new file mode 100644
index 0000000..adac115
Binary files /dev/null and b/image/Capture.PNG differ
diff --git a/image/FPS.PNG b/image/FPS.PNG
new file mode 100644
index 0000000..a06c083
Binary files /dev/null and b/image/FPS.PNG differ
diff --git a/src/init.js b/src/init.js
index 885240b..7e07f0f 100755
--- a/src/init.js
+++ b/src/init.js
@@ -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';
diff --git a/src/renderers/base.js b/src/renderers/base.js
index 8a975b9..be876c6 100755
--- a/src/renderers/base.js
+++ b/src/renderers/base.js
@@ -1,7 +1,22 @@
import TextureBuffer from './textureBuffer';
+import {NUM_LIGHTS} from "../scene";
+import { mat4, vec4, vec3 } from 'gl-matrix';
export const MAX_LIGHTS_PER_CLUSTER = 100;
+function count_x_distance(light_pos, w){
+ let distance = vec3.fromValues(1.0 / Math.sqrt(w * w + 1), 0 , - w / Math.sqrt(w * w + 1));
+ distance = vec3.dot(vec3.fromValues(light_pos[0], light_pos[1], light_pos[2]), distance);
+ return distance;
+}
+
+function count_y_distance(light_pos, w){
+ let distance = vec3.fromValues(0, 1.0 / Math.sqrt(w * w + 1), - w / Math.sqrt(w * w + 1));
+ distance = vec3.dot(vec3.fromValues(light_pos[0], light_pos[1], light_pos[2]), distance);
+ return distance;
+}
+
+
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
@@ -25,6 +40,103 @@ export default class BaseRenderer {
}
}
+ var yz = Math.tan(0.5 * camera.fov * Math.PI / 180.0);
+ var xz = camera.aspect * yz;
+ var z_step = (camera.far - camera.near) / this._zSlices;
+ var x_step = 2.0 * xz / this._xSlices;
+ var y_step = 2.0 * yz / this._ySlices;
+
+
+ for(let light_index = 0; light_index < NUM_LIGHTS; light_index++){
+ // get light position
+ let light_pos = vec4.create();
+ light_pos[0] = scene.lights[light_index].position[0];
+ light_pos[1] = scene.lights[light_index].position[1];
+ light_pos[2] = scene.lights[light_index].position[2];
+ light_pos[3] = 1.0;
+
+ vec4.transformMat4(light_pos, light_pos, viewMatrix);
+ light_pos[2] *= -1.0;
+ let radius = scene.lights[light_index].radius;
+
+ // find cluster range
+ let z_min, z_max;
+
+ // for(z_min = 0; z_min < this._zSlices; z_min++){
+ // let distance = light_pos[2] - (camera.near + z_min * z_step);
+ // if(distance <= radius){
+ // z_min = Math.max(0, z_min - 1);
+ // break;
+ // }
+ // }
+ // for(z_max = this._zSlices - 1; z_max > z_min; z_max--){
+ // let distance = (camera.near + z_max * z_step) - light_pos[2];
+ // if(distance <= radius){
+ // z_max = Math.min(this._zSlices, z_max + 1);
+ // break;
+ // }
+ // }
+ z_min = Math.floor((light_pos[2] - camera.near - radius) / z_step);
+ z_max = Math.floor((light_pos[2] - camera.near + radius) / z_step);
+ if(z_min >= this._zSlices || z_max < 0) continue;
+ z_min = Math.max(0, z_min);
+ z_max = Math.min(z_max, this._zSlices - 1);
+
+ let x_min, x_max;
+
+ for(x_min = 0; x_min < this._xSlices; x_min++){
+ let distance;
+ let w = -xz + x_min * x_step;
+ distance = count_x_distance(light_pos, w);
+ if(Math.abs(distance) <= radius){
+ break;
+ }
+ }
+ for(x_max = this._xSlices - 1; x_max > x_min; x_max--){
+ let distance;
+ let w = -xz + x_max * x_step;
+ distance = count_x_distance(light_pos, w);
+ if(Math.abs(distance) < radius){
+ break;
+ }
+ }
+
+ let y_min, y_max;
+ for(y_min = 0; y_min < this._ySlices; y_min++){
+ let distance;
+ let w = -yz + y_min * y_step;
+ distance = count_y_distance(light_pos, w);
+ if(Math.abs(distance) <= radius){
+ break;
+ }
+ }
+ for(y_max = this._ySlices - 1; y_max > x_min; y_max--){
+ let distance;
+ let w = -yz + y_max * y_step;
+ distance = count_y_distance(light_pos, w);
+ if(Math.abs(distance) <= radius){
+ break;
+ }
+ }
+ // update buffer
+ for(let z = z_min; z <= z_max; z++){
+ for(let y = y_min; y <= y_max; y++){
+ for(let x = x_min; x <= x_max; x++){
+ let i = x + y * this._xSlices + z * this._xSlices*this._ySlices;
+ let count = this._clusterTexture.buffer[this._clusterTexture.bufferIndex(i, 0)];
+ count++;
+ if(count <= MAX_LIGHTS_PER_CLUSTER){
+ this._clusterTexture.buffer[this._clusterTexture.bufferIndex(i, 0)] = count;
+ let floor = Math.floor(count / 4.0);
+ let i_new = this._clusterTexture.bufferIndex(i, floor);
+ this._clusterTexture.buffer[i_new + (count - floor * 4)] = light_index;
+ }else break;
+ }
+ }
+ }
+
+ }
+
this._clusterTexture.update();
}
}
\ No newline at end of file
diff --git a/src/renderers/clustered.js b/src/renderers/clustered.js
index 46b8278..3893d54 100755
--- a/src/renderers/clustered.js
+++ b/src/renderers/clustered.js
@@ -1,5 +1,5 @@
import { gl, WEBGL_draw_buffers, canvas } from '../init';
-import { mat4, vec4 } from 'gl-matrix';
+import { mat4, vec4, vec3 } from 'gl-matrix';
import { loadShaderProgram, renderFullscreenQuad } from '../utils';
import { NUM_LIGHTS } from '../scene';
import toTextureVert from '../shaders/deferredToTexture.vert.glsl';
@@ -7,162 +7,182 @@ 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;
+export const NUM_GBUFFERS = 2;
export default class ClusteredRenderer extends BaseRenderer {
- constructor(xSlices, ySlices, 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);
+ constructor(xSlices, ySlices, 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,
+ xSlices: xSlices,
+ ySlices: ySlices,
+ zSlices: zSlices,
+ maxLightPerCluster: MAX_LIGHTS_PER_CLUSTER,
+ width:canvas.width, height:canvas.height
+ }), {
+ uniforms: ['u_gbuffers[0]', 'u_gbuffers[1]', 'u_gbuffers[2]', 'u_gbuffers[3]','u_lightbuffer', 'u_clusterbuffer',
+ 'u_camera_view_mat', 'u_camera_near','u_camera_far', 'u_camera_pos'],
+ attribs: ['a_position', 'a_uv'],
+ });
+
+ this._projectionMatrix = mat4.create();
+ this._viewMatrix = mat4.create();
+ this._viewProjectionMatrix = mat4.create();
}
- if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE) {
- throw "Framebuffer incomplete";
+ 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);
}
- // 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);
- }
-
- 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];
+ 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);
}
- // 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);
+ 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
+ gl.uniform1f(this._progShade.u_camera_far, camera.far);
+ gl.uniform1f(this._progShade.u_camera_near, camera.near);
+ gl.uniform3f(this._progShade.u_camera_pos, camera.position.x, camera.position.y, camera.position.z);
+ gl.uniformMatrix4fv(this._progShade.u_camera_view_mat, false, this._viewMatrix);
+
+ // Set the light texture as a uniform input to the shader
+ gl.activeTexture(gl.TEXTURE0);
+ gl.bindTexture(gl.TEXTURE_2D, this._lightTexture.glTexture);
+ gl.uniform1i(this._progShade.u_lightbuffer, 0);
+
+ // Set the cluster texture as a uniform input to the shader
+ gl.activeTexture(gl.TEXTURE1);
+ gl.bindTexture(gl.TEXTURE_2D, this._clusterTexture.glTexture);
+ gl.uniform1i(this._progShade.u_clusterbuffer, 1);
+
+ // Bind g-buffers
+ 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]);
+ gl.uniform1i(this._progShade[`u_gbuffers[${i}]`], i + firstGBufferBinding);
+ }
+
+ renderFullscreenQuad(this._progShade);
}
-
- renderFullscreenQuad(this._progShade);
- }
-};
+};
\ No newline at end of file
diff --git a/src/renderers/forwardPlus.js b/src/renderers/forwardPlus.js
index a02649c..decf3b2 100755
--- a/src/renderers/forwardPlus.js
+++ b/src/renderers/forwardPlus.js
@@ -5,7 +5,7 @@ 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 BaseRenderer, {MAX_LIGHTS_PER_CLUSTER} from './base';
export default class ForwardPlusRenderer extends BaseRenderer {
constructor(xSlices, ySlices, zSlices) {
@@ -13,12 +13,14 @@ export default class ForwardPlusRenderer extends BaseRenderer {
// Create a texture to store light data
this._lightTexture = new TextureBuffer(NUM_LIGHTS, 8);
-
+
this._shaderProgram = loadShaderProgram(vsSource, fsSource({
- numLights: NUM_LIGHTS,
+ numLights: NUM_LIGHTS, xSlices: xSlices, ySlices: ySlices, zSlices: zSlices,
+ maxLightsPerCluster: MAX_LIGHTS_PER_CLUSTER, width: canvas.width, height: canvas.height,
}), {
- uniforms: ['u_viewProjectionMatrix', 'u_colmap', 'u_normap', 'u_lightbuffer', 'u_clusterbuffer'],
- attribs: ['a_position', 'a_normal', 'a_uv'],
+ uniforms: ['u_viewProjectionMatrix', 'u_colmap', 'u_normap', 'u_lightbuffer',
+ 'u_clusterbuffer', 'u_camera_near', 'u_camera_far', 'u_camera_view_mat'],
+ attribs: ['a_position', 'a_normal', 'a_uv'],
});
this._projectionMatrix = mat4.create();
@@ -64,6 +66,7 @@ export default class ForwardPlusRenderer extends BaseRenderer {
// Upload the camera matrix
gl.uniformMatrix4fv(this._shaderProgram.u_viewProjectionMatrix, false, this._viewProjectionMatrix);
+ gl.uniformMatrix4fv(this._shaderProgram.u_camera_view_mat, false, this._viewMatrix);
// Set the light texture as a uniform input to the shader
gl.activeTexture(gl.TEXTURE2);
@@ -76,6 +79,8 @@ export default class ForwardPlusRenderer extends BaseRenderer {
gl.uniform1i(this._shaderProgram.u_clusterbuffer, 3);
// TODO: Bind any other shader inputs
+ gl.uniform1f(this._shaderProgram.u_camera_near, camera.near);
+ gl.uniform1f(this._shaderProgram.u_camera_far, 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);
diff --git a/src/shaders/deferred.frag.glsl.js b/src/shaders/deferred.frag.glsl.js
index 50f1e75..9f1860e 100644
--- a/src/shaders/deferred.frag.glsl.js
+++ b/src/shaders/deferred.frag.glsl.js
@@ -1,20 +1,134 @@
export default function(params) {
- return `
+ return `
#version 100
precision highp float;
uniform sampler2D u_gbuffers[${params.numGBuffers}];
+ uniform sampler2D u_clusterbuffer;
+ uniform sampler2D u_lightbuffer;
+
+ uniform mat4 u_camera_view_mat;
+ uniform float u_camera_near;
+ uniform float u_camera_far;
+ uniform vec3 u_camera_pos;
+
varying vec2 v_uv;
+ 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 gb2 = texture2D(u_gbuffers[2], v_uv);
- // vec4 gb3 = texture2D(u_gbuffers[3], v_uv);
+ vec4 gb0 = texture2D(u_gbuffers[0], v_uv);
+ vec4 gb1 = texture2D(u_gbuffers[1], v_uv);
+
+ vec3 v_position = gb0.xyz;
+ vec3 albedo = gb1.rgb;
+ float theta = gb0[3];
+ float phi = gb1[3];
+ vec3 normal = vec3(cos(phi)*cos(theta), cos(phi) * sin(theta), sin(phi));
+
+ vec4 fragment_pos = u_camera_view_mat * vec4(v_position, 1.0);
+ int x = int(gl_FragCoord.x * float(${params.xSlices}) / float(${params.width}));
+ int y = int(gl_FragCoord.y * float(${params.ySlices}) / float(${params.height}));
+ int z = 0;
+ if (-fragment_pos.z > u_camera_near){
+ z = int((-fragment_pos.z - u_camera_near) / (float(u_camera_far - u_camera_near) / float(${params.zSlices})));
+ }
+
+ int index = x + y * ${params.xSlices} + z * ${params.xSlices} * ${params.ySlices};
+ float ratio = float(index + 1)/float(${params.xSlices} * ${params.ySlices} * ${params.zSlices} + 1);
+ int num_clustered = int (texture2D(u_clusterbuffer, vec2(ratio, 0.0)).r);
+ int fragment_count = int((${params.maxLightPerCluster} + 1) / 4) + 1;
+ vec3 specular = vec3(1.0);
+ vec3 ambientColor = vec3(0.8);
+ vec3 fragColor = vec3(0.0);
+
+ for (int i = 0; i < ${params.numLights}; ++i) {
+ if (i >= num_clustered) {
+ break;
+ }
+
+ int index_1 = int((i+1)/4);
+ float ratio_1 = float(index_1)/ float(fragment_count);
+ vec4 element = texture2D(u_clusterbuffer, vec2(ratio,ratio_1));
+ int pos = i + 1 - 4 * index_1;
+ int idx;
+ if (pos == 0){
+ idx = int(element[0]);
+ }else if (pos == 1){
+ idx = int(element[1]);
+ }else if (pos == 2){
+ idx = int(element[2]);
+ }else if (pos == 3){
+ idx = int(element[3]);
+ }
+
+ Light light = UnpackLight(idx);
+ float lightDistance = distance(light.position, v_position);
+ vec3 L = (light.position - v_position) / lightDistance;
+ vec3 eye_dir = normalize(u_camera_pos - v_position);
+
+ float lightIntensity = cubicGaussian(2.0 * lightDistance / light.radius);
+ float lambertTerm = max(dot(L, normal), 0.0);
+
+ specular *= pow(max(dot(normalize(L + eye_dir), normal), 0.0), 10.0);
+
+ //fragColor += vec3(lightIntensity) * ambientColor * light.color * (0.1 + albedo * lambertTerm + specular);
+
- gl_FragColor = vec4(v_uv, 0.0, 1.0);
+ fragColor += albedo * lambertTerm * light.color * vec3(lightIntensity);
+ }
+
+ gl_FragColor = vec4(fragColor, 1.0);
+ //gl_FragColor = vec4(v_uv, 0.0, 1.0);
}
`;
}
\ No newline at end of file
diff --git a/src/shaders/deferredToTexture.frag.glsl b/src/shaders/deferredToTexture.frag.glsl
index bafc086..eb491a1 100644
--- a/src/shaders/deferredToTexture.frag.glsl
+++ b/src/shaders/deferredToTexture.frag.glsl
@@ -21,9 +21,13 @@ void main() {
vec3 norm = applyNormalMap(v_normal, vec3(texture2D(u_normap, v_uv)));
vec3 col = vec3(texture2D(u_colmap, v_uv));
+ norm = normalize(norm);
+ float phi = asin(norm.z);
+ float theta = atan(norm.y, norm.x);
+
// TODO: populate your g buffer
- // gl_FragData[0] = ??
- // gl_FragData[1] = ??
- // gl_FragData[2] = ??
+ gl_FragData[0] = vec4(v_position, theta);
+ gl_FragData[1] = vec4(col, phi);
+ //gl_FragData[2] = vec3(norm, 0);
// gl_FragData[3] = ??
}
\ No newline at end of file
diff --git a/src/shaders/forwardPlus.frag.glsl.js b/src/shaders/forwardPlus.frag.glsl.js
index 022fda7..bd8f478 100644
--- a/src/shaders/forwardPlus.frag.glsl.js
+++ b/src/shaders/forwardPlus.frag.glsl.js
@@ -11,6 +11,13 @@ export default function(params) {
// TODO: Read this buffer to determine the lights influencing a cluster
uniform sampler2D u_clusterbuffer;
+
+ uniform float u_screen_width;
+ uniform float u_screen_height;
+
+ uniform float u_camera_near;
+ uniform float u_camera_far;
+ uniform mat4 u_camera_view_mat;
varying vec3 v_position;
varying vec3 v_normal;
@@ -78,18 +85,50 @@ export default function(params) {
vec3 albedo = texture2D(u_colmap, v_uv).rgb;
vec3 normap = texture2D(u_normap, v_uv).xyz;
vec3 normal = applyNormalMap(v_normal, normap);
-
+
+ vec4 fragment_pos = u_camera_view_mat * vec4(v_position, 1.0);
+
+ int x = int(gl_FragCoord.x / (float(u_screen_width) / float(${params.xSlices})));
+ int y = int(gl_FragCoord.y / (float(u_screen_height) / float(${params.ySlices})));
+ int z = int((-fragment_pos.z) / (float(u_camera_far - u_camera_near) / float(${params.zSlices})));
+
+ int cluster_index = x + y * ${params.xSlices} + z * ${params.ySlices} * ${params.zSlices};
+ int cluster_count = ${params.xSlices} * ${params.ySlices} * ${params.zSlices};
+
+ float ratio = float(cluster_index + 1) / float(cluster_count + 1);
+ int light_count = int(texture2D(u_clusterbuffer, vec2(ratio , 0)).r);
+ int element_size = int(float(${params.maxLightsPerCluster} + 1) / 4.0);
+
vec3 fragColor = vec3(0.0);
+
for (int i = 0; i < ${params.numLights}; ++i) {
- Light light = UnpackLight(i);
+ if(i >= light_count){
+ break;
+ }
+ int index = int((i + 1) / 4);
+ float ratio_1 = float(index + 1)/float(element_size + 1);
+ vec4 element = texture2D(u_clusterbuffer, vec2(ratio, ratio_1));
+ int pos = i + 1 - index * 4;
+ int idx;
+ if(pos == 0){
+ idx = int(element[0]);
+ }else if(pos == 1){
+ idx = int(element[1]);
+ }else if(pos == 2){
+ idx = int(element[2]);
+ }else if(pos == 3){
+ idx = int(element[3]);
+ }
+
+ Light light = UnpackLight(idx);
float lightDistance = distance(light.position, v_position);
vec3 L = (light.position - v_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);
+
+ fragColor += albedo * lambertTerm * light.color * vec3(lightIntensity);
}
const vec3 ambientLight = vec3(0.025);