diff --git a/js/003-data/gameVariableData.js b/js/003-data/gameVariableData.js
index d366e2ad04ba636e2c7da0ac9a114527c2c11c31..9c4d6c78d5e78c8d5f63397e19ca4cbe352a8b18 100644
--- a/js/003-data/gameVariableData.js
+++ b/js/003-data/gameVariableData.js
@@ -142,7 +142,6 @@ App.Data.defaultGameStateVariables = {
 	setZoomSpeed: 1,
 	setFaceCulling: false,
 	setTextureResolution: 1024,
-	setColorBurn: true,
 	showAgeDetail: 1,
 	showAppraisal: 1,
 	showAssignToScenes: 1,
diff --git a/src/art/artJS.js b/src/art/artJS.js
index 6c759a847392041f847cbc25748d82db073d8641..17fb6e1cccc8b83fe176632bbae10bc069ac2282 100644
--- a/src/art/artJS.js
+++ b/src/art/artJS.js
@@ -242,6 +242,8 @@ App.Art.webglArtElement = function(slave, artSize) {
 		App.Art.applyMaterials(slave, scene, p);
 		App.Art.applyMorphs(slave, scene, p);
 
+		console.log(scene);
+
 		// create UI and render based on active view
 		container.innerText = "";
 		App.Art.createWebglUI(container, slave, artSize, scene, p);
diff --git a/src/art/webgl/art.js b/src/art/webgl/art.js
index 5719c4994998047f6e4f2377d2f89c5c2690de6f..22ca02d63c8ad417c8bbe8197314678b1b907183 100644
--- a/src/art/webgl/art.js
+++ b/src/art/webgl/art.js
@@ -1128,10 +1128,10 @@ App.Art.applyMaterials = function(slave, scene, p) {
 	let scleraColorLeft = App.Art.hexToRgb(extractColor(hasLeftEye(slave) ? extractColor(slave.eye.left.sclera) : extractColor("black")));
 	let scleraColorRight = App.Art.hexToRgb(extractColor(hasRightEye(slave) ? extractColor(slave.eye.right.sclera) : extractColor("black")));
 
-	materials.push(["Iris_Left", "Kd", [irisColorLeft[0], irisColorLeft[1], irisColorLeft[2]]]);
-	materials.push(["Iris_Right", "Kd", [irisColorRight[0], irisColorRight[1], irisColorRight[2]]]);
-	materials.push(["Sclera_Left", "Kd", [scleraColorLeft[0] * 0.6, scleraColorLeft[1] * 0.6, scleraColorLeft[2] * 0.56]]);
-	materials.push(["Sclera_Right", "Kd", [scleraColorRight[0] * 0.6, scleraColorRight[1] * 0.6, scleraColorRight[2] * 0.56]]);
+	materials.push(["Iris_Left", "Kd", [irisColorLeft[0]*2, irisColorLeft[1]*2, irisColorLeft[2]*2]]);
+	materials.push(["Iris_Right", "Kd", [irisColorRight[0]*2, irisColorRight[1]*2, irisColorRight[2]*2]]);
+	materials.push(["Sclera_Left", "Kd", [scleraColorLeft[0]*0.8, scleraColorLeft[1]*0.8, scleraColorLeft[2]*0.8]]);
+	materials.push(["Sclera_Right", "Kd", [scleraColorRight[0]*0.8, scleraColorRight[1]*0.8, scleraColorRight[2]*0.8]]);
 
 	// expected skin color
 	let O =  App.Art.hexToRgb(skinColorCatcher(slave).skinColor);
@@ -1182,9 +1182,9 @@ App.Art.applyMaterials = function(slave, scene, p) {
 		materials.push(["WhiteToneAnus", "Ks", S]);
 		materials.push(["WhiteToneGenitalia", "Kd", mA]);
 		materials.push(["WhiteToneGenitalia", "Ks", S]);
-		materials.push(["WhiteFutalicious_Genitalia_G8F_Glans_Futalicious_Shell", "Kd", [mA[0]*0.8, mA[1]*0.8, mA[2]*0.8]]);
+		materials.push(["WhiteFutalicious_Genitalia_G8F_Glans_Futalicious_Shell", "Kd", [mA[0]*0.9, mA[1]*0.9, mA[2]*0.9]]);
 		materials.push(["WhiteFutalicious_Genitalia_G8F_Glans_Futalicious_Shell", "Ks", S]);
-		materials.push(["nipple_mask", "Kd", [areolaColor[0]*0.7/A[0], areolaColor[1]*0.7/A[1], areolaColor[2]*0.7/A[2]]]);
+		materials.push(["nipple_mask", "Kd", [areolaColor[0]/A[0], areolaColor[1]/A[1], areolaColor[2]/A[2]]]);
 		materials.push(["nipple_mask", "map_Kd", "base2/skin/torso white.jpg"]);
 	} else if (sqBO < sqAO && sqBO < sqCO && sqBO < sqDO) {
 		materials.push(["LightToneTorso", "Kd", mB]);
@@ -1207,9 +1207,9 @@ App.Art.applyMaterials = function(slave, scene, p) {
 		materials.push(["LightToneAnus", "Ks", S]);
 		materials.push(["LightToneGenitalia", "Kd", mB]);
 		materials.push(["LightToneGenitalia", "Ks", S]);
-		materials.push(["LightFutalicious_Genitalia_G8F_Glans_Futalicious_Shell", "Kd", [mB[0]*0.8, mB[1]*0.77, mB[2]*0.80]]);
+		materials.push(["LightFutalicious_Genitalia_G8F_Glans_Futalicious_Shell", "Kd", [mB[0]*0.8, mB[1]*0.77, mB[2]*0.8]]);
 		materials.push(["LightFutalicious_Genitalia_G8F_Glans_Futalicious_Shell", "Ks", S]);
-		materials.push(["nipple_mask", "Kd", [areolaColor[0]*0.8/B[0], areolaColor[1]*0.8/B[1], areolaColor[2]*0.8/B[2]]]);
+		materials.push(["nipple_mask", "Kd", [areolaColor[0]/B[0], areolaColor[1]/B[1], areolaColor[2]/B[2]]]);
 		materials.push(["nipple_mask", "map_Kd", "base2/skin/torso light.jpg"]);
 	} else if (sqCO < sqBO && sqCO < sqAO && sqCO < sqDO) {
 		materials.push(["MidToneTorso", "Kd", mC]);
@@ -1232,7 +1232,7 @@ App.Art.applyMaterials = function(slave, scene, p) {
 		materials.push(["MidToneAnus", "Ks", S]);
 		materials.push(["MidToneGenitalia", "Kd", mC]);
 		materials.push(["MidToneGenitalia", "Ks", S]);
-		materials.push(["MidFutalicious_Genitalia_G8F_Glans_Futalicious_Shell", "Kd", [mC[0]*0.65, mC[1]*0.65, mC[2]*0.65]]);
+		materials.push(["MidFutalicious_Genitalia_G8F_Glans_Futalicious_Shell", "Kd", [mC[0]*0.7, mC[1]*0.7, mC[2]*0.7]]);
 		materials.push(["MidFutalicious_Genitalia_G8F_Glans_Futalicious_Shell", "Ks", S]);
 		materials.push(["nipple_mask", "Kd", [areolaColor[0]/C[0], areolaColor[1]/C[1], areolaColor[2]/C[2]]]);
 		materials.push(["nipple_mask", "map_Kd", "base2/skin/torso mid.jpg"]);
@@ -1257,7 +1257,7 @@ App.Art.applyMaterials = function(slave, scene, p) {
 		materials.push(["DarkToneAnus", "Ks", S]);
 		materials.push(["DarkToneGenitalia", "Kd", mD]);
 		materials.push(["DarkToneGenitalia", "Ks", S]);
-		materials.push(["DarkFutalicious_Genitalia_G8F_Glans_Futalicious_Shell", "Kd", [mD[0]*0.75, mD[1]*0.75, mD[2]*0.75]]);
+		materials.push(["DarkFutalicious_Genitalia_G8F_Glans_Futalicious_Shell", "Kd", [mD[0]*0.85, mD[1]*0.85, mD[2]*0.85]]);
 		materials.push(["DarkFutalicious_Genitalia_G8F_Glans_Futalicious_Shell", "Ks", S]);
 		materials.push(["nipple_mask", "Kd", [areolaColor[0]/D[0], areolaColor[1]/D[1], areolaColor[2]/D[2]]]);
 		materials.push(["nipple_mask", "map_Kd", "base2/skin/torso dark.jpg"]);
diff --git a/src/art/webgl/engine.js b/src/art/webgl/engine.js
index 4b4e5608758fe14393430ba9dbc97c06e5e4d0fa..75cd35adcaa1237d93125bc96e6359a2a557b044 100644
--- a/src/art/webgl/engine.js
+++ b/src/art/webgl/engine.js
@@ -1,76 +1,164 @@
 'use strict';
 
 App.Art.Engine = class {
-	getVsSourceBg() {
+	getVsSourceGeometry() {
 		return  `#version 300 es
-                precision highp float;
+				precision highp float;
 
-                in vec2 vertexPosition;
-                out vec2 vertexPos;
-            
-                void main() {
-                    vertexPos = vertexPosition;
-                    gl_Position = vec4(vertexPosition, 0.0, 1.0);
-                }`;
-	}
+                uniform mat4 matModelViewProjection;
+                uniform mat4 matModelView;
+                uniform mat4 matModel;
+				uniform mat4 matNormal;
 
-	getFsSourceBg() {
-		return  `#version 300 es
-                precision highp float;
-                precision highp sampler2D;
+				in vec3 vertexNormal;
+				in vec3 vertexPosition;
+				in vec2 textureCoordinate;
 
-                uniform sampler2D textSampler;
-                uniform vec4 backgroundColor;
+				in vec3 vertexNormalMorph;
+				in vec3 vertexPositionMorph;
 
-                in vec2 vertexPos;
-                out vec4 outputColor;
+				out vec2 textureCoord;
+				out vec3 normal;
+				out vec3 pos;
 
-                void main() {
-                    vec2 textureCoord = vec2(vertexPos.s, -vertexPos.t) * 0.5 + 0.5;
-                    vec3 c = backgroundColor.rgb * texture(textSampler, textureCoord.st).rgb;
-                    outputColor  = vec4(c.rgb * backgroundColor.a, backgroundColor.a);
-                }`;
+				void main() {
+					gl_Position = matModelViewProjection * vec4(vertexPosition + vertexPositionMorph, 1.0);
+					normal = normalize((matNormal * vec4(vertexNormal + vertexNormalMorph, 1.0)).xyz);
+
+					textureCoord = textureCoordinate;
+					pos = (matModelView * vec4(vertexPosition + vertexPositionMorph, 1.0)).xyz;
+				}`;
 	}
 
-	getVsSource() {
+	getFsSourceGeometry() {
 		return  `#version 300 es
-                precision highp float;
+				precision highp float;
 
-                uniform mat4 matView;
-                uniform mat4 matProj;
-                uniform mat4 matRot;
-                uniform mat4 matTrans;
-                uniform mat4 matScale;
+				layout(location = 0) out vec3 gPosition;
+				layout(location = 1) out vec3 gNormal;
+				layout(location = 2) out float gAlpha;
 
-                in vec3 vertexNormal;
-                in vec3 vertexPosition;
-                in vec2 textureCoordinate;
-                in vec3 vertexTangent;
+				uniform sampler2D alpha;
 
-                in vec3 vertexNormalMorph;
-                in vec3 vertexPositionMorph;
+				in vec2 textureCoord;
+				in vec3 pos;
+				in vec3 normal;
 
-                out vec2 textureCoord;
-                out vec3 normal;
-                out vec3 pos;
-                out mat3 TBN;
+				void main() {
+					float map_d = texture(alpha, textureCoord).r;
 
-                void main() {
-                    gl_Position = matProj * matView * matTrans * matScale * matRot * vec4(vertexPosition + vertexPositionMorph, 1.0);
-                    normal = normalize((matRot * vec4(vertexNormal + vertexNormalMorph, 1.0)).xyz);
+					if (map_d < 0.85)
+						discard;
+					
+					gPosition = pos;
+					gNormal = normal;
+					gAlpha = map_d;
+				}`;
+	}
+
+	getVsSourceQuad() {
+		return  `#version 300 es
+				precision highp float;
+
+				in vec2 vertexPosition;
+				in vec2 textureCoordinate;
 
-                    vec3 T = normalize((matRot * vec4(vertexTangent, 1.0)).xyz);
-                    vec3 N = normalize((matRot * vec4(vertexNormal + vertexNormalMorph, 1.0)).xyz);
-                    T = normalize(T - dot(T, N) * N);
-                    vec3 B = cross(N, T);
-                    TBN = mat3(T, B, N);
+				out vec2 textureCoord;
 
-                    textureCoord = textureCoordinate;
-                    pos = (matTrans * matScale * matRot * vec4(vertexPosition + vertexPositionMorph, 1.0)).xyz;
+                void main() {
+					textureCoord = textureCoordinate;
+					gl_Position = vec4(vertexPosition, 0.0, 1.0);
                 }`;
 	}
 
-	getFsSource(dl, pl) {
+	getFsSourceSSAO() {
+		return  `#version 300 es
+				precision highp float;
+
+				out float ao;
+
+				in vec2 textureCoord;
+
+				uniform sampler2D gPosition;
+				uniform sampler2D gNormal;
+				uniform sampler2D gAlpha;
+				uniform sampler2D texNoise;
+
+				uniform vec3 samples[32];
+
+				uniform mat4 projection;
+				uniform mat4 view;
+
+				uniform float radius;
+				uniform float bias;
+				uniform float scale;
+
+				void main() {
+					vec2 resolution = vec2(textureSize(gPosition, 0));
+					vec3 pos = texture(gPosition, textureCoord).xyz;
+					vec3 normal = normalize(texture(gNormal, textureCoord).rgb);
+
+					// tile noise texture over screen based on screen dimensions divided by noise size
+					vec3 randomVec = normalize(texture(texNoise, textureCoord * resolution / vec2(3.0, 3.0)).xyz);
+
+					// create TBN
+					vec3 tangent = normalize(randomVec - normal * dot(randomVec, normal));
+					vec3 bitangent = cross(normal, tangent);
+					mat3 TBN = mat3(tangent, bitangent, normal);
+
+					// iterate over the sample kernel and calculate occlusion factor
+					float occlusion = 0.0;
+					for(int i = 0; i < 32; i++)
+					{
+						// get sample position
+						vec3 samplePos = TBN * samples[i]; // from tangent to view-space
+						samplePos = pos + samplePos * radius; 
+						
+						// project sample position
+						vec4 offset = vec4(samplePos, 1.0);
+						offset = projection * offset; // from view to clip-space
+						offset.xy /= offset.w; // perspective divide
+						offset.xy = offset.xy * 0.5 + 0.5; // transform to range 0.0 - 1.0
+						
+						float sampleDepth = texture(gPosition, offset.xy).z;
+						
+						// range check & accumulate
+						float rangeCheck = smoothstep(0.0, 1.0, radius / abs(pos.z - sampleDepth));
+						occlusion += (sampleDepth <= samplePos.z + bias ? 1.0 : 0.0) * rangeCheck;           
+					}
+					occlusion = 1.0 - (occlusion / scale);
+					
+					ao = occlusion * texture(gAlpha, textureCoord).r;
+				}`;
+	}
+
+	getFsSourceSSAOBlur() {
+		return  `#version 300 es
+				precision highp float;
+
+				out float ao;
+				in vec2 textureCoord;
+
+				uniform sampler2D ssaoInput;
+				uniform int blur;
+				
+				void main() 
+				{
+					vec2 texelSize = 1.0 / vec2(textureSize(ssaoInput, 0));
+					float result = 0.0;
+					for (int x = -blur; x <= blur; x++) 
+					{
+						for (int y = -blur; y <= blur; y++) 
+						{
+							vec2 offset = vec2(float(x), float(y)) * texelSize;
+							result += texture(ssaoInput, textureCoord + offset).r;
+						}
+					}
+					ao = result / (float(blur*2+1) * float(blur*2+1));
+				}`;
+	}
+
+	getFsSourceForwardPass(dl, pl) {
 		return `#version 300 es
                 precision highp float;
                 precision highp sampler2D;
@@ -87,8 +175,11 @@ App.Art.Engine = class {
 
                 uniform float whiteM;
                 uniform float gammaY;
+				uniform vec3 irradiance;
+				uniform float ssaoInt;
+				uniform float albedoInt;
 
-                uniform vec3 Ka;
+				uniform vec3 Ka;
                 uniform vec3 Kd;
                 uniform vec3 Ks;
 				uniform vec3 Ke;
@@ -96,117 +187,221 @@ App.Art.Engine = class {
                 uniform float Ns;
 
                 uniform float sNormals;
+				uniform float sSSAO;
+				uniform float sAO;
                 uniform float sAmbient;
                 uniform float sDiffuse;
                 uniform float sSpecular;
 				uniform float sEmission;
                 uniform float sAlpha;
                 uniform float sGamma;
+				uniform float sHDR;
                 uniform float sReinhard;
                 uniform float sNormal;
-				uniform float sColorBurn;
+
+				uniform float roughness;
+				uniform float metallic;
+				uniform vec3 fresnel;
                 
-                uniform vec3 lookDir;
+                uniform vec3 cameraPos;
 
                 uniform sampler2D textSampler[7];
 
-                in vec2 textureCoord;
-                in vec3 normal;
+				in vec2 textureCoord;
+				in vec3 normal;
                 in vec3 pos;
-                in mat3 TBN;
 
                 out vec4 outputColor;
 
+				const float PI = 3.14159265359;
+
+				float distributionGGX(vec3 N, vec3 H, float roughness)
+				{
+					float a      = roughness*roughness;
+					float a2     = a*a;
+					float NdotH  = max(dot(N, H), 0.0);
+					float NdotH2 = NdotH*NdotH;
+					
+					float num   = a2;
+					float denom = (NdotH2 * (a2 - 1.0) + 1.0);
+					denom = PI * denom * denom;
+					
+					return num / denom;
+				}
+				
+				float geometrySchlickGGX(float NdotV, float roughness)
+				{
+					float r = (roughness + 1.0);
+					float k = (r*r) / 8.0;
+				
+					float num   = NdotV;
+					float denom = NdotV * (1.0 - k) + k;
+					
+					return num / denom;
+				}
+
+				float geometrySmith(vec3 N, vec3 V, vec3 L, float roughness)
+				{
+					float NdotV = max(dot(N, V), 0.0);
+					float NdotL = max(dot(N, L), 0.0);
+					float ggx2  = geometrySchlickGGX(NdotV, roughness);
+					float ggx1  = geometrySchlickGGX(NdotL, roughness);
+					
+					return ggx1 * ggx2;
+				}
+
+				vec3 fresnelSchlick(float cosTheta, vec3 F0)
+				{
+					return F0 + (1.0 - F0) * pow(max(1.0 - cosTheta, 0.0), 5.0);
+				}
+
+				vec3 fresnelSchlickRoughness(float cosTheta, vec3 F0, float roughness)
+				{
+					return F0 + (max(vec3(1.0 - roughness), F0) - F0) * pow(max(1.0 - cosTheta, 0.0), 5.0);
+				}
+
+				mat3 createTBNMatrix(vec3 position, vec2 coord, vec3 normal)
+				{
+					vec3 tangent = normalize(dFdx(position)*dFdy(coord).t - dFdy(position)*dFdx(coord).t);
+					vec3 bitangent = cross(normal, tangent);
+					return mat3(tangent, bitangent, normal);
+				}
+
                 void main() {
+					vec2 resolution = vec2(textureSize(textSampler[0], 0));
+					mat3 TBN = createTBNMatrix(pos, textureCoord, normal);
+
                     vec3 new_normal = normal;
-                    vec3 map_Ka = vec3(0.0,0.0,0.0);
                     vec3 map_Kd = vec3(0.0,0.0,0.0);
                     vec3 map_Ks = vec3(0.0,0.0,0.0);
 					vec3 map_Ke = vec3(0.0,0.0,0.0);
                     float map_Ns = 0.0;
                     float map_d = 1.0;
+					float ao = 0.0;
+					float r = roughness;
 
-                    if (sNormal == 1.0) {
-                        vec3 map_Kn = texture(textSampler[5], textureCoord.st).rgb *2.0-1.0;
-                        if (map_Kn != vec3(-1.0,-1.0,-1.0))
-                            new_normal = normalize(TBN * map_Kn);
+					if (sNormal == 1.0) {
+                        vec3 map_Kn = texture(textSampler[5], textureCoord).rgb *2.0-1.0;
+                        if (map_Kn != vec3(-1.0,-1.0,-1.0)) {
+							new_normal = normalize(TBN * map_Kn);
+						}
                     }
 
-                    if (sAmbient == 1.0)
-                        map_Ka = Ka * texture(textSampler[0], textureCoord.st).rgb;
+					if (sSSAO == 1.0)
+						ao = texture(textSampler[0], vec2(gl_FragCoord.x, gl_FragCoord.y) / resolution).r;
 
-                    if (sDiffuse == 1.0)
-                        map_Kd = Kd * texture(textSampler[1], textureCoord.st).rgb;
+					if (sDiffuse == 1.0)
+                        map_Kd = Kd * texture(textSampler[1], textureCoord).rgb;
 
                     if (sSpecular == 1.0) {
-                        map_Ks = Ks * texture(textSampler[2], textureCoord.st).rgb;
-                        map_Ns = Ns * texture(textSampler[3], textureCoord.st).r;
+                        map_Ks = Ks * texture(textSampler[2], textureCoord).rgb;
+                        map_Ns = Ns * texture(textSampler[3], textureCoord).r;
+						r = (1.0 - map_Ks.r) * roughness;
                     }
 
-					if (sEmission == 1.0) {
-                        map_Ke = Ke * texture(textSampler[6], textureCoord.st).rgb;
-                    }
+					if (sEmission == 1.0)
+                        map_Ke = Ke * texture(textSampler[6], textureCoord).rgb;
 
                     if (sAlpha == 1.0)
-                        map_d = d * texture(textSampler[4], textureCoord.st).r;
+                        map_d = d * texture(textSampler[4], textureCoord).r;
 
 					if (map_d < 0.05)
 						discard;
 
-                    vec3 Ld = vec3(0.0,0.0,0.0);
-                    vec3 Ls = vec3(0.0,0.0,0.0);
-                    vec3 La = vec3(0.0,0.0,0.0);
+					// material parameters
+					vec3 albedo = pow(map_Kd, vec3(albedoInt));
+					ao = pow(ao, ssaoInt);
+
+					vec3 N = new_normal;
+					vec3 V = normalize(cameraPos - pos);
+					vec3 F0 = mix(fresnel, albedo, metallic);
+						
+					// reflectance equation
+					vec3 Lo = vec3(0.0);
 					vec3 Le = map_Ke;
+                    for (int i = 0; i < ${dl}; i++) {
 
-					if (sColorBurn == 1.0) {
-						float l1 = dot(map_Kd, vec3(0.2126,0.7152,0.0722));
-						float l2 = dot(map_Kd * map_Kd, vec3(0.2126,0.7152,0.0722));
-						map_Kd = l1/(l2+0.0001) * map_Kd * map_Kd;
+						// calculate per-light radiance
+						vec3 L = -lightVect[i];
+						vec3 H = normalize(V + L);
+						float attenuation = lightInt[i];
+						vec3 radiance     = lightColor[i] * attenuation;        
+						
+						// cook-torrance brdf
+						float NDF = distributionGGX(N, H, r);        
+						float G   = geometrySmith(N, V, L, r);      
+						vec3 F    = fresnelSchlick(max(dot(H, V), 0.0), F0);       
+						
+						vec3 numerator    = NDF * G * F;
+						float denominator = 4.0 * max(dot(N, V), 0.0) * max(dot(N, L), 0.0) + 0.001;
+						vec3 specular     = numerator / denominator;  
+
+						// kS is equal to Fresnel
+						vec3 kD = vec3(1.0) - F;
+						kD *= 1.0 - metallic;	  
+						
+						// add to outgoing radiance Lo
+						float NdotL = max(dot(N, L), 0.0);                
+						Lo += (kD * albedo / PI + specular) * radiance * NdotL;
 					}
 
-					map_Ka = map_Kd * map_Ka;
-					
-                    for (int i = 0; i < ${dl}; i++) {
-                        float angle = max(dot(-lightVect[i], new_normal),0.0);
-                        vec3 reflection = reflect(-lightVect[i], new_normal);
-                        float specular = pow(max(dot(reflection, lookDir),0.0), (0.0001+map_Ns));
-
-                        Ld += map_Kd * lightInt[i] * angle * lightColor[i];
-                        Ls += map_Ks * lightInt[i] * specular * angle * lightColor[i];
-                        La += map_Ka * lightInt[i] * (1.0-angle) * lightAmb[i] * lightColor[i];
-                    }
+					for (int i = 0; i < ${pl}; i++) {
+
+						// calculate per-light radiance
+						vec3 L = normalize(pointLightPos[i] - pos);
+						vec3 H = normalize(V + L);
+						float attenuation = pointLightInt[i];
+						vec3 radiance     = pointLightColor[i] * attenuation;        
+						
+						// cook-torrance brdf
+						float NDF = distributionGGX(N, H, r);        
+						float G   = geometrySmith(N, V, L, r);      
+						vec3 F    = fresnelSchlick(max(dot(H, V), 0.0), F0);       
+						
+						vec3 numerator    = NDF * G * F;
+						float denominator = 4.0 * max(dot(N, V), 0.0) * max(dot(N, L), 0.0) + 0.001;
+						vec3 specular     = numerator / denominator;  
+
+						// kS is equal to Fresnel
+						vec3 kD = vec3(1.0) - F;
+						kD *= 1.0 - metallic;	  
+						
+						// add to outgoing radiance Lo
+						float NdotL = max(dot(N, L), 0.0);                
+						Lo += (kD * albedo / PI + specular) * radiance * NdotL;
+					}
 
-                    for (int i = 0; i < ${pl}; i++) {
-                        vec3 pointLightDir = normalize(pos - pointLightPos[i]);
-                        float angle = max(dot(-pointLightDir, new_normal),0.0);
-                        vec3 reflection = reflect(-pointLightDir, new_normal);
-                        float specular = pow(max(dot(reflection, lookDir),0.0), (0.0001+map_Ns));
+					// ambient lighting
+					vec3 kS = fresnelSchlickRoughness(max(dot(N, V), 0.0), F0, r);
+					vec3 kD = vec3(1.0) - metallic;
 
-                        Ld += map_Kd * pointLightInt[i] * angle * pointLightColor[i];
-                        Ls += map_Ks * pointLightInt[i] * specular * angle * pointLightColor[i];
-                        La += map_Ka * pointLightInt[i] * (1.0-angle) * pointLightAmb[i] * pointLightColor[i];
-                    }
+					vec3 ambient = (kD * irradiance * albedo) * ao;
 
-                    vec3 vLighting = Ld + Ls + La + Le;
-                    vec3 c = vLighting;
+					vec3 c = ambient + Lo + Le;
 
-					if (sGamma == 1.0) {
-                        c.r = pow(c.r, (1.0/gammaY));
-                        c.g = pow(c.g, (1.0/gammaY));
-                        c.b = pow(c.b, (1.0/gammaY));
+					if (sHDR == 1.0) {
+						c = c / (c + vec3(1.0));
                     }
-					
-                    if (sReinhard == 1.0) {
+
+					if (sReinhard == 1.0) {
                         float l_old = 0.2126*c.r+0.7152*c.g+0.0722*c.b;
                         float numerator = l_old * (1.0 + (l_old / (whiteM*whiteM)));
                         float l_new = numerator / (1.0 + l_old);
                         c = c * (l_new / l_old);
                     }
 
+					if (sGamma == 1.0) {
+                        c = pow(c, vec3(1.0/gammaY));
+                    }
+					
                     if (sNormals == 1.0) {
                         c = new_normal;
                     }
 
+					if (sAO == 1.0) {
+						c = vec3(ao);
+					}
+
                     outputColor = vec4(c*map_d, map_d);
                 }`;
 	}
@@ -219,14 +414,20 @@ App.Art.Engine = class {
 			this.buffers.models[m] = new class {};
 		}
 
-		// init background buffers
-		this.buffers.backgroundPositionBuffer = this.gl.createBuffer();
-		this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.buffers.backgroundPositionBuffer);
-		this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array([-1, -1, 1, -1, 1, 1, -1, 1]), this.gl.STATIC_DRAW);
+		// init quad buffers
+		this.buffers.quadPositionBuffer = this.gl.createBuffer();
+		this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.buffers.quadPositionBuffer);
+		this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array([-1, 1, -1, -1, 1, 1, 1, -1]), this.gl.STATIC_DRAW);
 
-		this.buffers.backgroundIndexBuffer = this.gl.createBuffer();
-		this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.buffers.backgroundIndexBuffer);
-		this.gl.bufferData(this.gl.ELEMENT_ARRAY_BUFFER, new Uint16Array([0, 1, 2, 0, 2, 3]), this.gl.STATIC_DRAW);
+		this.buffers.quadTextureBuffer = this.gl.createBuffer();
+		this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.buffers.quadTextureBuffer);
+		this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array([0, 1, 0, 0, 1, 1, 1, 0]), this.gl.STATIC_DRAW);
+
+		this.buffers.quadIndexBuffer = this.gl.createBuffer();
+		this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.buffers.quadIndexBuffer);
+		this.gl.bufferData(this.gl.ELEMENT_ARRAY_BUFFER, new Uint16Array([0, 1, 2, 1, 2, 3]), this.gl.STATIC_DRAW);
+
+		this.initFrameBuffers(this.buffers, 1200, 1500);
 
 		// init model buffers
 		for (let m=0; m < this.buffers.models.length; m++) {
@@ -236,7 +437,6 @@ App.Art.Engine = class {
 			modelBuffers.verticesPositionBuffer = [];
 			modelBuffers.verticesNormalBuffer = [];
 			modelBuffers.verticesTextureCoordBuffer = [];
-			modelBuffers.verticesTangentBuffer = [];
 			modelBuffers.vertexCount = [];
 			modelBuffers.verticesMorphBuffer = [];
 			modelBuffers.verticesNormalMorphBuffer = [];
@@ -257,10 +457,6 @@ App.Art.Engine = class {
 				this.gl.bindBuffer(this.gl.ARRAY_BUFFER, modelBuffers.verticesTextureCoordBuffer[i]);
 				this.gl.bufferData(this.gl.ARRAY_BUFFER, this.base64ToFloat(modelData.figures[i].texts), this.gl.STATIC_DRAW);
 
-				modelBuffers.verticesTangentBuffer[i] = this.gl.createBuffer();
-				this.gl.bindBuffer(this.gl.ARRAY_BUFFER, modelBuffers.verticesTangentBuffer[i]);
-				this.gl.bufferData(this.gl.ARRAY_BUFFER, this.base64ToFloat(modelData.figures[i].tans), this.gl.STATIC_DRAW);
-
 				// return dummy morph
 				modelBuffers.verticesMorphBuffer[i] = this.gl.createBuffer();
 				this.gl.bindBuffer(this.gl.ARRAY_BUFFER, modelBuffers.verticesMorphBuffer[i]);
@@ -283,6 +479,123 @@ App.Art.Engine = class {
 		}
 	}
 
+	updateResolution(screenWidth, screenHeight) {
+		this.gl.bindTexture(this.gl.TEXTURE_2D, this.buffers.gPosition);
+		this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.RGBA32F, screenWidth, screenHeight, 0, this.gl.RGBA, this.gl.FLOAT, null);
+
+		this.gl.bindTexture(this.gl.TEXTURE_2D, this.buffers.gNormal);
+		this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.RGBA16F, screenWidth, screenHeight, 0, this.gl.RGBA, this.gl.FLOAT, null);
+
+		this.gl.bindTexture(this.gl.TEXTURE_2D, this.buffers.gAlpha);
+		this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.R16F, screenWidth, screenHeight, 0, this.gl.RED, this.gl.FLOAT, null);
+
+		this.gl.bindRenderbuffer(this.gl.RENDERBUFFER, this.buffers.rboDepth);
+		this.gl.renderbufferStorage(this.gl.RENDERBUFFER, this.gl.DEPTH_COMPONENT16, screenWidth, screenHeight);
+
+		this.gl.bindTexture(this.gl.TEXTURE_2D, this.buffers.ssaoColorBuffer);
+		this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.R16F, screenWidth, screenHeight, 0, this.gl.RED, this.gl.FLOAT, null);
+
+		this.gl.bindTexture(this.gl.TEXTURE_2D, this.buffers.ssaoColorBufferBlur);
+		this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.R16F, screenWidth, screenHeight, 0, this.gl.RED, this.gl.FLOAT, null);
+
+		this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null);
+	}
+
+	initFrameBuffers(buffers, screenWidth, screenHeight) {
+		// configure g-buffer framebuffer
+		buffers.gBuffer = this.gl.createFramebuffer();
+		this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, buffers.gBuffer);
+
+		// position color buffer
+		buffers.gPosition = this.gl.createTexture();
+		this.gl.bindTexture(this.gl.TEXTURE_2D, buffers.gPosition);
+		this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_S, this.gl.CLAMP_TO_EDGE);
+		this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_T, this.gl.CLAMP_TO_EDGE);
+		this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.NEAREST);
+		this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MAG_FILTER, this.gl.NEAREST);
+		this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.RGBA32F, screenWidth, screenHeight, 0, this.gl.RGBA, this.gl.FLOAT, null);
+		this.gl.framebufferTexture2D(this.gl.FRAMEBUFFER, this.gl.COLOR_ATTACHMENT0, this.gl.TEXTURE_2D, buffers.gPosition, 0);
+		// normal color buffer
+		buffers.gNormal = this.gl.createTexture();
+		this.gl.bindTexture(this.gl.TEXTURE_2D, buffers.gNormal);
+		this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.NEAREST);
+		this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MAG_FILTER, this.gl.NEAREST);
+		this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.RGBA16F, screenWidth, screenHeight, 0, this.gl.RGBA, this.gl.FLOAT, null);
+		this.gl.framebufferTexture2D(this.gl.FRAMEBUFFER, this.gl.COLOR_ATTACHMENT1, this.gl.TEXTURE_2D, buffers.gNormal, 0);
+		// alpha buffer
+		buffers.gAlpha = this.gl.createTexture();
+		this.gl.bindTexture(this.gl.TEXTURE_2D, buffers.gAlpha);
+		this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.NEAREST);
+		this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MAG_FILTER, this.gl.NEAREST);
+		this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.R16F, screenWidth, screenHeight, 0, this.gl.RED, this.gl.FLOAT, null);
+		this.gl.framebufferTexture2D(this.gl.FRAMEBUFFER, this.gl.COLOR_ATTACHMENT2, this.gl.TEXTURE_2D, buffers.gAlpha, 0);
+
+		let attachments = [];
+		attachments[0] = this.gl.COLOR_ATTACHMENT0;
+		attachments[1] = this.gl.COLOR_ATTACHMENT1;
+		attachments[2] = this.gl.COLOR_ATTACHMENT2;
+		this.gl.drawBuffers(attachments);
+
+		// create and attach depth buffer
+		buffers.rboDepth = this.gl.createRenderbuffer();
+		this.gl.bindRenderbuffer(this.gl.RENDERBUFFER, buffers.rboDepth);
+		this.gl.renderbufferStorage(this.gl.RENDERBUFFER, this.gl.DEPTH_COMPONENT16, screenWidth, screenHeight);
+		this.gl.framebufferRenderbuffer(this.gl.FRAMEBUFFER, this.gl.DEPTH_ATTACHMENT, this.gl.RENDERBUFFER, buffers.rboDepth);
+		this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null);
+
+		// SSAO color buffer
+		buffers.ssaoColorBuffer = this.gl.createTexture();
+		this.gl.bindTexture(this.gl.TEXTURE_2D, buffers.ssaoColorBuffer);
+		this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.NEAREST);
+		this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MAG_FILTER, this.gl.NEAREST);
+		this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.R16F, screenWidth, screenHeight, 0, this.gl.RED, this.gl.FLOAT, null);
+
+		buffers.ssaoFBO = this.gl.createFramebuffer();
+		this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, buffers.ssaoFBO);
+		this.gl.framebufferTexture2D(this.gl.FRAMEBUFFER, this.gl.COLOR_ATTACHMENT0, this.gl.TEXTURE_2D, buffers.ssaoColorBuffer, 0);
+
+		// SSAO blur buffer
+		buffers.ssaoColorBufferBlur = this.gl.createTexture();
+		this.gl.bindTexture(this.gl.TEXTURE_2D, buffers.ssaoColorBufferBlur);
+		this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.NEAREST);
+		this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MAG_FILTER, this.gl.NEAREST);
+		this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.R16F, screenWidth, screenHeight, 0, this.gl.RED, this.gl.FLOAT, null);
+
+		buffers.ssaoBlurFBO = this.gl.createFramebuffer();
+		this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, buffers.ssaoBlurFBO);
+		this.gl.framebufferTexture2D(this.gl.FRAMEBUFFER, this.gl.COLOR_ATTACHMENT0, this.gl.TEXTURE_2D, buffers.ssaoColorBufferBlur, 0);
+		this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null);
+
+		// generate sample kernel
+		this.buffers.ssaoKernel = [];
+		for (let i = 0; i < 32; i++) {
+			let sample = [Math.random() * 2.0 - 1.0, Math.random() * 2.0 - 1.0, Math.random()];
+			sample = this.vectorNormalize(sample);
+			sample = this.vectorMul(sample, Math.random());
+			let scale = i / 32.0;
+
+			// scale samples s.t. they're more aligned to center of kernel
+			scale = this.lerp(0.1, 1.0, scale * scale);
+			sample = this.vectorMul(sample, scale);
+			this.buffers.ssaoKernel.push(sample);
+		}
+
+		// generate noise texture
+		let ssaoNoise = [];
+		for (let i = 0; i < 9; i++) {
+			let noise = [Math.random() * 2.0 - 1.0, Math.random() * 2.0 - 1.0, 0.0, 1.0];
+			ssaoNoise = ssaoNoise.concat(noise);
+		}
+
+		buffers.noiseTexture = this.gl.createTexture();
+		this.gl.bindTexture(this.gl.TEXTURE_2D, buffers.noiseTexture);
+		this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_S, this.gl.REPEAT);
+		this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_T, this.gl.REPEAT);
+		this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.NEAREST);
+		this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MAG_FILTER, this.gl.NEAREST);
+		this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.RGBA16F, 3, 3, 0, this.gl.RGBA, this.gl.FLOAT, new Float32Array(ssaoNoise));
+	}
+
 	initMorphs(modelBuffers, modelData, dir) {
 		window.sceneBlocks = {}; // automatically populated during loading of morphs
 
@@ -382,7 +695,6 @@ App.Art.Engine = class {
 					if(typeof V.setTextureResolution !== "undefined") {
 						textureSize = V.setTextureResolution;
 					}
-
 					if (width > textureSize || height > textureSize) {
 						if (width > textureSize) {
 							width = textureSize;
@@ -423,65 +735,82 @@ App.Art.Engine = class {
 
 	initShaders(sceneParams) {
 		// compile shaders
-		let vertexShader = this.gl.createShader(this.gl.VERTEX_SHADER);
-		this.gl.shaderSource(vertexShader, this.getVsSource());
-		this.gl.compileShader(vertexShader);
-
-		let fragmentShader = this.gl.createShader(this.gl.FRAGMENT_SHADER);
-		this.gl.shaderSource(fragmentShader, this.getFsSource(sceneParams.directionalLights.length, sceneParams.pointLights.length));
-		this.gl.compileShader(fragmentShader);
-
-		this.shaderProgram = this.gl.createProgram();
-		this.gl.attachShader(this.shaderProgram, vertexShader);
-		this.gl.attachShader(this.shaderProgram, fragmentShader);
-		this.gl.linkProgram(this.shaderProgram);
-
-		let vertexShaderBg = this.gl.createShader(this.gl.VERTEX_SHADER);
-		this.gl.shaderSource(vertexShaderBg, this.getVsSourceBg());
-		this.gl.compileShader(vertexShaderBg);
-
-		let fragmentShaderBg = this.gl.createShader(this.gl.FRAGMENT_SHADER);
-		this.gl.shaderSource(fragmentShaderBg, this.getFsSourceBg());
-		this.gl.compileShader(fragmentShaderBg);
-
-		this.shaderProgramBg = this.gl.createProgram();
-		this.gl.attachShader(this.shaderProgramBg, vertexShaderBg);
-		this.gl.attachShader(this.shaderProgramBg, fragmentShaderBg);
-		this.gl.linkProgram(this.shaderProgramBg);
-
-		this.gl.useProgram(this.shaderProgram);
+		let vertexShaderGeometry = this.gl.createShader(this.gl.VERTEX_SHADER);
+		this.gl.shaderSource(vertexShaderGeometry, this.getVsSourceGeometry());
+		this.gl.compileShader(vertexShaderGeometry);
+
+		let fragmentShaderGeometry = this.gl.createShader(this.gl.FRAGMENT_SHADER);
+		this.gl.shaderSource(fragmentShaderGeometry, this.getFsSourceGeometry());
+		this.gl.compileShader(fragmentShaderGeometry);
+
+		let vertexShaderQuad = this.gl.createShader(this.gl.VERTEX_SHADER);
+		this.gl.shaderSource(vertexShaderQuad, this.getVsSourceQuad());
+		this.gl.compileShader(vertexShaderQuad);
+
+		let fragmentShaderSSAO = this.gl.createShader(this.gl.FRAGMENT_SHADER);
+		this.gl.shaderSource(fragmentShaderSSAO, this.getFsSourceSSAO());
+		this.gl.compileShader(fragmentShaderSSAO);
+
+		let fragmentShaderSSAOBlur = this.gl.createShader(this.gl.FRAGMENT_SHADER);
+		this.gl.shaderSource(fragmentShaderSSAOBlur, this.getFsSourceSSAOBlur());
+		this.gl.compileShader(fragmentShaderSSAOBlur);
+
+		let fragmentShaderForwardPass = this.gl.createShader(this.gl.FRAGMENT_SHADER);
+		this.gl.shaderSource(fragmentShaderForwardPass, this.getFsSourceForwardPass(sceneParams.directionalLights.length, sceneParams.pointLights.length));
+		this.gl.compileShader(fragmentShaderForwardPass);
+
+		this.shaderProgramGeometry = this.gl.createProgram();
+		this.gl.attachShader(this.shaderProgramGeometry, vertexShaderGeometry);
+		this.gl.attachShader(this.shaderProgramGeometry, fragmentShaderGeometry);
+		this.gl.linkProgram(this.shaderProgramGeometry);
+
+		this.shaderProgramSSAO = this.gl.createProgram();
+		this.gl.attachShader(this.shaderProgramSSAO, vertexShaderQuad);
+		this.gl.attachShader(this.shaderProgramSSAO, fragmentShaderSSAO);
+		this.gl.linkProgram(this.shaderProgramSSAO);
+
+		this.shaderProgramSSAOBlur = this.gl.createProgram();
+		this.gl.attachShader(this.shaderProgramSSAOBlur, vertexShaderQuad);
+		this.gl.attachShader(this.shaderProgramSSAOBlur, fragmentShaderSSAOBlur);
+		this.gl.linkProgram(this.shaderProgramSSAOBlur);
+
+		this.shaderProgramForwardPass = this.gl.createProgram();
+		this.gl.attachShader(this.shaderProgramForwardPass, vertexShaderGeometry);
+		this.gl.attachShader(this.shaderProgramForwardPass, fragmentShaderForwardPass);
+		this.gl.linkProgram(this.shaderProgramForwardPass);
 
 		// enable vertex attributes
-		this.backgroundPositionAttribute = this.gl.getAttribLocation(this.shaderProgramBg, "vertexPosition");
-		this.gl.enableVertexAttribArray(this.backgroundPositionAttribute);
 
-		this.vertexPositionAttribute = this.gl.getAttribLocation(this.shaderProgram, "vertexPosition");
+		this.vertexPositionAttribute = this.gl.getAttribLocation(this.shaderProgramGeometry, "vertexPosition");
 		this.gl.enableVertexAttribArray(this.vertexPositionAttribute);
 
-		this.textureCoordAttribute = this.gl.getAttribLocation(this.shaderProgram, "textureCoordinate");
+		this.textureCoordAttribute = this.gl.getAttribLocation(this.shaderProgramGeometry, "textureCoordinate");
 		this.gl.enableVertexAttribArray(this.textureCoordAttribute);
 
-		this.vertexNormalAttribute = this.gl.getAttribLocation(this.shaderProgram, "vertexNormal");
+		this.vertexNormalAttribute = this.gl.getAttribLocation(this.shaderProgramGeometry, "vertexNormal");
 		this.gl.enableVertexAttribArray(this.vertexNormalAttribute);
 
-		this.vertexTangentAttribute = this.gl.getAttribLocation(this.shaderProgram, "vertexTangent");
-		this.gl.enableVertexAttribArray(this.vertexTangentAttribute);
-
-		this.vertexNormalMorphAttribute = this.gl.getAttribLocation(this.shaderProgram, "vertexNormalMorph");
+		this.vertexNormalMorphAttribute = this.gl.getAttribLocation(this.shaderProgramGeometry, "vertexNormalMorph");
 		this.gl.enableVertexAttribArray(this.vertexNormalMorphAttribute);
 
-		this.vertexPositionMorphAttribute = this.gl.getAttribLocation(this.shaderProgram, "vertexPositionMorph");
+		this.vertexPositionMorphAttribute = this.gl.getAttribLocation(this.shaderProgramGeometry, "vertexPositionMorph");
 		this.gl.enableVertexAttribArray(this.vertexPositionMorphAttribute);
+
+		this.quadPositionAttribute = this.gl.getAttribLocation(this.shaderProgramSSAO, "vertexPosition");
+		this.gl.enableVertexAttribArray(this.quadPositionAttribute);
+
+		this.quadTextureAttribute = this.gl.getAttribLocation(this.shaderProgramSSAO, "textureCoordinate");
+		this.gl.enableVertexAttribArray(this.quadTextureAttribute);
 	}
 
 	bind(sceneData, sceneParams, dir) {
 		this.offscreenCanvas = document.createElement("canvas");
 		this.gl = this.offscreenCanvas.getContext("webgl2", {alpha:true, premultipliedAlpha: true});
+		this.gl.getExtension('EXT_color_buffer_float');
 
 		this.gl.enable(this.gl.DEPTH_TEST);
+		this.gl.depthMask(true);
 		this.gl.depthFunc(this.gl.LEQUAL);
-		this.gl.enable(this.gl.BLEND);
-		this.gl.blendEquation( this.gl.FUNC_ADD );
 		this.gl.blendFunc(this.gl.ONE, this.gl.ONE_MINUS_SRC_ALPHA);
 
 		this.initBuffers(sceneData, dir);
@@ -490,10 +819,8 @@ App.Art.Engine = class {
 	}
 
 	render(sceneParams, canvas) {
-		// set render resolution
-		this.offscreenCanvas.width = sceneParams.settings.rwidth;
-		this.offscreenCanvas.height = sceneParams.settings.rheight;
-		this.gl.viewport(0, 0, this.gl.drawingBufferWidth, this.gl.drawingBufferHeight);
+		// update logic
+		this.update(sceneParams);
 
 		// set culling
 		if (V.setFaceCulling) {
@@ -502,19 +829,32 @@ App.Art.Engine = class {
 		} else {
 			this.gl.disable(this.gl.CULL_FACE);
 		}
-
-		// draw background
 		this.gl.clearColor(0, 0, 0, 0);
-		this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT);
-		this.gl.useProgram(this.shaderProgramBg);
-		if (sceneParams.background.visible) {
-			this.drawBackground(sceneParams);
-		}
 
 		// draw scene
-		this.gl.clear(this.gl.DEPTH_BUFFER_BIT);
-		this.gl.useProgram(this.shaderProgram);
-		this.drawScene(sceneParams);
+		this.gl.disable(this.gl.BLEND);
+		this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.buffers.gBuffer);
+		this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT);
+		this.gl.useProgram(this.shaderProgramGeometry);
+		this.drawGeometry(sceneParams);
+		this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null);
+		this.gl.enable(this.gl.BLEND);
+
+		this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.buffers.ssaoFBO);
+		this.gl.clear(this.gl.COLOR_BUFFER_BIT);
+		this.gl.useProgram(this.shaderProgramSSAO);
+		this.drawSSAO(sceneParams);
+		this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null);
+
+		this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.buffers.ssaoBlurFBO);
+		this.gl.clear(this.gl.COLOR_BUFFER_BIT);
+		this.gl.useProgram(this.shaderProgramSSAOBlur);
+		this.drawSSAOBlur(sceneParams);
+		this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null);
+
+		this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT);
+		this.gl.useProgram(this.shaderProgramForwardPass);
+		this.drawForwardPass(sceneParams);
 
 		// clone from offscreen to real canvas
 		let ctx = canvas.getContext('2d', {alpha:true});
@@ -522,21 +862,13 @@ App.Art.Engine = class {
 		ctx.drawImage(this.gl.canvas, 0, 0, canvas.width, canvas.height);
 	}
 
-	drawBackground(sceneParams) {
-		this.gl.uniform1i(this.gl.getUniformLocation(this.shaderProgramBg, "textSampler"), 0);
-		this.gl.uniform4fv(this.gl.getUniformLocation(this.shaderProgramBg, "backgroundColor"), sceneParams.background.color);
-
-		this.gl.activeTexture(this.gl.TEXTURE0);
-		this.gl.bindTexture(this.gl.TEXTURE_2D, this.textures[sceneParams.background.filename]);
-
-		this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.buffers.backgroundPositionBuffer);
-		this.gl.vertexAttribPointer(this.backgroundPositionAttribute, 2, this.gl.FLOAT, false, 0, 0);
-
-		this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.buffers.backgroundIndexBuffer);
-		this.gl.drawElements(this.gl.TRIANGLES, 6, this.gl.UNSIGNED_SHORT, 0);
-	}
+	update(sceneParams) {
+		// set render resolution
+		this.offscreenCanvas.width = sceneParams.settings.rwidth;
+		this.offscreenCanvas.height = sceneParams.settings.rheight;
+		this.gl.viewport(0, 0, this.gl.drawingBufferWidth, this.gl.drawingBufferHeight);
+		this.updateResolution(sceneParams.settings.rwidth, sceneParams.settings.rheight);
 
-	drawScene(sceneParams) {
 		// create camera
 		let camRotX = this.degreeToRad(-sceneParams.camera.xr);
 		let camRotY = this.degreeToRad(-sceneParams.camera.yr);
@@ -553,45 +885,192 @@ App.Art.Engine = class {
 		// create scene transforms
 		let matProj = this.matrixMakeProjection(sceneParams.camera.fov, sceneParams.settings.rheight/sceneParams.settings.rwidth, sceneParams.camera.fnear, sceneParams.camera.ffar);
 		let matView = this.matrixInverse(matCamera);
+		this.buffers.matProj = matProj;
+		this.buffers.matView = matView;
 
-		// set scene uniforms
-		let sColorBurn = true;
-		if(typeof V.setColorBurn !== "undefined") {
-			sColorBurn = V.setColorBurn;
+		for (let m=0; m < this.buffers.models.length; m++) {
+			let modelBuffers = this.buffers.models[m];
+			let modelParams = sceneParams.models[m];
+
+			if(!modelParams.visible) {
+				continue;
+			}
+
+			// create model transforms
+			this.applyMorphs(modelParams, modelBuffers);
+
+			let matRot = this.matrixMakeRotation(this.degreeToRad(modelParams.transform.xr), this.degreeToRad(modelParams.transform.yr), this.degreeToRad(modelParams.transform.zr));
+			let matTrans = this.matrixMakeTranslation(modelParams.transform.x, modelParams.transform.y, modelParams.transform.z);
+			let matScale = this.matrixMakeScaling( modelParams.transform.scale);
+			let matModel = this.matrixMulMatrix4(matScale, this.matrixMulMatrix4(matTrans, matRot));
+			let matModelView = this.matrixMulMatrix4(matModel, matView);
+			let matModelViewProjection = this.matrixMulMatrix4(matModelView, matProj);
+			let matNormal = this.matrixTranspose(this.matrixInverse(matModelView));
+
+			modelBuffers.matModel = matModel;
+			modelBuffers.matModelView = matModelView;
+			modelBuffers.matModelViewProjection = matModelViewProjection;
+			modelBuffers.matNormal = matNormal;
+		}
+	}
+
+	drawGeometry(sceneParams) {
+		// process each model in the scene
+		for (let m=0; m < this.buffers.models.length; m++) {
+			let modelBuffers = this.buffers.models[m];
+			let modelParams = sceneParams.models[m];
+
+			if(!modelParams.visible) {
+				continue;
+			}
+
+			// set model uniform
+			this.gl.uniformMatrix4fv(this.gl.getUniformLocation(this.shaderProgramGeometry, "matModelViewProjection"), false, new Float32Array(this.matrixFlatten(modelBuffers.matModelViewProjection)));
+			this.gl.uniformMatrix4fv(this.gl.getUniformLocation(this.shaderProgramGeometry, "matModelView"), false, new Float32Array(this.matrixFlatten(modelBuffers.matModelView)));
+			this.gl.uniformMatrix4fv(this.gl.getUniformLocation(this.shaderProgramGeometry, "matModel"), false, new Float32Array(this.matrixFlatten(modelBuffers.matModel)));
+			this.gl.uniformMatrix4fv(this.gl.getUniformLocation(this.shaderProgramGeometry, "matNormal"), false, new Float32Array(this.matrixFlatten(modelBuffers.matNormal)));
+
+			for (let i=0, count=0; i < modelParams.figures.length; i++) {
+				if(!modelParams.figures[i].visible) {
+					count += modelParams.figures[i].surfaces.length;
+					continue;
+				}
+
+				// bind vertex buffers per figure
+				this.gl.bindBuffer(this.gl.ARRAY_BUFFER, modelBuffers.verticesPositionBuffer[i]);
+				this.gl.vertexAttribPointer(this.vertexPositionAttribute, 3, this.gl.FLOAT, false, 0, 0);
+
+				this.gl.bindBuffer(this.gl.ARRAY_BUFFER, modelBuffers.verticesTextureCoordBuffer[i]);
+				this.gl.vertexAttribPointer(this.textureCoordAttribute, 2, this.gl.FLOAT, false, 0, 0);
+
+				this.gl.bindBuffer(this.gl.ARRAY_BUFFER, modelBuffers.verticesNormalBuffer[i]);
+				this.gl.vertexAttribPointer(this.vertexNormalAttribute, 3, this.gl.FLOAT, false, 0, 0);
+
+				this.gl.bindBuffer(this.gl.ARRAY_BUFFER, modelBuffers.verticesMorphBuffer[i]);
+				this.gl.vertexAttribPointer(this.vertexPositionMorphAttribute, 3, this.gl.FLOAT, false, 0, 0);
+
+				this.gl.bindBuffer(this.gl.ARRAY_BUFFER, modelBuffers.verticesNormalMorphBuffer[i]);
+				this.gl.vertexAttribPointer(this.vertexNormalMorphAttribute, 3, this.gl.FLOAT, false, 0, 0);
+
+				// bind materials per surface and set uniforms
+				for (let j=0; j < modelParams.figures[i].surfaces.length; j++, count++) {
+					let visible = modelParams.figures[i].surfaces[j].visible;
+
+					for (let h=0; h < modelParams.figures[i].surfaces[j].matIds.length; h++) {
+						let matId = modelParams.figures[i].surfaces[j].matIds[h];
+						let matIdx = sceneParams.materials.map(e => e.matId).indexOf(matId);
+						if (matIdx === -1) {
+							continue;
+						}
+						let mat = sceneParams.materials[matIdx];
+
+						if (mat.d > 0 && visible) {
+							this.gl.activeTexture(this.gl.TEXTURE0);
+							this.gl.bindTexture(this.gl.TEXTURE_2D, this.textures[mat.map_D]);
+							this.gl.uniform1i(this.gl.getUniformLocation(this.shaderProgramGeometry, "alpha"), 0);
+
+							this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, modelBuffers.verticesIndexBuffer[count]);
+							this.gl.drawElements(this.gl.TRIANGLES, modelBuffers.indexSizes[count], this.gl.UNSIGNED_INT, 0);
+						}
+					}
+				}
+			}
 		}
+	}
+
+	drawSSAO(sceneParams) {
+		this.gl.uniformMatrix4fv(this.gl.getUniformLocation(this.shaderProgramSSAO, "projection"), false, new Float32Array(this.matrixFlatten(this.buffers.matProj)));
+		this.gl.uniformMatrix4fv(this.gl.getUniformLocation(this.shaderProgramSSAO, "view"), false, new Float32Array(this.matrixFlatten(this.buffers.matView)));
+
+		for (let i = 0; i < 32; ++i) {
+			this.gl.uniform3fv(this.gl.getUniformLocation(this.shaderProgramSSAO, "samples[" + i + "]"), this.buffers.ssaoKernel[i]);
+		}
+
+		this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramSSAO, "radius"), sceneParams.ssao.radius);
+		this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramSSAO, "bias"), sceneParams.ssao.bias);
+		this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramSSAO, "scale"), sceneParams.ssao.scale);
 
-		this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgram, "sNormals"), sceneParams.settings.normals);
-		this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgram, "sAmbient"), sceneParams.settings.ambient);
-		this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgram, "sDiffuse"), sceneParams.settings.diffuse);
-		this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgram, "sSpecular"), sceneParams.settings.specular);
-		this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgram, "sEmission"), sceneParams.settings.emission);
-		this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgram, "sNormal"), sceneParams.settings.normal);
-		this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgram, "sAlpha"), sceneParams.settings.alpha);
-		this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgram, "sReinhard"), sceneParams.settings.reinhard);
-		this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgram, "sGamma"), sceneParams.settings.gamma);
-		this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgram, "whiteM"), sceneParams.settings.whiteM);
-		this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgram, "gammaY"), sceneParams.settings.gammaY);
-		this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgram, "sColorBurn"), Number(sColorBurn));
+		this.gl.activeTexture(this.gl.TEXTURE0);
+		this.gl.bindTexture(this.gl.TEXTURE_2D, this.buffers.gPosition);
+		this.gl.uniform1i(this.gl.getUniformLocation(this.shaderProgramSSAO, "gPosition"), 0);
+
+		this.gl.activeTexture(this.gl.TEXTURE1);
+		this.gl.bindTexture(this.gl.TEXTURE_2D, this.buffers.gNormal);
+		this.gl.uniform1i(this.gl.getUniformLocation(this.shaderProgramSSAO, "gNormal"), 1);
+
+		this.gl.activeTexture(this.gl.TEXTURE2);
+		this.gl.bindTexture(this.gl.TEXTURE_2D, this.buffers.gAlpha);
+		this.gl.uniform1i(this.gl.getUniformLocation(this.shaderProgramSSAO, "gAlpha"), 2);
+
+		this.gl.activeTexture(this.gl.TEXTURE3);
+		this.gl.bindTexture(this.gl.TEXTURE_2D, this.buffers.noiseTexture);
+		this.gl.uniform1i(this.gl.getUniformLocation(this.shaderProgramSSAO, "texNoise"), 3);
+
+		this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.buffers.quadPositionBuffer);
+		this.gl.vertexAttribPointer(this.quadPositionAttribute, 2, this.gl.FLOAT, false, 0, 0);
+
+		this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.buffers.quadTextureBuffer);
+		this.gl.vertexAttribPointer(this.quadTextureAttribute, 2, this.gl.FLOAT, false, 0, 0);
+
+		this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.buffers.quadIndexBuffer);
+		this.gl.drawElements(this.gl.TRIANGLES, 6, this.gl.UNSIGNED_SHORT, 0);
+	}
+
+	drawSSAOBlur(sceneParams) {
+		this.gl.activeTexture(this.gl.TEXTURE0);
+		this.gl.bindTexture(this.gl.TEXTURE_2D, this.buffers.ssaoColorBuffer);
+
+		this.gl.uniform1i(this.gl.getUniformLocation(this.shaderProgramSSAOBlur, "blur"), sceneParams.ssao.blur);
+
+		this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.buffers.quadPositionBuffer);
+		this.gl.vertexAttribPointer(this.quadPositionAttribute, 2, this.gl.FLOAT, false, 0, 0);
+
+		this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.buffers.quadTextureBuffer);
+		this.gl.vertexAttribPointer(this.quadTextureAttribute, 2, this.gl.FLOAT, false, 0, 0);
+
+		this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.buffers.quadIndexBuffer);
+		this.gl.drawElements(this.gl.TRIANGLES, 6, this.gl.UNSIGNED_SHORT, 0);
+	}
+
+	drawForwardPass(sceneParams) {
+		// set scene uniforms
+		this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "sNormals"), sceneParams.settings.normals);
+		this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "sAO"), sceneParams.settings.ao);
+		this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "sSSAO"), sceneParams.settings.ssao);
+		this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "sAmbient"), sceneParams.settings.ambient);
+		this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "sDiffuse"), sceneParams.settings.diffuse);
+		this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "sSpecular"), sceneParams.settings.specular);
+		this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "sEmission"), sceneParams.settings.emission);
+		this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "sNormal"), sceneParams.settings.normal);
+		this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "sAlpha"), sceneParams.settings.alpha);
+		this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "sReinhard"), sceneParams.settings.reinhard);
+		this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "sGamma"), sceneParams.settings.gamma);
+		this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "whiteM"), sceneParams.settings.whiteM);
+		this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "gammaY"), sceneParams.settings.gammaY);
+		this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "sHDR"), sceneParams.settings.hdr);
+		this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "roughness"), sceneParams.pbr.roughness);
+		this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "metallic"), sceneParams.pbr.metallic);
+		this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "albedoInt"), sceneParams.pbr.albedo);
+		this.gl.uniform3fv(this.gl.getUniformLocation(this.shaderProgramForwardPass, "fresnel"), sceneParams.pbr.fresnel);
+		this.gl.uniform3fv(this.gl.getUniformLocation(this.shaderProgramForwardPass, "irradiance"), sceneParams.pbr.irradiance);
+		this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "ssaoInt"), sceneParams.ssao.intensity);
 
 		for (let i = 0; i < sceneParams.directionalLights.length; i++) {
 			let lightVect = this.polarToCart(this.degreeToRad(sceneParams.directionalLights[i].yr), this.degreeToRad(sceneParams.directionalLights[i].xr));
-			this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgram, "lightAmb[" + i + "]"), sceneParams.directionalLights[i].ambient);
-			this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgram, "lightInt[" + i + "]"), sceneParams.directionalLights[i].intensity);
-			this.gl.uniform3fv(this.gl.getUniformLocation(this.shaderProgram, "lightColor[" + i + "]"), sceneParams.directionalLights[i].color);
-			this.gl.uniform3fv(this.gl.getUniformLocation(this.shaderProgram, "lightVect[" + i + "]"), lightVect);
+			this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "lightAmb[" + i + "]"), sceneParams.directionalLights[i].ambient);
+			this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "lightInt[" + i + "]"), sceneParams.directionalLights[i].intensity);
+			this.gl.uniform3fv(this.gl.getUniformLocation(this.shaderProgramForwardPass, "lightColor[" + i + "]"), sceneParams.directionalLights[i].color);
+			this.gl.uniform3fv(this.gl.getUniformLocation(this.shaderProgramForwardPass, "lightVect[" + i + "]"), lightVect);
 		}
 
 		for (let i = 0; i < sceneParams.pointLights.length; i++) {
-			this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgram, "pointLightAmb[" + i + "]"), sceneParams.pointLights[i].ambient);
-			this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgram, "pointLightInt[" + i + "]"), sceneParams.pointLights[i].intensity);
-			this.gl.uniform3fv(this.gl.getUniformLocation(this.shaderProgram, "pointLightColor[" + i + "]"), sceneParams.pointLights[i].color);
-			this.gl.uniform3fv(this.gl.getUniformLocation(this.shaderProgram, "pointLightPos[" + i + "]"), [sceneParams.pointLights[i].x, sceneParams.pointLights[i].y, sceneParams.pointLights[i].z]);
+			this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "pointLightAmb[" + i + "]"), sceneParams.pointLights[i].ambient);
+			this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "pointLightInt[" + i + "]"), sceneParams.pointLights[i].intensity);
+			this.gl.uniform3fv(this.gl.getUniformLocation(this.shaderProgramForwardPass, "pointLightColor[" + i + "]"), sceneParams.pointLights[i].color);
+			this.gl.uniform3fv(this.gl.getUniformLocation(this.shaderProgramForwardPass, "pointLightPos[" + i + "]"), [sceneParams.pointLights[i].x, sceneParams.pointLights[i].y, sceneParams.pointLights[i].z]);
 		}
 
-		this.gl.uniform3fv(this.gl.getUniformLocation(this.shaderProgram, "lookDir"), lookDir);
-
-		this.gl.uniformMatrix4fv(this.gl.getUniformLocation(this.shaderProgram, "matProj"), false, new Float32Array(this.matrixFlatten(matProj)));
-		this.gl.uniformMatrix4fv(this.gl.getUniformLocation(this.shaderProgram, "matView"), false, new Float32Array(this.matrixFlatten(matView)));
+		this.gl.uniform3fv(this.gl.getUniformLocation(this.shaderProgramForwardPass, "cameraPos"), [sceneParams.camera.x, sceneParams.camera.y, sceneParams.camera.z]);
 
 		// process each model in the scene
 		for (let m=0; m < this.buffers.models.length; m++) {
@@ -602,17 +1081,11 @@ App.Art.Engine = class {
 				continue;
 			}
 
-			// create model transforms
-			this.applyMorphs(modelParams, modelBuffers);
-
-			let matRot = this.matrixMakeRotation(this.degreeToRad(modelParams.transform.xr), this.degreeToRad(modelParams.transform.yr), this.degreeToRad(modelParams.transform.zr));
-			let matTrans = this.matrixMakeTranslation(modelParams.transform.x, modelParams.transform.y, modelParams.transform.z);
-			let matScale = this.matrixMakeScaling( modelParams.transform.scale);
-
-			// set model uniforms
-			this.gl.uniformMatrix4fv(this.gl.getUniformLocation(this.shaderProgram, "matTrans"), false, new Float32Array(this.matrixFlatten(matTrans)));
-			this.gl.uniformMatrix4fv(this.gl.getUniformLocation(this.shaderProgram, "matScale"), false, new Float32Array(this.matrixFlatten(matScale)));
-			this.gl.uniformMatrix4fv(this.gl.getUniformLocation(this.shaderProgram, "matRot"), false, new Float32Array(this.matrixFlatten(matRot)));
+			// set model uniform
+			this.gl.uniformMatrix4fv(this.gl.getUniformLocation(this.shaderProgramForwardPass, "matModelViewProjection"), false, new Float32Array(this.matrixFlatten(modelBuffers.matModelViewProjection)));
+			this.gl.uniformMatrix4fv(this.gl.getUniformLocation(this.shaderProgramForwardPass, "matModelView"), false, new Float32Array(this.matrixFlatten(modelBuffers.matModelView)));
+			this.gl.uniformMatrix4fv(this.gl.getUniformLocation(this.shaderProgramForwardPass, "matModel"), false, new Float32Array(this.matrixFlatten(modelBuffers.matModel)));
+			this.gl.uniformMatrix4fv(this.gl.getUniformLocation(this.shaderProgramForwardPass, "matNormal"), false, new Float32Array(this.matrixFlatten(modelBuffers.matNormal)));
 
 			for (let i=0, count=0; i < modelParams.figures.length; i++) {
 				if(!modelParams.figures[i].visible) {
@@ -630,9 +1103,6 @@ App.Art.Engine = class {
 				this.gl.bindBuffer(this.gl.ARRAY_BUFFER, modelBuffers.verticesNormalBuffer[i]);
 				this.gl.vertexAttribPointer(this.vertexNormalAttribute, 3, this.gl.FLOAT, false, 0, 0);
 
-				this.gl.bindBuffer(this.gl.ARRAY_BUFFER, modelBuffers.verticesTangentBuffer[i]);
-				this.gl.vertexAttribPointer(this.vertexTangentAttribute, 3, this.gl.FLOAT, false, 0, 0);
-
 				this.gl.bindBuffer(this.gl.ARRAY_BUFFER, modelBuffers.verticesMorphBuffer[i]);
 				this.gl.vertexAttribPointer(this.vertexPositionMorphAttribute, 3, this.gl.FLOAT, false, 0, 0);
 
@@ -653,39 +1123,39 @@ App.Art.Engine = class {
 
 						if (mat.d > 0 && visible) {
 							this.gl.activeTexture(this.gl.TEXTURE0);
-							this.gl.bindTexture(this.gl.TEXTURE_2D, this.textures[mat.map_Ka]);
-							this.gl.uniform1i(this.gl.getUniformLocation(this.shaderProgram, "textSampler[0]"), 0);
+							this.gl.bindTexture(this.gl.TEXTURE_2D, this.buffers.ssaoColorBufferBlur);
+							this.gl.uniform1i(this.gl.getUniformLocation(this.shaderProgramForwardPass, "textSampler[0]"), 0);
 
 							this.gl.activeTexture(this.gl.TEXTURE1);
 							this.gl.bindTexture(this.gl.TEXTURE_2D, this.textures[mat.map_Kd]);
-							this.gl.uniform1i(this.gl.getUniformLocation(this.shaderProgram, "textSampler[1]"), 1);
+							this.gl.uniform1i(this.gl.getUniformLocation(this.shaderProgramForwardPass, "textSampler[1]"), 1);
 
 							this.gl.activeTexture(this.gl.TEXTURE2);
 							this.gl.bindTexture(this.gl.TEXTURE_2D, this.textures[mat.map_Ks]);
-							this.gl.uniform1i(this.gl.getUniformLocation(this.shaderProgram, "textSampler[2]"), 2);
+							this.gl.uniform1i(this.gl.getUniformLocation(this.shaderProgramForwardPass, "textSampler[2]"), 2);
 
 							this.gl.activeTexture(this.gl.TEXTURE3);
 							this.gl.bindTexture(this.gl.TEXTURE_2D, this.textures[mat.map_Ns]);
-							this.gl.uniform1i(this.gl.getUniformLocation(this.shaderProgram, "textSampler[3]"), 3);
+							this.gl.uniform1i(this.gl.getUniformLocation(this.shaderProgramForwardPass, "textSampler[3]"), 3);
 
 							this.gl.activeTexture(this.gl.TEXTURE4);
 							this.gl.bindTexture(this.gl.TEXTURE_2D, this.textures[mat.map_D]);
-							this.gl.uniform1i(this.gl.getUniformLocation(this.shaderProgram, "textSampler[4]"), 4);
+							this.gl.uniform1i(this.gl.getUniformLocation(this.shaderProgramForwardPass, "textSampler[4]"), 4);
 
 							this.gl.activeTexture(this.gl.TEXTURE5);
 							this.gl.bindTexture(this.gl.TEXTURE_2D, this.textures[mat.map_Kn]);
-							this.gl.uniform1i(this.gl.getUniformLocation(this.shaderProgram, "textSampler[5]"), 5);
+							this.gl.uniform1i(this.gl.getUniformLocation(this.shaderProgramForwardPass, "textSampler[5]"), 5);
 
 							this.gl.activeTexture(this.gl.TEXTURE6);
 							this.gl.bindTexture(this.gl.TEXTURE_2D, this.textures[mat.map_Ke]);
-							this.gl.uniform1i(this.gl.getUniformLocation(this.shaderProgram, "textSampler[6]"), 6);
+							this.gl.uniform1i(this.gl.getUniformLocation(this.shaderProgramForwardPass, "textSampler[6]"), 6);
 
-							this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgram, "d"), mat.d);
-							this.gl.uniform3fv(this.gl.getUniformLocation(this.shaderProgram, "Ka"), mat.Ka);
-							this.gl.uniform3fv(this.gl.getUniformLocation(this.shaderProgram, "Kd"), mat.Kd);
-							this.gl.uniform3fv(this.gl.getUniformLocation(this.shaderProgram, "Ks"), mat.Ks);
-							this.gl.uniform3fv(this.gl.getUniformLocation(this.shaderProgram, "Ke"), mat.Ke);
-							this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgram, "Ns"), mat.Ns);
+							this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "d"), mat.d);
+							this.gl.uniform3fv(this.gl.getUniformLocation(this.shaderProgramForwardPass, "Ka"), mat.Ka);
+							this.gl.uniform3fv(this.gl.getUniformLocation(this.shaderProgramForwardPass, "Kd"), mat.Kd);
+							this.gl.uniform3fv(this.gl.getUniformLocation(this.shaderProgramForwardPass, "Ks"), mat.Ks);
+							this.gl.uniform3fv(this.gl.getUniformLocation(this.shaderProgramForwardPass, "Ke"), mat.Ke);
+							this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgramForwardPass, "Ns"), mat.Ns);
 
 							// draw materials
 							this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, modelBuffers.verticesIndexBuffer[count]);
@@ -885,6 +1355,25 @@ App.Art.Engine = class {
 			    m1[2][0] * m2[0][2] + m1[2][1] * m2[1][2] + m1[2][2] * m2[2][2]]];
 	}
 
+	matrixMulMatrix4(m1, m2) {
+		return [[m1[0][0] * m2[0][0] + m1[0][1] * m2[1][0] + m1[0][2] * m2[2][0] + m1[0][3] * m2[3][0],
+			m1[0][0] * m2[0][1] + m1[0][1] * m2[1][1] + m1[0][2] * m2[2][1] + m1[0][3] * m2[3][1],
+			m1[0][0] * m2[0][2] + m1[0][1] * m2[1][2] + m1[0][2] * m2[2][2] + m1[0][3] * m2[3][2],
+			m1[0][0] * m2[0][3] + m1[0][1] * m2[1][3] + m1[0][2] * m2[2][3] + m1[0][3] * m2[3][3]],
+		    [m1[1][0] * m2[0][0] + m1[1][1] * m2[1][0] + m1[1][2] * m2[2][0] + m1[1][3] * m2[3][0],
+			m1[1][0] * m2[0][1] + m1[1][1] * m2[1][1] + m1[1][2] * m2[2][1] + m1[1][3] * m2[3][1],
+			m1[1][0] * m2[0][2] + m1[1][1] * m2[1][2] + m1[1][2] * m2[2][2] + m1[1][3] * m2[3][2],
+			m1[1][0] * m2[0][3] + m1[1][1] * m2[1][3] + m1[1][2] * m2[2][3] + m1[1][3] * m2[3][3]],
+		    [m1[2][0] * m2[0][0] + m1[2][1] * m2[1][0] + m1[2][2] * m2[2][0] + m1[2][3] * m2[3][0],
+			m1[2][0] * m2[0][1] + m1[2][1] * m2[1][1] + m1[2][2] * m2[2][1] + m1[2][3] * m2[3][1],
+			m1[2][0] * m2[0][2] + m1[2][1] * m2[1][2] + m1[2][2] * m2[2][2] + m1[2][3] * m2[3][2],
+			m1[2][0] * m2[0][3] + m1[2][1] * m2[1][3] + m1[2][2] * m2[2][3] + m1[2][3] * m2[3][3]],
+		    [m1[3][0] * m2[0][0] + m1[3][1] * m2[1][0] + m1[3][2] * m2[2][0] + m1[3][3] * m2[3][0],
+			m1[3][0] * m2[0][1] + m1[3][1] * m2[1][1] + m1[3][2] * m2[2][1] + m1[3][3] * m2[3][1],
+			m1[3][0] * m2[0][2] + m1[3][1] * m2[1][2] + m1[3][2] * m2[2][2] + m1[3][3] * m2[3][2],
+			m1[3][0] * m2[0][3] + m1[3][1] * m2[1][3] + m1[3][2] * m2[2][3] + m1[3][3] * m2[3][3]]];
+	}
+
 	matrixMulVector(m, v) {
 		return [v[0] * m[0][0] + v[1] * m[1][0] + v[2] * m[2][0],
 			    v[0] * m[0][1] + v[1] * m[1][1] + v[2] * m[2][1],
@@ -898,11 +1387,21 @@ App.Art.Engine = class {
 			    [-(m[3][0]*m[0][0]+m[3][1]*m[0][1]+m[3][2]*m[0][2]), -(m[3][0]*m[1][0]+m[3][1]*m[1][1]+m[3][2]*m[1][2]), -(m[3][0]*m[2][0]+m[3][1]*m[2][1]+m[3][2]*m[2][2]), 1]];
 	}
 
+	matrixTranspose(m) {
+		return [[m[0][0], m[1][0], m[2][0], m[3][0]],
+			    [m[0][1], m[1][1], m[2][1], m[3][1]],
+			    [m[0][2], m[1][2], m[2][2], m[3][2]],
+			    [m[0][3], m[1][3], m[2][3], m[3][3]]];
+	}
+
 	matrixFlatten(m) {
 		return [m[0][0], m[0][1], m[0][2], m[0][3],
 			    m[1][0], m[1][1], m[1][2], m[1][3],
 			    m[2][0], m[2][1], m[2][2], m[2][3],
 			    m[3][0], m[3][1], m[3][2], m[3][3]];
 	}
-};
 
+	lerp(a, b, f) {
+		return a + f * (b - a);
+	}
+};
diff --git a/src/art/webgl/ui.js b/src/art/webgl/ui.js
index 2f6d90315a4bbe8aba6aed2b0f332413eab21f5a..d58a042a8d7424c6e9c88f1d9da6bb8fc4c8fd4b 100644
--- a/src/art/webgl/ui.js
+++ b/src/art/webgl/ui.js
@@ -186,17 +186,6 @@ App.Art.createWebglUI = function(container, slave, artSize, scene, p) {
 	scene.settings.rwidth = cvs.width * V.setSuperSampling;
 	scene.settings.rheight = cvs.height * V.setSuperSampling;
 
-	scene.settings.gamma = false;
-	scene.settings.gammaY = 1.0;
-
-	scene.settings.reinhard = true;
-	scene.settings.whiteM = 1.20;
-
-	if (!V.setColorBurn) {
-		scene.settings.whiteM = 1.30;
-		scene.directionalLights[0].intensity = 1.15;
-	}
-
 	App.Art.engine.render(scene, cvs);
 
 	/*
diff --git a/src/gui/options/options.js b/src/gui/options/options.js
index 3fc9ed2dcd3d682eb0507ffa842470b02ca434cd..02f80bedbdee502c565f741e60811a831fc11a16 100644
--- a/src/gui/options/options.js
+++ b/src/gui/options/options.js
@@ -1128,8 +1128,6 @@ App.UI.artOptions = function() {
 			options.addOption("Texture resolution", "setTextureResolution")
 				.addValue("1024", 1024).on().addValue("2048", 2048).off().addValue("4096", 4096).off()
 				.addComment("Refresh the page to take affect.");
-			options.addOption("Color burn", "setColorBurn")
-				.addValue("Enabled", true).on().addValue("Disabled", false).off();
 		}
 
 		options.addOption("PA avatar art is", "seeAvatar")