// Screen Space Local Reflections for non-transparent surfaces.
// Post-process, 2D fullscreen quad.
_SSLR.vsh
{
	include generic_vertex.vsh
}

_SSLR.fsh
{
	uniform sampler2DRect	u_ScreenBuffer0;
	uniform sampler2DRect	u_SpecularBuffer;
	uniform sampler2DRect	u_ScreenBuffer1;
	uniform sampler2DRect	u_ScreenBuffer2;
	uniform sampler2DRect	u_ScreenBuffer3;
	uniform sampler2DRect	u_ScreenBuffer4;
	uniform sampler2DRect	u_ScreenBuffer5;
	uniform sampler2DRect	u_ScreenBuffer6;
	uniform sampler2DRect	u_ScreenBuffer7;
	uniform sampler2DRect	u_NormalBuffer;
	uniform sampler2DRect	u_DepthBuffer;
	uniform	mat4		u_ModelViewMatrix3D;
	uniform mat4		u_UnprojectMatrix;
	uniform	mat4		u_ProjectionMatrix;
	uniform vec3		u_depthParms;		// x = -zFar * zNear, y = zFar - zNear, z = zFar
	uniform vec2		u_ScreenSizes;		// screen width, height
	uniform vec3		u_ViewOrigin;

	out	vec3	fragData;

#if defined(FAKE_PPL)
	include UnPackNormal.inc
#endif

	include sslr.inc

	void main(void)
	{
		vec4 specular = texture(u_SpecularBuffer, gl_FragCoord.xy);
		if (specular.a == 1.0)
		{
			discard;	// no portal surfaces draw
			return;
		}

		float depth = texture(u_DepthBuffer, gl_FragCoord.xy).r;
		if (depth == 1.0)
		{	// sky don't reflect
			discard;
			return;
		}

		// Get fragment normal in world space
		vec4 sN = texture(u_NormalBuffer, gl_FragCoord.xy);
#if defined(FAKE_PPL)
		vec3 N = UnPackNormal(sN.xy);
#else
		vec3 N = sN.xyz;
#endif

		vec3 luma = vec3(0.2125, 0.7154, 0.0721);

		float scale;
		float sharpness;
		if (abs(sN.a) == 0.5)
		{	// Specular model
			scale = dot(luma, specular.rgb);
			sharpness = specular.a;
		}
		else
		{	// PBR model (HACK: reflection scale = 1 - roughness)
			scale = sharpness = 1.0 - specular.b;	// specular.b = roughness
		}
		if (scale <= 0.01)
		{
			discard;
			return;
		}

		// reconstruct fragment position in world space
		vec4 P = u_UnprojectMatrix * vec4(gl_FragCoord.xy, depth, 1.0);
		vec3 worldCoord = P.xyz / P.w;

		// convert all from world to clip space
		vec3 hitCoord = (u_ModelViewMatrix3D * vec4(worldCoord, 1.0)).xyz;
		vec3 pN1 = worldCoord + N;
		pN1 = (u_ModelViewMatrix3D * vec4(pN1, 1.0)).xyz;
		N = normalize((pN1 - hitCoord) * mix(1.0, -1.0, float(gl_FrontFacing)));
		vec3 ViewOrigin = (u_ModelViewMatrix3D * vec4(u_ViewOrigin, 1.0)).xyz;
		vec3 viewDir = normalize(hitCoord - ViewOrigin);
		vec3 reflectDir = normalize(reflect(viewDir, N));

		// Fresnel
		float VN = dot(viewDir, N);
		if (VN >= -0.001)
		{
			discard;
			return;
		}
		scale *= pow(1.0 + VN, 1.6);
		if (scale <= 0.01)
		{
			discard;
			return;
		}

		// Ray tracing...
		int i;
		vec2 uv = vec2(0.0);	// shut up compiler
		float stepSize, csDepth = 0.0, f, t = 0.0;	// shut up compiler

		// Linear search:
		for (i = 0, stepSize = STEP_SIZE; i < MAX_STEPS_LINEAR; i++, stepSize *= STEP_SIZE_MUL)
		{
			hitCoord += reflectDir * stepSize;
			uv = VS2SS(hitCoord);

			// Out of texture space --> break
			// because sampling from a texture, the range is from 0.0 to 1.0
			if (uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0)
			{
				i = MAX_STEPS_LINEAR;
				break;
			}

			csDepth = GetDepth(uv * u_ScreenSizes.xy);
			t = -hitCoord.z - csDepth;
			if (t >= 0.0)
				break;	// intersection
		}
		if (i == MAX_STEPS_LINEAR && abs(u_depthParms.z - csDepth) >= 8.0)
		{
			discard;	// MAX_STEPS_LINEAR reached
			return;
		}

		// fade depending on the ray length
		f = float(i) * MAX_STEPS_LINEAR_INVF;
		f *= f;
//		scale *= 1.0 - f * f;	do it lower...

		// Binary search:
		bool isSky = false;
		if (abs(t) > Z_THRESHOLD)
		{
			// go back to the middle
			stepSize *= 0.5;
			hitCoord -= reflectDir * stepSize;
			uv = VS2SS(hitCoord) * u_ScreenSizes.xy;
			csDepth = GetDepth(uv);
			for (i = 0; i < MAX_STEPS_BINARY; i++, stepSize *= 0.5)
			{
				hitCoord += reflectDir * stepSize * (step(-hitCoord.z, csDepth) - 0.5);
				uv = VS2SS(hitCoord);
				csDepth = GetDepth(uv * u_ScreenSizes.xy);
				t = abs(-hitCoord.z - csDepth);
				if (t <= Z_THRESHOLD)
					break;	// found it
				if (abs(u_depthParms.z - csDepth) < 8.0)	// sky?
				{
					isSky = true;
					break;	// found it
				}
			}
		}

		// fade on screen border, avoids abrupt reflection disappearing
		vec2 a = vec2(0.5) - abs(vec2(0.5) - uv);
		a = clamp(a * SCREEN_FALLOFF, vec2(0.0), vec2(1.0));
		scale *= a.x * a.y;

		if (!isSky)
		{
			scale *= 1.0 - f * f;

			// avoid reflection of objects in foreground	TODO: upgrade
			scale /= 1.0 + t * FOREGROUND_FALLOFF;
		}

		if (scale <= 0.01)
		{
			discard;
			return;
		}

		uv *= u_ScreenSizes.xy;
		vec3 c = vec3(0.0);	// shut up compiler
		if (sharpness > 240.0/256.0)
			c = texture(u_ScreenBuffer0, uv).rgb;
		if (sharpness > 224.0/256.0)
			c = texture(u_ScreenBuffer1, uv * (1.0/2.0)).rgb;
		if (sharpness > 208.0/256.0)
			c = texture(u_ScreenBuffer2, uv * (1.0/3.0)).rgb;
		if (sharpness > 192.0/256.0)
			c = texture(u_ScreenBuffer3, uv * (1.0/4.0)).rgb;
		if (sharpness > 176.0/256.0)
			c = texture(u_ScreenBuffer4, uv * (1.0/5.0)).rgb;
		if (sharpness > 160.0/256.0)
			c = texture(u_ScreenBuffer5, uv * (1.0/6.0)).rgb;
		if (sharpness > 144.0/256.0)
			c = texture(u_ScreenBuffer6, uv * (1.0/7.0)).rgb;
		if (sharpness <= 144.0/256.0)
			c = texture(u_ScreenBuffer7, uv * (1.0/8.0)).rgb;

		if (abs(sN.a) != 0.5)
		{	// PBR model
			vec3 m = vec3(dot(luma, c));
			c = mix(m, c, specular.r);	// specular.r = metallic
		}

		fragData = mix(texture(u_ScreenBuffer0, gl_FragCoord.xy).rgb, c, scale);
	}
}



