#version 330 core

layout(location=0) out vec4 vFragColor;	//wyjście shadera fragmentów

//uniformy
uniform mat4 MV;			//macierz modelu i widoku
uniform sampler2DShadow shadowMap;	//tekstura mapy cienia
uniform vec3 light_position;	        //położenie światła w przestrzeni obiektu
uniform vec3 diffuse_color;		//kolor światła rozpraszanego
uniform bool bIsLightPass;		//znacznik przebiegu ze światłem
									//jest to przebieg bez cieni

//dane wejściowe z shadera wierzchołków
smooth in vec3 vEyeSpaceNormal;	        //interpolowany wektor normalny w przestrzeni oka      
smooth in vec3 vEyeSpacePosition;	//interpolowane położenie wierzchołka w przestrzeni oka
smooth in vec4 vShadowCoords;		//interpolowane współrzędne cienia

//stałe shadera
const float k0 = 1.0;	//wygaszanie stałe
const float k1 = 0.0;	//wygaszanie liniowe
const float k2 = 0.0;	//wygaszanie kwadratowe
 
//usuń znak komentarza, aby włączyć odpowiedni tryb PCF
//#define STRATIFIED_3x3
//#define STRATIFIED_4x4 
#define RANDOM_SAMPLING

#ifdef RANDOM_SAMPLING
//generator liczb pseudolosowych
float random(vec4 seed) {
	float dot_product = dot(seed, vec4(12.9898,78.233,45.164,94.673));
    return fract(sin(dot_product) * 43758.5453);
}
#endif

void main() { 
	//Jeśli jest to przebieg ze światłem, nie generujemy cieni i po prostu wychodzimy,
	//ponieważ potrzebujemy jedynie danych o głębi, które są zaoisane w odpowiednim przyłączu FBO
	if(bIsLightPass)
		return;

	//wyznaczanie położenia swiatła w przestrzeni oka
	vec4 vEyeSpaceLightPosition = MV*vec4(light_position,1);

	//wyznaczanie wektora światła
	vec3 L = (vEyeSpaceLightPosition.xyz-vEyeSpacePosition);

	//Wyznaczanie odległości do źródła światła
	float d = length(L);

	//normalizacja wektora światła
	L = normalize(L);

	//obliczanie składowej rozproszenia i wprowadzenie wygaszania
	float attenuationAmount = 1.0/(k0 + (k1*d) + (k2*d*d));
	float diffuse = max(0, dot(vEyeSpaceNormal, L)) * attenuationAmount;	
	 
	//jeśli współrzędna w jest > 1, jesteśmy w połówce przedniej
	//i należy wygenerować cień. Jeśli usuniesz ten warunek, zobaczysz 
	//cienie po obu stronach źródła światła, gdy będzie ono blisko podłoża  
	//Spróbuj to zrobić, a zobaczysz, co mam na myśli.
	if(vShadowCoords.w>1) {

		//Dla PCF bierzemy kilka próbek mapy cienia 
		//i uśredniamy ich wartości. Wynik jest potem używany do 
		//ustalenia jasności fragmentu. Do pobierania próbek służy
		//funkcja textureProjOffset, która przyjmuje przesunięcie z bieżącego
		//punktu mapy cienia i zwraca wartość pobranej próbki.

		float sum = 0;
		float shadow = 1;

		//sąsiedztwo 3x3 
		#ifdef STRATIFIED_3x3
		sum += textureProjOffset(shadowMap, vShadowCoords, ivec2(-2,-2));
		sum += textureProjOffset(shadowMap, vShadowCoords, ivec2(-2, 0));
		sum += textureProjOffset(shadowMap, vShadowCoords, ivec2(-2, 2));

		sum += textureProjOffset(shadowMap, vShadowCoords, ivec2( 0,-2));
		sum += textureProjOffset(shadowMap, vShadowCoords, ivec2( 0, 0));
		sum += textureProjOffset(shadowMap, vShadowCoords, ivec2( 0, 2));

		sum += textureProjOffset(shadowMap, vShadowCoords, ivec2( 2,-2));
		sum += textureProjOffset(shadowMap, vShadowCoords, ivec2( 2, 0));
		sum += textureProjOffset(shadowMap, vShadowCoords, ivec2( 2, 2));
		shadow = sum/9.0;
		#endif
		
		//sąsiedztwo 4x4 
		#ifdef STRATIFIED_4x4
		sum += textureProjOffset(shadowMap, vShadowCoords, ivec2(-2,-2));
		sum += textureProjOffset(shadowMap, vShadowCoords, ivec2(-1,-2));
		sum += textureProjOffset(shadowMap, vShadowCoords, ivec2( 1,-2));
		sum += textureProjOffset(shadowMap, vShadowCoords, ivec2( 2,-2));

		sum += textureProjOffset(shadowMap, vShadowCoords, ivec2(-2,-1));
		sum += textureProjOffset(shadowMap, vShadowCoords, ivec2(-1,-1));
		sum += textureProjOffset(shadowMap, vShadowCoords, ivec2( 1,-1));
		sum += textureProjOffset(shadowMap, vShadowCoords, ivec2( 2,-1));

		sum += textureProjOffset(shadowMap, vShadowCoords, ivec2(-2, 1));
		sum += textureProjOffset(shadowMap, vShadowCoords, ivec2(-1, 1));
		sum += textureProjOffset(shadowMap, vShadowCoords, ivec2( 1, 1));
		sum += textureProjOffset(shadowMap, vShadowCoords, ivec2( 2, 1));
 
		sum += textureProjOffset(shadowMap, vShadowCoords, ivec2(-2, 2));
		sum += textureProjOffset(shadowMap, vShadowCoords, ivec2(-1, 2));
		sum += textureProjOffset(shadowMap, vShadowCoords, ivec2( 1, 2));
		sum += textureProjOffset(shadowMap, vShadowCoords, ivec2( 2, 2));

		shadow = sum/16.0;
		#endif

		#ifdef RANDOM_SAMPLING	 
		for(int i=0;i<16;i++) {
			float indexA = (random(vec4(gl_FragCoord.xyx, i))*0.25);
			float indexB = (random(vec4(gl_FragCoord.yxy, i))*0.25); 
			sum += textureProj(shadowMap, vShadowCoords+vec4(indexA, indexB, 0, 0));
		}
		shadow = sum/16.0;
		#endif

		//przyciemnienie (rozjaśnienie) fragmentu na podstawie wartości zmiennej shadow
		diffuse = mix(diffuse, diffuse*shadow, 0.5); 
	}
	//wyprowadzenie iloczynu składowej rozproszenia i koloru rozproszenia 
	//jako kolor bieżącego fragmentu
	vFragColor = diffuse*vec4(diffuse_color, 1);	 
}