diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..28f1ba75 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +node_modules +.DS_Store \ No newline at end of file diff --git a/README.md b/README.md index ec3c73ba..10b9a9eb 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,52 @@ WebGL Clustered and Forward+ Shading ====================== -**University of Pennsylvania, CIS 565: GPU Programming and Architecture, Project 5** +**University of Pennsylvania, CIS 565: GPU Programming and Architecture, Project 6** -* (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) +* Srinath Rajagopalan +* Tested on: Google Chrome (v78), Macbook Pro, Intel-Core-i5 8th Gen, Intel Iris Graphics -### Live Online +

+ +

-[![](img/thumb.png)](http://TODO.github.io/Project5B-WebGL-Deferred-Shading) -### Demo Video/GIF +### WebGL Shading -[![](img/video.png)](TODO) +In this project, we use the web browser using WebGL to do real-time rendering. We explore three rendering techniques and analyze their performance. At at 1000-ft view, for forward rendering, we loop through all the light sources, and for each of them we compute the interaction with all the geometries present in the scene and determine projection of a 3D vertex onto to a 2D canvas and figure out how to color it. This is accompolished by the graphics pipeline consisting of a vertex shader followed by the fragment shader (where most of the heavy-lifiting happens). -### (TODO: Your README) +## Forward Plus Rendering -*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. +The naive approach described above searches through ALL light source - geometry pair. But we can do better. We tile the 3D scene into discrete cells. For each light source we compute the tiles which will be affected by the light. Since not all lights will affect all the objects, we can reduce our search space significantly. This is similar to the uniform-grid cell construction we employed in the Boids-Flocking project. However, not all the tiles are of equal volume. We can imaging a frustum extending from camera plane a max-reach depth sliced into `(xSlice, ySLice, zSlice)` tiles. -This assignment has a considerable amount of performance analysis compared -to implementation work. Complete the implementation early to leave time! +

+ +

+ +## Deferred Rendering +In forward we compute the geometry intersection each time we loop through a light source. This can be expensive so it is better to pre-compute the intersections and store it in a geometry buffer. This way the logic to calculate lighting is decoupled from the complexity of the scene. After this, the logic for deferred rendering is pretty much the same as forward rendering. + +

+ +

+ + +## Blinn–Phong Illumination +To improve lighting, we implement lighting claculations as described in [Blinn-Phong Reflection Model](https://en.wikipedia.org/wiki/Blinn%E2%80%93Phong_reflection_model) + +We can observed the effects of the illumination below, + +Without Blinn-Phong | With Blinn-Phong | +:-------------------------:|:-------------------------: +![](data/without.png)| ![](data/with.png) + +## Performance Analysis + +

+ +

+ +Unsurprisingly, forward+ is faster than forward because we don't loop over all the lights for each vertex. Deferred rendering is faster because pre-computing the geometry intersections avoids them being calculated multiple times in the shader. ### Credits diff --git a/data/demo.gif b/data/demo.gif new file mode 100644 index 00000000..178dc474 Binary files /dev/null and b/data/demo.gif differ diff --git a/data/move1.gif b/data/move1.gif new file mode 100644 index 00000000..081f635d Binary files /dev/null and b/data/move1.gif differ diff --git a/data/move2.gif b/data/move2.gif new file mode 100644 index 00000000..3c0b05e8 Binary files /dev/null and b/data/move2.gif differ diff --git a/data/performance.png b/data/performance.png new file mode 100644 index 00000000..099ce9dd Binary files /dev/null and b/data/performance.png differ diff --git a/data/with.png b/data/with.png new file mode 100644 index 00000000..1bd2813b Binary files /dev/null and b/data/with.png differ diff --git a/data/without.png b/data/without.png new file mode 100644 index 00000000..da7731ee Binary files /dev/null and b/data/without.png differ diff --git a/src/init.js b/src/init.js index 885240b2..7e07f0ff 100644 --- 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 8a975b94..c8bb30df 100644 --- a/src/renderers/base.js +++ b/src/renderers/base.js @@ -1,30 +1,110 @@ import TextureBuffer from './textureBuffer'; +import { NUM_LIGHTS } from '../scene'; +import { mat4, vec4, vec3 } from 'gl-matrix'; 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; + // 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; + + this.nearWidth = 0.0; + this.nearHeight = 0.0; + this.farWidth = 0.0; + this.farHeight = 0.0; } 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(); + // 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; + } + } + } + + var nearHeight = 2 * camera.near * Math.tan(camera.fov * 0.5 * Math.PI/180.0); + var nearWidth = camera.aspect * nearHeight; + + this.nearHeight = nearHeight; + this.nearWidth = nearWidth; + + var farHeight = 2 * camera.far * Math.tan(camera.fov * 0.5 * Math.PI/180.0); + var farWidth = camera.aspect * farHeight; + + this.farWidth = farWidth; + this.farHeight = farHeight; + + var depth = camera.far - camera.near; + + for(let lightId = 0; lightId < NUM_LIGHTS; lightId++) { + + var lightPos = vec4.fromValues( + scene.lights[lightId].position[0], + scene.lights[lightId].position[1], + scene.lights[lightId].position[2], + 1.0, + ); + + var radius = scene.lights[lightId].radius; + + vec4.transformMat4(lightPos, lightPos, viewMatrix); + lightPos[2] = -1.0 * lightPos[2]; + + var depthAtPos = lightPos[2]; + var lambda = (Math.abs(depthAtPos) - camera.near)/(1.0*camera.far - 1.0*camera.near); + var widthAtLightPos = nearWidth + (farWidth-nearWidth)*lambda; + var heightAtLightPos = nearHeight + (farHeight-nearHeight)*lambda; + + var xstep = widthAtLightPos/(1.0*this._xSlices); + var ystep = heightAtLightPos/(1.0*this._ySlices); + var zstep = (camera.far-camera.near)/(1.0*this._zSlices); + + var xMin = parseInt(Math.floor((lightPos[0] + 0.5*widthAtLightPos - radius) / xstep)); + var xMax = parseInt(Math.floor((lightPos[0] + 0.5*widthAtLightPos + radius) / xstep)); + xMin = Math.min(Math.max(xMin, 0), this._xSlices-1); + xMax = Math.min(Math.max(xMax, 0), this._xSlices-1); + + + var yMin = parseInt(Math.floor((lightPos[1] + 0.5*heightAtLightPos - radius) / ystep)); + var yMax = parseInt(Math.floor((lightPos[1] + 0.5*heightAtLightPos + radius) / ystep));; + yMin = Math.min(Math.max(yMin, 0), this._ySlices-1); + yMax = Math.min(Math.max(yMax, 0), this._ySlices-1); + + + var zMin = parseInt(Math.floor((lightPos[2] - radius - camera.near)/zstep)); + var zMax = parseInt(Math.floor((lightPos[2] + radius - camera.near)/zstep)); + zMin = Math.min(Math.max(zMin, 0), this._zSlices-1); + zMax = Math.min(Math.max(zMax, 0), this._zSlices-1); + + //console.log(zMin, zMax, xMin, xMax); + for (let z = zMin; z <= zMax; ++z) { + for (let y = yMin; y <= yMax; ++y) { + for (let x = xMin; x <= xMax; ++x) { + let clusterIdx = x + y *this._xSlices + z *this._xSlices*this._ySlices; + + let numLights = this._clusterTexture.buffer[this._clusterTexture.bufferIndex(clusterIdx, 0)]; + if(numLights < MAX_LIGHTS_PER_CLUSTER) { + numLights = numLights + 1; + this._clusterTexture.buffer[this._clusterTexture.bufferIndex(clusterIdx, 0)] = numLights; + let row = Math.floor(numLights/4.0); + let col = numLights % 4.0; + this._clusterTexture.buffer[this._clusterTexture.bufferIndex(clusterIdx, row)+col] = lightId; + } + } + } + } + } + + this._clusterTexture.update(); } } \ No newline at end of file diff --git a/src/renderers/clustered.js b/src/renderers/clustered.js index 46b82784..f3af8e75 100644 --- 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, vec3, vec4 } from 'gl-matrix'; import { loadShaderProgram, renderFullscreenQuad } from '../utils'; import { NUM_LIGHTS } from '../scene'; import toTextureVert from '../shaders/deferredToTexture.vert.glsl'; @@ -7,7 +7,7 @@ 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; @@ -28,10 +28,20 @@ export default class ClusteredRenderer extends BaseRenderer { 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'], - }); + textureWidth: this._clusterTexture._elementCount, + textureHeight: this._clusterTexture._pixelsPerElement + }), { + uniforms: ['u_gbuffers[0]', 'u_gbuffers[1]', 'u_gbuffers[2]', 'u_gbuffers[3]', + 'u_lightbuffer', + 'u_clusterbuffer', + 'u_cameraPos', + 'u_nearWidth', 'u_nearHeight', + 'u_farWidth', 'u_farHeight', + 'u_near', 'u_far', + 'u_xSlices', 'u_ySlices', 'u_zSlices', + 'u_viewMatrix'], + attribs: ['a_uv'], + }); this._projectionMatrix = mat4.create(); this._viewMatrix = mat4.create(); @@ -153,10 +163,32 @@ export default class ClusteredRenderer extends BaseRenderer { // Use this shader program gl.useProgram(this._progShade.glShaderProgram); + // 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); + // TODO: Bind any other shader inputs + gl.uniform1f(this._progShade.u_nearWidth, this.nearWidth); + gl.uniform1f(this._progShade.u_nearHeight, this.nearHeight); + gl.uniform1f(this._progShade.u_farWidth, this.farWidth); + gl.uniform1f(this._progShade.u_farHeight, this.farHeight); + gl.uniform1f(this._progShade.u_near, camera.near); + gl.uniform1f(this._progShade.u_far, camera.far); + gl.uniform1i(this._progShade.u_xSlices, this._xSlices); + gl.uniform1i(this._progShade.u_ySlices, this._ySlices); + gl.uniform1i(this._progShade.u_zSlices, this._zSlices); + gl.uniformMatrix4fv(this._progShade.u_viewMatrix, false, this._viewMatrix); + var cameraPos = vec3.fromValues(camera.position.x,camera.position.y,camera.position.z); + gl.uniform3fv(this._progShade.u_cameraPos, cameraPos); // Bind g-buffers - const firstGBufferBinding = 0; // You may have to change this if you use other texture slots + const firstGBufferBinding = NUM_GBUFFERS; // 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]); diff --git a/src/renderers/forwardPlus.js b/src/renderers/forwardPlus.js index a02649c0..5db6d141 100644 --- a/src/renderers/forwardPlus.js +++ b/src/renderers/forwardPlus.js @@ -5,79 +5,100 @@ 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) { - super(xSlices, ySlices, zSlices); + constructor(xSlices, ySlices, zSlices) { + super(xSlices, ySlices, zSlices); - // Create a texture to store light data - this._lightTexture = new TextureBuffer(NUM_LIGHTS, 8); - - this._shaderProgram = loadShaderProgram(vsSource, fsSource({ - numLights: NUM_LIGHTS, - }), { - uniforms: ['u_viewProjectionMatrix', 'u_colmap', 'u_normap', 'u_lightbuffer', 'u_clusterbuffer'], - attribs: ['a_position', 'a_normal', 'a_uv'], - }); + // Create a texture to store light data + this._lightTexture = new TextureBuffer(NUM_LIGHTS, 8); + + this._shaderProgram = loadShaderProgram(vsSource, fsSource({ + numLights: NUM_LIGHTS, + maxLights: MAX_LIGHTS_PER_CLUSTER, + textureWidth: this._clusterTexture._elementCount, + textureHeight: this._clusterTexture._pixelsPerElement + }), { + uniforms: ['u_viewProjectionMatrix', 'u_colmap', 'u_normap', 'u_lightbuffer', + 'u_clusterbuffer', 'u_cameraPos', + 'u_nearWidth', 'u_nearHeight', + 'u_farWidth', 'u_farHeight', + 'u_near', 'u_far', + 'u_xSlices', 'u_ySlices', 'u_zSlices', + 'u_viewMatrix'], + attribs: ['a_position', 'a_normal', 'a_uv'], + }); - this._projectionMatrix = mat4.create(); - this._viewMatrix = mat4.create(); - this._viewProjectionMatrix = mat4.create(); - } + this._projectionMatrix = mat4.create(); + this._viewMatrix = mat4.create(); + this._viewProjectionMatrix = mat4.create(); + } - render(camera, scene) { - // 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(camera, scene) { + // 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); - // Update cluster texture which maps from cluster index to light list - this.updateClusters(camera, this._viewMatrix, scene); - - // 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; + // Update cluster texture which maps from cluster index to light list + this.updateClusters(camera, this._viewMatrix, scene); + + // 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(); + 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(); - // Bind the default null framebuffer which is the screen - gl.bindFramebuffer(gl.FRAMEBUFFER, null); + // Bind the default null framebuffer which is the screen + gl.bindFramebuffer(gl.FRAMEBUFFER, null); - // Render to the whole screen - gl.viewport(0, 0, canvas.width, canvas.height); + // Render to the whole screen + gl.viewport(0, 0, canvas.width, canvas.height); - // Clear the frame - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + // Clear the frame + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - // Use this shader program - gl.useProgram(this._shaderProgram.glShaderProgram); + // Use this shader program + gl.useProgram(this._shaderProgram.glShaderProgram); - // Upload the camera matrix - gl.uniformMatrix4fv(this._shaderProgram.u_viewProjectionMatrix, false, this._viewProjectionMatrix); + // Upload the camera matrix + gl.uniformMatrix4fv(this._shaderProgram.u_viewProjectionMatrix, false, this._viewProjectionMatrix); - // 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._shaderProgram.u_lightbuffer, 2); + // 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._shaderProgram.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._shaderProgram.u_clusterbuffer, 3); + // 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._shaderProgram.u_clusterbuffer, 3); - // TODO: Bind any other shader inputs + // TODO: Bind any other shader inputs + gl.uniform1f(this._shaderProgram.u_nearWidth, this.nearWidth); + gl.uniform1f(this._shaderProgram.u_nearHeight, this.nearHeight); + gl.uniform1f(this._shaderProgram.u_farWidth, this.farWidth); + gl.uniform1f(this._shaderProgram.u_farHeight, this.farHeight); + gl.uniform1f(this._shaderProgram.u_near, camera.near); + gl.uniform1f(this._shaderProgram.u_far, camera.far); + gl.uniform1i(this._shaderProgram.u_xSlices, this._xSlices); + gl.uniform1i(this._shaderProgram.u_ySlices, this._ySlices); + gl.uniform1i(this._shaderProgram.u_zSlices, this._zSlices); + gl.uniformMatrix4fv(this._shaderProgram.u_viewMatrix, false, this._viewMatrix); + var cameraPos = vec3.fromValues(camera.position.x,camera.position.y,camera.position.z); + gl.uniform3fv(this._shaderProgram.u_cameraPos, cameraPos); - // 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); - } + // 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); + } }; \ No newline at end of file diff --git a/src/scene.js b/src/scene.js index 35f6700f..6fbe7e29 100644 --- a/src/scene.js +++ b/src/scene.js @@ -8,7 +8,7 @@ export const LIGHT_RADIUS = 5.0; export const LIGHT_DT = -0.03; // TODO: This controls the number of lights -export const NUM_LIGHTS = 100; +export const NUM_LIGHTS = 150; class Scene { constructor() { diff --git a/src/shaders/deferred.frag.glsl.js b/src/shaders/deferred.frag.glsl.js index 50f1e755..a6e560ba 100644 --- a/src/shaders/deferred.frag.glsl.js +++ b/src/shaders/deferred.frag.glsl.js @@ -4,17 +4,135 @@ export default function(params) { precision highp float; uniform sampler2D u_gbuffers[${params.numGBuffers}]; - + + uniform sampler2D u_clusterbuffer; + uniform sampler2D u_lightbuffer; + uniform float u_nearWidth; + uniform float u_nearHeight; + uniform float u_farWidth; + uniform float u_farHeight; + uniform float u_far; + uniform float u_near; + uniform int u_xSlices; + uniform int u_ySlices; + uniform int u_zSlices; + uniform mat4 u_viewMatrix; + uniform vec3 u_cameraPos; + 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); + vec4 gb2 = texture2D(u_gbuffers[2], v_uv); + + vec3 v_position = gb0.rgb; + vec3 albedo = gb1.rgb; + vec3 normal = gb2.rgb; + + vec4 pos = u_viewMatrix * vec4(v_position, 1.0); + + vec3 fragColor = vec3(0.0); + + float lambda = (abs(pos.z) - u_near)/(u_far*1.0 - u_near*1.0); + float u_width = u_nearWidth + (u_farWidth-u_nearWidth)*lambda; + float u_height = u_nearHeight + (u_farHeight-u_nearHeight)*lambda; + + float xstep = u_width/float(u_xSlices); + float ystep = u_height/float(u_ySlices); + float zstep = (u_far - u_near)/float(u_zSlices); + + int x = int(floor((pos.x + 0.5*u_width)/xstep)); + int y = int(floor((pos.y + 0.5*u_height)/ystep)); + int z = int(floor((abs(pos.z) - u_near)/zstep)); + + int clusterId = x + y * u_xSlices + z * u_xSlices * u_ySlices; + + int numLights = int(ExtractFloat(u_clusterbuffer, + ${params.textureWidth}, ${params.textureHeight}, clusterId, 0)); + + for (int i = 1; i < ${params.textureHeight}*4-1; ++i) { + + if(i > numLights) { + break; + } + + int lightId = int(ExtractFloat(u_clusterbuffer, + ${params.textureWidth}, ${params.textureHeight}, clusterId, i)); + + Light light = UnpackLight(lightId); + 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); + + // Bling-Phong + vec3 lightCameraDir = normalize(light.position - u_cameraPos); + vec3 lightToPoint = normalize(light.position - v_position); + vec3 halfDir = normalize(lightCameraDir + lightToPoint); + + float spec = pow(max(dot(normal, halfDir), 0.0), 2.0); + vec3 specColor = 0.01 * spec * light.color; + + fragColor += albedo * lambertTerm * light.color * vec3(lightIntensity); + fragColor += specColor; + } + + const vec3 ambientLight = vec3(0.025); + fragColor += albedo * ambientLight; - gl_FragColor = vec4(v_uv, 0.0, 1.0); + gl_FragColor = vec4(fragColor, 1.0); } `; } \ No newline at end of file diff --git a/src/shaders/deferredToTexture.frag.glsl b/src/shaders/deferredToTexture.frag.glsl index bafc086d..3bf83186 100644 --- a/src/shaders/deferredToTexture.frag.glsl +++ b/src/shaders/deferredToTexture.frag.glsl @@ -3,7 +3,7 @@ precision highp float; uniform sampler2D u_colmap; -uniform sampler2D u_normap; +uniform sampler2D u_normap; varying vec3 v_position; varying vec3 v_normal; @@ -22,8 +22,7 @@ void main() { vec3 col = vec3(texture2D(u_colmap, v_uv)); // TODO: populate your g buffer - // gl_FragData[0] = ?? - // gl_FragData[1] = ?? - // gl_FragData[2] = ?? - // gl_FragData[3] = ?? + gl_FragData[0] = vec4(v_position, 1.0); + gl_FragData[1] = vec4(col, 1.0); + gl_FragData[2] = vec4(norm, 1.0); } \ No newline at end of file diff --git a/src/shaders/forwardPlus.frag.glsl.js b/src/shaders/forwardPlus.frag.glsl.js index 022fda7a..fa2fea33 100644 --- a/src/shaders/forwardPlus.frag.glsl.js +++ b/src/shaders/forwardPlus.frag.glsl.js @@ -11,6 +11,17 @@ export default function(params) { // TODO: Read this buffer to determine the lights influencing a cluster uniform sampler2D u_clusterbuffer; + uniform float u_nearWidth; + uniform float u_nearHeight; + uniform float u_farWidth; + uniform float u_farHeight; + uniform float u_far; + uniform float u_near; + uniform int u_xSlices; + uniform int u_ySlices; + uniform int u_zSlices; + uniform mat4 u_viewMatrix; + uniform vec3 u_cameraPos; varying vec3 v_position; varying vec3 v_normal; @@ -81,15 +92,51 @@ export default function(params) { vec3 fragColor = vec3(0.0); - for (int i = 0; i < ${params.numLights}; ++i) { - Light light = UnpackLight(i); + vec4 pos = u_viewMatrix * vec4(v_position, 1.0); + + float lambda = (abs(pos.z) - u_near)/(u_far*1.0 - u_near*1.0); + float u_width = u_nearWidth + (u_farWidth-u_nearWidth)*lambda; + float u_height = u_nearHeight + (u_farHeight-u_nearHeight)*lambda; + + float xstep = u_width/float(u_xSlices); + float ystep = u_height/float(u_ySlices); + float zstep = (u_far - u_near)/float(u_zSlices); + + int x = int(floor((pos.x + 0.5*u_width)/xstep)); + int y = int(floor((pos.y + 0.5*u_height)/ystep)); + int z = int(floor((abs(pos.z) - u_near)/zstep)); + + int clusterId = x + y * u_xSlices + z * u_xSlices * u_ySlices; + + int numLights = int(ExtractFloat(u_clusterbuffer, + ${params.textureWidth}, ${params.textureHeight}, clusterId, 0)); + + for (int i = 1; i < ${params.textureHeight}*4-1; ++i) { + + if(i > numLights) { + break; + } + + int lightId = int(ExtractFloat(u_clusterbuffer, + ${params.textureWidth}, ${params.textureHeight}, clusterId, i)); + + Light light = UnpackLight(lightId); 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); + // Bling-Phong + vec3 lightCameraDir = normalize(light.position - u_cameraPos); + vec3 lightToPoint = normalize(light.position - v_position); + vec3 halfDir = normalize(lightCameraDir + lightToPoint); + + float spec = pow(max(dot(normal, halfDir), 0.0), 2.0); + vec3 specColor = 0.01 * spec * light.color; + fragColor += albedo * lambertTerm * light.color * vec3(lightIntensity); + fragColor += specColor; } const vec3 ambientLight = vec3(0.025);