// Screen Space Local Reflections for transparent surfaces.
// 3D draw.
_SSLR_Transparent.vsh
{
	uniform	mat4	u_ModelViewProjectionMatrix;
	out	vec3	v_Vertex;
	out	vec3	v_Normal;
	include deforms.inc
	void main(void)
	{
		mat2 dummy;
		vec4 glVertex = DeformGeometry(dummy);
		gl_Position = u_ModelViewProjectionMatrix * glVertex;
		v_Vertex = glVertex.xyz;
		v_Normal = attr_Normal;
	}
}

_SSLR_Transparent.fsh
{
	uniform sampler2DRect	u_ScreenBuffer;
	uniform sampler2DRect	u_DepthBuffer;
	uniform	mat4		u_ModelViewMatrix;
	uniform	mat4		u_ProjectionMatrix;
	uniform vec3		u_depthParms;		// x = -zFar * zNear, y = zFar - zNear, z = zFar
	uniform vec2		u_ScreenSizes;		// screen width, height

	in	vec3	v_Vertex;
	in	vec3	v_Normal;

	out	vec3	fragData;

	include sslr.inc

	void main(void)
	{
		// get fragment position in clip space
		vec3 hitCoord = (u_ModelViewMatrix * vec4(v_Vertex, 1.0)).xyz;
		// get fragment normal in clip space
		vec3 pN1 = v_Vertex + v_Normal;
		pN1 = (u_ModelViewMatrix * vec4(pN1, 1.0)).xyz;
		vec3 N = normalize((pN1 - hitCoord) * mix(1.0, -1.0, float(gl_FrontFacing)));
		// get view vector in clip space
		vec3 viewDir = normalize(hitCoord);
		// get reflect vector in clip space
		vec3 reflectDir = normalize(reflect(viewDir, N));

		// Fresnel
		float VN = dot(viewDir, N);
		if (VN >= -0.001)
		{
			discard;
			return;
		}
		float scale = pow(1.0 + VN, 1.6);
		if (scale <= 0.01)
		{
			discard;
			return;
		}

		// Ray tracing...
		int i;
		vec2 uv = vec2(0.0);	// shut up compiler
		float stepSize, csDepth = 0.0, f, t = 0.0;	// shut up compiler

		// Linear search:
		for (i = 0, stepSize = STEP_SIZE; i < MAX_STEPS_LINEAR; i++, stepSize *= STEP_SIZE_MUL)
		{
			hitCoord += reflectDir * stepSize;
			uv = VS2SS(hitCoord);

			// Out of texture space --> break
			// because sampling from a texture, the range is from 0.0 to 1.0
			if (uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0)
			{
				i = MAX_STEPS_LINEAR;
				break;
			}

			csDepth = GetDepth(uv * u_ScreenSizes.xy);
			t = -hitCoord.z - csDepth;
			if (t >= 0.0)
				break;	// intersection
		}
		if (i == MAX_STEPS_LINEAR && abs(u_depthParms.z - csDepth) >= 8.0)
		{
			discard;	// MAX_STEPS_LINEAR reached
			return;
		}

		// fade depending on the ray length
		f = float(i) * MAX_STEPS_LINEAR_INVF;
		f *= f;
//		scale *= 1.0 - f * f;	do it lower...

		// Binary search:
		bool isSky = false;
		if (abs(t) > Z_THRESHOLD)
		{
			// go back to the middle
			stepSize *= 0.5;
			hitCoord -= reflectDir * stepSize;
			uv = VS2SS(hitCoord) * u_ScreenSizes.xy;
			csDepth = GetDepth(uv);
			for (i = 0; i < MAX_STEPS_BINARY; i++, stepSize *= 0.5)
			{
				hitCoord += reflectDir * stepSize * (step(-hitCoord.z, csDepth) - 0.5);
				uv = VS2SS(hitCoord);
				csDepth = GetDepth(uv * u_ScreenSizes.xy);
				t = abs(-hitCoord.z - csDepth);
				if (t <= Z_THRESHOLD)
					break;	// found it
				if (abs(u_depthParms.z - csDepth) < 8.0)	// sky?
				{
					isSky = true;
					break;	// found it
				}
			}
		}

		// fade on screen border, avoids abrupt reflection disappearing
		vec2 a = vec2(0.5) - abs(vec2(0.5) - uv);
		a = clamp(a * SCREEN_FALLOFF, vec2(0.0), vec2(1.0));
		scale *= a.x * a.y;

		if (!isSky)
		{
			scale *= 1.0 - f * f;

			// avoid reflection of objects in foreground	TODO: upgrade
			scale /= 1.0 + t * FOREGROUND_FALLOFF;
		}

		if (scale <= 0.01)
		{
			discard;
			return;
		}

		// get reflected fragment
		fragData = mix(texture(u_ScreenBuffer, gl_FragCoord.xy).rgb, texture(u_ScreenBuffer, uv * u_ScreenSizes.xy).rgb, scale);
	}
}
