diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..70b1f675 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +node_modules +README.html \ No newline at end of file diff --git a/README.md b/README.md index ec3c73ba..5c7e23c1 100644 --- a/README.md +++ b/README.md @@ -3,28 +3,66 @@ 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) +* Klayton Wittler + * [LinkedIn](https://www.linkedin.com/in/klayton-wittler/) +* Tested on: **Google Chrome 78.0.3904.97** on + Windows 10 Pro, i7-7700K @ 4.20GHz 16.0GB, GTX 1070 8.192GB (my PC) -### Live Online +![demo](images/disco.gif) -[![](img/thumb.png)](http://TODO.github.io/Project5B-WebGL-Deferred-Shading) +## Sections -### Demo Video/GIF +* [Introduction](#introduction) +* [Live Online](#live-online) +* [Implementation](#implemenatation) + * [Forward plus](#forward-plus) + * [Clustered](#clustered) + * [Blinn-Phong](#blinn-phong) +* [Performance Analaysis](#performance-analysis) +* [Credits](#credits) -[![](img/video.png)](TODO) +## Live Online -### (TODO: Your README) +[![](images/clustered1.png)](http://klaywittler.github.io/Project6-WebGL-Clustered-Deferred-Forward-Plus) +*Note: click the image to go to online demo.* -*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. -This assignment has a considerable amount of performance analysis compared -to implementation work. Complete the implementation early to leave time! +## Implementation +This project shows how to efficiently implement a virtual disco party scene. Check out the live demo above. -### Credits +![explanation](images/explanation.png) + +### Forward Plus + +Foward plus divides the environment into tiles in screen space where the minimum and maximum depth within each tile is determined. Lights are culled by determining if they have any effect on a given tile. After this process, rendering is similar to regular forward rendering. + +### Clustered + +Deferred clusting works by splitting the process into buffers. Visibility and material is given its own shader and stores its results in a buffer and light and shading is given another shader which subsequently stores its results in another buffer. These buffers can be combined at the end to give the final image. This method is the most computationally efficient in scenes with enormous amounts of lights. + +### Blinn-Phong + +![blinn-phong](images/blinn-phong.png) + +Inorder to capture specular lighting, the [Blinn-Phong](https://en.wikipedia.org/wiki/Blinn%E2%80%93Phong_reflection_model) shading can be used. This requires the calculation of the half vector, H, and view vector, V, to do a dot product with the surface normal in order to figure out how to add on specular components of lighting. + +There seems to be some bug in the implementation of forward plus as the specular component creates a weird artifact. + +![artifact](images/artifact.gif) + +However the implemenation in clustered gives a nice disco feeling + +![disco](images/disco.gif) + +## Performance Analysis + +![performance](images/Performance.png) + +Clustered is able to best handle increases in number of lights with hardly and performance decrease as the lights increase. Forward+ sees some performance hits as the number of lights increase but seems to have platued at around 60 FPS. Foward rendering sees an exponential performance decrease as the number of lights increase. At lower numbers the results are inconclusive since the frame rate is capped at 120 FPS. + + +## Credits * [Three.js](https://github.com/mrdoob/three.js) by [@mrdoob](https://github.com/mrdoob) and contributors * [stats.js](https://github.com/mrdoob/stats.js) by [@mrdoob](https://github.com/mrdoob) and contributors diff --git a/images/Performance.png b/images/Performance.png new file mode 100644 index 00000000..151490a0 Binary files /dev/null and b/images/Performance.png differ diff --git a/images/artifact.gif b/images/artifact.gif new file mode 100644 index 00000000..5b2ff859 Binary files /dev/null and b/images/artifact.gif differ diff --git a/images/atrifact.png b/images/atrifact.png new file mode 100644 index 00000000..359411c9 Binary files /dev/null and b/images/atrifact.png differ diff --git a/images/blinn-phong.png b/images/blinn-phong.png new file mode 100644 index 00000000..46983f32 Binary files /dev/null and b/images/blinn-phong.png differ diff --git a/images/clustered1.png b/images/clustered1.png new file mode 100644 index 00000000..3d45dbd0 Binary files /dev/null and b/images/clustered1.png differ diff --git a/images/disco.gif b/images/disco.gif new file mode 100644 index 00000000..ca5fd156 Binary files /dev/null and b/images/disco.gif differ diff --git a/images/disco.png b/images/disco.png new file mode 100644 index 00000000..fd7a158a Binary files /dev/null and b/images/disco.png differ diff --git a/images/explanation.png b/images/explanation.png new file mode 100644 index 00000000..8328a084 Binary files /dev/null and b/images/explanation.png differ diff --git a/images/forwardplus.png b/images/forwardplus.png new file mode 100644 index 00000000..42d0a0d1 Binary files /dev/null and b/images/forwardplus.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..c532ca27 100644 --- a/src/renderers/base.js +++ b/src/renderers/base.js @@ -1,30 +1,96 @@ import TextureBuffer from './textureBuffer'; +import {Sphere, Box3, Vector3} from 'three'; +import { mat4, vec4, vec3, vec2 } 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; - } - - 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; + } + } + } + + let depthLength = camera.far - camera.near; + let viewHeight = 2.0 * Math.tan(camera.fov*Math.PI/360.0); + + let nearHeight = viewHeight * camera.near; + let nearWidth = viewHeight * camera.aspect * camera.near; + + let farHeight = viewHeight * camera.far; + let farWidth = viewHeight * camera.aspect * camera.far; + + let dz = depthLength / this._zSlices; + + for(let light = 0; light < scene.lights.length; light++){ + let radius = scene.lights[light].radius; + let position = scene.lights[light].position; + var screenPos = vec4.fromValues(position[0], position[1], position[2], 1.0); + vec4.transformMat4(screenPos, screenPos, viewMatrix); + screenPos[2] *= -1.0; + + let lambda = (screenPos[2] - camera.near)/depthLength; + + let width = nearWidth * (1-lambda) + farWidth * lambda; + let height = nearHeight * (1-lambda) + farHeight * lambda; + let dx = width / this._xSlices; + let dy = height / this._ySlices; + + let xStart = Math.floor((screenPos[0] - radius + width/2) / dx); + let xEnd = Math.floor((screenPos[0] + radius + width/2) / dx); + + let yStart = Math.floor((screenPos[1] - radius + height/2) / dy); + let yEnd = Math.floor((screenPos[1] + radius + height/2) / dy); + + let zStart = Math.floor((Math.abs(screenPos[2]) - radius - camera.near) / dz); + let zEnd = Math.floor((Math.abs(screenPos[2]) + radius - camera.near) / dz); + + xStart = Math.max(0, Math.min(this._xSlices - 1, xStart)); + xEnd = Math.max(0, Math.min(this._xSlices - 1, xEnd)); + + yStart = Math.max(0, Math.min(this._ySlices - 1, yStart)); + yEnd = Math.max(0, Math.min(this._ySlices - 1, yEnd)); + + zStart = Math.max(0, Math.min(this._zSlices - 1, zStart)); + zEnd = Math.max(0, Math.min(this._zSlices - 1, zEnd)); + + for (let z = zStart; z <= zEnd; ++z) { + for (let y = yStart; y <= yEnd; ++y) { + for (let x = xStart; x <= xEnd; ++x) { + let i = x + y * this._xSlices + z * this._xSlices * this._ySlices; + let lightIdx = this._clusterTexture.bufferIndex(i, 0); + let num_lights = this._clusterTexture.buffer[lightIdx] + 1; + + if(num_lights <= MAX_LIGHTS_PER_CLUSTER){ + this._clusterTexture.buffer[lightIdx] = num_lights; + + let t = Math.floor(num_lights/4.0); + let nextIdx = this._clusterTexture.bufferIndex(i, t) + num_lights - t*4;; + + this._clusterTexture.buffer[nextIdx] = light; + } + } + } + } + + } + + this._clusterTexture.update(); + } } \ No newline at end of file diff --git a/src/renderers/clustered.js b/src/renderers/clustered.js index 46b82784..3322cda2 100644 --- a/src/renderers/clustered.js +++ b/src/renderers/clustered.js @@ -8,8 +8,9 @@ import QuadVertSource from '../shaders/quad.vert.glsl'; import fsSource from '../shaders/deferred.frag.glsl.js'; import TextureBuffer from './textureBuffer'; import BaseRenderer from './base'; +import { MAX_LIGHTS_PER_CLUSTER } from './base.js' -export const NUM_GBUFFERS = 4; +export const NUM_GBUFFERS = 2; export default class ClusteredRenderer extends BaseRenderer { constructor(xSlices, ySlices, zSlices) { @@ -28,8 +29,13 @@ export default class ClusteredRenderer extends BaseRenderer { this._progShade = loadShaderProgram(QuadVertSource, fsSource({ numLights: NUM_LIGHTS, numGBuffers: NUM_GBUFFERS, + maxLightsPerCluster: MAX_LIGHTS_PER_CLUSTER, + slicesX: xSlices, + slicesY: ySlices, + slicesZ: zSlices, }), { - uniforms: ['u_gbuffers[0]', 'u_gbuffers[1]', 'u_gbuffers[2]', 'u_gbuffers[3]'], + uniforms: ['u_gbuffers[0]', 'u_gbuffers[1]', 'u_gbuffers[2]', 'u_gbuffers[3]', + 'u_lightbuffer', 'u_clusterbuffer', 'u_viewMatrix', 'u_near', 'u_far', 'u_screenW', 'u_screenH'], attribs: ['a_uv'], }); @@ -41,7 +47,7 @@ export default class ClusteredRenderer extends BaseRenderer { setupDrawBuffers(width, height) { this._width = width; this._height = height; - + this._fbo = gl.createFramebuffer(); //Create, bind, and store a depth target texture for the FBO @@ -153,7 +159,24 @@ export default class ClusteredRenderer extends BaseRenderer { // Use this shader program gl.useProgram(this._progShade.glShaderProgram); - // TODO: Bind any other shader inputs + // 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.uniformMatrix4fv(this._progCopy.u_viewMatrix, false, this._viewMatrix); + + gl.uniformMatrix4fv(this._progShade.u_viewMatrix, false, this._viewMatrix); + gl.uniform1f(this._progShade.u_near, camera.near); + gl.uniform1f(this._progShade.u_far, camera.far); + gl.uniform1f(this._progShade.u_screenW, canvas.width); + gl.uniform1f(this._progShade.u_screenH, canvas.height); // Bind g-buffers const firstGBufferBinding = 0; // You may have to change this if you use other texture slots diff --git a/src/renderers/forwardPlus.js b/src/renderers/forwardPlus.js index a02649c0..c2c9dcbe 100644 --- a/src/renderers/forwardPlus.js +++ b/src/renderers/forwardPlus.js @@ -6,18 +6,24 @@ import vsSource from '../shaders/forwardPlus.vert.glsl'; import fsSource from '../shaders/forwardPlus.frag.glsl.js'; import TextureBuffer from './textureBuffer'; import BaseRenderer from './base'; +import { MAX_LIGHTS_PER_CLUSTER } from './base.js' 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, + numLights: NUM_LIGHTS, + maxLightsPerCluster: MAX_LIGHTS_PER_CLUSTER, + slicesX: xSlices, + slicesY: ySlices, + slicesZ: zSlices, }), { - 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_near', 'u_far', 'u_screenW', 'u_screenH'], attribs: ['a_position', 'a_normal', 'a_uv'], }); @@ -75,7 +81,12 @@ export default class ForwardPlusRenderer extends BaseRenderer { 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.uniformMatrix4fv(this._shaderProgram.u_viewMatrix, false, this._viewMatrix); + gl.uniform1f(this._shaderProgram.u_near, camera.near); + gl.uniform1f(this._shaderProgram.u_far, camera.far); + gl.uniform1f(this._shaderProgram.u_screenW, canvas.width); + gl.uniform1f(this._shaderProgram.u_screenH, canvas.height); // 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 50f1e755..19f41038 100644 --- a/src/shaders/deferred.frag.glsl.js +++ b/src/shaders/deferred.frag.glsl.js @@ -1,20 +1,135 @@ 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; + + // Redoing the impl from ForwardPlus to get texel index + uniform mat4 u_viewMatrix; + uniform float u_screenW; + uniform float u_screenH; + uniform float u_near; + uniform float u_far; + + int u_slicesX = ${params.slicesX}; + int u_slicesY = ${params.slicesY}; + int u_slicesZ = ${params.slicesZ}; + float height = ceil(float(${params.maxLightsPerCluster} + 1) / 4.0); + int num_clusters = u_slicesX * u_slicesY * u_slicesZ; varying vec2 v_uv; + + vec3 applyNormalMap(vec3 geomnor, vec3 normap) { + normap = normap * 2.0 - 1.0; + vec3 up = normalize(vec3(0.001, 1, 0.001)); + vec3 surftan = normalize(cross(geomnor, up)); + vec3 surfbinor = cross(geomnor, surftan); + return normap.y * surftan + normap.x * surfbinor + normap.z * geomnor; + } + + 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 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); - gl_FragColor = vec4(v_uv, 0.0, 1.0); + vec3 v_position = gb0.xyz; + vec3 albedo = gb1.rgb; + //vec3 normal = gb2.xyz; + float xy2 = gb0.w * gb0.w + gb1.w * gb1.w; + vec3 normal = vec3(gb0.w, gb1.w, sqrt(1.0 - xy2)); + + vec4 cameraPos = u_viewMatrix * vec4(v_position, 1.0); + + int ix = int(gl_FragCoord.x * float(u_slicesX) / u_screenW); + int iy = int(gl_FragCoord.y * float(u_slicesY) / u_screenH); + int iz = int((cameraPos.z - u_near) * float(u_slicesZ) / (u_far - u_near)); + + int idx = ix + iy * u_slicesX + iz * u_slicesX * u_slicesY; + + int num_lights = int(ExtractFloat( u_clusterbuffer, num_clusters, ${params.maxLightsPerCluster}, idx, 0)); + + vec3 fragColor = vec3(0.0); + + for (int i = 0; i < ${params.numLights}; ++i) { + if (i >= num_lights) {break;} + + int iL = int(ExtractFloat(u_clusterbuffer, num_clusters, int(height), idx, i+1)); + Light light = UnpackLight(iL); + + 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); + + vec3 viewDir = normalize(-v_position); + vec3 halfDir = normalize(L + viewDir); + float specAngle = max(dot(halfDir, normal), 0.0); + float specular = pow(specAngle, 4200.0); + + fragColor += albedo * lambertTerm * light.color * vec3(lightIntensity) + specular*light.color; + } + + const vec3 ambientLight = vec3(0.025); + fragColor += albedo * ambientLight; + + 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..0d62e321 100644 --- a/src/shaders/deferredToTexture.frag.glsl +++ b/src/shaders/deferredToTexture.frag.glsl @@ -4,6 +4,7 @@ precision highp float; uniform sampler2D u_colmap; uniform sampler2D u_normap; +uniform mat4 u_viewMatrix; varying vec3 v_position; varying vec3 v_normal; @@ -22,8 +23,9 @@ 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, v_normal.x); + gl_FragData[1] = vec4(col, v_normal.y); + /*gl_FragData[0] = vec4(v_position, 0); + gl_FragData[1] = vec4(col, 0); + gl_FragData[2] = vec4(v_normal, 0);*/ } \ No newline at end of file diff --git a/src/shaders/deferredToTexture.vert.glsl b/src/shaders/deferredToTexture.vert.glsl index 9850c7f8..c43bb1ed 100644 --- a/src/shaders/deferredToTexture.vert.glsl +++ b/src/shaders/deferredToTexture.vert.glsl @@ -2,6 +2,7 @@ precision highp float; uniform mat4 u_viewProjectionMatrix; +uniform mat4 u_viewMatrix; attribute vec3 a_position; attribute vec3 a_normal; diff --git a/src/shaders/forwardPlus.frag.glsl.js b/src/shaders/forwardPlus.frag.glsl.js index 022fda7a..6422e693 100644 --- a/src/shaders/forwardPlus.frag.glsl.js +++ b/src/shaders/forwardPlus.frag.glsl.js @@ -12,6 +12,17 @@ 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_screenW; + uniform float u_screenH; + uniform float u_near; + uniform float u_far; + int u_slicesX = ${params.slicesX}; + int u_slicesY = ${params.slicesY}; + int u_slicesZ = ${params.slicesZ}; + float height = ceil(float(${params.maxLightsPerCluster} + 1) / 4.0); + int num_clusters = u_slicesX * u_slicesY * u_slicesZ; + varying vec3 v_position; varying vec3 v_normal; varying vec2 v_uv; @@ -79,17 +90,35 @@ export default function(params) { vec3 normap = texture2D(u_normap, v_uv).xyz; vec3 normal = applyNormalMap(v_normal, normap); + vec4 cameraPos = u_viewMatrix * vec4(v_position, 1.0); + + int ix = int(gl_FragCoord.x * float(u_slicesX) / u_screenW); + int iy = int(gl_FragCoord.y * float(u_slicesY) / u_screenH); + int iz = int((-cameraPos.z - u_near) * float(u_slicesZ) / (u_far - u_near)); + + int idx = ix + iy * u_slicesX + iz * u_slicesX * u_slicesY; + int num_lights = int(ExtractFloat( u_clusterbuffer, num_clusters, ${params.maxLightsPerCluster}, idx, 0)); + vec3 fragColor = vec3(0.0); for (int i = 0; i < ${params.numLights}; ++i) { - Light light = UnpackLight(i); + if(i >= num_lights) { break; } + + int iL = int(ExtractFloat(u_clusterbuffer, num_clusters, int(height), idx, i+1)); + Light light = UnpackLight(iL); + 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); + vec3 viewDir = normalize(-v_position); + vec3 halfDir = normalize(L + viewDir); + float specAngle = max(dot(halfDir, normal), 0.0); + float specular = pow(specAngle, 4200.0); + + fragColor += albedo * lambertTerm * light.color * vec3(lightIntensity) + 0.1*specular*light.color; } const vec3 ambientLight = vec3(0.025);