float GetLogarithmicDepth(float dist) {
	return (far * (dist - near)) / (dist * (far - near));
}

float GetLinearDepth2(float depth) {
    return 2.0 * near * far / (far + near - (2.0 * depth - 1.0) * (far - near));
}

vec4 DistortShadow(vec4 shadowpos, float distortFactor) {
	shadowpos.xy *= 1.0 / distortFactor;
	shadowpos.z = shadowpos.z * 0.2;
	shadowpos = shadowpos * 0.5 + 0.5;

	return shadowpos;
}

vec4 GetWorldSpace(float shadowdepth, vec2 texCoord) {
	vec4 viewPos = gbufferProjectionInverse * (vec4(texCoord, shadowdepth, 1.0) * 2.0 - 1.0);
	viewPos /= viewPos.w;

	vec4 wpos = gbufferModelViewInverse * viewPos;
	wpos /= wpos.w;
	
	return wpos;
}

vec4 GetShadowSpace(vec4 wpos) {
	wpos = shadowModelView * wpos;
	wpos = shadowProjection * wpos;
	wpos /= wpos.w;
	
	float distb = sqrt(wpos.x * wpos.x + wpos.y * wpos.y);
	float distortFactor = 1.0 - shadowMapBias + distb * shadowMapBias;
	wpos = DistortShadow(wpos,distortFactor);
	
	return wpos;
}

