float GetLightShaftsFalloff(vec3 viewPos, float z0) {
	vec3 lightVec = sunVec * ((timeAngle < 0.5325 || timeAngle > 0.9675) ? 1.0 : -1.0);
	float VoL = dot(normalize(viewPos.xyz), lightVec);

	#ifdef OVERWORLD
	float factor = mix(LIGHT_SHAFT_MORNING_FALLOFF, LIGHT_SHAFT_DAY_FALLOFF, timeBrightness);
		  factor = mix(LIGHT_SHAFT_NIGHT_FALLOFF, factor, sunVisibility);
		  factor*= mix(1.0, LIGHT_SHAFT_WEATHER_FALLOFF, rainStrength) * 0.1;
		  factor = min(factor, 0.999);

	float reverseFactor = 1.0 - factor;

	float falloff = clamp(VoL * 0.5 + 0.5, 0.0, 1.0);
	falloff = factor / (1.0 - reverseFactor * falloff) - factor;
	falloff = clamp(falloff * 1.015 / reverseFactor - 0.015, 0.0, 1.0);
	falloff = mix(1.0, falloff, 0.03125 * eBS + 0.96875);
	#endif
	
	#ifdef END
	VoL = pow(VoL * 0.5 + 0.5, 16.0) * 0.75 + 0.25;
	float falloff = VoL;
	#endif

	falloff *= float(z0 > 0.56) / 7.0;

	return falloff;
}

bool IsRayMarcherHit(float currentDist, float maxDist, float linearZ0, float linearZ1, vec3 translucent) {
	bool isMaxReached = currentDist >= maxDist;
	bool opaqueReached = currentDist > linearZ1;
	bool solidTransparentReached = currentDist > linearZ0 && translucent == vec3(0.0);
	
	return isMaxReached || opaqueReached || solidTransparentReached;
}

vec3 DistortShadow(vec3 shadowPos) {
	float distb = sqrt(dot(shadowPos.xy, shadowPos.xy));
	float distortFactor = 1.0 - shadowMapBias + distb * shadowMapBias;
	
	shadowPos.xy *= 1.0 / distortFactor;
	shadowPos.z = shadowPos.z * 0.2;
	shadowPos = shadowPos * 0.5 + 0.5;

	return shadowPos;
}

bool IsSampleInShadowmap(vec3 shadowPos) {
	return length(shadowPos.xy * 2.0 - 1.0) < 1.0;
}

vec3 SampleShadow(vec3 sampleShadowPos) {
	float shadow0 = shadow2D(shadowtex0, sampleShadowPos.xyz).z;
	
	vec3 shadowCol = vec3(0.0);
	#ifdef SHADOW_COLOR
	if (shadow0 < 1.0) {
		float shadow1 = shadow2D(shadowtex1, sampleShadowPos.xyz).z;
		if (shadow1 > 0.0) {
			shadowCol = texture2D(shadowcolor0, sampleShadowPos.xy).rgb;
			shadowCol *= shadowCol * shadow1;
			#ifdef WATER_CAUSTICS
			shadowCol *= 16.0 - 15.0 * (1.0 - (1.0 - shadow0) * (1.0 - shadow0));
			#endif
		}
	}
	#endif

	shadow0 *= shadow0;
	shadowCol *= shadowCol;
	
	vec3 shadow = clamp(shadowCol * (1.0 - shadow0) + shadow0, vec3(0.0), vec3(16.0));

	return shadow;
}

vec3 ApplyShadowTint(vec3 shadow, vec3 translucent, vec3 waterTint, float currentDist, float linearZ0) {
	if (currentDist > linearZ0) {
		shadow *= translucent;
	} else if (isEyeInWater == 1.0) {
		#ifdef WATER_SHADOW_COLOR
		shadow *= 0.125 * (1.0 + eBS);
		#else
		shadow *= waterTint * 0.01 * (1.0 + eBS);
		#endif
	}

	return shadow;
}

vec3 Sample3DNoise(vec3 shadow, vec3 worldPos) {
	vec3 noisePos = worldPos + cameraPosition;
	noisePos += vec3(time * 4.0, 0, 0);
	noisePos.xz /= 512.0;

	float yResolution = 3.0;
	float yOffsetScale = 0.35;
	float yLow  = floor(noisePos.y / yResolution) * yOffsetScale;
	float yHigh = yLow + yOffsetScale;
	float yBlend = fract(noisePos.y / yResolution);

	float noiseLow  = texture2D(noisetex, noisePos.xz + yLow).r;
	float noiseHigh = texture2D(noisetex, noisePos.xz + yHigh).r;

	float noise = mix(noiseLow, noiseHigh, yBlend);
	noise = sin(noise * 28.0 + time * 4.0) * 0.25 + 0.5;

	shadow *= noise;

	return shadow;
}

//Light shafts based on Robobo1221's implementation
vec3 GetLightShafts(vec3 viewPos, float z0, float z1, float dither, vec3 translucent) {
	vec3 vl = vec3(0.0);

	#ifdef TAA
	#if TAA_MODE == 0
	dither = fract(dither + frameCounter * 0.618);
	#else
	dither = fract(dither + frameCounter * 0.5);
	#endif
	#endif
	
	float falloff = GetLightShaftsFalloff(viewPos, z0);

	if (falloff > 0.0) {
		float maxDist = 128.0;
		
		float linearZ0 = GetLinearDepth(z0, gbufferProjectionInverse);
		float linearZ1 = GetLinearDepth(z1, gbufferProjectionInverse);

		vec3 worldPos = ToWorld(viewPos);
		vec3 viewDir = normalize(viewPos.xyz);
		vec3 worldDir = normalize(worldPos.xyz);

		worldDir /= -viewDir.z;
		
		vec3 waterTint = waterColor.rgb / (waterColor.a * waterColor.a);
		waterTint = mix(vec3(1.0), waterTint, pow(waterAlpha, 0.25));
		
		for(int i = 0; i < 7; i++) {
			float currentDist = exp2(i + dither) - 0.95;

			if (IsRayMarcherHit(currentDist, maxDist, linearZ0, linearZ1, translucent)) break;

			vec3 sampleWorldPos = worldDir * currentDist;
			vec3 sampleShadowPos = ToShadow(sampleWorldPos);
			sampleShadowPos = DistortShadow(sampleShadowPos);

			sampleShadowPos.z += 0.0512 / shadowMapResolution;

			if (IsSampleInShadowmap(sampleShadowPos)) {
				vec3 shadow = SampleShadow(sampleShadowPos);
				shadow = ApplyShadowTint(shadow, translucent, waterTint, currentDist, linearZ0);

				#ifdef END
				shadow = Apply3DNoise(shadow, sampleWorldPos);
				#endif
				
				vl += shadow;
			}
			else{
				vl += 1.0;
			}
		}
		
		#ifdef IS_IRIS
		vl *= clamp((cameraPosition.y - bedrockLevel + 6.0) / 8.0, 0.0, 1.0);
		#else
		#if MC_VERSION >= 11800
		vl *= clamp((cameraPosition.y + 70.0) / 8.0, 0.0, 1.0);
		#else
		vl *= clamp((cameraPosition.y + 6.0) / 8.0, 0.0, 1.0);
		#endif
		#endif
		
		#ifdef SKY_UNDERGROUND
		vl *= mix(clamp((cameraPosition.y - 48.0) / 16.0, 0.0, 1.0), 1.0, eBS);
		#endif

		vl = sqrt(vl * falloff);
		if(dot(vl, vl) > 0.0) vl += (dither - 0.25) / 128.0;
	}
	
	return vl;
}