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