//Light shafts from Robobo1221 (modified)
vec3 GetLightShafts(float pixeldepth0, float pixeldepth1, vec3 color, float dither) {
	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
	
	vec3 screenPos = vec3(texCoord, pixeldepth0);
	vec4 viewPos = gbufferProjectionInverse * (vec4(screenPos, 1.0) * 2.0 - 1.0);
	viewPos /= viewPos.w;
	
	vec3 lightVec = sunVec * ((timeAngle < 0.5325 || timeAngle > 0.9675) ? 1.0 : -1.0);
	float VoL = dot(normalize(viewPos.xyz), lightVec);

	#ifdef OVERWORLD
	// Verlixia: airy long-reach light shafts — higher scattering, lower density
	float visfactor = mix(LIGHT_SHAFT_MORNING_FALLOFF * 1.6, LIGHT_SHAFT_DAY_FALLOFF * 1.6, timeBrightness);
		  visfactor = mix(LIGHT_SHAFT_NIGHT_FALLOFF * 1.4, visfactor, sunVisibility);
		  visfactor*= mix(1.0, LIGHT_SHAFT_WEATHER_FALLOFF * 0.7, rainStrength) * 0.08;
		  visfactor = min(visfactor, 0.999);

	float invvisfactor = 1.0 - visfactor;

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

	visibility *= 0.14285 * float(pixeldepth0 > 0.56);

	// Verlixia: no if(visibility>0) guard — all warps run the march uniformly.
	// Pixels with visibility==0 contribute zero and get multiplied out at the end.
	{
		float maxDist = 128.0;
		
		float depth0 = GetLinearDepth2(pixeldepth0);
		float depth1 = GetLinearDepth2(pixeldepth1);
		vec4 worldposition = vec4(0.0);
		vec4 shadowposition = vec4(0.0);
		
		vec3 watercol = mix(vec3(1.0),
							waterColor.rgb / (waterColor.a * waterColor.a),
							pow(waterAlpha, 0.25));

		// Verlixia opt: viewPos already unprojected above — extract world-space ray
		// direction from it once. Avoids GetLogarithmicDepth + GetWorldSpace (2 mat4
		// unprojects) every iteration.
		vec3 viewRayDir = normalize((gbufferModelViewInverse * vec4(viewPos.xyz, 0.0)).xyz);

		// Verlixia opt: shadow transform is linear — decompose into a constant origin
		// and a per-unit-distance step. Replaces 2 mat4×vec4 ops in GetShadowSpace
		// with 1 vec4 MAD per sample. Mathematically identical for orthographic shadows.
		//   shadowMVP * vec4(d*ray, 1) = shadowOrigin + d * shadowStep
		vec4 shadowOrigin = shadowProjection * (shadowModelView * vec4(0.0, 0.0, 0.0, 1.0));
		shadowOrigin /= shadowOrigin.w; // orthographic → w always 1, kept for safety
		vec4 shadowStep   = shadowProjection * (shadowModelView * vec4(viewRayDir, 0.0));
		
		// Verlixia opt: 5 samples with 1.4x exponent scale covers the same ~1–96 block range
		// as the original 7 samples, saving 2 shadow texture lookups per pixel per frame.
		for(int i = 0; i < 5; i++) {
			float minDist = exp2(i * 1.4 + dither) - 0.95;
			if (minDist >= maxDist || depth1 < minDist || (depth0 < minDist && color == vec3(0.0))) break;

			// Cheap ray evaluation — replaces GetWorldSpace + GetLogarithmicDepth
			worldposition = vec4(viewRayDir * minDist, 1.0);

			// Inline shadow transform using precomputed origin + per-unit step
			vec4 sp = shadowOrigin + minDist * shadowStep;
			float distb = sqrt(sp.x * sp.x + sp.y * sp.y);
			float distortFactor = 1.0 - shadowMapBias + distb * shadowMapBias;
			shadowposition = DistortShadow(sp, distortFactor);
			shadowposition.z += 0.0512 / shadowMapResolution;

			vec2 shadowCoord = shadowposition.xy * 2.0 - 1.0;
			// Branchless bounds select: inShadowMap is 1.0 inside the map, 0.0 outside.
			// Avoids warp divergence between pixels near/far from the shadow frustum edge.
			float inShadowMap = step(dot(shadowCoord, shadowCoord), 1.0);

			float shadow0 = shadow2D(shadowtex0, shadowposition.xyz).z;
			
			vec3 shadowCol = vec3(0.0);
			#ifdef SHADOW_COLOR
			// Branchless color shadow: always sample, zero out via (1-shadow0) weight.
			float shadow1 = shadow2D(shadowtex1, shadowposition.xyz).z;
			shadowCol = texture2D(shadowcolor0, shadowposition.xy).rgb;
			shadowCol *= shadowCol * shadow1 * step(shadow0, 0.999);
			#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));
			// Outside shadow map → treat as fully lit (1.0); blend with inShadowMap mask.
			shadow = mix(vec3(1.0), shadow, inShadowMap);

			// Branchless depth/water select using mix()
			vec3 aboveFogColor = mix(shadow, shadow * color, step(depth0, minDist));
			#ifdef WATER_SHADOW_COLOR
			vec3 underwaterColor = shadow * 0.125 * (1.0 + eBS);
			#else
			vec3 underwaterColor = shadow * watercol * 0.01 * (1.0 + eBS);
			#endif
			shadow = mix(aboveFogColor, underwaterColor,
			             step(depth0, minDist) < 0.5 ? isEyeInWater : 0.0);

				#ifdef END
				vec3 npos = worldposition.xyz + cameraPosition.xyz + vec3(frametime * 4.0, 0, 0);
				float n3da = texture2D(noisetex, npos.xz / 512.0 + floor(npos.y / 3.0) * 0.35).r;
				float n3db = texture2D(noisetex, npos.xz / 512.0 + floor(npos.y / 3.0 + 1.0) * 0.35).r;
				float noise = mix(n3da, n3db, fract(npos.y / 3.0));
				noise = sin(noise * 28.0 + frametime * 4.0) * 0.25 + 0.5;
				shadow *= noise;
				#endif
			
			vl += shadow;
		}

		#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

		#ifdef UNDERGROUND_SKY
		vl *= mix(clamp((cameraPosition.y - 48.0) / 16.0, 0.0, 1.0), 1.0, eBS);
		#endif

		// Verlixia: softer, more spread light shafts.
		// Multiply by visibility here (branchless) instead of guarding the whole block.
		vl = pow(vl * visibility, vec3(0.45));
		if(dot(vl, vl) > 0.0) vl += (dither - 0.25) / 128.0;
	}
	
	return vl;
}