diff --git a/README.md b/README.md
index ec3c73b..24cb0a8 100755
--- a/README.md
+++ b/README.md
@@ -3,26 +3,65 @@ 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)
+* Liam Dugan -- Fall 2018
+ * [LinkedIn](https://www.linkedin.com/in/liam-dugan-95a961135/), [personal website](http://liamdugan.com/)
+* Tested on: **Google Chrome 71.0** Windows 10, Intel(R) Xeon(R) CPU E5-2687W v3 @ 3.10GHz 32GB, TITAN V 28.4GB (Lab Computer)
+
+
+
### Live Online
-[](http://TODO.github.io/Project5B-WebGL-Deferred-Shading)
+[Click here to see the project in action!](http://liamdugan.github.io/Project5-WebGL-Clustered-Deferred-Forward-Plus)
+
+What are Forward+ and Clustered Rendering?
+=============
+
+Forward+ and Clustered are special rendering techniques used to optimize lighting calculations in scenes with many moving light sources.
+
+
+
+Usually, the way light calculations are handled is to loop over all lights in the scene for each fragment
+(i.e. pixel) and calculate the effect each and every light has on the pixel for every frame.
+This is clearly not the most efficient way, since there will be many distant lights with little to no effect
+on the pixel taking up valuable processing time.
+
+**Forward+ Rendering** (Or sometimes called **Tiled Rendering**), splits the screen up into individual tiles and keeps track of a
+data structure that denotes which lights are within which tiles.
+
+
+
+This data structure is then passed to the GPU so that each pixel only must loop over the lights in their specific
+cluster and not all lights in the scene.
+
+**Deferred Clustered Rendering** takes this one step further and involves the depth of each fragment as well to further
+cull unnecessary light sources. In addition, in order to avoid having to compute the fragment transforms multiple times, the rendering is broken up into two stages. The first stage computes the vertex transforms and stores all of the
+data into a G-buffer, then the second stage uses that information and the cluster data structure to compute the lighting.
+
+Performance Analysis
+================
+
+As we can see from the graph below, the performance data shows that for our small scene, the simpler algorithm is best
+even at a somewhat large number of lights. This may also be due to the power and memory capability of the GPU that I tested on.
+I tested on a TITAN V, which has plenty of computation capability and so the lack of memory reads that the
+Foward rendering technique had probably gave it a very large advantage over the very read/write heavy Forward+ and Clustered.
-### Demo Video/GIF
+
-[](TODO)
+To help combat this bottleneck I implemented a packing optimization to reduce the total number of G-buffer
+spaces used, but it only had a very minor effect towards the end.
-### (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.
+One of the other main reasons why I suspect that my code is running slow is the lack of precision when
+checking sphere frustum intersections. I check if the center of the light is within frustum orthocenter to corner + light radius units
+of the frustum center, which is simply an approximation of the actual intersection algorithm (which involves checking for 6 planes).
-This assignment has a considerable amount of performance analysis compared
-to implementation work. Complete the implementation early to leave time!
+Since the GPU is much better at handling computational work than memory reads and since the lack of precision likely amounted to
+a larger cluster to light data structure, this would slow the algorithm down a lot since it has to read and write more data.
+I suspect that if I were to have implemented this optimization then we would see the Clustered rendering perform well on very closed but long scenes with many light sources and
+we would see the forward+ rendering work well for the more open and far away scenes with many light sources. (Forward would of course do better on scenes with fewer lights).
### Credits
diff --git a/images/9-Forward-Plus.png b/images/9-Forward-Plus.png
new file mode 100644
index 0000000..d9b2632
Binary files /dev/null and b/images/9-Forward-Plus.png differ
diff --git a/images/Analysis1.png b/images/Analysis1.png
new file mode 100644
index 0000000..bd929ea
Binary files /dev/null and b/images/Analysis1.png differ
diff --git a/images/Analysis2.png b/images/Analysis2.png
new file mode 100644
index 0000000..4a2d2be
Binary files /dev/null and b/images/Analysis2.png differ
diff --git a/images/Clustered.PNG b/images/Clustered.PNG
new file mode 100644
index 0000000..4d7489e
Binary files /dev/null and b/images/Clustered.PNG differ
diff --git a/images/MoneyShot.bmp b/images/MoneyShot.bmp
new file mode 100644
index 0000000..6f9fd98
Binary files /dev/null and b/images/MoneyShot.bmp differ
diff --git a/images/final.gif b/images/final.gif
new file mode 100644
index 0000000..ca8aa78
Binary files /dev/null and b/images/final.gif differ
diff --git a/src/init.js b/src/init.js
index 885240b..850f0a0 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';
@@ -16,7 +16,7 @@ export function abort(message) {
// Get the canvas element
export const canvas = document.getElementById('canvas');
-
+
// Initialize the WebGL context
const glContext = canvas.getContext('webgl');
@@ -60,7 +60,7 @@ stats.domElement.style.top = '0px';
document.body.appendChild(stats.domElement);
// Initialize camera
-export const camera = new PerspectiveCamera(75, canvas.clientWidth / canvas.clientHeight, 0.1, 1000);
+export const camera = new PerspectiveCamera(75, canvas.clientWidth / canvas.clientHeight, 0.1, 100);
// Initialize camera controls
export const cameraControls = new OrbitControls(camera, canvas);
diff --git a/src/renderers/base.js b/src/renderers/base.js
index 8a975b9..37bc880 100755
--- a/src/renderers/base.js
+++ b/src/renderers/base.js
@@ -1,6 +1,7 @@
import TextureBuffer from './textureBuffer';
export const MAX_LIGHTS_PER_CLUSTER = 100;
+export const PI = 3.14159265359;
export default class BaseRenderer {
constructor(xSlices, ySlices, zSlices) {
@@ -15,16 +16,71 @@ export default class BaseRenderer {
// 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...
+ // view matrix takes things in view space and transforms them into world space
+ // now the question is: are the lights in view space?
+
+ // okay so we know that the lights are in world space. Now the question is:
+ // how large are these slices (especially the z one). We can assume the
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;
+
+ // number of lights in the cluster starts out as 0
+ let numLightsInCluster = 0;
+
+ // get the position of the cluster's center in camera space
+ // as well as the height width and depth
+ let fovRadians = camera.fov * (Math.PI / 180.0);
+ let frustumCenterZ = ((z + 0.5) / this._zSlices) * (camera.far - camera.near);
+
+ let frustumDepth = (camera.far - camera.near) / this._zSlices;
+ let screenHeight = 2.0 * (frustumCenterZ * Math.tan(fovRadians / 2));
+ let screenWidth = screenHeight * camera.aspect;
+ let frustumHeight = screenHeight / this._ySlices;
+ let frustumWidth = screenWidth / this._xSlices;
+
+ let frustumCenterY = (y + 0.5) * frustumHeight;
+ let frustumCenterX = (x + 0.5) * frustumWidth;
+
+ let frustumDiagonalLength = Math.sqrt(Math.pow((frustumWidth / 2.0), 2) +
+ Math.pow((frustumHeight / 2.0), 2) +
+ Math.pow((frustumDepth / 2.0), 2));
+
+ // now we loop through each light and see if it's within the cluster (using the view matrix)
+ for (let j = 0; j < scene.lights.length; ++j) {
+
+ // TODO: Get the w for perspective divide***
+ // get the light's position in camera space by *manually* multiplying the view matrix *sigh*
+ let lightCenterX = viewMatrix[0] * scene.lights[j].position[0] +
+ viewMatrix[4] * scene.lights[j].position[1] +
+ viewMatrix[8] * scene.lights[j].position[2] +
+ viewMatrix[12] * 1;
+ let lightCenterY = viewMatrix[1] * scene.lights[j].position[0] +
+ viewMatrix[5] * scene.lights[j].position[1] +
+ viewMatrix[9] * scene.lights[j].position[2] +
+ viewMatrix[13] * 1;
+ let lightCenterZ = viewMatrix[2] * scene.lights[j].position[0] +
+ viewMatrix[6] * scene.lights[j].position[1] +
+ viewMatrix[10] * scene.lights[j].position[2] +
+ viewMatrix[14] * 1;
+
+ let distance = Math.sqrt(Math.pow(frustumCenterX - lightCenterX, 2) +
+ Math.pow(frustumCenterY - lightCenterY, 2));
+
+ // If the sphere intersects the cluster
+ if (distance < (scene.lights[j].radius*10 + frustumDiagonalLength) && numLightsInCluster < MAX_LIGHTS_PER_CLUSTER)
+ {
+ numLightsInCluster += 1;
+ this._clusterTexture.buffer[this._clusterTexture.bufferIndex(i, Math.floor(numLightsInCluster/4)) + (numLightsInCluster % 4)] = j;
+ }
+ }
+
// Reset the light count to 0 for every cluster
- this._clusterTexture.buffer[this._clusterTexture.bufferIndex(i, 0)] = 0;
+ this._clusterTexture.buffer[this._clusterTexture.bufferIndex(i, 0)] = numLightsInCluster;
}
}
}
-
this._clusterTexture.update();
}
}
\ No newline at end of file
diff --git a/src/renderers/clustered.js b/src/renderers/clustered.js
index 46b8278..74acfb5 100755
--- a/src/renderers/clustered.js
+++ b/src/renderers/clustered.js
@@ -9,7 +9,7 @@ import fsSource from '../shaders/deferred.frag.glsl.js';
import TextureBuffer from './textureBuffer';
import BaseRenderer from './base';
-export const NUM_GBUFFERS = 4;
+export const NUM_GBUFFERS = 3;
export default class ClusteredRenderer extends BaseRenderer {
constructor(xSlices, ySlices, zSlices) {
@@ -29,8 +29,9 @@ export default class ClusteredRenderer extends BaseRenderer {
numLights: NUM_LIGHTS,
numGBuffers: NUM_GBUFFERS,
}), {
- uniforms: ['u_gbuffers[0]', 'u_gbuffers[1]', 'u_gbuffers[2]', 'u_gbuffers[3]'],
- attribs: ['a_uv'],
+ uniforms: ['u_viewProjectionMatrix', 'u_lightbuffer', 'u_gbuffers[0]', 'u_gbuffers[1]', 'u_gbuffers[2]','u_clusterbuffer',
+ 'u_screenWidth', 'u_screenHeight', 'u_far', 'u_near', 'u_xSlices', 'u_ySlices', 'u_zSlices', 'u_cameraPos'],
+ attribs: ['a_position'],
});
this._projectionMatrix = mat4.create();
@@ -81,7 +82,6 @@ export default class ClusteredRenderer extends BaseRenderer {
// 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);
}
@@ -102,7 +102,7 @@ export default class ClusteredRenderer extends BaseRenderer {
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);
@@ -127,6 +127,9 @@ export default class ClusteredRenderer extends BaseRenderer {
// 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 clusters for the frame
+ 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];
@@ -141,9 +144,6 @@ export default class ClusteredRenderer extends BaseRenderer {
// 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);
@@ -155,8 +155,29 @@ export default class ClusteredRenderer extends BaseRenderer {
// TODO: Bind any other shader inputs
+ 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);
+
+ gl.uniformMatrix4fv(this._progShade.u_viewProjectionMatrix, false, this._viewProjectionMatrix);
+
+ gl.uniform1f(this._progShade.u_far, camera.far);
+ gl.uniform1f(this._progShade.u_near, camera.near);
+ gl.uniform1f(this._progShade.u_screenHeight, canvas.height);
+ gl.uniform1f(this._progShade.u_screenWidth, canvas.width);
+ gl.uniform1f(this._progShade.u_xSlices, this._xSlices);
+ gl.uniform1f(this._progShade.u_ySlices, this._ySlices);
+ gl.uniform1f(this._progShade.u_zSlices, this._zSlices);
+ gl.uniform3f(this._progShade.u_cameraPos, camera.position.x, camera.position.y, camera.position.z);
+
+
// Bind g-buffers
- const firstGBufferBinding = 0; // You may have to change this if you use other texture slots
+ 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]);
diff --git a/src/renderers/forwardPlus.js b/src/renderers/forwardPlus.js
index a02649c..319b546 100755
--- a/src/renderers/forwardPlus.js
+++ b/src/renderers/forwardPlus.js
@@ -13,11 +13,11 @@ 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,
}), {
- uniforms: ['u_viewProjectionMatrix', 'u_colmap', 'u_normap', 'u_lightbuffer', 'u_clusterbuffer'],
+ uniforms: ['u_viewProjectionMatrix', 'u_colmap', 'u_normap', 'u_lightbuffer', 'u_clusterbuffer',
+ 'u_screenWidth', 'u_screenHeight', 'u_far', 'u_near', 'u_xSlices', 'u_ySlices', 'u_zSlices'],
attribs: ['a_position', 'a_normal', 'a_uv'],
});
@@ -76,6 +76,13 @@ export default class ForwardPlusRenderer extends BaseRenderer {
gl.uniform1i(this._shaderProgram.u_clusterbuffer, 3);
// TODO: Bind any other shader inputs
+ gl.uniform1f(this._shaderProgram.u_far, camera.far);
+ gl.uniform1f(this._shaderProgram.u_near, camera.near);
+ gl.uniform1f(this._shaderProgram.u_screenHeight, canvas.height);
+ gl.uniform1f(this._shaderProgram.u_screenWidth, canvas.width);
+ gl.uniform1f(this._shaderProgram.u_xSlices, this._xSlices);
+ gl.uniform1f(this._shaderProgram.u_ySlices, this._ySlices);
+ gl.uniform1f(this._shaderProgram.u_zSlices, this._zSlices);
// 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..cee56ea 100644
--- a/src/shaders/deferred.frag.glsl.js
+++ b/src/shaders/deferred.frag.glsl.js
@@ -2,19 +2,139 @@ export default function(params) {
return `
#version 100
precision highp float;
+
+ uniform mat4 u_viewProjectionMatrix;
+ uniform float u_near;
+ uniform float u_far;
+ uniform float u_screenWidth;
+ uniform float u_screenHeight;
+ uniform float u_xSlices;
+ uniform float u_ySlices;
+ uniform float u_zSlices;
+ uniform vec3 u_cameraPos;
+
uniform sampler2D u_gbuffers[${params.numGBuffers}];
-
+ uniform sampler2D u_lightbuffer;
+ uniform sampler2D u_clusterbuffer;
+
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.0));
+ vec4 v2 = texture2D(u_lightbuffer, vec2(u, 0.5));
+ 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 normal = gb0.xyz;
+ vec3 albedo = vec3(gb0.w, gb1.x, gb1.y);
+ vec3 v_position = vec3(gb1.z, gb1.w, gb2.x);
+
+ // vec3 normal = texture2D(u_gbuffers[0], v_uv).xyz; // normal
+ // vec3 albedo = texture2D(u_gbuffers[1], v_uv).rgb; // albedo
+ // vec3 v_position = texture2D(u_gbuffers[2], v_uv).xyz; // world space position
+
+ vec3 fragColor = vec3(0.0);
+
+ // okay so here we gotta transform the position to screen space
+ vec4 screenSpacePos = u_viewProjectionMatrix * vec4(v_position, 1.0);
+ float oldZ = screenSpacePos.z / (u_far - u_near);
+ screenSpacePos = screenSpacePos / screenSpacePos.w;
+
+ // then figure out which cluster we're inside?
+ // so we need the screen height and width and we gotta know what z caps out at
+ float clusterWidthX = u_screenWidth / u_xSlices;
+ float clusterHeightY = u_screenHeight / u_ySlices;
+ float clusterDepthZ = (u_far - u_near) / u_zSlices;
+
+ // now to get my cluster index
+ int cx = int((((screenSpacePos.x + 1.0) / 2.0) * u_screenWidth) / clusterWidthX);
+ int cy = int((((screenSpacePos.y + 1.0) / 2.0) * u_screenHeight) / clusterHeightY);
+ int cz = int(oldZ * u_zSlices);
+ //int cz = int((((oldZ + 1.0) / 2.0) * (u_far - u_near)) / clusterDepthZ);
+ int clusterIndex = cx + cy * int(u_xSlices) + cz * int(u_xSlices) * int(u_ySlices);
+
+ // get the number of lights in the cluster
+ float numLights = ExtractFloat(u_clusterbuffer, int(u_xSlices * u_ySlices * u_zSlices), int(26), clusterIndex, 0);
+
+ for (int i = 0; i < 100; ++i) {
+ int lightIndex = int(ExtractFloat(u_clusterbuffer, int(u_xSlices * u_ySlices * u_zSlices), int(26), clusterIndex, i+1));
+ if (lightIndex != 0) {
+ Light light = UnpackLight(lightIndex);
+ 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);
+
+ // Now perform Blinn-Phong shading to calculate specular value
+ // Reference: https://learnopengl.com/Advanced-Lighting/Advanced-Lighting
+ vec3 lightDir = normalize(light.position - v_position);
+ vec3 viewDir = normalize(u_cameraPos - v_position);
+ vec3 halfwayDir = normalize(lightDir + viewDir);
+
+ float shininess = 4.0;
+ float spec = pow(max(dot(normal, halfwayDir), 0.0), shininess);
+
+ //fragColor += albedo * lambertTerm * light.color * vec3(lightIntensity) * spec;
+ fragColor += albedo * light.color * vec3(lightIntensity) * spec;
+ }
+ }
+
+ 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 bafc086..de44394 100644
--- a/src/shaders/deferredToTexture.frag.glsl
+++ b/src/shaders/deferredToTexture.frag.glsl
@@ -18,12 +18,12 @@ vec3 applyNormalMap(vec3 geomnor, vec3 normap) {
}
void main() {
- vec3 norm = applyNormalMap(v_normal, vec3(texture2D(u_normap, v_uv)));
- vec3 col = vec3(texture2D(u_colmap, v_uv));
+ vec3 albedo = texture2D(u_colmap, v_uv).rgb;
+ vec3 normap = texture2D(u_normap, v_uv).xyz;
+ vec3 normal = applyNormalMap(v_normal, normap);
// TODO: populate your g buffer
- // gl_FragData[0] = ??
- // gl_FragData[1] = ??
- // gl_FragData[2] = ??
- // gl_FragData[3] = ??
+ gl_FragData[0] = vec4(normal.x, normal.y, normal.z, albedo.r);
+ gl_FragData[1] = vec4(albedo.g, albedo.b, v_position.x, v_position.y);
+ gl_FragData[2] = vec4(v_position.z, 0.0, 0.0, 0.0);
}
\ No newline at end of file
diff --git a/src/shaders/forwardPlus.frag.glsl.js b/src/shaders/forwardPlus.frag.glsl.js
index 022fda7..9962a98 100644
--- a/src/shaders/forwardPlus.frag.glsl.js
+++ b/src/shaders/forwardPlus.frag.glsl.js
@@ -5,6 +5,15 @@ export default function(params) {
#version 100
precision highp float;
+ uniform mat4 u_viewProjectionMatrix;
+ uniform float u_near;
+ uniform float u_far;
+ uniform float u_screenWidth;
+ uniform float u_screenHeight;
+ uniform float u_xSlices;
+ uniform float u_ySlices;
+ uniform float u_zSlices;
+
uniform sampler2D u_colmap;
uniform sampler2D u_normap;
uniform sampler2D u_lightbuffer;
@@ -63,6 +72,8 @@ export default function(params) {
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) {
@@ -81,21 +92,51 @@ export default function(params) {
vec3 fragColor = vec3(0.0);
- for (int i = 0; i < ${params.numLights}; ++i) {
- Light light = UnpackLight(i);
- 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);
+ // okay so here we gotta transform the position to screen space
+ vec4 screenSpacePos = u_viewProjectionMatrix * vec4(v_position, 1.0);
+ float oldZ = screenSpacePos.z / (u_far - u_near);
+ screenSpacePos = screenSpacePos / screenSpacePos.w;
+
+ // then figure out which cluster we're inside?
+ // so we need the screen height and width and we gotta know what z caps out at
+ float clusterWidthX = u_screenWidth / u_xSlices;
+ float clusterHeightY = u_screenHeight / u_ySlices;
+ float clusterDepthZ = (u_far - u_near) / u_zSlices;
+
+ // now to get my cluster index
+ int cx = int((((screenSpacePos.x + 1.0) / 2.0) * u_screenWidth) / clusterWidthX);
+ int cy = int((((screenSpacePos.y + 1.0) / 2.0) * u_screenHeight) / clusterHeightY);
+ int cz = int(oldZ * u_zSlices);
+ //int cz = int((((oldZ + 1.0) / 2.0) * (u_far - u_near)) / clusterDepthZ);
+ int clusterIndex = cx + cy * int(u_xSlices) + cz * int(u_xSlices) * int(u_ySlices);
+
+ // get the number of lights in the cluster
+ float numLights = ExtractFloat(u_clusterbuffer, int(u_xSlices * u_ySlices * u_zSlices), int(26), clusterIndex, 0);
+
+ for (int i = 0; i < 100; ++i) {
+ int lightIndex = int(ExtractFloat(u_clusterbuffer, int(u_xSlices * u_ySlices * u_zSlices), int(26), clusterIndex, i+1));
+ if (lightIndex != 0) {
+ Light light = UnpackLight(lightIndex);
+ 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);
+
+ }
}
const vec3 ambientLight = vec3(0.025);
fragColor += albedo * ambientLight;
- gl_FragColor = vec4(fragColor, 1.0);
+
+ //gl_FragColor = vec4(fragColor, 1.0);
+ //vec4 clusters = vec4(float(cx) / u_xSlices, float(cy) / u_ySlices, float(cz) / u_zSlices, 1.0);
+ //gl_FragColor = mix(clusters, vec4(fragColor,1.0), 0.5);
+ gl_FragColor = vec4(fragColor.r, fragColor.g, fragColor.b, 1.0);
+
}
`;
}