diff --git a/README.md b/README.md index ec3c73b..a253edf 100755 --- a/README.md +++ b/README.md @@ -3,26 +3,62 @@ 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) +* Yu Sun +* [LinkedIn](https://www.linkedin.com/in/yusun3/) +* Tested on: Tested on: Windows 10 , i7-6700HQ CPU @ 2.60GHz × 8 , GeForce GTX 960M/PCIe/SSE2, 7.7GB Memory (Personal Laptop) +## Introduction +In the previous project, a basic shading method was implemented. However, we notice that the implementation was not +very efficient since we were doing multiple computation such as vertex transformation and rasterization many times for +each object even if not all lights would affect a single project. Therefore, in this project, more efficient shading methods such as +forward plus shading and clustered deferred shading is implemented and compared. -### Live Online +``` +* Clustered forward plus shading +* clustered deferred shading with Blinn-Phong effect +* G-buffer usage optimization with 2-component normal +``` -[![](img/thumb.png)](http://TODO.github.io/Project5B-WebGL-Deferred-Shading) +### Clusters +Both the forward plus shading and deferred shading take advantage of the concept of clusters. The idea is that +computing shading for each object with each light is too computation expensive. To save the amount of computation needed, +we divide the whole frame into grids, and define a radius of influence for the lights. Thus, we save the amount of +computation by only computing lights that would have an influence on the object. + +### Forward Shading and Deferred Shading +For forward shading, we are doing computation one step by one step, and ends up computing many times for objects that are actually +occluded in the scene. This is not very efficient when there are a huge number of lights. + +For deferred shading, we store the depth, normals, colors and other information we needed into a g-buffer, and do the +shading for only objects that's visible in the scene. However, deferred shading does suffer from limitation of memory +bandwidth since it reads from g-buffer for each light. -### Demo Video/GIF +## Live Online + +Wait to be added. +[![](img/thumb.png)](http://TODO.github.io/Project5B-WebGL-Deferred-Shading) -[![](img/video.png)](TODO) +## Demo Video/GIF -### (TODO: Your README) +![](img/demo.gif) -*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. +## Analysis +The chart belows shows the amount of time taken to render each frame (averaged across 10 iterations) for the three shading +methods introduced. +![](img/pa.png) +It is clear that when the number of lights in the scene are small, all three methods don't have a significant difference +in terms of performance. However, as the number of lights increase, the time required for the naive forward shading method +increases significantly. While the clustered deferred shading keeps about the same performance. -This assignment has a considerable amount of performance analysis compared -to implementation work. Complete the implementation early to leave time! +I also use 2-component normal to reduce the g-buffer size. Therefore, reducing the time that would require to read from +memory. However, I only have the implementation for two component g-buffer so I don't have the exact number of the amount +of time it saves. However, rationally I would think the gain over time wouldn't be too significant. Since the g-buffer +are stored in continuous memory, and when the program reads it can make use of cache as well. The significance however, +lies in the amount of memory that we can save from this optimization. This is significant when we have huge amount of data +that we need to store. +### Known Issue +The rendering using forward plus and clustered deferred led to some artifacts when the number of lights increase. +I don't have the time to investigate deeply but I believe this is caused by the scene division and value rounding. ### Credits diff --git a/img/.DS_Store b/img/.DS_Store new file mode 100644 index 0000000..686e110 Binary files /dev/null and b/img/.DS_Store differ diff --git a/img/demo.gif b/img/demo.gif new file mode 100644 index 0000000..8ec65bd Binary files /dev/null and b/img/demo.gif differ diff --git a/img/pa.png b/img/pa.png new file mode 100644 index 0000000..f52afcf Binary files /dev/null and b/img/pa.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/main.js b/src/main.js index 0438a6d..791c3f0 100755 --- a/src/main.js +++ b/src/main.js @@ -9,7 +9,7 @@ const FORWARD_PLUS = 'Forward+'; const CLUSTERED = 'Clustered'; const params = { - renderer: FORWARD_PLUS, + renderer: CLUSTERED, _renderer: null, }; @@ -38,9 +38,22 @@ camera.position.set(-10, 8, 0); cameraControls.target.set(0, 2, 0); gl.enable(gl.DEPTH_TEST); +var count = 0; +var time = 0; +var prev_time = 0; function render() { + count++; + if (count >= 10) count = 0; scene.update(); params._renderer.render(camera, scene); + let t1 = performance.now(); + time += (t1 - prev_time); + prev_time = t1; + if (count === 0 ){ + console.log("Rendering took " + time/10 + " milliseconds."); + time = 0; + } + } makeRenderLoop(render)(); \ No newline at end of file diff --git a/src/renderers/base.js b/src/renderers/base.js index 8a975b9..f931838 100755 --- 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, vec3, vec4 } from 'gl-matrix'; export const MAX_LIGHTS_PER_CLUSTER = 100; +function getDistanceX(lightPos, x){ + let xNormal = vec3.fromValues(1.0 / Math.sqrt(x * x + 1), 0 , -x / Math.sqrt(x * x + 1)); + return vec3.dot(vec3.fromValues(lightPos[0], lightPos[1], lightPos[2]), xNormal); +} + +function getDistanceY(lightPos, y){ + let yNormal = vec3.fromValues(0, 1.0 / Math.sqrt(y * y + 1), -y / Math.sqrt(y * y + 1)); + return vec3.dot(vec3.fromValues(lightPos[0], lightPos[1], lightPos[2]), yNormal); +} + export default class BaseRenderer { - constructor(xSlices, ySlices, zSlices) { - // Create a texture to store cluster data. Each cluster stores the number of lights followed by the light indices - this._clusterTexture = new TextureBuffer(xSlices * ySlices * zSlices, MAX_LIGHTS_PER_CLUSTER + 1); - this._xSlices = xSlices; - this._ySlices = ySlices; - this._zSlices = zSlices; - } - - updateClusters(camera, viewMatrix, scene) { - // TODO: Update the cluster texture with the count and indices of the lights in each cluster - // This will take some time. The math is nontrivial... - - for (let z = 0; z < this._zSlices; ++z) { - for (let y = 0; y < this._ySlices; ++y) { - for (let x = 0; x < this._xSlices; ++x) { - let i = x + y * this._xSlices + z * this._xSlices * this._ySlices; - // Reset the light count to 0 for every cluster - this._clusterTexture.buffer[this._clusterTexture.bufferIndex(i, 0)] = 0; - } - } + 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; } - this._clusterTexture.update(); - } + 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; + } + } + } + + // with 'z-normalized' + const y_half = Math.tan(camera.fov * 0.5 * Math.PI /180.0); + const x_half = camera.aspect * y_half; + const y_step = y_half * 2.0 / this._ySlices; + const x_step = x_half * 2.0 / this._xSlices; + const z_step = (camera.far - camera.near) / this._zSlices; + + + for (let i = 0; i < NUM_LIGHTS; i++){ + + let xMin, xMax, yMin, yMax, zMin, zMax; + let lightPos = vec4.fromValues(scene.lights[i].position[0], scene.lights[i].position[1], + scene.lights[i].position[2], 1.0); + vec4.transformMat4(lightPos, lightPos, viewMatrix); + lightPos[2] *= -1.0; + + let lightRadius = scene.lights[i].radius; + let z = lightPos[2] - camera.near; + + zMin = Math.floor((z - lightRadius) / z_step); + zMax = Math.floor((z + lightRadius) / z_step); + + if (zMin >= this._zSlices || zMax < 0) continue; + zMin = Math.max(0, zMin); + zMax = Math.min(zMax, this._zSlices - 1); + + + for (xMin = 0; xMin <= this._xSlices; xMin++) { + let xSeg = -x_half + xMin * x_step; + let dist = getDistanceX(lightPos, xSeg); + if (Math.abs(dist) < lightRadius) break; + } + + for (xMax = this._xSlices; xMax >= xMin; xMax--){ + let xSeg = -x_half + xMax * x_step; + let dist = getDistanceX(lightPos, xSeg); + if (Math.abs(dist) < lightRadius) break; + } + + for (yMin = 0; yMin <= this._ySlices; yMin++){ + let ySeg = -y_half + yMin * y_step; + let dist = getDistanceY(lightPos, ySeg); + if (Math.abs(dist) < lightRadius) break; + } + + for (yMax = this._ySlices; yMax >= yMin; yMax--){ + let ySeg = -y_half + yMax * y_step; + let dist = getDistanceY(lightPos, ySeg); + if (Math.abs(dist) < lightRadius) break; + } + + for (let x = xMin; x < xMax; x++){ + for(let y = yMin; y < yMax; y++){ + for(let z = zMin; z <= zMax; z++){ + let pixel = x + y * this._xSlices + z * this._xSlices * this._ySlices; + let num_light = this._clusterTexture.buffer[this._clusterTexture.bufferIndex(pixel, 0)] + 1; + if (num_light > MAX_LIGHTS_PER_CLUSTER) break; + this._clusterTexture.buffer[this._clusterTexture.bufferIndex(pixel, 0)] += 1; + let texel = this._clusterTexture.bufferIndex(pixel, Math.floor(num_light / 4.0)); + let offset = num_light - Math.floor(num_light / 4.0) * 4; + this._clusterTexture.buffer[texel + offset] = i; + } + } + } + + + } + + this._clusterTexture.update(); + } } \ No newline at end of file diff --git a/src/renderers/clustered.js b/src/renderers/clustered.js index 46b8278..da84dc7 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,9 +7,9 @@ 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) { @@ -28,9 +28,14 @@ export default class ClusteredRenderer extends BaseRenderer { 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]'], - attribs: ['a_uv'], + uniforms: ['u_gbuffers[0]', 'u_gbuffers[1]', 'u_gbuffers[2]', 'u_gbuffers[3]','u_lightbuffer', 'u_clusterbuffer', + 'u_viewMatrix', 'u_cameraNear','u_cameraFar', 'u_cameraPos'], + attribs: ['a_position', 'a_uv'], }); this._projectionMatrix = mat4.create(); @@ -154,9 +159,23 @@ export default class ClusteredRenderer extends BaseRenderer { gl.useProgram(this._progShade.glShaderProgram); // TODO: Bind any other shader inputs + gl.uniform1f(this._progShade.u_cameraFar, camera.far); + gl.uniform1f(this._progShade.u_cameraNear, camera.near); + gl.uniform3f(this._progShade.u_cameraPos, camera.position.x, camera.position.y, camera.position.z); + gl.uniformMatrix4fv(this._progShade.u_viewMatrix, 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 = 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..28a5f5b 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) { @@ -15,9 +15,11 @@ export default class ForwardPlusRenderer extends BaseRenderer { this._lightTexture = new TextureBuffer(NUM_LIGHTS, 8); this._shaderProgram = loadShaderProgram(vsSource, fsSource({ - numLights: NUM_LIGHTS, + numLights: NUM_LIGHTS, xSlices:xSlices, ySlices:ySlices, zSlices:zSlices, + maxLightPerCluster:MAX_LIGHTS_PER_CLUSTER, width:canvas.width, height:canvas.height, }), { - uniforms: ['u_viewProjectionMatrix', 'u_colmap', 'u_normap', 'u_lightbuffer', 'u_clusterbuffer'], + uniforms: ['u_viewProjectionMatrix', 'u_colmap', 'u_normap', 'u_lightbuffer', 'u_clusterbuffer', + 'u_viewMatrix', 'u_cameraNear','u_cameraFar'], attribs: ['a_position', 'a_normal', 'a_uv'], }); @@ -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_viewMatrix, 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_cameraFar, camera.far); + gl.uniform1f(this._shaderProgram.u_cameraNear, camera.near); // 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..49b7ac3 100644 --- a/src/shaders/deferred.frag.glsl.js +++ b/src/shaders/deferred.frag.glsl.js @@ -5,16 +5,130 @@ export default function(params) { uniform sampler2D u_gbuffers[${params.numGBuffers}]; + uniform sampler2D u_clusterbuffer; + uniform sampler2D u_lightbuffer; + + uniform mat4 u_viewMatrix; + uniform float u_cameraNear; + uniform float u_cameraFar; + 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); + + vec3 v_position = gb0.xyz; + vec3 albedo = gb1.rgb; + float theta = gb0[3]; + float phi = gb1[3]; + vec3 norm = vec3(cos(phi)*cos(theta), cos(phi) * sin(theta), sin(phi)); + + vec4 pos = u_viewMatrix * vec4(v_position, 1.0); + int x_cluster = int(gl_FragCoord.x * float(${params.xSlices}) / float(${params.width})); + int y_cluster = int(gl_FragCoord.y * float(${params.ySlices}) / float(${params.height})); + int z_cluster = 0; + if (-pos.z > u_cameraNear){ + z_cluster = int((-pos.z -u_cameraNear)* float(${params.zSlices}) / float(u_cameraFar - u_cameraNear)); + } + + int index = x_cluster + y_cluster * ${params.xSlices} + z_cluster * ${params.xSlices} * ${params.ySlices}; + float u = float(index + 1)/float(${params.xSlices} * ${params.ySlices} * ${params.zSlices} + 1); + int num_clustered = int (texture2D(u_clusterbuffer, vec2(u, 0.0))[0]); + vec3 specular = vec3(1.0); + vec3 ambientColor = vec3(0.9); + vec3 fragColor = vec3(0.0); + + for (int i = 0; i < ${params.numLights}; ++i) { + // a lot of offset of 1 errors !! + if (i >= num_clustered) break; + int V = int((${params.maxLightPerCluster} + 1) / 4) + 1; + int vi = int((i+1)/4); + // only this way can get the correct fraction + float v = float(vi)/ float(V); + vec4 pixel = texture2D(u_clusterbuffer, vec2(u,v)); + int offset = i + 1 - 4 * vi; + int idx; + if (offset == 0){ + idx = int(pixel[0]); + }else if (offset == 1){ + idx = int(pixel[1]); + }else if (offset == 2){ + idx = int(pixel[2]); + }else if (offset == 3){ + idx = int(pixel[3]); + } + + Light light = UnpackLight(idx); + float lightDistance = distance(light.position, v_position); + vec3 L = (light.position - v_position) / lightDistance; + vec3 eyeDir = normalize(u_cameraPos - v_position); + + float lightIntensity = cubicGaussian(2.0 * lightDistance / light.radius); + float lambertTerm = max(dot(L, norm), 0.0); + + specular *= pow(max(dot(normalize(L + eyeDir), norm), 0.0), 16.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..641fe7f 100644 --- a/src/shaders/deferredToTexture.frag.glsl +++ b/src/shaders/deferredToTexture.frag.glsl @@ -21,9 +21,15 @@ 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 theta = atan(norm.y, norm.x); + float phi = asin(norm.z); + gl_FragData[0] = vec4(v_position, theta); + gl_FragData[1] = vec4(col, phi); + // TODO: populate your g buffer - // gl_FragData[0] = ?? - // gl_FragData[1] = ?? - // gl_FragData[2] = ?? + // gl_FragData[0] = vec4(v_pos, norm.x); + // gl_FragData[1] = vec4(col, norm.y); + // gl_FragData[2] = vec4(norm.z, 0., 0., 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..30d3ae3 100644 --- a/src/shaders/forwardPlus.frag.glsl.js +++ b/src/shaders/forwardPlus.frag.glsl.js @@ -11,6 +11,10 @@ export default function(params) { // TODO: Read this buffer to determine the lights influencing a cluster uniform sampler2D u_clusterbuffer; + + uniform mat4 u_viewMatrix; + uniform float u_cameraNear; + uniform float u_cameraFar; varying vec3 v_position; varying vec3 v_normal; @@ -78,11 +82,41 @@ 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); - vec3 fragColor = vec3(0.0); - + + vec4 pos = u_viewMatrix * vec4(v_position, 1.0); + int x_cluster = int(gl_FragCoord.x * float(${params.xSlices}) / float(${params.width})); + int y_cluster = int(gl_FragCoord.y * float(${params.ySlices}) / float(${params.height})); + int z_cluster = 0; + if (-pos.z > u_cameraNear){ + z_cluster = int((-pos.z -u_cameraNear)* float(${params.zSlices}) / float(u_cameraFar - u_cameraNear)); + } + + int index = x_cluster + y_cluster * ${params.xSlices} + z_cluster * ${params.xSlices} * ${params.ySlices}; + float u = float(index + 1)/float(${params.xSlices} * ${params.ySlices} * ${params.zSlices} + 1); + int num_clustered = int (texture2D(u_clusterbuffer, vec2(u, 0.0))[0]); + for (int i = 0; i < ${params.numLights}; ++i) { - Light light = UnpackLight(i); + // a lot of offset of 1 errors !! + if (i >= num_clustered) break; + int V = int((${params.maxLightPerCluster} + 1)/4 + 1); + int vi = int((i+1)/4); + // only this way can get the correct fraction + float v = float(vi)/ float(V); + vec4 pixel = texture2D(u_clusterbuffer, vec2(u,v)); + int offset = i + 1 - 4 * vi; + int idx; + if (offset == 0){ + idx = int(pixel[0]); + }else if (offset == 1){ + idx = int(pixel[1]); + }else if (offset == 2){ + idx = int(pixel[2]); + }else if (offset == 3){ + idx = int(pixel[3]); + } + + Light light = UnpackLight(idx); float lightDistance = distance(light.position, v_position); vec3 L = (light.position - v_position) / lightDistance;