diff --git a/README.md b/README.md index ec3c73b..993acc2 100755 --- a/README.md +++ b/README.md @@ -3,26 +3,51 @@ 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) +* Eric Chiu +* Tested on: Macbook Pro (Retina, 15-inch, Mid 2015), OS X El Capitan 10.11.6, 2.5 GHz Intel Core i7, AMD Radeon R9 M370X 2048 MB -### Live Online -[![](img/thumb.png)](http://TODO.github.io/Project5B-WebGL-Deferred-Shading) +## Live Online -### Demo Video/GIF +[![](images/clustered-phong.gif)](https://echiu1997.github.io/Project5-WebGL-Clustered-Deferred-Forward-Plus/) -[![](img/video.png)](TODO) +## Description -### (TODO: Your README) +This project implements different types of GPU rendering using Javascript, WebGL, and GPU hardware. Features include forward rendering, forward plus rendering, clustered deferred rendering, lambert shading, phong shading, toon shading, iridescent shading, and sobel shading. -*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. +## Performance Analysis -This assignment has a considerable amount of performance analysis compared -to implementation work. Complete the implementation early to leave time! +The following chart shows the frames per second of the rendering as the number of lights increase for forward, forward plus, and clustered deferred rendering. Clearly, clustered deferred rendering is faster than forward and forward plus rendering, and forward plus rendering is faster than forward rendering. This is because forward plus rendering categorizes lights into clusters, and the fragment shader will only need to check lights that influence the cluster the geometry fragment is in. Clustered deferred rendering takes this a step further and introduces extra buffers that store 2D textures of depth, normals, and color information. Lighting will then be applied to the 2D textures instead of every geometry to produce the final render. This means that the lighting loop will only have to be run once rather than every geometry. +![](./images/forward-clustered.png) + +The following chart shows the frames per second of the rendering as the number of lights increase for default clustered deferred rendering, and optimized deferred rendering using 2-component normals. Since normals will always have a magnitude of 1, we can compress the normal's x and y components into two vec4s by multiplying it by the view matrix before packing, and then multiplying these vec4s by the inverse view matrix in the shader to uncompress them. The performance difference is not super significant but using fewer buffers clearly improves performance. I believe that the difference in frames per second between the default and optimized implementations will remain linear as the number of lights increase. + +![](./images/default-optimized.png) + +## Forward Plus + +![](./images/forward-plus.gif) + +## Clustered Lambert Shading + +![](./images/clustered-lambert.gif) + +## Clustered Phong Shading + +![](./images/clustered-phong.gif) + +## Clustered Toon Shading + +![](./images/clustered-toon.gif) + +## Clustered Iridescent Shading + +![](./images/clustered-iridescence.gif) + +## Clustered Sobel Shading + +![](./images/clustered-sobel.gif) ### Credits diff --git a/images/clustered-iridescence.gif b/images/clustered-iridescence.gif new file mode 100644 index 0000000..ad54289 Binary files /dev/null and b/images/clustered-iridescence.gif differ diff --git a/images/clustered-lambert.gif b/images/clustered-lambert.gif new file mode 100644 index 0000000..ba7d5f5 Binary files /dev/null and b/images/clustered-lambert.gif differ diff --git a/images/clustered-phong.gif b/images/clustered-phong.gif new file mode 100644 index 0000000..700b870 Binary files /dev/null and b/images/clustered-phong.gif differ diff --git a/images/clustered-sobel.gif b/images/clustered-sobel.gif new file mode 100644 index 0000000..2c2f3c6 Binary files /dev/null and b/images/clustered-sobel.gif differ diff --git a/images/clustered-toon.gif b/images/clustered-toon.gif new file mode 100644 index 0000000..005c82f Binary files /dev/null and b/images/clustered-toon.gif differ diff --git a/images/default-optimized.png b/images/default-optimized.png new file mode 100644 index 0000000..a94002e Binary files /dev/null and b/images/default-optimized.png differ diff --git a/images/forward-clustered.png b/images/forward-clustered.png new file mode 100644 index 0000000..f87b2e8 Binary files /dev/null and b/images/forward-clustered.png differ diff --git a/images/forward-plus.gif b/images/forward-plus.gif new file mode 100644 index 0000000..17f8585 Binary files /dev/null and b/images/forward-plus.gif 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/renderers/base.js b/src/renderers/base.js index 8a975b9..d0ab15e 100755 --- a/src/renderers/base.js +++ b/src/renderers/base.js @@ -1,8 +1,16 @@ import TextureBuffer from './textureBuffer'; +import { mat4, vec4, vec3, vec2 } from 'gl-matrix'; +import { NUM_LIGHTS } from '../scene'; export const MAX_LIGHTS_PER_CLUSTER = 100; -export default class BaseRenderer { +function clamp(num, min, max) +{ + return Math.min(Math.max(num, min), max); +} + +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); @@ -11,9 +19,8 @@ export default class BaseRenderer { 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... + updateClusters(camera, viewMatrix, scene) + { for (let z = 0; z < this._zSlices; ++z) { for (let y = 0; y < this._ySlices; ++y) { @@ -25,6 +32,76 @@ export default class BaseRenderer { } } + + + // NEW: Update the cluster texture with the count and indices of the lights in each cluster + // This will take some time. The math is nontrivial... + + var halfHeight = Math.tan(camera.fov / 2.0 * (Math.PI/180.0)); + var halfWidth = halfHeight * camera.aspect; + + for (let lightIndex = 0; lightIndex < NUM_LIGHTS; ++lightIndex) + { + // get light information + let light = scene.lights[lightIndex]; + let lightRadius = light.radius; + var lightPos = vec4.fromValues(light.position[0], light.position[1], light.position[2], 1.0); + vec4.transformMat4(lightPos, lightPos, viewMatrix); + lightPos[2] *= -1.0; + + // slice in x direction + let xDim = halfWidth * lightPos[2] * 2.0; + let xStep = xDim / this._xSlices; + let xStart = Math.floor((lightPos[0] - lightRadius + (xDim / 2.0)) / xStep) - 1; + let xEnd = Math.floor((lightPos[0] + lightRadius + (xDim / 2.0)) / xStep) + 1; + + // slice in y direction + let yDim = halfHeight * lightPos[2] * 2.0; + let yStep = yDim / this._ySlices; + let yStart = Math.floor((lightPos[1] - lightRadius + (yDim / 2.0)) / yStep); + let yEnd = Math.floor((lightPos[1] + lightRadius + (yDim / 2.0)) / yStep); + + // slice in z direction + let zDim = (camera.far - camera.near); + let zStep = zDim / this._zSlices; + let zStart = Math.floor((lightPos[2] - lightRadius) / zStep); + let zEnd = Math.floor((lightPos[2] + lightRadius) / zStep); + + // make sure start and end x,y,z is within x,y,z slices + if((zStart < 0 && zEnd < 0) || (zStart >= this._zSlices && zEnd >= this._zSlices)) continue; + if((yStart < 0 && yEnd < 0) || (yStart >= this._ySlices && yEnd >= this._ySlices)) continue; + if((xStart < 0 && xEnd < 0) || (xStart >= this._xSlices && xEnd >= this._xSlices)) continue; + + // clamp the start and end x,y,z + xStart = clamp(xStart, 0, this._xSlices-1); + xEnd = clamp(xEnd, 0, this._xSlices-1); + yStart = clamp(yStart, 0, this._ySlices-1); + yEnd = clamp(yEnd, 0, this._ySlices-1); + zStart = clamp(zStart, 0, this._zSlices-1); + zEnd = clamp(zEnd, 0, this._zSlices-1); + + // iterate through start and end x,y,z + for (let z = zStart; z <= zEnd; z++) { + for (let y = yStart; y <= yEnd; y++) { + for (let x = xStart; x <= xEnd; x++) + { + let clusterIndex = x + (y * this._xSlices) + (z * this._ySlices * this._xSlices); + let numLightIndex = this._clusterTexture.bufferIndex(clusterIndex, 0); + let numLights = 1 + this._clusterTexture.buffer[numLightIndex]; + + if (numLights <= MAX_LIGHTS_PER_CLUSTER) { + let col = Math.floor(numLights * 0.25); + let row = Math.floor(numLights % 4); + this._clusterTexture.buffer[numLightIndex] = numLights; + this._clusterTexture.buffer[this._clusterTexture.bufferIndex(clusterIndex, col) + row] = lightIndex; + } + } + } + } + } + + + this._clusterTexture.update(); } } \ No newline at end of file diff --git a/src/renderers/clustered.js b/src/renderers/clustered.js index 46b8278..adcb77c 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, vec2} from 'gl-matrix'; import { loadShaderProgram, renderFullscreenQuad } from '../utils'; import { NUM_LIGHTS } from '../scene'; import toTextureVert from '../shaders/deferredToTexture.vert.glsl'; @@ -8,28 +8,42 @@ 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"; -export const NUM_GBUFFERS = 4; +export const NUM_GBUFFERS = 2; export default class ClusteredRenderer extends BaseRenderer { constructor(xSlices, ySlices, zSlices) { super(xSlices, ySlices, zSlices); - + this.setupDrawBuffers(canvas.width, canvas.height); - + // Create a texture to store light data 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_viewMatrix', + 'u_invViewMatrix', + 'u_clusterbuffer', + 'u_lightbuffer', + 'u_screenWidth', + 'u_screenHeight', + 'u_camera_near', + 'u_camera_far'], attribs: ['a_uv'], }); @@ -43,7 +57,7 @@ export default class ClusteredRenderer extends BaseRenderer { this._height = height; this._fbo = gl.createFramebuffer(); - + //Create, bind, and store a depth target texture for the FBO this._depthTex = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, this._depthTex); @@ -71,7 +85,7 @@ export default class ClusteredRenderer extends BaseRenderer { gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.FLOAT, null); gl.bindTexture(gl.TEXTURE_2D, null); - gl.framebufferTexture2D(gl.FRAMEBUFFER, attachments[i], gl.TEXTURE_2D, this._gbuffers[i], 0); + gl.framebufferTexture2D(gl.FRAMEBUFFER, attachments[i], gl.TEXTURE_2D, this._gbuffers[i], 0); } if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE) { @@ -108,6 +122,9 @@ export default class ClusteredRenderer extends BaseRenderer { mat4.invert(this._viewMatrix, camera.matrixWorld.elements); mat4.copy(this._projectionMatrix, camera.projectionMatrix.elements); mat4.multiply(this._viewProjectionMatrix, this._projectionMatrix, this._viewMatrix); + // NEW: find the inverse view matrix + let invViewMatrix = mat4.create(); + mat4.invert(invViewMatrix,this._viewMatrix); // Render to the whole screen gl.viewport(0, 0, canvas.width, canvas.height); @@ -123,10 +140,12 @@ export default class ClusteredRenderer extends BaseRenderer { // Upload the camera matrix gl.uniformMatrix4fv(this._progCopy.u_viewProjectionMatrix, false, this._viewProjectionMatrix); + // NEW: upload the view matrix + 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 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]; @@ -153,7 +172,29 @@ export default class ClusteredRenderer extends BaseRenderer { // Use this shader program gl.useProgram(this._progShade.glShaderProgram); - // TODO: Bind any other shader inputs + + // NEW + + // Upload camera matrices + gl.uniformMatrix4fv(this._progShade.u_viewMatrix, false, this._viewMatrix); + gl.uniformMatrix4fv(this._progShade.u_invViewMatrix, false, invViewMatrix); + + // 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); + + // Bind any other shader inputs + gl.uniform1f(this._progShade.u_screenWidth, canvas.width); + gl.uniform1f(this._progShade.u_screenHeight, canvas.height); + gl.uniform1f(this._progShade.u_camera_far,camera.far); + gl.uniform1f(this._progShade.u_camera_near,camera.near); + // 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 a02649c..b89e65d 100755 --- a/src/renderers/forwardPlus.js +++ b/src/renderers/forwardPlus.js @@ -6,6 +6,7 @@ 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'; export default class ForwardPlusRenderer extends BaseRenderer { constructor(xSlices, ySlices, zSlices) { @@ -16,9 +17,24 @@ export default class ForwardPlusRenderer extends BaseRenderer { 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'], - attribs: ['a_position', 'a_normal', 'a_uv'], + uniforms: ['u_viewProjectionMatrix', + 'u_viewMatrix', + 'u_colmap', + 'u_normap', + 'u_lightbuffer', + 'u_clusterbuffer', + 'u_screenWidth', + 'u_screenHeight', + 'u_camera_near', + 'u_camera_far'], + attribs: ['a_position', + 'a_normal', + 'a_uv'], }); this._projectionMatrix = mat4.create(); @@ -64,6 +80,8 @@ export default class ForwardPlusRenderer extends BaseRenderer { // Upload the camera matrix gl.uniformMatrix4fv(this._shaderProgram.u_viewProjectionMatrix, false, this._viewProjectionMatrix); + // NEW: Upload view matrix + gl.uniformMatrix4fv(this._shaderProgram.u_viewMatrix, false, this._viewMatrix); // Set the light texture as a uniform input to the shader gl.activeTexture(gl.TEXTURE2); @@ -75,7 +93,11 @@ 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 + // NEW: Bind any other shader inputs + gl.uniform1f(this._shaderProgram.u_screenWidth, canvas.width); + gl.uniform1f(this._shaderProgram.u_screenHeight, canvas.height); + gl.uniform1f(this._shaderProgram.u_camera_near, camera.near); + gl.uniform1f(this._shaderProgram.u_camera_far, 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/scene.js b/src/scene.js index 35f6700..2086b33 100644 --- a/src/scene.js +++ b/src/scene.js @@ -5,10 +5,10 @@ import { gl } from './init'; export const LIGHT_MIN = [-14, 0, -6]; export const LIGHT_MAX = [14, 20, 6]; export const LIGHT_RADIUS = 5.0; -export const LIGHT_DT = -0.03; +export const LIGHT_DT = -0.1; // TODO: This controls the number of lights -export const NUM_LIGHTS = 100; +export const NUM_LIGHTS = 150; class Scene { constructor() { diff --git a/src/shaders/deferred.frag.glsl.js b/src/shaders/deferred.frag.glsl.js index 50f1e75..fa4d05a 100644 --- a/src/shaders/deferred.frag.glsl.js +++ b/src/shaders/deferred.frag.glsl.js @@ -4,17 +4,194 @@ export default function(params) { precision highp float; uniform sampler2D u_gbuffers[${params.numGBuffers}]; - varying vec2 v_uv; + + // NEW: variables for deffered + uniform sampler2D u_clusterbuffer; + uniform sampler2D u_lightbuffer; - 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); - - gl_FragColor = vec4(v_uv, 0.0, 1.0); + // From Forward Plus + uniform mat4 u_viewMatrix; + uniform mat4 u_invViewMatrix; + uniform float u_screenWidth; + uniform float u_screenHeight; + uniform float u_camera_near; + uniform float u_camera_far; + + // Helper functions from Forward Plus + + 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; + } + } + + // extract data from g buffers and do lighting + void main() + { + // NEW: extract data from g buffers + vec4 gbuffer0 = texture2D(u_gbuffers[0], v_uv); + vec4 gbuffer1 = texture2D(u_gbuffers[1], v_uv); + vec3 albedo = gbuffer0.rgb; + vec3 v_position = gbuffer1.xyz; + + // NEW: calculations for the normal + vec2 snor = vec2(gbuffer0.w, gbuffer1.w); + float zValue = sqrt(1.0 - gbuffer0.w * gbuffer0.w - gbuffer1.w * gbuffer1.w); + vec3 normal = normalize(vec4(u_invViewMatrix * vec4(snor, zValue, 0.0)).xyz); + + // From forward plus + int clusterXIndex = int( gl_FragCoord.x / (float(u_screenWidth) / float(${params.xSlices})) ); + int clusterYIndex = int( gl_FragCoord.y / (float(u_screenHeight) / float(${params.ySlices})) ); + vec4 cameraPos = u_viewMatrix * vec4(v_position,1.0); + int clusterZIndex = int( (-cameraPos.z - u_camera_near) / (float(u_camera_far - u_camera_near) / float(${params.zSlices})) ); + int clusterIndex = clusterXIndex + clusterYIndex * ${params.xSlices} + clusterZIndex * ${params.xSlices} * ${params.ySlices}; + //NEW: fragment position used for blinn-phong shading + vec4 fragPos = u_invViewMatrix * vec4(0.0, 0.0, 0.0, 1.0); + + // From forward plus + int numClusters = ${params.xSlices} * ${params.ySlices} * ${params.zSlices}; + float u = float(clusterIndex + 1) / float(numClusters + 1); + int numClusterLights = int(texture2D(u_clusterbuffer, vec2(u, 0)).r); + int texelsPerCol = int(float(${params.maxLightsPerCluster} + 1) * 0.25) + 1; + + + vec3 fragColor = vec3(0.0); + + for (int i = 0; i < ${params.numLights}; ++i) + { + + if(i >= numClusterLights) { break; } + int texIndex = int(float(i+1) * 0.25); + float V = float(texIndex+1) / float(texelsPerCol+1); + vec4 texel = texture2D(u_clusterbuffer, vec2(u,V)); + + int lightIndex; + int texelComponent = (i + 1) - (texIndex * 4); + if (texelComponent == 0) { lightIndex = int(texel[0]); } + else if (texelComponent == 1) { lightIndex = int(texel[1]); } + else if (texelComponent == 2) { lightIndex = int(texel[2]); } + else if (texelComponent == 3) { lightIndex = int(texel[3]); } + Light light = UnpackLight(lightIndex); + + float lightDistance = distance(light.position, v_position); + vec3 L = (light.position - v_position) / lightDistance; + float lightIntensity = 0.6*cubicGaussian(2.0 * lightDistance / light.radius); + + // default lambert shading + float lambertComponent = max(dot(L, normal), 0.0); + float specularComponent = 0.0; + + /* + // NEW: Toon Shading + float lambertStep = 5.1; + float lerpU = 0.80; + float toonLambertComponent = floor(lambertComponent * lambertStep) / lambertStep; + lambertComponent = lambertComponent * (1.0 - lerpU) + toonLambertComponent * lerpU; + specularComponent = 0.0; + */ + + + // NEW: Blinn-Phong Shading + vec3 cameraDirection = normalize(vec3(fragPos) - v_position); + vec3 lightDirection = normalize(L); + float theta = max(0.0, dot(normalize(lightDirection + cameraDirection), normal)); + specularComponent = pow(theta,1000.0); + + + /* + // NEW: Iridescence + vec3 cameraDirection2 = normalize(vec3(fragPos) - v_position); + float angle = dot(cameraDirection2, normal); + float r = 0.005*abs(cos(3.0*angle + 1.0)); + float g = 0.005*abs(cos(3.0*angle + 2.0)); + float b = 0.005*abs(cos(3.0*angle + 3.0)); + fragColor += vec3(r, g, b); + */ + + /* + // NEW: Sobel + vec4 col1 = -1.0*texture2D(u_gbuffers[0], vec2(v_uv.x-0.001, v_uv.y-0.001)); + vec4 col2 = -2.0*texture2D(u_gbuffers[0], vec2(v_uv.x-0.001, v_uv.y)); + vec4 col3 = -1.0*texture2D(u_gbuffers[0], vec2(v_uv.x-0.001, v_uv.y+0.001)); + vec4 col4 = 1.0*texture2D(u_gbuffers[0], vec2(v_uv.x+0.001, v_uv.y-0.001)); + vec4 col5 = 2.0*texture2D(u_gbuffers[0], vec2(v_uv.x+0.001, v_uv.y)); + vec4 col6 = 1.0*texture2D(u_gbuffers[0], vec2(v_uv.x+0.001, v_uv.y+0.001)); + vec4 gx = col1 + col2 + col3 + col4 + col5 + col6; + vec4 col7 = 1.0*texture2D(u_gbuffers[0], vec2(v_uv.x-0.001, v_uv.y-0.001)); + vec4 col8 = 2.0*texture2D(u_gbuffers[0], vec2(v_uv.x, v_uv.y-0.001)); + vec4 col9 = 1.0*texture2D(u_gbuffers[0], vec2(v_uv.x+0.001, v_uv.y-0.001)); + vec4 col10 = -1.0*texture2D(u_gbuffers[0], vec2(v_uv.x-0.001, v_uv.y+0.001)); + vec4 col11 = -2.0*texture2D(u_gbuffers[0], vec2(v_uv.x, v_uv.y+0.001)); + vec4 col12 = -1.0*texture2D(u_gbuffers[0], vec2(v_uv.x+0.001, v_uv.y+0.001)); + vec4 gy = col7 + col8 + col9 + col10 + col11 + col12; + vec4 g = sqrt(gx*gx + gy*gy); + float lerpT = 0.7; + albedo = albedo * (1.0 - lerpT) + vec3(g.rgb) * lerpT; + */ + + fragColor += albedo * (lambertComponent + 3.0 * specularComponent) * light.color * vec3(lightIntensity); + + } + + // NEW: add some ambient lighting + vec3 ambientComponent = 0.05 * albedo; + fragColor += ambientComponent; + + 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..3057084 100644 --- a/src/shaders/deferredToTexture.frag.glsl +++ b/src/shaders/deferredToTexture.frag.glsl @@ -4,6 +4,8 @@ precision highp float; uniform sampler2D u_colmap; uniform sampler2D u_normap; +// NEW +uniform mat4 u_viewMatrix; varying vec3 v_position; varying vec3 v_normal; @@ -21,6 +23,11 @@ void main() { vec3 norm = applyNormalMap(v_normal, vec3(texture2D(u_normap, v_uv))); vec3 col = vec3(texture2D(u_colmap, v_uv)); + // NEW + vec3 viewNorm = normalize(vec3(u_viewMatrix * vec4(norm,0.0))); + gl_FragData[0] = vec4(col, viewNorm.x); + gl_FragData[1] = vec4(v_position, viewNorm.y); + // TODO: populate your g buffer // gl_FragData[0] = ?? // gl_FragData[1] = ?? diff --git a/src/shaders/forwardPlus.frag.glsl.js b/src/shaders/forwardPlus.frag.glsl.js index 022fda7..c03a653 100644 --- a/src/shaders/forwardPlus.frag.glsl.js +++ b/src/shaders/forwardPlus.frag.glsl.js @@ -1,7 +1,6 @@ export default function(params) { return ` - // TODO: This is pretty much just a clone of forward.frag.glsl.js - + #version 100 precision highp float; @@ -9,9 +8,15 @@ export default function(params) { uniform sampler2D u_normap; uniform sampler2D u_lightbuffer; - // TODO: Read this buffer to determine the lights influencing a cluster + // Read this buffer to determine the lights influencing a cluster uniform sampler2D u_clusterbuffer; + uniform mat4 u_viewMatrix; + uniform float u_screenWidth; + uniform float u_screenHeight; + uniform float u_camera_near; + uniform float u_camera_far; + varying vec3 v_position; varying vec3 v_normal; varying vec2 v_uv; @@ -75,27 +80,58 @@ export default function(params) { } void main() { + vec3 albedo = texture2D(u_colmap, v_uv).rgb; vec3 normap = texture2D(u_normap, v_uv).xyz; vec3 normal = applyNormalMap(v_normal, normap); + + // NEW + int clusterXIndex = int(gl_FragCoord.x / (float(u_screenWidth) / float(${params.xSlices})) ); + int clusterYIndex = int(gl_FragCoord.y / (float(u_screenHeight) / float(${params.ySlices})) ); + vec4 cameraPos = u_viewMatrix * vec4(v_position, 1.0); + cameraPos.z = -cameraPos.z; + int clusterZIndex = int( (cameraPos.z - u_camera_near) / + (float(u_camera_far - u_camera_near) / float(${params.zSlices}))); + int clusterIndex = clusterXIndex + clusterYIndex * ${params.xSlices} + clusterZIndex * ${params.xSlices} * ${params.ySlices}; + + int numClusters = ${params.xSlices} * ${params.ySlices} * ${params.zSlices}; + float u = float(clusterIndex + 1) / float(numClusters + 1); + int numClusterLights = int(texture2D(u_clusterbuffer, vec2(u, 0)).r); + int texelsPerCol = int(float(${params.maxLightsPerCluster} + 1) * 0.25) + 1; + + vec3 fragColor = vec3(0.0); - for (int i = 0; i < ${params.numLights}; ++i) { - Light light = UnpackLight(i); + for (int i = 0; i < ${params.numLights}; ++i) + { + // NEW + if (i >= numClusterLights) { break; } + int texIndex = int(float(i + 1) * 0.25); + float v = float(texIndex + 1) / float(texelsPerCol + 1); + vec4 texel = texture2D(u_clusterbuffer, vec2(u,v)); + + int lightIndex; + int texelComponent = (i + 1) - (texIndex * 4); + if (texelComponent == 0) { lightIndex = int(texel[0]); } + else if (texelComponent == 1) { lightIndex = int(texel[1]); } + else if (texelComponent == 2) { lightIndex = int(texel[2]); } + else if (texelComponent == 3) { lightIndex = int(texel[3]); } + Light light = UnpackLight(lightIndex); + float lightDistance = distance(light.position, v_position); vec3 L = (light.position - v_position) / lightDistance; - float lightIntensity = cubicGaussian(2.0 * lightDistance / light.radius); - float lambertTerm = max(dot(L, normal), 0.0); - - fragColor += albedo * lambertTerm * light.color * vec3(lightIntensity); + + float lambertComponent = max(dot(L, normal), 0.0); + fragColor += albedo * lambertComponent * light.color * vec3(lightIntensity); } const vec3 ambientLight = vec3(0.025); fragColor += albedo * ambientLight; gl_FragColor = vec4(fragColor, 1.0); + } `; } diff --git a/webpack.config.js b/webpack.config.js index be99a75..33bb705 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -44,7 +44,7 @@ module.exports = function(env) { } }), ].filter(p => p), - devtool: 'source-map', + devtool: 'none', devServer: { port: 5650, publicPath: '/build/'