	#version 300 es

	precision highp float;
	
    in vec2 uv;
    in vec3 position;
	
	out vec2 v_uv;
	
    uniform mat4 viewProjection;

    

    void main(void) {
        v_uv = uv;
		
		gl_Position = viewProjection *  vec4(position, 1.0);
    }
	
	// #keplerEngine - Split 
	
	#version 300 es

	precision highp float;

	#define PI 3.14159265358979323846
	#define ANISOTROPY 0
	#define USE_IES_PROFILE 0
	#define TRANSPARENT_MATERIAL 0
	#define TRANSLUCENT_MATERIAL 0
	#define CUBEMAP_EDGE_FIXUP 0
	
	
	uniform vec3 cameraPosition;
	uniform samplerCube reflectionSampler;
	uniform float lightType;
	uniform float environmentLuminance;
	
	#include "physically_based_shading.shader"
	
	
	uniform float viewMode;
	
	uniform vec3 lightGeometry;
	
	uniform float clearCoat;
	uniform vec3 clearCoatColor;
	uniform float clearCoatThickness;
	uniform float clearCoatRoughness;
	uniform vec3 lightDirection;	
	uniform float attenuation; 

	uniform vec3 lightPosition;
	uniform vec3 lightColor;	
	uniform float lightIntensity;
	uniform float metallic;

	
	#define TEXTURED_MATERIAL 1
	
	out vec4 fragmentColor;

	in vec2 v_uv;
	

	
	
	uniform float reflectance;
	

	
	uniform float anisotropy;
	
	

	uniform float roughness;



	uniform sampler2D diffuseSampler;
	uniform sampler2D normalSampler;
	uniform sampler2D tangentSampler;
	uniform sampler2D infoSampler;
	
	uniform sampler2D ambientOcclusionSampler;
	uniform sampler2D materialSampler;
	uniform sampler2D shadowNoiseSampler;
	uniform sampler2D shadowDepthSampler;
	
	
	
	
	
	
	uniform float luma_z;
	uniform float far;
	

	uniform mat4 InvProjection;
	uniform mat4 lightViewProjection;
	uniform vec2 screenSize;
	uniform float shadowBias;

	const float PackUpscale = 256. / 255.; // fraction -> 0..1 (including 1)
	const float UnpackDownscale = 255. / 256.; // 0..1 -> fraction (excluding 1)

	const vec3 PackFactors = vec3( 256. * 256. * 256., 256. * 256.,  256. );
	const vec4 UnpackFactors = UnpackDownscale / vec4( PackFactors, 1. );

	const float ShiftRight8 = 1. / 256.;

	vec4 packDepthToRGBA( const in float v ) {
		vec4 r = vec4( fract( v * PackFactors ), v );
		r.yzw -= r.xyz * ShiftRight8; // tidy overflow
		return r * PackUpscale;
	}

	float unpackRGBAToDepth( const in vec4 v ) {
		return dot( v, UnpackFactors );
	}
	
	
	
	float shadow_sample(sampler2D depthMap, vec2 coord)
	{
		return ( texture(depthMap, coord.xy).x );//DecodeFloatRGBA() ;
	}
	
	vec2 DoubleSampleRotated(sampler2D depthMap, vec4 p, vec4 rotMatr, vec4 kernel) {

		vec4 rotatedOff;

		rotatedOff = rotMatr.xyzw * kernel.xxww +
					 rotMatr.zwxy * kernel.yyzz;

		vec4 fetchPos = p.xyxy + rotatedOff;// + rotatedOff

		vec2 result;
		
		result.x = shadow_sample(depthMap, fetchPos.xy);
		result.y = shadow_sample(depthMap, fetchPos.zw);

		
		return result;
	}
	
	
	
	float PCF(sampler2D depthMap, vec4 p, vec2 randDirTC, float depth)
	{
	
		vec2 kernelRadius = vec2(4.0);
		
		vec4 irreg_kernel_2d[8];
		irreg_kernel_2d[0] = vec4(-0.556641,-0.037109,-0.654297, 0.111328); 
		irreg_kernel_2d[1] = vec4(0.173828,0.111328,0.064453, -0.359375); 
		irreg_kernel_2d[2] = vec4(0.001953,0.082031,-0.060547, 0.078125); 
		irreg_kernel_2d[3] = vec4(0.220703,-0.359375,-0.062500, 0.001953);
		irreg_kernel_2d[4] = vec4(0.242188,0.126953,-0.250000, -0.140625); 
		irreg_kernel_2d[5] = vec4(0.070313,-0.025391,0.148438, 0.082031); 
		irreg_kernel_2d[6] = vec4(-0.078125,0.013672,-0.314453, 0.013672);
		irreg_kernel_2d[7] = vec4(0.117188,-0.140625,-0.199219, 0.117188);

		vec2 vInvShadowMapWH = vec2(1.0 / 2048.0);
		
		const int kernelSize = 8;
		
		mediump float P_Z = depth; // p.z;

		vec4 p0 = vec4(p.xyz, 1.0);

		
		mediump vec2 rotScale = vec2(kernelRadius.y * 2.0);

		float shadowTest = 0.0;

		#define KERNEL_STEP_SIZE 2

			vec2 rotSample = 2.0 * texture(shadowDepthSampler, randDirTC.xy).xy - 1.0;
			rotSample.xy = normalize(rotSample.xy);
			rotSample.xy *= (kernelRadius.xy * vInvShadowMapWH.xy);

			vec4 rot = vec4(rotSample.x, -rotSample.y, rotSample.y, rotSample.x);

			const int kernelOffset = 0;

			for(int i=kernelOffset; i<kernelSize; i+=KERNEL_STEP_SIZE) // Loop over taps
			{

				mediump vec4 sampleDepth = vec4(0.0);
				vec4 irr =  irreg_kernel_2d[i+0];
				sampleDepth.xy = DoubleSampleRotated(depthMap, p0, rot, irr);
				sampleDepth.zw = DoubleSampleRotated(depthMap, p0, rot, irreg_kernel_2d[i+1]);
			
				mediump vec4 InShadow;
				InShadow.x = ( P_Z < sampleDepth.x + shadowBias ) ? 1. : 0.0;
				InShadow.y = ( P_Z < sampleDepth.y + shadowBias ) ? 1. : 0.0;
				InShadow.z = ( P_Z < sampleDepth.z + shadowBias  ) ? 1. : 0.0;
				InShadow.w = ( P_Z < sampleDepth.w + shadowBias  ) ? 1. : 0.0;

		
				const mediump float quality = 8.0; // 8 == high
				const mediump float fInvSamplNum = (1.0 / quality);
				
				shadowTest += dot(InShadow, vec4(fInvSamplNum));
			}

		return shadowTest;
	}

	float DecodeFloatRGBA( vec4 rgba ) {
		return (rgba).x;
	
	  //return dot( rgba, vec4(1.0, 1.0 / 255.0, 1.0 / 65025.0, 1.0 / 160581375.0) );
	}
	
	float poissonPCFmultitap(vec4 projCoords, float shadowDepth, vec2 uv)
	{
		const mediump float step = 1.0 - 1.0 / 8.0;
		const mediump float fScale = 0.025; // 0.025
		
		mediump float n = 0.0;
		
		mediump vec3 directions[8];
		float vSampleScale = 1.0 / 2048.0;
		
		
		directions[0] = normalize(vec3( 1.0, 1.0, 1.0))*fScale*(n+=step);
		directions[1] = normalize(vec3(-1.0,-1.0,-1.0))*fScale*(n+=step);
		directions[2] = normalize(vec3(-1.0,-1.0, 1.0))*fScale*(n+=step);
		directions[3] = normalize(vec3(-1.0, 1.0,-1.0))*fScale*(n+=step);
		directions[4] = normalize(vec3(-1.0, 1.0 ,1.0))*fScale*(n+=step);
		directions[5] = normalize(vec3( 1.0,-1.0,-1.0))*fScale*(n+=step);
		directions[6] = normalize(vec3( 1.0,-1.0, 1.0))*fScale*(n+=step);
		directions[7] = normalize(vec3( 1.0, 1.0,-1.0))*fScale*(n+=step);

		mediump vec3 randomSample = texture(shadowNoiseSampler, vec2(64.0, 64.0) * uv.xy / 4.0).xyz  * 2.0 - 1.0;
		
		

		float sum = 0.0;

		 for( int i = 0; i < 4; i++ ) {
		 	vec3 sampler = reflect(directions[0], randomSample) * vSampleScale;	
			
			float pixelDepth = DecodeFloatRGBA( texture(shadowDepthSampler, projCoords.xy+ sampler.xy ) ) ; // + sampler.xy     + sampler.z

			if( pixelDepth + shadowBias > shadowDepth) {
				sum += 1.0;
			} else {
				sum += 0.0;
			}
		 }

		return sum;
	}
		
	float linestep(float min, float max, float value) {  
		return clamp((value - min) / (max - min), 0., 1.);
	}

	float reduceBleeding(float p_max, float amount) {
		return linestep(amount, 1.0, p_max);
	}

	float ChebyshevUpperBound(vec2 moments, float distance) {  
		if (distance <= moments.x)
			return 1.0;
			
		float g_minVariance = .0007;

		float variance = moments.y - (moments.x*moments.x);
		variance = max(variance,g_minVariance);

		float d = distance - moments.x;
		float p_max = variance / (variance + d*d);

		return reduceBleeding(p_max, shadowBias);
	}
	
	float calculateShadowOcclusion( vec3 worldPosition, vec2 uv ) {

		vec4 projCoords = lightViewProjection *  vec4(worldPosition, 1.0) ;
		
		float shadowDepth = length( lightPosition - worldPosition ) / 99.0;

		projCoords.xy /= projCoords.w;
		projCoords = 0.51 * projCoords + 0.5;

		vec4 moments = texture( shadowDepthSampler, projCoords.xy );
		mediump vec2 randomSample = texture(shadowNoiseSampler, vec2(1024.) * uv / 64.0).xy  * 2.0 - 1.0;
			
		float outFrustum = 0.0;
	
		if(projCoords.x <  0.0 || projCoords.x > 1.0) {
			return 0.0;
		}
		
		if(projCoords.y <  0.0 || projCoords.y > 1.0) {
			return 0.0;
		}
		
		if(projCoords.z <  1.0) {
			return 0.0;
		}	
			
		return ChebyshevUpperBound(moments.xy, shadowDepth);
		return poissonPCFmultitap(projCoords, shadowDepth, uv);
		//return PCF( shadowDepthSampler, projCoords, randomSample, shadowDepth );


	}
	
	vec3 sampleReflection( vec3 r ) {
	
		return texture(reflectionSampler, r).xyz;
		
	}
	
	vec3 cubemapReflection( deferredMaterialData materialData ) {
	
		vec3 viewDir = ( cameraPosition - materialData.position );
		vec3 reflectionVector = reflect(-viewDir, normalize( materialData.normal ));
		vec3 reflectionSample = sampleReflection( reflectionVector );
		
		return reflectionSample;
	}
	
	
	const float gamma = 2.2;
	
	float toLinear(float v) {
	  return pow(v, gamma);
	}

	vec2 toLinear(vec2 v) {
	  return pow(v, vec2(gamma));
	}

	vec3 toLinear(vec3 v) {
	  return pow(v, vec3(gamma));
	}

	vec4 toLinear(vec4 v) {
	  return vec4(toLinear(v.rgb), v.a);
	}


	vec3 HDR_ACES(const vec3 x) {
		// Narkowicz 2015, "ACES Filmic Tone Mapping Curve"
		const float a = 2.51;
		const float b = 0.03;
		const float c = 2.43;
		const float d = 0.59;
		const float e = 0.14;
		return (x * (a * x + b)) / (x * (c * x + d) + e);
	}

	vec3 tonemap(const vec3 x) {
		return HDR_ACES(x);
	}

	float linearToSRGB(float c) {
		return (c <= 0.0031308) ? c * 12.92 : (pow(abs(c), 1.0 / 2.4) * 1.055) - 0.055;
	}

	vec3 linearToSRGB(vec3 c) {
		return vec3(linearToSRGB(c.r), linearToSRGB(c.g), linearToSRGB(c.b));
	}

	#define VIEWMODE_LIT 0.0
	#define VIEWMODE_LIGHTNING 1.0
	#define VIEWMODE_UNLIT 2.0
	#define VIEWMODE_REFLECTION 3.0
	#define VIEWMODE_NORMAL 4.0
	#define VIEWMODE_AMBIENTOCCLUSION 5.0
	#define VIEWMODE_ROUGHNESS 6.0
	#define VIEWMODE_SHADOW 7.0



	void main() {

		vec4 normalDepth 			= texture(normalSampler, 	v_uv);
		vec4 diffuseRoughness 		= texture(diffuseSampler, 	v_uv);
		vec4 positionMaterialIndex 	= texture(infoSampler, 		v_uv);
		vec4 depthNormal 			= texture(normalSampler, 	v_uv);
		vec4 shadowAmbientOcclusion = texture(ambientOcclusionSampler, v_uv);
		
		// Material properties
		deferredMaterialData materialData;
		
		materialData.ambientOcclusion 	= shadowAmbientOcclusion.x;
		materialData.normal 			= normalize( depthNormal.xyz );
		materialData.baseColor 			= sRGBtoLinear(diffuseRoughness.rgb);
		materialData.position 			= positionMaterialIndex.xyz;
		materialData.index 				= positionMaterialIndex.w;
		materialData.roughness 			= diffuseRoughness.w;

		materialData.clearCoat 			= clearCoat;
		materialData.clearCoatRoughness = clearCoatRoughness;
		materialData.clearCoatThickness = clearCoatThickness;
		materialData.clearCoatColor 	= clearCoatColor;
		
		materialData.alpha 				= 1.0;
		materialData.shadowOcclusion 	= shadowAmbientOcclusion.y; //calculateShadowOcclusion( materialData.position, v_uv );
		materialData.metallic			= metallic;
		materialData.reflectance		= reflectance;

		
		// Light properties
		deferredLightData lightData;
		
		lightData.position 			= lightPosition;
		lightData.direction 		= lightDirection;
		lightData.color				= lightColor;
		lightData.intensity			= lightIntensity;
		lightData.inverseFalloff 	= lightGeometry.x;
		lightData.length 			= lightGeometry.y;
		lightData.radius 			= lightGeometry.z;
		
		
		
		if( viewMode == VIEWMODE_LIGHTNING ){
			materialData.baseColor = vec3(1.0);
		}
		
		vec4 color = physically_based_shading( materialData, lightData ) *  materialData.ambientOcclusion;
			
		float exposure = 1.7;

		color.rgb *= exposure;

		vec3 finalRender = color.rgb;// + diffuse * materialData.ambientOcclusion

		// For fxaa
		float luma = sqrt( dot(color.rgb, vec3(0.299, 0.587, 0.114)) );

		fragmentColor = vec4(linearToSRGB(tonemap(color.rgb)), luma);

		if( viewMode == VIEWMODE_UNLIT ){
			fragmentColor = vec4(linearToSRGB(tonemap( materialData.baseColor )), luma);
		}
		
		if( viewMode == VIEWMODE_REFLECTION ){
			vec3 cubeReflectionSample = cubemapReflection( materialData );
		
			fragmentColor = vec4(linearToSRGB(tonemap( cubeReflectionSample )), luma);
		}
		
		if( viewMode == VIEWMODE_NORMAL ){
			fragmentColor = vec4(materialData.normal, luma);
		}
		
		if( viewMode == VIEWMODE_AMBIENTOCCLUSION ){
			fragmentColor = vec4(vec3(materialData.ambientOcclusion), luma);
		}
		
		if( viewMode == VIEWMODE_ROUGHNESS ){
			fragmentColor = vec4(vec3(materialData.roughness), luma);
		}
		
		if( viewMode == VIEWMODE_SHADOW ){
			fragmentColor = vec4(vec3(materialData.shadowOcclusion), luma);
		}
		
		
		
		if(materialData.index == 100.0) {
		
			vec3 cubeReflectionSample = cubemapReflection( materialData );
			fragmentColor = vec4( cubeReflectionSample, 1.0 );
			
		}
		
	}
	