diff --git a/README.md b/README.md index a903608..6b4fd9b 100644 --- a/README.md +++ b/README.md @@ -3,26 +3,58 @@ WebGL Clustered Deferred 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) +* Jonathan Lee +* Tested on: **Google Chrome 61.0.3163.100** on + Windows 10, Xeon ES-1630 @ 3.70GHz 32GB, GTX 1070 24GB (SigLab) -### Live Online +# Overview -[![](img/thumb.png)](http://TODO.github.io/Project5B-WebGL-Deferred-Shading) +In this project, I was able to explore **Clustered Forward+ and Deferred Shading**. -### Demo Video/GIF +### Live Demo -[![](img/video.png)](TODO) +[![](images/shading.gif)](http://agentlee.github.io/Project5-WebGL-Clustered-Deferred-Forward-Plus) -### (TODO: Your README) +# Clustering +Both of these implementations of Forward+ and Deferred Shading were done using clustering. All of the operations are computed in camera space and the camera frustum gets split into clusters. -*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. +# Clustered Forward+ -This assignment has a considerable amount of performance analysis compared -to implementation work. Complete the implementation early to leave time! +![](images/forward+.gif) +Forward+ is an extension of Forward Shading except we use the light's z position determines how to find the cluster it's in. Once we find the cluster that the light's in, the shader only computes for the lights that lie within the cluster. + +# Clustered Deferred + +![](images/deferred.gif) + +Deferred Shading utilizes the same logic of dividing the camera frustum based on the light's position. The shading, however, requires two passes. Fragments get written the the g-buffer and store position, color, and normal values. + +### Blinn Shading + +| 100 Shininess | 200 Shininess | 500 Shininess | +|---------------------------|----------------------------|--------------------------| +| ![](images/100shine.gif) | ![](images/200shine.gif) | ![](images/500shine.gif) | + +| Without Blinn | With Blinn | +|---------------------------|----------------------------------| +| ![](images/500lights.gif) | ![](images/500lightsblinn.gif) | + +# Analysis + +## Forward vs. Forward+ vs. Deferred Shading + +![](images/shadingchart.png) +![](images/shadingfps.PNG) + +Clearly, Deferred Shading performs much better than Forward+ and Forward Shading. The divide between the clustered shaders and Foward begins rapidly around 200 lights. Deferred becomes nearly impossible to see at around 15000 lights. + +## Normal Compression in Deferred Shading + +![](images/deferredchart.png) +![](images/deferredfps.PNG) + +To do compression, I stored `normal.x` and `normal.y` at the `w` position of the `vec4` that also stores position and color. Rather than reading in the value from a third g-buffer that only stores the normal, `normal.z` can be calculated in the shader. Surpsingly, I found a huge dropoff between uncompressed and compressed normals after 500 lights. ### Credits @@ -31,3 +63,10 @@ to implementation work. Complete the implementation early to leave time! * [webgl-debug](https://github.com/KhronosGroup/WebGLDeveloperTools) by Khronos Group Inc. * [glMatrix](https://github.com/toji/gl-matrix) by [@toji](https://github.com/toji) and contributors * [minimal-gltf-loader](https://github.com/shrekshao/minimal-gltf-loader) by [@shrekshao](https://github.com/shrekshao) + +### References +* [Blinn Shading](https://en.wikipedia.org/wiki/Blinn%E2%80%93Phong_shading_model) +* [Various Camera Stuff](http://www.txutxi.com/) +* [OpenGL-Tutorail](http://www.opengl-tutorial.org) +* [Foward+ vs. Deferred](https://www.3dgep.com/forward-plus_) +* CIS560 and CIS565 Slides \ No newline at end of file diff --git a/images/100shine.gif b/images/100shine.gif new file mode 100644 index 0000000..f9ff04d Binary files /dev/null and b/images/100shine.gif differ diff --git a/images/200shine.gif b/images/200shine.gif new file mode 100644 index 0000000..2925ec4 Binary files /dev/null and b/images/200shine.gif differ diff --git a/images/500lights.gif b/images/500lights.gif new file mode 100644 index 0000000..0441507 Binary files /dev/null and b/images/500lights.gif differ diff --git a/images/500lightsblinn.gif b/images/500lightsblinn.gif new file mode 100644 index 0000000..ba433f7 Binary files /dev/null and b/images/500lightsblinn.gif differ diff --git a/images/500shine.gif b/images/500shine.gif new file mode 100644 index 0000000..29bd497 Binary files /dev/null and b/images/500shine.gif differ diff --git a/images/blinn.PNG b/images/blinn.PNG new file mode 100644 index 0000000..41e8b49 Binary files /dev/null and b/images/blinn.PNG differ diff --git a/images/deferred.gif b/images/deferred.gif new file mode 100644 index 0000000..8fe4cc7 Binary files /dev/null and b/images/deferred.gif differ diff --git a/images/deferredchart.png b/images/deferredchart.png new file mode 100644 index 0000000..ec82e39 Binary files /dev/null and b/images/deferredchart.png differ diff --git a/images/deferredfps.PNG b/images/deferredfps.PNG new file mode 100644 index 0000000..b7458a3 Binary files /dev/null and b/images/deferredfps.PNG differ diff --git a/images/forward+.gif b/images/forward+.gif new file mode 100644 index 0000000..9cdd096 Binary files /dev/null and b/images/forward+.gif differ diff --git a/images/shading.gif b/images/shading.gif new file mode 100644 index 0000000..7e9e927 Binary files /dev/null and b/images/shading.gif differ diff --git a/images/shadingFPS.PNG b/images/shadingFPS.PNG new file mode 100644 index 0000000..00b521c Binary files /dev/null and b/images/shadingFPS.PNG differ diff --git a/images/shadingchart.png b/images/shadingchart.png new file mode 100644 index 0000000..c103d90 Binary files /dev/null and b/images/shadingchart.png differ diff --git a/src/.DS_Store b/src/.DS_Store new file mode 100644 index 0000000..8a95085 Binary files /dev/null and b/src/.DS_Store differ diff --git a/src/aabb.js b/src/aabb.js new file mode 100644 index 0000000..f69b594 --- /dev/null +++ b/src/aabb.js @@ -0,0 +1,15 @@ +import { mat4, vec4, vec3 } from 'gl-matrix'; + +export default class AABB +{ + constructor(pos, radius) + { + this.min = vec3.fromValues( pos[0] - radius, + pos[1] - radius, + pos[2] - radius); + + this.max = vec3.fromValues( pos[0] + radius, + pos[1] + radius, + pos[2] + radius); + } +} \ No newline at end of file diff --git a/src/init.js b/src/init.js index 1b09377..9de2fc2 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/main.js b/src/main.js index 1cbbf9a..f8b2e8b 100644 --- a/src/main.js +++ b/src/main.js @@ -9,7 +9,7 @@ const CLUSTERED_FORWARD_PLUS = 'Clustered Forward+'; const CLUSTERED_DEFFERED = 'Clustered Deferred'; const params = { - renderer: CLUSTERED_FORWARD_PLUS, + renderer: CLUSTERED_DEFFERED, _renderer: null, }; diff --git a/src/renderers/clustered.js b/src/renderers/clustered.js index 9521fbd..c14ab3a 100644 --- a/src/renderers/clustered.js +++ b/src/renderers/clustered.js @@ -4,6 +4,46 @@ import TextureBuffer from './textureBuffer'; export const MAX_LIGHTS_PER_CLUSTER = 100; +const DEBUG_SHADER = false; + +function clamp(num, min, max) +{ + let flooredNum = Math.floor(num); + + if(flooredNum < min) { + return min; + } + else if(flooredNum > max) { + return max; + } + else { + return flooredNum; + } +} + +function printClusterBounds(minX, maxX, minY, maxY, minZ, maxZ) +{ + console.log("xMin: " + minX); + console.log("xMax: " + maxX); + // if(minX > maxX) { + // console.log("FUHX"); + // } + + console.log("yMin: " + minY); + console.log("yMax: " + maxY); + // if(minY > maxY) { + // console.log("FUHX"); + // } + + console.log("zMin: " + minZ); + console.log("zMax: " + maxZ); + // if(minZ > maxZ) { + // console.log("FUHX"); + // } + + console.log("--------------"); +} + export default class ClusteredRenderer { constructor(xSlices, ySlices, zSlices) { // Create a texture to store cluster data. Each cluster stores the number of lights followed by the light indices @@ -13,6 +53,24 @@ export default class ClusteredRenderer { this._zSlices = zSlices; } + // Check to see if the cluster is within the camera + lightInCluster(minX, maxX, minY, maxY, minZ, maxZ) + { + if(minX < 0 && maxX < 0 || minX > this.xSlices && maxX > this.xSlices) { + return false; + } + + if(minY < 0 && maxY < 0 || minY > this.ySlices && maxY > this.ySlices) { + return false; + } + + if(minZ < 0 && maxZ < 0 || minZ > this.zSlices && maxZ > this.zSlices) { + return false; + } + + return true; + } + 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... @@ -27,6 +85,126 @@ export default class ClusteredRenderer { } } + // Convert fov from degrees to radians + var fov = camera.fov * (Math.PI / 180.0); + var screenHeight = 2.0 * Math.tan(fov / 2); + var screenWidth = camera.aspect * screenHeight; + + // Z logistics + var near = camera.near; + var far = camera.far; + var depth = (far - near); + var zStride = depth / this._zSlices; + + for(let i = 0; i < NUM_LIGHTS; i++) { + let light = scene.lights[i]; + let lightPos = vec4.fromValues(light.position[0], light.position[1], light.position[2], 1.0); + let lightRad = light.radius; + + // Take the lightPos from world to camera space + vec4.transformMat4(lightPos, lightPos, viewMatrix); + lightPos[2] *= -1; + + // Get height and width based on the light's position + let height = screenHeight * lightPos[2]; + let width = screenWidth * lightPos[2]; + + // Strides + let yStride = height / this._ySlices; + let xStride = width / this._xSlices; + + // Min and Max bounds for the cluster + let minX, maxX; + let minY, maxY; + let minZ, maxZ; + + if(DEBUG_SHADER) { + minX = 0; + maxX = 14; + + minY = 0; + maxY = 14; + + minZ = 0; + maxZ = 14; + } + else { + let lightMin = vec3.fromValues( lightPos[0] - lightRad, + lightPos[1] - lightRad, + lightPos[2] - lightRad); + let lightMax = vec3.fromValues( lightPos[0] + lightRad, + lightPos[1] + lightRad, + lightPos[2] + lightRad); + + minX = Math.floor((lightMin[0] + (width / 2)) / xStride); + maxX = Math.floor((lightMax[0] + (width / 2)) / xStride); + + minY = Math.floor((lightMin[1] + (height / 2)) / yStride); + maxY = Math.floor((lightMax[1] + (height / 2)) / yStride); + + minZ = Math.floor(lightMin[2] / zStride); + maxZ = Math.floor(lightMax[2] / zStride); + + // Offset the min/max values by the slices since the clusters will be indexed [0, slices - 1] + minX = clamp(minX, 0, this._xSlices - 1); + maxX = clamp(maxX, 0, this._xSlices - 1); + + minY = clamp(minY, 0, this._ySlices - 1); + maxY = clamp(maxY, 0, this._ySlices - 1); + + minZ = clamp(minZ, 0, this._zSlices - 1); + maxZ = clamp(maxZ, 0, this._zSlices - 1); + + // Check if the light is outside the cluster frustum + if(!this.lightInCluster(minX, maxX, minY, maxY, minZ, maxZ)) { + continue; + } + } + + // Update the buffer + for(let z = minZ; z <= maxZ; z++) { + for(let y = minY; y <= maxY; y++) { + for(let x = minX; x <= maxX; x++) { + // ------------------cluster0-----------------cluster1---------------cluster2---------------(u)- + // component0 |count|lid0|lid1|lid2 || count|lid0|lid1|lid2 || count|lid0|lid1|lid2 + // component1 |lid3 |lid4|lid5|lid6 || lid3 |lid4|lid5|lid6 || lid3 |lid4|lid5|lid6 + // component2 |lid7 |lid8|lid9|lid10|| lid7 |lid8|lid9|lid10|| lid7 |lid8|lid9|lid10 + // (v) + + // Cluster Index (u) + // ------------------cluster0-----------------cluster1---------------cluster2---------------(u)- + let clusterIndex = x + (y * this._xSlices) + (z * this._xSlices * this._ySlices); + + // Index of where the count lies in each cluster + let lightCountIndex = this._clusterTexture.bufferIndex(clusterIndex, 0); + // Get the number of lights + let lightCount = this._clusterTexture.buffer[lightCountIndex]; + // Increment the number of lights + lightCount++; + + // Safety check to make sure we don't exceed the amount of lights allowed + if(lightCount <= MAX_LIGHTS_PER_CLUSTER) { + // Update the light count + this._clusterTexture.buffer[lightCountIndex] = lightCount; + + // Find the component (v) + // component0 + let component = Math.floor((lightCount) / 4); + + // Get the light id in the cluster (lid0, lid1, etc.) + // |count|lid0|lid1|lid2 + let lightClusterID = (lightCount) % 4; + + // Update the pixel to include the current light + let texelIndex = this._clusterTexture.bufferIndex(clusterIndex, component); + let componentIndex = (lightCount) - (component * 4); + this._clusterTexture.buffer[texelIndex + componentIndex] = i; + } + } + } + } + } + this._clusterTexture.update(); } } \ No newline at end of file diff --git a/src/renderers/clusteredDeferred.js b/src/renderers/clusteredDeferred.js index 5e28e84..3fb276c 100644 --- a/src/renderers/clusteredDeferred.js +++ b/src/renderers/clusteredDeferred.js @@ -8,6 +8,7 @@ import QuadVertSource from '../shaders/quad.vert.glsl'; import fsSource from '../shaders/deferred.frag.glsl.js'; import TextureBuffer from './textureBuffer'; import ClusteredRenderer from './clustered'; +import {MAX_LIGHTS_PER_CLUSTER} from './clustered'; export const NUM_GBUFFERS = 4; @@ -21,20 +22,33 @@ export default class ClusteredDeferredRenderer extends ClusteredRenderer { this._lightTexture = new TextureBuffer(NUM_LIGHTS, 8); this._progCopy = loadShaderProgram(toTextureVert, toTextureFrag, { - uniforms: ['u_viewProjectionMatrix', 'u_colmap', 'u_normap'], + uniforms: [ + 'u_viewProjectionMatrix', + 'u_viewMatrix', + 'u_colmap', + 'u_normap' + ], attribs: ['a_position', 'a_normal', 'a_uv'], }); this._progShade = loadShaderProgram(QuadVertSource, fsSource({ numLights: NUM_LIGHTS, numGBuffers: NUM_GBUFFERS, + xSlices: xSlices, + ySlices: ySlices, + zSlices: zSlices, + maxLightsPerCluster: MAX_LIGHTS_PER_CLUSTER }), { - 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_viewProjectionMatrix', 'u_viewMatrix', 'u_invViewMatrix', + 'u_height', 'u_width', 'u_nearZ', 'u_farZ', + 'u_lightbuffer', 'u_clusterbuffer'], attribs: ['a_uv'], }); this._projectionMatrix = mat4.create(); this._viewMatrix = mat4.create(); + this._invViewMatrix = mat4.create(); this._viewProjectionMatrix = mat4.create(); } @@ -109,6 +123,9 @@ export default class ClusteredDeferredRenderer extends ClusteredRenderer { mat4.copy(this._projectionMatrix, camera.projectionMatrix.elements); mat4.multiply(this._viewProjectionMatrix, this._projectionMatrix, this._viewMatrix); + // Inverse View Matrix + mat4.invert(this._invViewMatrix, this._viewMatrix); + // Render to the whole screen gl.viewport(0, 0, canvas.width, canvas.height); @@ -123,10 +140,14 @@ export default class ClusteredDeferredRenderer extends ClusteredRenderer { // Upload the camera matrix gl.uniformMatrix4fv(this._progCopy.u_viewProjectionMatrix, false, this._viewProjectionMatrix); + gl.uniformMatrix4fv(this._progCopy.u_viewMatrix, false, this._viewMatrix); // 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 cluster texture which maps from cluster index to light list + this.updateClusters(camera, this._viewMatrix, scene, NUM_LIGHTS); + // 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 +162,6 @@ export default class ClusteredDeferredRenderer extends ClusteredRenderer { // 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); @@ -153,10 +171,31 @@ export default class ClusteredDeferredRenderer extends ClusteredRenderer { // Use this shader program gl.useProgram(this._progShade.glShaderProgram); - // TODO: Bind any other shader inputs + // Bind any other shader inputs + // Upload the camera matrix + gl.uniformMatrix4fv(this._progShade.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._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); + + gl.uniformMatrix4fv(this._progShade.u_viewMatrix, false, this._viewMatrix); + gl.uniformMatrix4fv(this._progShade.u_invViewMatrix, false, this._invViewMatrix); + + gl.uniform1f (this._progShade.u_width, canvas.width); + gl.uniform1f (this._progShade.u_height, canvas.height); + + gl.uniform1f (this._progShade.u_farZ, camera.far); + gl.uniform1f (this._progShade.u_nearZ, camera.near); // Bind g-buffers - const firstGBufferBinding = 0; // You may have to change this if you use other texture slots + const firstGBufferBinding = 10; // 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]); @@ -165,4 +204,4 @@ export default class ClusteredDeferredRenderer extends ClusteredRenderer { renderFullscreenQuad(this._progShade); } -}; +}; \ No newline at end of file diff --git a/src/renderers/clusteredForwardPlus.js b/src/renderers/clusteredForwardPlus.js index 9e8afbe..cae856b 100644 --- a/src/renderers/clusteredForwardPlus.js +++ b/src/renderers/clusteredForwardPlus.js @@ -6,6 +6,7 @@ import vsSource from '../shaders/clusteredForward.vert.glsl'; import fsSource from '../shaders/clusteredForward.frag.glsl.js'; import TextureBuffer from './textureBuffer'; import ClusteredRenderer from './clustered'; +import { MAX_LIGHTS_PER_CLUSTER } from './clustered'; export default class ClusteredForwardPlusRenderer extends ClusteredRenderer { constructor(xSlices, ySlices, zSlices) { @@ -16,8 +17,23 @@ export default class ClusteredForwardPlusRenderer extends ClusteredRenderer { this._shaderProgram = loadShaderProgram(vsSource, fsSource({ numLights: NUM_LIGHTS, + xSlices : xSlices, + ySlices : ySlices, + zSlices : zSlices, + maxLightsPerCluster : MAX_LIGHTS_PER_CLUSTER }), { - uniforms: ['u_viewProjectionMatrix', 'u_colmap', 'u_normap', 'u_lightbuffer', 'u_clusterbuffer'], + uniforms: [ + 'u_viewProjectionMatrix', + 'u_viewMatrix', + 'u_colmap', + 'u_normap', + 'u_lightbuffer', + 'u_clusterbuffer', + 'u_width', + 'u_height', + 'u_nearZ', + 'u_farZ' + ], attribs: ['a_position', 'a_normal', 'a_uv'], }); @@ -76,6 +92,13 @@ export default class ClusteredForwardPlusRenderer extends ClusteredRenderer { gl.uniform1i(this._shaderProgram.u_clusterbuffer, 3); // TODO: Bind any other shader inputs + gl.uniformMatrix4fv(this._shaderProgram.u_viewMatrix, false, this._viewMatrix); + + gl.uniform1f(this._shaderProgram.u_width, canvas.width); + gl.uniform1f(this._shaderProgram.u_height, canvas.height); + + gl.uniform1f(this._shaderProgram.u_nearZ, camera.near); + gl.uniform1f(this._shaderProgram.u_farZ, camera.far); // Draw the scene. This function takes the shader program so that the model's textures can be bound to the right inputs scene.draw(this._shaderProgram); diff --git a/src/shaders/clusteredForward.frag.glsl b/src/shaders/clusteredForward.frag.glsl new file mode 100644 index 0000000..8c26961 --- /dev/null +++ b/src/shaders/clusteredForward.frag.glsl @@ -0,0 +1,149 @@ +#version 100 + precision highp float; + + uniform sampler2D u_colmap; + uniform sampler2D u_normap; + uniform sampler2D u_lightbuffer; + + uniform mat4 u_viewMatrix; + + uniform float u_width; + uniform float u_height; + + uniform float u_nearZ; + uniform float u_farZ; + + // TODO: Read this buffer to determine the lights influencing a cluster + uniform sampler2D u_clusterbuffer; + + varying vec3 v_position; + varying vec3 v_normal; + 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() + { + 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); + + // GLSL type inconsistencies + // https://stackoverflow.com/questions/33579110/glsl-type-inconsistencies + float x = gl_FragCoord.x; + float y = gl_FragCoord.y; + // Get the position in camera space + vec4 v_posCamera = u_viewMatrix * vec4(v_position, 1.0); + float z = -v_posCamera[2]; + + // Use gl_FragCoord to get xyz values + // http://www.txutxi.com/?p=182 + float xStride = float(u_width) / float(${params.xSlices}); + float yStride = float(u_height) / float(${params.ySlices}); + float zStride = float(u_farZ - u_nearZ) / float(${params.zSlices}); + int xCluster = int(float(x) / xStride); + int yCluster = int(float(y) / yStride); + int zCluster = int(float(z - u_nearZ) / zStride); + + // Find which cluster the current fragment lies in + // Cluster index + int index = xCluster + (yCluster * ${params.xSlices}) + (zCluster * ${params.xSlices} *${params.ySlices}); + + int numClusters = ${params.xSlices} * ${params.ySlices} * ${params.zSlices}; + float u = float(index + 1) / float(numClusters + 1); + + // Get how many lights are in the cluster + int numLightsInCluster = int(texture2D(u_clusterbuffer, vec2(u,0)).r); + + int numTexels = int( ceil( float(${params.maxLightsPerCluster} + 1) / float(4.0)) ); + + for (int i = 0; i < ${params.numLights}; ++i) { + if(i < numLightsInCluster) { + int lightIndex = int(ExtractFloat(u_clusterbuffer, numClusters, numTexels, index, i + 1)); + + Light light = UnpackLight(lightIndex); + float lightDistance = distance(light.position, v_position); + vec3 L = (light.position - v_position) / lightDistance; + + vec3 lightPos = light.position; + + float lightIntensity = cubicGaussian(2.0 * lightDistance / light.radius); + float lambertTerm = max(dot(L, normal), 0.0); + + fragColor += albedo * lambertTerm * light.color * vec3(lightIntensity); + } + else { + break; + } + + // fragColor = vec3(float(xCluster) / 15.0); + } + + 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/clusteredForward.frag.glsl.js b/src/shaders/clusteredForward.frag.glsl.js index 022fda7..c3f5241 100644 --- a/src/shaders/clusteredForward.frag.glsl.js +++ b/src/shaders/clusteredForward.frag.glsl.js @@ -4,98 +4,171 @@ export default function(params) { #version 100 precision highp float; - + uniform sampler2D u_colmap; uniform sampler2D u_normap; uniform sampler2D u_lightbuffer; - + + uniform mat4 u_viewMatrix; + + uniform float u_width; + uniform float u_height; + + uniform float u_nearZ; + uniform float u_farZ; + // TODO: Read this buffer to determine the lights influencing a cluster uniform sampler2D u_clusterbuffer; - + varying vec3 v_position; varying vec3 v_normal; 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; + + #define TOON false + + 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; + + 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]; - } + + 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; + + 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() { - 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); - - 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); - } - - const vec3 ambientLight = vec3(0.025); - fragColor += albedo * ambientLight; - - gl_FragColor = vec4(fragColor, 1.0); + 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() + { + 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); + + // GLSL type inconsistencies + // https://stackoverflow.com/questions/33579110/glsl-type-inconsistencies + float x = gl_FragCoord.x; + float y = gl_FragCoord.y; + // Get the position in camera space + vec4 v_posCamera = u_viewMatrix * vec4(v_position, 1.0); + float z = -v_posCamera[2]; + + // Use gl_FragCoord to get xyz values + // http://www.txutxi.com/?p=182 + float xStride = float(u_width) / float(${params.xSlices}); + float yStride = float(u_height) / float(${params.ySlices}); + float zStride = float(u_farZ - u_nearZ) / float(${params.zSlices}); + int xCluster = int(float(x) / xStride); + int yCluster = int(float(y) / yStride); + int zCluster = int(float(z - u_nearZ) / zStride); + + // Find which cluster the current fragment lies in + // Cluster index + int index = xCluster + (yCluster * ${params.xSlices}) + (zCluster * ${params.xSlices} *${params.ySlices}); + + int numClusters = ${params.xSlices} * ${params.ySlices} * ${params.zSlices}; + float u = float(index + 1) / float(numClusters + 1); + + // Get how many lights are in the cluster + int numLightsInCluster = int(texture2D(u_clusterbuffer, vec2(u,0)).r); + + int numTexels = int( ceil( float(${params.maxLightsPerCluster} + 1) / float(4.0)) ); + + for (int i = 0; i < ${params.numLights}; ++i) { + if(i < numLightsInCluster) { + int lightIndex = int(ExtractFloat(u_clusterbuffer, numClusters, numTexels, index, i + 1)); + + Light light = UnpackLight(lightIndex); + float lightDistance = distance(light.position, v_position); + vec3 L = (light.position - v_position) / lightDistance; + + vec3 lightPos = light.position; + + float lightIntensity = cubicGaussian(2.0 * lightDistance / light.radius); + float lambertTerm = max(dot(L, normal), 0.0); + + fragColor += albedo * lambertTerm * light.color * vec3(lightIntensity); + + vec3 lightDir = normalize(light.position - v_position); + float intenseness = dot(lightDir, normal); + if(TOON) { + if(intenseness > 0.95) { + fragColor *= vec3(1.0, 1.0, 1.0); + } + else if(intenseness > 0.5) { + fragColor *= vec3(0.7, 0.7, 0.7); + } + else if(intenseness > 0.25) { + fragColor *= vec3(0.35, 0.35, 0.35); + } + else { + fragColor *= vec3(0.1, 0.1, 0.1); + } + } + } + else { + break; + } + + // fragColor = vec3(float(xCluster) / 15.0); + } + + 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/deferred.frag.glsl b/src/shaders/deferred.frag.glsl new file mode 100644 index 0000000..11e7416 --- /dev/null +++ b/src/shaders/deferred.frag.glsl @@ -0,0 +1,202 @@ +#version 100 +precision highp float; + +uniform sampler2D u_clusterbuffer; + +uniform sampler2D u_colmap; +uniform sampler2D u_normap; +uniform sampler2D u_lightbuffer; + +uniform sampler2D u_gbuffers[${params.numGBuffers}]; + +uniform mat4 u_viewMatrix; +uniform mat4 u_invViewMatrix; + +uniform float u_width; +uniform float u_height; + +uniform float u_nearZ; +uniform float u_farZ; + +varying vec2 v_uv; + +#define BLINN false +const float shininess = 500.0; +const vec3 specularColor = vec3(.2, .2, .2); + +#define TOON true + +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; + } +} + +vec3 uncompressNormal(vec2 compressed) +{ +compressed -= 0.5; +compressed *= 2.0; + +float z = sqrt(1.0 - (compressed.x*compressed.x) - (compressed.y*compressed.y)); +vec3 normal = vec3(compressed.x, compressed.y, z); + +return normalize(normal); +} + +void main() { +// TODO: extract data from g buffers and do lighting +vec4 gb0 = texture2D(u_gbuffers[0], v_uv); // v_position +vec4 gb1 = texture2D(u_gbuffers[1], v_uv); // albedo +vec4 gb2 = texture2D(u_gbuffers[2], v_uv); // norm +// vec4 gb3 = texture2D(u_gbuffers[3], v_uv); + +// Grab the individual components from the textures +vec3 v_position = gb0.rgb; +vec3 albedo = gb1.rgb; +vec2 _normal = vec2(gb0.a, gb1.a); + +vec3 compressed = uncompressNormal(_normal); +vec3 normal = vec3(u_invViewMatrix * vec4(compressed, 0.0)); + +vec3 fragColor = vec3(0.0); + +// GLSL type inconsistencies +// https://stackoverflow.com/questions/33579110/glsl-type-inconsistencies +float x = gl_FragCoord.x; +float y = gl_FragCoord.y; +// Get the position in camera space +vec4 v_posCamera = u_viewMatrix * vec4(v_position, 1.0); +float z = -v_posCamera[2]; + +// Use gl_FragCoord to get xyz values +// http://www.txutxi.com/?p=182 +float xStride = float(u_width) / float(${params.xSlices}); +float yStride = float(u_height) / float(${params.ySlices}); +float zStride = float(u_farZ - u_nearZ) / float(${params.zSlices}); +int xCluster = int(float(x) / xStride); +int yCluster = int(float(y) / yStride); +int zCluster = int(float(z - u_nearZ) / zStride); + +// Find which cluster the current fragment lies in +// Cluster index +int index = xCluster + (yCluster * ${params.xSlices}) + (zCluster * ${params.xSlices} *${params.ySlices}); + +int numClusters = ${params.xSlices} * ${params.ySlices} * ${params.zSlices}; +float u = float(index + 1) / float(numClusters + 1); + +// Get how many lights are in the cluster +int numLightsInCluster = int(texture2D(u_clusterbuffer, vec2(u,0)).r); + +int numTexels = int( ceil( float(${params.maxLightsPerCluster} + 1) / float(4.0)) ); + +for(int i = 0; i < ${params.numLights}; i++) { + if(i >= numLightsInCluster) { + break; + } + + int lightIndex = int(ExtractFloat(u_clusterbuffer, numClusters, numTexels, index, i + 1)); + Light light = UnpackLight(lightIndex); + float lightDistance = distance(light.position, v_position); + vec3 L = (light.position - v_position) / lightDistance; + + vec3 lightPos = light.position; + + float lightIntensity = cubicGaussian(2.0 * lightDistance / light.radius); + float lambertTerm = max(dot(L, normal), 0.0); + + // Blinn + // https://en.wikipedia.org/wiki/Blinn%E2%80%93Phong_shading_model + vec3 lightDir = normalize(light.position - v_position); + vec3 viewDir = normalize(-v_position); + vec3 halfDir = normalize(viewDir + lightDir); + + float specularAngle = max(dot(halfDir, normal), 0.0); + float specular = 0.0; + + if(BLINN && lambertTerm > 0.0) { + specular = pow(specularAngle, shininess); + } + + fragColor += albedo * lambertTerm * light.color * vec3(lightIntensity) + specular * specularColor; + + if(TOON) { + if(lambertTerm > 0.95) { + fragColor *= vec4(1.0, 1.0, 1.0, 1.0); + } + else if(lambertTerm > 0.5) { + fragColor *= vec4(0.7, 0.7, 0.7, 1.0); + } + else if(lambertTerm > 0.25) { + fragColor *= vec4(0.35, 0.35, 0.35, 1.0); + } + else { + fragColor *= vec4(0.1, 0.1, 0.1, 1.0); + } + } + + + // fragColor += albedo * lambertTerm * light.color * vec3(lightIntensity); +} + +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/deferred.frag.glsl.js b/src/shaders/deferred.frag.glsl.js index 50f1e75..6cee45c 100644 --- a/src/shaders/deferred.frag.glsl.js +++ b/src/shaders/deferred.frag.glsl.js @@ -3,18 +3,338 @@ export default function(params) { #version 100 precision highp float; + uniform sampler2D u_clusterbuffer; + + uniform sampler2D u_colmap; + uniform sampler2D u_normap; + uniform sampler2D u_lightbuffer; + uniform sampler2D u_gbuffers[${params.numGBuffers}]; + uniform mat4 u_viewMatrix; + uniform mat4 u_invViewMatrix; + + uniform float u_width; + uniform float u_height; + + uniform float u_nearZ; + uniform float u_farZ; + varying vec2 v_uv; + + #define TOON true + + #define BLINN false + const float shininess = 500.0; + const vec3 specularColor = vec3(.2, .2, .2); + + 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; + } + } + + vec3 uncompressNormal(vec2 compressed) + { + compressed -= 0.5; + compressed *= 2.0; + + float z = sqrt(1.0 - (compressed.x*compressed.x) - (compressed.y*compressed.y)); + vec3 normal = vec3(compressed.x, compressed.y, z); + + return normalize(normal); + } + 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); // v_position + vec4 gb1 = texture2D(u_gbuffers[1], v_uv); // albedo + vec4 gb2 = texture2D(u_gbuffers[2], v_uv); // norm // vec4 gb3 = texture2D(u_gbuffers[3], v_uv); - gl_FragColor = vec4(v_uv, 0.0, 1.0); + // Grab the individual components from the textures + vec3 v_position = gb0.rgb; + vec3 albedo = gb1.rgb; + vec2 _normal = vec2(gb0.a, gb1.a); + + vec3 compressed = uncompressNormal(_normal); + vec3 normal = vec3(u_invViewMatrix * vec4(compressed, 0.0)); + + vec3 fragColor = vec3(0.0); + + // GLSL type inconsistencies + // https://stackoverflow.com/questions/33579110/glsl-type-inconsistencies + float x = gl_FragCoord.x; + float y = gl_FragCoord.y; + // Get the position in camera space + vec4 v_posCamera = u_viewMatrix * vec4(v_position, 1.0); + float z = -v_posCamera[2]; + + // Use gl_FragCoord to get xyz values + // http://www.txutxi.com/?p=182 + float xStride = float(u_width) / float(${params.xSlices}); + float yStride = float(u_height) / float(${params.ySlices}); + float zStride = float(u_farZ - u_nearZ) / float(${params.zSlices}); + int xCluster = int(float(x) / xStride); + int yCluster = int(float(y) / yStride); + int zCluster = int(float(z - u_nearZ) / zStride); + + // Find which cluster the current fragment lies in + // Cluster index + int index = xCluster + (yCluster * ${params.xSlices}) + (zCluster * ${params.xSlices} *${params.ySlices}); + + int numClusters = ${params.xSlices} * ${params.ySlices} * ${params.zSlices}; + float u = float(index + 1) / float(numClusters + 1); + + // Get how many lights are in the cluster + int numLightsInCluster = int(texture2D(u_clusterbuffer, vec2(u,0)).r); + + int numTexels = int( ceil( float(${params.maxLightsPerCluster} + 1) / float(4.0)) ); + + for(int i = 0; i < ${params.numLights}; i++) { + if(i >= numLightsInCluster) { + break; + } + + int lightIndex = int(ExtractFloat(u_clusterbuffer, numClusters, numTexels, index, i + 1)); + Light light = UnpackLight(lightIndex); + float lightDistance = distance(light.position, v_position); + vec3 L = (light.position - v_position) / lightDistance; + + vec3 lightPos = light.position; + + float lightIntensity = cubicGaussian(2.0 * lightDistance / light.radius); + float lambertTerm = max(dot(L, normal), 0.0); + + // Blinn + // https://en.wikipedia.org/wiki/Blinn%E2%80%93Phong_shading_model + vec3 lightDir = normalize(light.position - v_position); + vec3 viewDir = normalize(-v_position); + vec3 halfDir = normalize(viewDir + lightDir); + + float specularAngle = max(dot(halfDir, normal), 0.0); + float specular = 0.0; + + if(BLINN && lambertTerm > 0.0) { + specular = pow(specularAngle, shininess); + } + + // fragColor += albedo * lambertTerm * light.color * vec3(lightIntensity) + specular * specularColor; + + float intenseness = dot(L, normal); + if(TOON) { + vec3 toon = vec3(0.0); + if(intenseness > .75) { + toon = vec3(1.0, 1.0, 1.0); + } + else if(intenseness > 0.5) { + toon = vec3(0.7, 0.7, 0.7); + } + else if(intenseness > 0.25) { + toon = vec3(0.35, 0.35, 0.35); + } + else { + toon = vec3(0.1, 0.1, 0.1); + } + + fragColor += albedo * toon * light.color * vec3(lightIntensity) + specular * specularColor; + + // float rampCoord = dot(normalize(lightDir), normal) * 0.5 + 0.5; + // fragColor *= rampCoord; + } + else { + fragColor += albedo * lambertTerm * light.color * vec3(lightIntensity) + specular * specularColor; + } + + // fragColor += albedo * lambertTerm * light.color * vec3(lightIntensity); + } + + const vec3 ambientLight = vec3(0.025); + fragColor += albedo * ambientLight; + + gl_FragColor = vec4(fragColor, 1.0); } `; -} \ No newline at end of file +} + +// Uncompressed +/* +export default function(params) { + return ` + #version 100 + precision highp float; + + uniform sampler2D u_clusterbuffer; + + uniform sampler2D u_colmap; + uniform sampler2D u_normap; + uniform sampler2D u_lightbuffer; + + uniform sampler2D u_gbuffers[${params.numGBuffers}]; + + uniform mat4 u_viewMatrix; + uniform mat4 u_invViewMatrix; + + uniform float u_width; + uniform float u_height; + + uniform float u_nearZ; + uniform float u_farZ; + + 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); // v_position + vec4 gb1 = texture2D(u_gbuffers[1], v_uv); // albedo + vec4 gb2 = texture2D(u_gbuffers[2], v_uv); // norm + // vec4 gb3 = texture2D(u_gbuffers[3], v_uv); + + // Grab the individual components from the textures + vec3 v_pos = gb0.rgb; + vec3 albedo = gb1.rgb; + vec3 normal = gb2.rgb; + + vec3 fragColor = vec3(0.0); + + for(int i = 0; i < ${params.numLights}; i++) { + Light light = UnpackLight(i); + float lightDistance = distance(light.position, v_pos); + vec3 L = (light.position - v_pos) / lightDistance; + + vec3 lightPos = light.position; + + 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); + } + `; + } +*/ \ No newline at end of file diff --git a/src/shaders/deferredToTexture.frag.glsl b/src/shaders/deferredToTexture.frag.glsl index bafc086..5e14358 100644 --- a/src/shaders/deferredToTexture.frag.glsl +++ b/src/shaders/deferredToTexture.frag.glsl @@ -5,10 +5,14 @@ precision highp float; uniform sampler2D u_colmap; uniform sampler2D u_normap; +uniform mat4 u_viewMatrix; + varying vec3 v_position; varying vec3 v_normal; varying vec2 v_uv; +#define COMPRESSED false + vec3 applyNormalMap(vec3 geomnor, vec3 normap) { normap = normap * 2.0 - 1.0; vec3 up = normalize(vec3(0.001, 1, 0.001)); @@ -21,9 +25,14 @@ void main() { vec3 norm = applyNormalMap(v_normal, vec3(texture2D(u_normap, v_uv))); vec3 col = vec3(texture2D(u_colmap, v_uv)); + // Shift the normal to positive space + norm = normalize(vec3(u_viewMatrix * vec4(norm, 0.0))); + norm *= 0.5; + norm += 0.5; + // TODO: populate your g buffer - // gl_FragData[0] = ?? - // gl_FragData[1] = ?? - // gl_FragData[2] = ?? + gl_FragData[0] = vec4(v_position, norm.x); + gl_FragData[1] = vec4(col, norm.y); + // gl_FragData[2] = vec4(norm, 0.0); // gl_FragData[3] = ?? } \ No newline at end of file