/*
 * =======================================================================================
 *
 * ATMOSPHERIC, VOLUMETRIC & ENVIRONMENTAL SYSTEM
 * Lunara Shader - Complete Atmospheric Effects
 *
 * =======================================================================================
 *
 * Copyright (c) 2025 Lunara Development
 * All rights reserved. Unauthorized reproduction or distribution is prohibited.
 *
 * This unified module combines all atmospheric rendering systems:
 * - Atmospheric horizon rendering with realistic scattering simulation
 * - Volumetric fog with atmospheric scattering simulation
 * - Volumetric light shafts with realistic shadow casting
 * - Dynamic lighting with quality-adaptive sampling
 * - Environmental effects integration (rain, biome-specific)
 * - Sky rendering with day/night transitions and weather effects
 * - Celestial effects including stars, aurora borealis, and night nebula
 * - End dimension star systems with magical coloring
 * - Rainbow effects and environmental weather systems
 * - Disabled volumetric effects (ender beams) for cleaner visuals
 *
 * Based on volumetric tracing techniques from Robobo1221, extensively modified
 * and for performance and visual quality.
 *
 * =======================================================================================
 */

#ifndef LU_INCLUDE_ATMOSPHERIC_VOLUMETRIC_SYSTEM
#define LU_INCLUDE_ATMOSPHERIC_VOLUMETRIC_SYSTEM

//=======================================================================================
// DEPENDENCIES
//=======================================================================================

#include "/lib/sky/scatter/coord.glsl"
#include "/lib/colors/core.glsl"

#ifdef LU_CAVE_FOG
    #include "/lib/sky/aero/depth.glsl"
#endif

//=======================================================================================
// VOLUMETRIC CONFIGURATION
//=======================================================================================

/**
 * Vertical stretch factor for atmospheric layers
 * Controls the thickness and distribution of volumetric effects
 */
const float volumetricStretch = 5.5;

/**
 * Total height of volumetric atmospheric layer
 * Calculated as double the stretch factor for symmetric distribution
 */
const float volumetricHeight = volumetricStretch * 2.0;

//=======================================================================================
// DEPTH AND DISTANCE UTILITIES
//=======================================================================================

/**
 * Converts depth buffer value to linear depth distance
 * Essential for accurate volumetric sampling calculations
 * @param depth: Depth buffer value (0.0 - 1.0)
 * @return: Linear depth distance in world units
 */
float lu_GetDepth(float depth) {
    return 2.0 * near * far / (far + near - (2.0 * depth - 1.0) * (far - near));
}

/**
 * Converts linear distance to depth buffer coordinate
 * Used for converting world distances back to screen space
 * @param dist: Linear world distance
 * @return: Depth coordinate for projection calculations
 */
float lu_GetDistX(float dist) {
    return (far * (dist - near)) / (dist * (far - near));
}

//=======================================================================================
// ENVIRONMENTAL EFFECTS FUNCTIONS
//=======================================================================================

#define LU_RAINBOW_DIAMETER 1.00
#define LU_RAINBOW_STYLE 1

/**
 * Ender Beams Effect (DISABLED)
 * Originally provided mystical light beams in End dimension
 * Disabled to remove floating volumetric effects for cleaner visuals
 * @return: Always returns vec3(0.0) - no effect
 */
vec3 lu_DrawEnderBeams(float VdotU, vec3 playerPos) {
    // Lunara Shader: Disabled ender beams to remove floating light beams
    return vec3(0.0);
}

/**
 * Rainbow Effects System
 * atmospheric rainbow rendering with post-rain conditions
 * @param translucentMult: Translucent material interaction factor
 * @param z0: Near depth buffer value
 * @param z1: Far depth buffer value
 * @param lViewPos: View space distance to near surface
 * @param lViewPos1: View space distance to far surface
 * @param VdotL: View-light dot product for rainbow positioning
 * @param dither: Temporal dithering value
 * @return: RGB rainbow color contribution
 */
vec3 lu_GetRainbow(vec3 translucentMult, float z0, float z1, float lViewPos, float lViewPos1, float VdotL, float dither) {
    vec3 rainbow = vec3(0.0);

    float rainbowTime = lu_min1(lu_max0(SdotU - 0.1) / 0.15);
    rainbowTime = clamp(rainbowTime - lu_pow2(lu_pow2(lu_pow2(noonFactor))) * 8.0, 0.0, 0.85);

    #if LU_RAINBOWS == 1 // After Rain
        rainbowTime *= lu_sqrt2(lu_max0(wetness - 0.333) * 1.5) * invRainFactor * inRainy;
    #endif

    if (rainbowTime > 0.001) {
        float cloudLinearDepth = texelFetch(colortex4, texelCoord, 0).r;
        float cloudDistance = lu_pow2(cloudLinearDepth + OSIEBCA * dither) * far;
        if (cloudDistance < lViewPos1) lViewPos = cloudDistance;

        float rainbowLength = max(far, 128.0) * 0.9;

        float rainbowCoord = lu_clamp01(1.0 - (VdotL + 0.75) / (0.0625 * LU_RAINBOW_DIAMETER));
        float rainbowFactor = rainbowCoord * (1.0 - rainbowCoord);
              rainbowFactor = lu_pow2(lu_pow2(rainbowFactor * 3.7));
              rainbowFactor *= lu_pow2(lu_min1(lViewPos / rainbowLength));
              rainbowFactor *= rainbowTime;
              #ifdef LU_CAVE_FOG
                  rainbowFactor *= 1.0 - lu_GetDepthFactor();
              #endif

        if (rainbowFactor > 0.0) {
            #if LU_RAINBOW_STYLE == 1
                // rainbow with multiple layers
                float rainbowCoordM = pow(rainbowCoord, 1.4 + max(rainbowCoord - 0.5, 0.0) * 1.6);
                rainbowCoordM = smoothstep(0.0, 1.0, rainbowCoordM) * 0.85;
                rainbowCoordM += (dither - 0.5) * 0.1;

                // Triple-layer rainbow for realism
                rainbow += clamp(abs(mod(rainbowCoordM * 6.0 + vec3(-0.55,4.3,2.2) ,6.0)-3.0)-1.0, 0.0, 1.0);
                rainbowCoordM += 0.1;
                rainbow += clamp(abs(mod(rainbowCoordM * 6.0 + vec3(-0.55,4.3,2.2) ,6.0)-3.0)-1.0, 0.0, 1.0);
                rainbowCoordM -= 0.2;
                rainbow += clamp(abs(mod(rainbowCoordM * 6.0 + vec3(-0.55,4.3,2.2) ,6.0)-3.0)-1.0, 0.0, 1.0);
                rainbow /= 3.0;

                // Red enhancement for outer edge
                rainbow.r += lu_pow2(max(rainbowCoord - 0.5, 0.0)) * (max(1.0 - rainbowCoord, 0.0)) * 26.0;
                rainbow = pow(rainbow, vec3(2.2)) * vec3(0.25, 0.075, 0.25) * 3.0;
            #else
                // Simple rainbow style
                float rainbowCoordM = pow(rainbowCoord, 1.35);
                rainbowCoordM = smoothstep(0.0, 1.0, rainbowCoordM);
                rainbow += clamp(abs(mod(rainbowCoordM * 6.0 + vec3(0.0,4.0,2.0) ,6.0)-3.0)-1.0, 0.0, 1.0);
                rainbow *= rainbow * (3.0 - 2.0 * rainbow);
                rainbow = pow(rainbow, vec3(2.2)) * vec3(0.25, 0.075, 0.25) * 3.0;
            #endif

            // Apply translucent material interaction
            if (z1 > z0 && lViewPos < rainbowLength)
                rainbow *= mix(translucentMult, vec3(1.0), lViewPos / rainbowLength);

            rainbow *= rainbowFactor;
        }
    }

    return rainbow;
}

/**
 * Lunara Shader: Cinematic Nether Atmosphere
 * Volumetric haze + light-shaft style god rays in the Nether dimension.
 * Procedurally generated, no extra texture sampling needed.
 *
 * @param color: Base scene color
 * @param translucentMult: Translucent material multiplier
 * @param nPlayerPos: Normalized player position (view direction-ish)
 * @param playerPos: Player world position relative to camera
 * @param lViewPos: View distance (close)
 * @param lViewPos1: Extended view distance
 * @param dither: Temporal dither value
 * @return: vec4(rgb, alpha) blended over scene
 */
vec4 lu_GetNetherStorm(vec3 color, vec3 translucentMult, vec3 nPlayerPos, vec3 playerPos, float lViewPos, float lViewPos1, float dither) {
    #ifndef NETHER
        return vec4(0.0);
    #else
    // === Atmospheric Nether fog (visible but not overwhelming) ===
    // Warm tint pulled from the current biome's fog so it blends naturally.
    vec3 hazeTint = fogColor * 1.30 + vec3(0.07, 0.03, 0.04);

    // Distance falloff: starts at 4 blocks, builds up smoothly.
    float dist = max(lViewPos1 - 4.0, 0.0);
    float density = 1.0 - exp(-dist * 0.040);
    density *= float(LU_NETHER_STORM_I) * 1.80;

    // Soft animated breathing so the haze feels alive, no hard streaks.
    float t = frameTimeCounter;
    float breathe = 0.88 + 0.12 * sin(t * 0.25 + playerPos.y * 0.04);
    density *= breathe;

    // Gentle vertical god-ray glow only when looking up (toward ceiling fire glow).
    vec3 vDir = normalize(playerPos);
    float VdotUp = clamp(vDir.y, 0.0, 1.0);
    float rays   = pow(VdotUp, 3.0) * 0.25;

    vec3 stormColor = hazeTint * (1.0 + rays * 1.5);
    float alpha = clamp(density + rays * 0.20, 0.0, 0.85); // dense atmospheric layer

    return vec4(stormColor, alpha);
    #endif
}

/**
 * Lunara Shader: End Tornado Storm
 * Strong swirling fog effect specific to The End dimension.
 * Builds visual density with distance and rotates around the player's vertical axis.
 */
vec4 lu_GetEndStorm(vec3 color, vec3 translucentMult, vec3 nPlayerPos, vec3 playerPos, float lViewPos, float lViewPos1, float dither) {
    #ifndef END
        return vec4(0.0);
    #else
    // Lunara: simple End atmospheric haze.
    // Pure distance fog. No animation, no noise, no swirl.
    float dist    = max(lViewPos1 - 4.0, 0.0);
    float density = (1.0 - exp(-dist * 0.012)) * float(LU_END_STORM_I);
    vec3  hazeCol = vec3(0.18, 0.13, 0.26);   // soft cool violet
    return vec4(hazeCol, clamp(density, 0.0, 0.55));
    #endif
}

//=======================================================================================
// LU_SHADOW PROCESSING FUNCTIONS
//=======================================================================================

/**
 * Applies shadow map distortion for accurate shadow sampling
 * Compensates for perspective distortion in shadow mapping
 * @param shadowpos: Shadow space position
 * @param distortFactor: Distortion compensation factor
 * @return: Corrected shadow position for sampling
 */
vec4 lu_DistortShadow(vec4 shadowpos, float distortFactor) {
    shadowpos.xy *= 1.0 / distortFactor;
    shadowpos.z = shadowpos.z * 0.2;
    shadowpos = shadowpos * 0.5 + 0.5;
    return shadowpos;
}

//=======================================================================================
// NOISE GENERATION FUNCTIONS
//=======================================================================================

/**
 * Generates 3D noise for volumetric effects
 * @param p: 3D position for noise sampling
 */
float lu_Noise3D(vec3 p) {
    p.z = fract(p.z) * 128.0;
    float iz = floor(p.z);
    float fz = fract(p.z);
    vec2 a_off = vec2(23.0, 29.0) * (iz) / 128.0;
    vec2 b_off = vec2(23.0, 29.0) * (iz + 1.0) / 128.0;
    float a = texture2D(noisetex, p.xy + a_off).r;
    float b = texture2D(noisetex, p.xy + b_off).r;
    return mix(a, b, fz);
}

//=======================================================================================
// HIGH QUALITY SKY RENDERING MODULE
//=======================================================================================

/**
 * Main atmospheric horizon rendering function with scattering simulation
 * @param VdotU: View direction dot up vector [-1.0, 1.0]
 * @param VdotS: View direction dot sun/moon vector [-1.0, 1.0]
 * @param dither: Temporal dithering value [0.0, 1.0]
 * @param doGlare: Enable celestial glare effects (sun/moon)
 * @param doGround: Enable ground horizon effects
 * @return: RGB sky color with realistic atmospheric scattering
 *
 * Features:
 * - Realistic atmospheric Rayleigh scattering
 * - Dynamic day/night color transitions
 * - Weather-based atmospheric modifications
 * - Underwater fog integration
 * - Cave environment compatibility
 * - Celestial glare with procedural sun/moon support
 */
#ifdef OVERWORLD
vec3 lu_GetSkyHorizon(float VdotU, float VdotS, float dither, bool doGlare, bool doGround) {
    //===================================================================================
    // ATMOSPHERIC VARIABLE PREPARATION
    //===================================================================================

    // Night factor calculations for smooth transitions
    float nightFactorSqrt2 = lu_sqrt2(nightFactor);
    float nightFactorM = lu_sqrt2(nightFactorSqrt2) * 0.4;

    // Sun direction calculations with precision
    float VdotSM1 = lu_pow2(max(VdotS, 0.0));      // Sun intensity factor
    float VdotSM2 = lu_pow2(VdotSM1);              // sun effect
    float VdotSM3 = lu_pow2(lu_pow2(max(-VdotS, 0.0))); // Anti-sun direction
    float VdotSML = sunVisibility > 0.5 ? VdotS : -VdotS; // Light source selection

    // View direction calculations for horizon effects
    float VdotUmax0 = max(VdotU, 0.0);          // Above horizon only
    float VdotUmax0M = 1.0 - lu_pow2(VdotUmax0);   // Inverted horizon factor

    //===================================================================================
    // SKY COLOR PREPARATION
    //===================================================================================

    // Dynamic sky color mixing based on time of day and sun position
    vec3 upColor = mix(
        nightUpSkyColor * (1.5 - 0.5 * nightFactorSqrt2 + nightFactorM * VdotSM3 * 1.5),
        dayUpSkyColor,
        sunFactor
    );

    vec3 middleColor = mix(
        nightMiddleSkyColor * (3.0 - 2.0 * nightFactorSqrt2),
        dayMiddleSkyColor * (1.0 + VdotSM2 * 0.3),
        sunFactor
    );

    vec3 downColor = mix(
        nightDownSkyColor,
        dayDownSkyColor,
        (sunFactor + sunVisibility) * 0.5
    );

    //===================================================================================
    // ATMOSPHERIC SCATTERING SIMULATION
    //===================================================================================

    // === PRIMARY SKY GRADIENT ===
    // Simulate atmospheric density gradient from zenith to horizon
    float VdotUM1 = lu_pow2(1.0 - VdotUmax0);
    VdotUM1 = pow(VdotUM1, 1.0 - VdotSM2 * 0.4);  // Sun influence on gradient
    VdotUM1 = mix(VdotUM1, 1.0, rainFactor2 * 0.15); // Weather modification
    vec3 finalSky = mix(upColor, middleColor, VdotUM1);

    // === SUNSET/SUNRISE SCATTERING ===
    // Rayleigh scattering during golden hour
    float VdotUM2 = lu_pow2(1.0 - abs(VdotU));
    VdotUM2 = VdotUM2 * VdotUM2 * (3.0 - 2.0 * VdotUM2); // Smoothstep for natural transition
    VdotUM2 *= (0.7 - nightFactorM + VdotSM1 * (0.3 + nightFactorM)) * invNoonFactor * sunFactor;
    finalSky = mix(finalSky, sunsetDownSkyColorP * (1.0 + VdotSM1 * 0.3), VdotUM2 * invRainFactor);

    // === HORIZON GROUND SCATTERING ===
    // Simulate atmospheric perspective and ground interaction
    float VdotUM3 = min(lu_max0(-VdotU + 0.08) / 0.35, 1.0);
    VdotUM3 = lu_smoothstep1(VdotUM3);

    // RGB scattering simulation
    vec3 scatteredGroundMixer = vec3(
        VdotUM3 * VdotUM3,  // Red scattering (longer wavelength)
        lu_sqrt1(VdotUM3),     // Green scattering (medium wavelength)
        lu_sqrt3(VdotUM3)      // Blue scattering (shorter wavelength)
    );
    scatteredGroundMixer = mix(vec3(VdotUM3), scatteredGroundMixer, 0.75 - 0.5 * rainFactor);
    finalSky = mix(finalSky, downColor, scatteredGroundMixer);

    //===================================================================================
    // ENVIRONMENTAL EFFECTS
    //===================================================================================

    // === GROUND HORIZON EFFECTS ===
    if (doGround) {
        // Darken sky below horizon for realistic ground interaction
        finalSky *= lu_smoothstep1(lu_pow2(1.0 + min(VdotU, 0.0)));
    }

    // === UNDERWATER FOG INTEGRATION ===
    if (isEyeInWater == 1) {
        // underwater visibility with realistic fog mixing
        finalSky = mix(finalSky * 3.0, waterFogColor, VdotUmax0M);
    }

    //===================================================================================
    // CELESTIAL GLARE SYSTEM
    //===================================================================================

    // sun/moon glare with procedural support
    #if !(defined(DISABLE_UNBOUND_SUN_MOON) && (LU_SUN_MOON_STYLE >= 2))
    if (doGlare && 0.0 < VdotSML) {
        // atmospheric scattering for glare
        float glareScatter = 4.0 * (2.0 - lu_clamp01(VdotS * 1000.0));
        float VdotSM4 = pow(abs(VdotS), glareScatter);

        // Realistic glare intensity calculation
        float visfactor = 0.075;
        float glare = visfactor / (1.0 - (1.0 - visfactor) * VdotSM4) - visfactor;

        // Time-based glare modulation
        glare *= 0.5 + lu_pow2(noonFactor) * 1.2;
        glare *= 1.0 - rainFactor * 0.5;

        // Dynamic glare color based on conditions
        float glareWaterFactor = isEyeInWater * sunVisibility;
        vec3 glareColor = mix(vec3(0.38, 0.4, 0.5) * 0.7, vec3(0.5), sunVisibility);
        glareColor = glareColor + glareWaterFactor * vec3(7.0);

        // Apply glare to final sky
        finalSky += glare * shadowTime * glareColor;
    }
    #endif

    //===================================================================================
    // CAVE ENVIRONMENT INTEGRATION
    //===================================================================================

    #ifdef LU_CAVE_FOG
        // Blend sky with cave fog for underground environments
        finalSky = mix(finalSky, caveFogColor, LU_GetCaveFactor() * VdotUmax0M);
    #endif

    //===================================================================================
    // LU_FINAL PROCESSING
    //===================================================================================

    // Apply temporal dithering to reduce color banding
    finalSky += (dither - 0.5) / 128.0;

    return finalSky;
}
#else
vec3 lu_GetSkyHorizon(float VdotU, float VdotS, float dither, bool doGlare, bool doGround) {
    // Fallback for non-overworld dimensions
    return vec3(0.1, 0.1, 0.2);
}
#endif

//=======================================================================================
// PERFORMANCE OPTIMIZED SKY MODULE
//=======================================================================================

/**
 * Lightweight sky rendering for performance-critical scenarios
 * @param VdotU: View direction dot up vector [-1.0, 1.0]
 * @param VdotS: View direction dot sun/moon vector [-1.0, 1.0]
 * @param dither: Temporal dithering value [0.0, 1.0]
 * @param doGlare: Enable simplified celestial glare
 * @param doGround: Enable basic ground effects
 * @return: RGB sky color with simplified scattering
 *
 * Features:
 * - Simplified atmospheric calculations
 * - Basic day/night transitions
 * - Reduced computational overhead
 * - Maintained visual quality for distant views
 */
#ifdef OVERWORLD
vec3 lu_GetOptimizedSkyHorizon(float VdotU, float VdotS, float dither, bool doGlare, bool doGround) {
    //===================================================================================
    // SIMPLIFIED VARIABLE SETUP
    //===================================================================================

    float VdotUmax0 = max(VdotU, 0.0);
    float VdotUmax0M = 1.0 - lu_pow2(VdotUmax0);

    //===================================================================================
    // BASIC COLOR PREPARATION
    //===================================================================================

    vec3 upColor = mix(nightUpSkyColor, dayUpSkyColor, sunFactor);
    vec3 middleColor = mix(nightMiddleSkyColor, dayMiddleSkyColor, sunFactor);

    //===================================================================================
    // SIMPLIFIED SKY GRADIENT
    //===================================================================================

    // Basic sky gradient with weather influence
    float VdotUM1 = lu_pow2(1.0 - VdotUmax0);
    VdotUM1 = mix(VdotUM1, 1.0, rainFactor2 * 0.2);
    vec3 finalSky = mix(upColor, middleColor, VdotUM1);

    // Simplified sunset effects
    float VdotUM2 = lu_pow2(1.0 - abs(VdotU));
    VdotUM2 *= invNoonFactor * sunFactor * (0.8 + 0.2 * VdotS);
    finalSky = mix(finalSky, sunsetDownSkyColorP * (shadowTime * 0.6 + 0.2), VdotUM2 * invRainFactor);

    //===================================================================================
    // BASIC ENVIRONMENTAL EFFECTS
    //===================================================================================

    // Simplified ground darkening
    finalSky *= lu_pow2(lu_pow2(1.0 + min(VdotU, 0.0)));

    // Basic underwater fog
    if (isEyeInWater == 1) {
        finalSky = mix(finalSky, waterFogColor, VdotUmax0M);
    }

    // Simplified celestial glare
    #if !(defined(DISABLE_UNBOUND_SUN_MOON) && (LU_SUN_MOON_STYLE >= 2))
    finalSky *= 1.0 + mix(nightFactor, 0.5 + 0.7 * noonFactor, VdotS * 0.5 + 0.5) * lu_pow2(lu_pow2(lu_pow2(VdotS)));
    #endif

    // Basic cave fog integration
    #ifdef LU_CAVE_FOG
        finalSky = mix(finalSky, caveFogColor, LU_GetCaveFactor() * VdotUmax0M);
    #endif

    return finalSky;
}
#else
vec3 lu_GetOptimizedSkyHorizon(float VdotU, float VdotS, float dither, bool doGlare, bool doGround) {
    // Fallback for non-overworld dimensions
    return vec3(0.1, 0.1, 0.2);
}
#endif

//=======================================================================================
// VOLUMETRIC FOG SAMPLING FUNCTIONS
//=======================================================================================

/**
 * Samples volumetric noise at specified position
 * @param tracePos: World position for sampling
 * @param modifiedPos: Output modified position for coordinate calculations
 * @param altitudeLevel: Atmospheric altitude level
 * @return: true if volumetric effect should be rendered at this position
 */
bool lu_SampleVolumetricNoise(vec3 tracePos, out vec3 modifiedPos, float altitudeLevel) {
    vec3 tracePosition = tracePos;
    tracePosition.xz += 0.6 * cameraPosition.xz;

    modifiedPos = tracePosition;
    modifiedPos.xz *= 1.0 + 0.2 * altitudeLevel;

    float windOffset = frameTimeCounter * 0.003;
    modifiedPos.x += windOffset;
    modifiedPos.z += windOffset * 0.5;

    float layerNoise = lu_Noise3D(modifiedPos * vec3(0.002, 0.04, 0.002));
    float detailNoise = lu_Noise3D(modifiedPos * vec3(0.008, 0.16, 0.008));

    float combinedNoise = layerNoise * 0.7 + detailNoise * 0.3;
    float altitudeFactor = 1.0 - smoothstep(0.0, volumetricHeight, abs(tracePos.y - altitudeLevel));

    return (combinedNoise * altitudeFactor) > 0.4;
}

/**
 * Main volumetric fog rendering function
 * Handles atmospheric fog with lighting and shadow integration
 * @param depthOutput: Output depth value for fog layer
 * @param ditherNoise: Temporal dithering for smooth sampling
 * @param altitudeLevel: Current atmospheric altitude level
 * @param renderDistance: Maximum rendering distance
 * @param viewPosition: Camera view direction
 * @param maxSamples: Maximum number of samples for quality control
 * @return: Volumetric fog color and opacity
 */
vec4 lu_GetVolumetricFog(out float depthOutput, float ditherNoise, float altitudeLevel, float renderDistance,
                      vec3 viewPosition, int maxSamples) {
    vec4 volumetricResult = vec4(0.0);
    depthOutput = 1.0;

    // Early exit conditions for performance optimization
    if (abs(altitudeLevel - cameraPosition.y) > volumetricHeight) return volumetricResult;

    // Initialize sampling parameters
    float maxDistance = min(renderDistance * 0.8, 320.0);
    float rayStep = maxDistance / float(maxSamples);
    vec3 rayDirection = normalize(viewPosition);

    // Atmospheric alignment calculations
    float upDotProduct = dot(rayDirection, upVector);
    float sunDotProduct = dot(rayDirection, sunVector);
    float sunAlignment = sunDotProduct * 0.5 + 0.5;

    // Quality-based distance adjustments
    #ifdef DISTANT_HORIZONS
        if (abs(cameraPosition.y - altitudeLevel) < volumetricStretch) {
            maxDistance = min(maxDistance, renderDistance * 0.6);
        }
    #endif

    // Ray marching through volumetric space
    for (int sampleIndex = 0; sampleIndex < maxSamples; sampleIndex++) {
        float sampleDistance = (float(sampleIndex) + ditherNoise) * rayStep;
        if (sampleDistance > maxDistance) break;

        vec3 samplePosition = cameraPosition + rayDirection * sampleDistance;
        vec3 modifiedPosition;

        // Sample volumetric noise at current position
        if (lu_SampleVolumetricNoise(samplePosition, modifiedPosition, altitudeLevel)) {
            // Calculate horizontal distance for fog density
            vec2 horizontalOffset = samplePosition.xz - cameraPosition.xz;
            float horizontalDistance = length(horizontalOffset);
            float distanceThreshold = maxDistance * 0.85;

            if (horizontalDistance > distanceThreshold) continue;

            // Lighting calculations
            float heightShading = 1.0;
            float distanceMultiplier = 1.0;

            #ifdef LU_DEFERRED1
                // lighting with shadow sampling
                vec3 lightRayDirection = sunVector * rayStep * 0.5;
                vec3 lightSamplePos = samplePosition;
                float lightAccumulation = 0.0;
                float lightRayStep = rayStep * 0.25;
                float lightingMultiplier = 0.8 + 0.2 * sunVisibility;
                float shadingMultiplier = 0.4;

                // Sample lighting along sun direction
                lightSamplePos += gradientNoise * lightRayStep;
                #ifdef LU_DEFERRED1
                    lightAccumulation -= texture2D(colortex3, lu_GetRoundedAtmosphericCoord(lightSamplePos.xz, 0.125)).b * shadingMultiplier;
                #else
                    lightAccumulation -= texture2D(gaux4, lu_GetRoundedAtmosphericCoord(lightSamplePos.xz, 0.125)).b * shadingMultiplier;
                #endif

                // Second light sample with offset
                lightSamplePos += gradientNoise * lightRayStep;
                #ifdef LU_DEFERRED1
                    lightAccumulation -= texture2D(colortex3, lu_GetRoundedAtmosphericCoord(lightSamplePos.xz, 0.125)).b * shadingMultiplier;
                #else
                    lightAccumulation -= texture2D(gaux4, lu_GetRoundedAtmosphericCoord(lightSamplePos.xz, 0.125)).b * shadingMultiplier;
                #endif

                // Combine lighting factors
                float sunContribution = sunAlignment * shadowTime * 0.25;
                sunContribution += 0.5 * heightShading + 0.08;
                heightShading = sunContribution * lightAccumulation * lightingMultiplier;
            #endif

            // Calculate final volumetric color
            vec3 volumetricColor = cloudAmbientColor + cloudLightColor * (0.07 + heightShading);

            // Blend with sky color based on distance
            vec3 skyColor = lu_GetSkyHorizon(upDotProduct, sunDotProduct, ditherNoise, true, false);
            #ifdef ATM_COLOR_MULTS
                skyColor *= sqrtAtmColorMult;
            #endif

            float distanceRatio = (distanceThreshold - horizontalDistance) / distanceThreshold;
            float fogBlendFactor = clamp(distanceRatio, 0.0, 0.75);

            #ifndef DISTANT_HORIZONS
                float fogDistance = fogBlendFactor;
            #else
                float fogDistance = lu_pow1_5(clamp(distanceRatio, 0.0, 1.0)) * 0.75;
            #endif

            // Apply sky and visibility multipliers
            float skyMultiplier1 = 1.0 - 0.2 * (1.0 - skyFade) * max(sunVisibility2, nightFactor);
            float skyMultiplier2 = 1.0 - 0.33333 * skyFade;

            volumetricColor = mix(skyColor, volumetricColor * skyMultiplier1, fogDistance * skyMultiplier2);
            volumetricColor *= lu_pow2(1.0 - maxBlindnessDarkness);

            // Set output values and return
            depthOutput = sqrt(sampleDistance / renderDistance);
            volumetricResult.a = pow(fogBlendFactor * 1.33333, 0.5 + 10.0 * pow(abs(sunAlignment), 90.0)) * distanceMultiplier;
            volumetricResult.rgb = volumetricColor;
            break;
        }
    }

    return volumetricResult;
}

//=======================================================================================
// VOLUMETRIC LIGHT SHAFT RENDERING
//=======================================================================================

/**
 * volumetric light shaft rendering system
 * Creates realistic light beams through atmospheric particles with shadow casting
 * @param color: Scene color to modify (input/output)
 * @param vlFactor: Volumetric light intensity factor (input/output)
 * @param translucentMult: Translucent surface color multiplier
 * @param lViewPos0: Linear view position (near)
 * @param lViewPos1: Linear view position (far)
 * @param nViewPos: Normalized view position
 * @param VdotL: View direction dot light direction
 * @param VdotU: View direction dot up vector
 * @param texCoord: Screen texture coordinates
 * @param z0: Near depth value
 * @param z1: Far depth value
 * @param dither: Temporal dithering for smooth sampling
 * @return: Volumetric light contribution (RGB + alpha)
 */
vec4 lu_GetVolumetricLight(inout vec3 color, inout float vlFactor, vec3 translucentMult, float lViewPos0, float lViewPos1,
                        vec3 nViewPos, float VdotL, float VdotU, vec2 texCoord, float z0, float z1, float dither) {
    vec4 volumetricLight = vec4(0.0);
    float vlMult = 1.0 - maxBlindnessDarkness;

    #if LU_SHADOW_QUALITY > -1
        // Optifine shadow map resolution handling
        vec2 shadowMapResolutionM = textureSize(shadowtex0, 0);
    #endif

    #ifdef OVERWORLD
        vec3 vlColor = lightColor;
        vec3 vlColorReducer = vec3(1.0);
        float vlSceneIntensity = isEyeInWater != 1 ? vlFactor : 1.0;

        #ifdef SPECIAL_BIOME_WEATHER
            vlSceneIntensity = mix(vlSceneIntensity, 1.0, inDry * rainFactor);
        #endif

        // Night-time light shaft adjustments
        if (sunVisibility < 0.5) {
            vlSceneIntensity = 0.0;

            float vlMultNightModifier = 0.6 + 0.4 * lu_max0(far - lViewPos1) / far;
            #ifdef SPECIAL_PALE_GARDEN_LIGHTSHAFTS
                vlMultNightModifier = mix(vlMultNightModifier, 1.0, inPaleGarden);
            #endif
            vlMult *= vlMultNightModifier;

            vlColor = normalize(pow(vlColor, vec3(1.0 - lu_max0(1.0 - 1.5 * nightFactor))));
            vlColor *= 0.0766 + 0.0766 * vsBrightness;
        } else {
            vlColorReducer = 1.0 / sqrt(vlColor);
        }

        #ifdef SPECIAL_PALE_GARDEN_LIGHTSHAFTS
            vlSceneIntensity = mix(vlSceneIntensity, 1.0, inPaleGarden);
            vlMult *= 1.0 + (3.0 * inPaleGarden) * (1.0 - sunVisibility);
        #endif

        // Atmospheric alignment calculations
        float rainyNight = (1.0 - sunVisibility) * rainFactor;
        float VdotLM = max((VdotL + 1.0) / 2.0, 0.0);
        float VdotUmax0 = max(VdotU, 0.0);
        float VdotUM = mix(lu_pow2(1.0 - VdotUmax0), 1.0, 0.5 * vlSceneIntensity);
              VdotUM = lu_smoothstep1(VdotUM);
              VdotUM = pow(VdotUM, min(lViewPos1 / far, 1.0) * (3.0 - 2.0 * vlSceneIntensity));
        vlMult *= mix(VdotUM * VdotLM, 1.0, 0.4 * rainyNight) * vlTime;
        vlMult *= mix(invNoonFactor2 * 0.875 + 0.125, 1.0, max(vlSceneIntensity, rainFactor2));

        // === ADVANCED GOD RAYS: Henyey-Greenstein phase + intensity boost ===
        #ifdef LU_GODRAYS_ENHANCED
            float lu_gAniso = float(LU_GODRAYS_ANISOTROPY) * 0.01;
            float lu_gAniso2 = lu_gAniso * lu_gAniso;
            // Henyey-Greenstein: realistic Mie forward-scattering glow around the light source
            float lu_hgDenom = 1.0 + lu_gAniso2 - 2.0 * lu_gAniso * VdotL;
            float lu_hgPhase = (1.0 - lu_gAniso2) / max(pow(lu_hgDenom, 1.5), 1e-4);
            // Blend the phase highlight in only when the sun is up; cap to avoid blowout
            float lu_hgBoost = mix(1.0, 0.55 + 0.85 * min(lu_hgPhase, 4.0), sunVisibility * (1.0 - 0.6 * rainFactor));
            vlMult *= lu_hgBoost;
            // User intensity scaling (also lifts night/rain shafts a touch)
            vlMult *= float(LU_GODRAYS_INTENSITY) * 0.01;
        #endif

        // Quality-based sample count determination
        #if LU_LIGHTSHAFT_QUALI == 4
            int sampleCount = vlSceneIntensity < 0.5 ? 30 : 50;
        #elif LU_LIGHTSHAFT_QUALI == 3
            int sampleCount = vlSceneIntensity < 0.5 ? 15 : 30;
        #elif LU_LIGHTSHAFT_QUALI == 2
            int sampleCount = vlSceneIntensity < 0.5 ? 10 : 20;
        #elif LU_LIGHTSHAFT_QUALI == 1
            int sampleCount = vlSceneIntensity < 0.5 ? 6 : 12;
        #endif

        #ifdef LIGHTSHAFT_SMOKE
            float totalSmoke = 0.0;
        #endif
    #else
        // End dimension light shaft handling
        translucentMult = sqrt(translucentMult);
        float vlSceneIntensity = 0.0;

        #ifndef LU_LOW_QUALITY_ENDER_NEBULA
            int sampleCount = 16;
        #else
            int sampleCount = 10;
        #endif
    #endif

    // Distance and sampling calculations
    float addition = 1.0;
    float maxDist = mix(max(far, 96.0) * 0.55, 80.0, vlSceneIntensity);

    #if LU_WATER_FOG_MULT != 100
        if (isEyeInWater == 1) {
            #define LU_WATER_FOG_MULT_M LU_WATER_FOG_MULT * 0.01;
            maxDist /= LU_WATER_FOG_MULT_M;
        }
    #endif

    float distMult = maxDist / (sampleCount + addition);
    float sampleMultIntense = isEyeInWater != 1 ? 1.0 : 0.85;
    float viewFactor = 1.0 - 0.7 * lu_pow2(dot(nViewPos.xy, nViewPos.xy));

    // Depth calculations
    float depth0 = lu_GetDepth(z0);
    float depth1 = lu_GetDepth(z1);
    #ifdef END
        if (z0 == 1.0) depth0 = 1000.0;
        if (z1 == 1.0) depth1 = 1000.0;
    #endif

    // Perspective distortion approximation
    maxDist *= viewFactor;
    distMult *= viewFactor;

    #ifdef OVERWORLD
        float maxCurrentDist = min(depth1, maxDist);
    #else
        float maxCurrentDist = min(depth1, far);
    #endif

    // Ray marching for volumetric light sampling
    for (int i = 0; i < sampleCount; i++) {
        float currentDist = (i + dither) * distMult + addition;
        if (currentDist > maxCurrentDist) break;

        // Calculate world position for current sample
        vec4 viewPos = gbufferProjectionInverse * (vec4(texCoord, lu_GetDistX(currentDist), 1.0) * 2.0 - 1.0);
        viewPos /= viewPos.w;
        vec4 wpos = gbufferModelViewInverse * viewPos;
        vec3 playerPos = wpos.xyz / wpos.w;

        #ifdef END
            #ifdef DISTANT_HORIZONS
                playerPos *= sqrt(renderDistance / far);
            #endif
            vec4 enderBeamSample = vec4(lu_DrawEnderBeams(VdotU, playerPos), 1.0);
            enderBeamSample /= sampleCount;
        #endif

        float shadowSample = 1.0;
        vec3 vlSample = vec3(1.0);

        #if LU_SHADOW_QUALITY > -1
            // Shadow map sampling and calculations
            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;
            vec4 shadowPosition = lu_DistortShadow(wpos, distortFactor);

            #ifdef OVERWORLD
                float percentComplete = currentDist / maxDist;
                float sampleMult = mix(percentComplete * 3.0, sampleMultIntense, max(rainFactor, vlSceneIntensity));
                if (currentDist < 5.0) sampleMult *= lu_smoothstep1(clamp(currentDist / 5.0, 0.0, 1.0));
                sampleMult /= sampleCount;
            #endif

            if (length(shadowPosition.xy * 2.0 - 1.0) < 1.0) {
                // Shadow texture sampling with GPU compatibility fix
                shadowSample = texelFetch(shadowtex0, ivec2(shadowPosition.xy * shadowMapResolutionM), 0).x;
                shadowSample = clamp((shadowSample - shadowPosition.z) * 65536.0, 0.0, 1.0);
                vlSample = vec3(shadowSample);

                #if LU_SHADOW_QUALITY >= 1
                    if (shadowSample == 0.0) {
                        float testsample = shadow2D(shadowtex1, shadowPosition.xyz).z;
                        if (testsample == 1.0) {
                            vec3 colsample = texture2D(shadowcolor1, shadowPosition.xy).rgb * 4.0;
                            colsample *= colsample;
                            vlSample = colsample;
                            shadowSample = 1.0;
                            #ifdef OVERWORLD
                                vlSample *= vlColorReducer;
                            #endif
                        }
                    } else {
                        #ifdef OVERWORLD
                            // Water surface tinting for underwater observations
                            if (translucentMult != vec3(1.0) && currentDist > depth0) {
                                vec3 tinter = vec3(1.0);
                                if (isEyeInWater == 1) {
                                    vec3 translucentMultM = translucentMult * 2.8;
                                    tinter = pow(translucentMultM, vec3(sunVisibility * 3.0 * lu_clamp01(playerPos.y * 0.03)));
                                } else {
                                    tinter = 0.1 + 0.9 * lu_pow2(lu_pow2(translucentMult * 1.7));
                                }
                                vlSample *= mix(vec3(1.0), tinter, lu_clamp01(oceanAltitude - cameraPosition.y));
                            }
                        #endif

                        if (isEyeInWater == 1 && translucentMult == vec3(1.0)) vlSample = vec3(0.0);
                    }
                #endif
            }
        #endif

        // Apply translucency effects beyond depth
        if (currentDist > depth0) vlSample *= translucentMult;

        #ifdef OVERWORLD
            #ifdef LIGHTSHAFT_SMOKE
                // Procedural smoke generation for atmospheric effects
                vec3 smokePos = 0.0015 * (playerPos + cameraPosition);
                vec3 smokeWind = frameTimeCounter * vec3(0.002, 0.001, 0.0);
                float smoke = 0.65 * lu_Noise3D(smokePos + smokeWind)
                            + 0.25 * lu_Noise3D((smokePos - smokeWind) * 3.0)
                            + 0.10 * lu_Noise3D((smokePos + smokeWind) * 9.0);
                smoke = lu_smoothstep1(lu_smoothstep1(lu_smoothstep1(smoke)));
                totalSmoke += smoke * shadowSample * sampleMult;
            #endif

            volumetricLight += vec4(vlSample, shadowSample) * sampleMult;
        #else
            volumetricLight += vec4(vlSample, shadowSample) * enderBeamSample;
        #endif
    }

    #ifdef LIGHTSHAFT_SMOKE
        volumetricLight *= pow(totalSmoke / volumetricLight.a, min(1.0 - volumetricLight.a, 0.5));
        volumetricLight.rgb /= pow(0.5, 1.0 - volumetricLight.a);
    #endif

    // Scene-aware light shaft intensity adjustment
    #if defined OVERWORLD && LU_LIGHTSHAFT_BEHAVIOUR == 1 && LU_SHADOW_QUALITY >= 1
        if (viewWidth + viewHeight - gl_FragCoord.x - gl_FragCoord.y < 1.5) {
            if (frameCounter % int(0.06666 / frameTimeSmooth + 0.5) == 0) {
                int salsX = 5;
                int salsY = 5;
                float heightThreshold = 6.0;

                vec2 viewM = 1.0 / vec2(salsX, salsY);
                float salsSampleSum = 0.0;
                int salsSampleCount = 0;

                for (float i = 0.25; i < salsX; i++) {
                    for (float h = 0.45; h < salsY; h++) {
                        vec2 coord = 0.3 + 0.4 * viewM * vec2(i, h);
                        ivec2 icoord = ivec2(coord * shadowMapResolutionM);
                        float salsSample = texelFetch(shadowtex0, icoord, 0).x;

                        if (salsSample < 0.55) {
                            float sampledHeight = texture2D(shadowcolor1, coord).a;
                            if (sampledHeight > 0.0) {
                                sampledHeight = lu_max0(sampledHeight - 0.25) / 0.05;
                                salsSampleSum += sampledHeight;
                                salsSampleCount++;
                            }
                        }
                    }
                }

                float salsCheck = salsSampleSum / salsSampleCount;
                int reduceAmount = 2;

                int skyCheck = 0;
                for (float i = 0.1; i < 1.0; i += 0.2) {
                    skyCheck += int(texelFetch(depthtex0, ivec2(view.x * i, view.y * 0.9), 0).x == 1.0);
                }

                if (skyCheck >= 4) {
                    salsCheck = 0.0;
                    reduceAmount = 3;
                }

                if (salsCheck > heightThreshold) {
                    vlFactor = min(vlFactor + OSIEBCA, 1.0);
                } else {
                    vlFactor = max(vlFactor - OSIEBCA * reduceAmount, 0.0);
                }
            }
        } else {
            vlFactor = 0.0;
        }
    #endif

    #ifdef OVERWORLD
        // Final color adjustments based on atmospheric conditions
        vlColor = pow(vlColor, vec3(0.5 + 0.5 * invNoonFactor * invRainFactor + 0.3 * rainFactor));
        vlColor *= 1.0 - (0.3 + 0.3 * noonFactor) * rainFactor - 0.5 * rainyNight;

        #if LU_LIGHTSHAFT_DAY_I != 100 || LU_LIGHTSHAFT_NIGHT_I != 100 || LU_LIGHTSHAFT_RAIN_I != 100
            #define LU_LIGHTSHAFT_DAY_IM LU_LIGHTSHAFT_DAY_I * 0.01
            #define LU_LIGHTSHAFT_NIGHT_IM LU_LIGHTSHAFT_NIGHT_I * 0.01
            #define LU_LIGHTSHAFT_RAIN_IM LU_LIGHTSHAFT_RAIN_I * 0.01

            if (isEyeInWater == 0) {
                #if LU_LIGHTSHAFT_DAY_I != 100 || LU_LIGHTSHAFT_NIGHT_I != 100
                    vlColor.rgb *= mix(LU_LIGHTSHAFT_NIGHT_IM, LU_LIGHTSHAFT_DAY_IM, sunVisibility);
                #endif
                #if LU_LIGHTSHAFT_RAIN_I != 100
                    vlColor.rgb *= mix(1.0, LU_LIGHTSHAFT_RAIN_IM, rainFactor);
                #endif
            }
        #endif

        volumetricLight.rgb *= vlColor;
    #endif

    volumetricLight.rgb *= vlMult;
    volumetricLight = max(volumetricLight, vec4(0.0));

    #ifdef DISTANT_HORIZONS
        if (isEyeInWater == 0) {
            #ifdef OVERWORLD
                float lViewPosM = lViewPos0;
                if (z0 >= 1.0) {
                    float z0DH = texelFetch(dhDepthTex, texelCoord, 0).r;
                    vec4 screenPosDH = vec4(texCoord, z0DH, 1.0);
                    vec4 viewPosDH = dhProjectionInverse * (screenPosDH * 2.0 - 1.0);
                    viewPosDH /= viewPosDH.w;
                    lViewPosM = length(viewPosDH.xyz);
                }
                lViewPosM = min(lViewPosM, renderDistance * 0.6);

                float dhVlStillIntense = max(max(vlSceneIntensity, rainFactor), nightFactor * 0.5);
                volumetricLight *= mix(0.0003 * lViewPosM, 1.0, dhVlStillIntense);
            #else
                volumetricLight *= lu_min1(lViewPos1 * 3.0 / renderDistance);
            #endif
        }
    #endif

    return volumetricLight;
}

//=======================================================================================
// STARS SYSTEM MODULE
//=======================================================================================

/**
 * Generates pseudo-random noise for star placement
 * @param pos: 2D position coordinate
 * @return: Random value [0.0, 1.0] for star generation
 */
float lu_GetStarNoise(vec2 pos) {
    return fract(sin(dot(pos, vec2(12.9898, 4.1414))) * 43758.54953);
}

/**
 * Calculates star coordinate mapping from view position
 * @param viewPos: View space position vector
 * @param sphereness: Spherical projection factor [0.0, 1.0]
 * @return: 2D star coordinate for texture sampling
 */
vec2 lu_GetStarCoord(vec3 viewPos, float sphereness) {
    vec3 wpos = normalize((gbufferModelViewInverse * vec4(viewPos * 1000.0, 1.0)).xyz);
    vec3 starCoord = wpos / (wpos.y + length(wpos.xz) * sphereness);
    starCoord.x += 0.006 * syncedTime;  // Subtle star movement
    return starCoord.xz;
}

/**
 * Generates overworld star field with configurable density
 * @param starCoord: 2D star coordinate from GetStarCoord
 * @param VdotU: View direction dot up vector
 * @param VdotS: View direction dot sun vector
 * @return: RGB star color contribution
 */
vec3 lu_GetStars(vec2 starCoord, float VdotU, float VdotS) {
    if (VdotU < 0.0) return vec3(0.0);  // No stars below horizon

    starCoord *= 0.2;
    float starFactor = 1024.0;
    vec2 rawCoord = starCoord; // keep continuous coord for galaxy/meteor
    starCoord = floor(starCoord * starFactor) / starFactor;

    // Multi-layer star noise for realistic distribution
    float star = 1.0;
    float starSeed = lu_GetStarNoise(starCoord.xy);
    star *= starSeed;
    star *= lu_GetStarNoise(starCoord.xy + 0.1);
    star *= lu_GetStarNoise(starCoord.xy + 0.23);

    #if LU_NIGHT_STAR_AMOUNT == 2
        star -= 0.7;  // High star density
    #else
        star -= 0.6;  // Standard star density
        star *= 0.65;
    #endif
    star = lu_max0(star);
    star *= star;  // Square for more dramatic falloff

    // === ADVANCED NIGHT SKY: per-star twinkle ===
    #ifdef LU_STAR_TWINKLE
        float twinklePhase = starSeed * 6.2831 + syncedTime * (0.8 + starSeed * 1.6);
        float twinkle = 0.55 + 0.45 * sin(twinklePhase);
        star *= twinkle;
    #endif

    // Apply atmospheric and lighting conditions
    star *= lu_min1(VdotU * 3.0) * lu_max0(1.0 - pow(abs(VdotS) * 1.002, 100.0));
    star *= invRainFactor * lu_pow2(lu_pow2(invNoonFactor2)) * (1.0 - 0.5 * sunVisibility);

    // === ADVANCED NIGHT SKY: per-star stellar color classes ===
    vec3 starColor = vec3(0.38, 0.4, 0.5);
    #ifdef LU_STAR_COLORS
        float hueSeed = lu_GetStarNoise(starCoord.xy + 0.51);
        // 0.0..0.33 = blue giant, 0.33..0.66 = white, 0.66..1.0 = orange/red
        vec3 blueStar   = vec3(0.30, 0.42, 0.65);
        vec3 whiteStar  = vec3(0.50, 0.50, 0.55);
        vec3 orangeStar = vec3(0.65, 0.42, 0.28);
        vec3 stellar = mix(blueStar, whiteStar, lu_clamp01(hueSeed * 3.0));
        stellar = mix(stellar, orangeStar, lu_clamp01(hueSeed * 3.0 - 2.0));
        starColor = stellar;
    #endif

    vec3 result = 40.0 * star * starColor;

    // === ADVANCED NIGHT SKY: Milky Way galaxy band ===
    #ifdef LU_GALAXY_BAND
        // Diagonal band across the sky (rotated coords)
        vec2 gCoord = rawCoord;
        float gAngle = 0.55; // band tilt
        float cs = cos(gAngle), sn = sin(gAngle);
        vec2 gRot = vec2(cs * gCoord.x - sn * gCoord.y, sn * gCoord.x + cs * gCoord.y);
        // Distance from band centerline (band runs along rotated x-axis)
        float bandDist = abs(gRot.y);
        float band = exp(-bandDist * bandDist * 18.0); // soft gaussian band
        // Cloudy structure inside the band using star-noise mips
        float gNoise = lu_GetStarNoise(floor(rawCoord * 64.0) / 64.0)
                     * 0.6 + 0.4 * lu_GetStarNoise(floor(rawCoord * 16.0) / 16.0);
        gNoise = lu_smoothstep1(gNoise);
        // Slight along-band brightness variation
        float along = 0.7 + 0.3 * sin(gRot.x * 2.4);
        vec3 galaxyTint = mix(vec3(0.18, 0.10, 0.32), vec3(0.10, 0.18, 0.42), gNoise);
        // Sky visibility gates: only above horizon, fade in noon/rain/sun like stars
        float galaxyVis = lu_min1(VdotU * 2.5)
                        * invRainFactor
                        * lu_pow2(lu_pow2(invNoonFactor2))
                        * (1.0 - 0.5 * sunVisibility);
        result += band * gNoise * along * galaxyTint * (float(LU_GALAXY_I) * 0.01) * galaxyVis;
    #endif

    // === ADVANCED NIGHT SKY: shooting stars / meteors ===
    #ifdef LU_SHOOTING_STARS
        // Multi-meteor cinematic shower system.
        // 6 independent meteors with staggered spawn cycles for near-constant activity.
        const int meteorCount = 6;
        for (int m = 0; m < meteorCount; m++) {
            float mSeed = float(m) * 13.71 + 1.3;
            // Stagger meteors so they don't all spawn together
            float gap = 9.0 / float(LU_SHOOTING_STARS_RATE);
            float phaseOffset = float(m) / float(meteorCount);
            float cycle = floor(syncedTime / gap + phaseOffset + mSeed * 0.01);
            float lifeT = fract(syncedTime / gap + phaseOffset + mSeed * 0.01);

            // Random spawn pos - rawCoord is ~[-0.25..0.25] across visible sky
            // (sphereness=0.5 in sky_basic, then *0.2 here). Keep spawn well inside.
            vec2 spawn = vec2(
                lu_GetStarNoise(vec2(cycle, mSeed)) * 0.30 - 0.15,
                lu_GetStarNoise(vec2(cycle + 0.7, mSeed)) * 0.20 - 0.05
            );
            // Bias direction toward "downward" feel: pick angle in lower half
            // (downward-slanting meteors look more like real shooting stars)
            float dirRand = lu_GetStarNoise(vec2(cycle * 1.3, mSeed));
            float dirA = mix(3.4, 6.0, dirRand); // 195deg..344deg range (down-left to down-right)
            vec2 dir = vec2(cos(dirA), sin(dirA));

            // Visible window: first 35% of cycle
            float visT = lu_clamp01(lifeT * 2.86);
            float fade = lu_smoothstep1(1.0 - visT);
            // Quick brighten-then-fade for dramatic flash
            float flash = smoothstep(0.0, 0.05, visT) * (1.0 - smoothstep(0.05, 1.0, visT));
            flash = max(flash, fade * 0.6);

            // Move along path - cross ~0.25 of coord space over visible window (visT 0..1)
            // visible duration in lifeT = 0.35, so speed_in_lifeT = 0.25/0.35 ~= 0.7
            float speed = 0.7;
            vec2 head = spawn + dir * lifeT * speed;
            vec2 toPix = rawCoord - head;

            float perp = abs(dot(toPix, vec2(-dir.y, dir.x)));
            float along2 = dot(toPix, -dir); // distance behind head (positive = trail)
            float along2c = max(along2, 0.0);

            // Long brilliant trail - tuned to small coord space
            float trailLen = 0.20;
            float trail = lu_clamp01(along2c / trailLen);
            float trailFade = exp(-along2c * 10.0);

            // Head + tail + glow widths in coord space
            // Want core ~0.005 wide, tail ~0.015, glow ~0.04
            float core   = exp(-perp * perp * 40000.0);
            float tail   = exp(-perp * perp * 4500.0) * trail * trailFade;
            float glow   = exp(-perp * perp * 600.0)  * trail * trailFade * 0.35;

            float meteor = (core * 1.6 + tail + glow * 0.8) * flash;

            // Gate to active window + above horizon + night
            meteor *= step(lifeT, 0.35) * lu_min1(VdotU * 2.5)
                    * invRainFactor * lu_pow2(invNoonFactor2) * (1.0 - 0.85 * sunVisibility);

            // Per-meteor color variation: white-blue, gold, green
            float colorRand = lu_GetStarNoise(vec2(cycle + 2.1, mSeed));
            vec3 mColorA = vec3(0.95, 1.00, 1.10); // cool white-blue
            vec3 mColorB = vec3(1.20, 0.95, 0.55); // warm gold
            vec3 mColorC = vec3(0.65, 1.10, 0.85); // greenish (rare)
            vec3 mColor;
            if (colorRand < 0.6) mColor = mColorA;
            else if (colorRand < 0.92) mColor = mColorB;
            else mColor = mColorC;

            result += meteor * mColor * 25.0;
        }
    #endif

    // Final safety: clamp away any NaN/Inf that may have leaked from
    // exp() / asin() in advanced sky paths so the sky never goes black.
    result = max(result, vec3(0.0));
    if (any(isnan(result)) || any(isinf(result))) result = vec3(0.0);
    return result;
}

//=======================================================================================
// GIANT PLANET MODULE (advanced night sky)
//=======================================================================================

float lu_planetNoise(vec2 p) {
    vec2 i = floor(p);
    vec2 f = fract(p);
    f = f * f * (3.0 - 2.0 * f);
    float a = lu_GetStarNoise(i);
    float b = lu_GetStarNoise(i + vec2(1.0, 0.0));
    float c = lu_GetStarNoise(i + vec2(0.0, 1.0));
    float d = lu_GetStarNoise(i + vec2(1.0, 1.0));
    return mix(mix(a, b, f.x), mix(c, d, f.x), f.y);
}

float lu_planetFbm(vec2 p) {
    float v = 0.0;
    float a = 0.5;
    for (int i = 0; i < 4; i++) {
        v += a * lu_planetNoise(p);
        p *= 2.07;
        a *= 0.5;
    }
    return v;
}

// Render a single planet at given world direction.
// style: 1=gas giant, 2=rocky moon, 3=ringed, 4=ice/blue, 5=red mars-like
vec3 lu_RenderOnePlanet(vec3 wDir, vec3 planetDir, float radius, int style, vec3 tintMult) {
    float cosA = dot(wDir, planetDir);
    if (cosA < 0.85) return vec3(0.0);

    vec3 up = abs(planetDir.y) < 0.95 ? vec3(0.0, 1.0, 0.0) : vec3(1.0, 0.0, 0.0);
    vec3 tangent = normalize(cross(up, planetDir));
    vec3 bitangent = cross(planetDir, tangent);
    vec3 offset = wDir - planetDir * cosA;
    vec2 d = vec2(dot(offset, tangent), dot(offset, bitangent));

    float r = length(d);
    if (r > radius * 2.2) return vec3(0.0);

    float edgeAA = radius * 0.04;
    float diskMask = lu_clamp01((radius - r) / edgeAA);

    float rim = exp(-(r - radius) * (r - radius) / (radius * radius * 0.06));
    rim *= lu_clamp01((r - radius * 0.95) / (radius * 0.6));

    vec2 nUV = d / max(radius, 1e-4);
    vec2 nUVc = nUV;
    float nuvLen2 = dot(nUVc, nUVc);
    if (nuvLen2 > 1.0) nUVc /= sqrt(nuvLen2);
    float nz = sqrt(max(1.0 - dot(nUVc, nUVc), 0.0));
    vec3 sphereN = vec3(nUVc, nz);

    vec3 lightDir = normalize(vec3(-0.55, 0.35, 0.75));
    float NdotL = lu_clamp01(dot(sphereN, lightDir));
    float wrapped = NdotL * 0.5 + 0.5;
    float terminator = pow(wrapped, 1.4);

    vec2 surfUV = vec2(
        atan(sphereN.x, max(sphereN.z, 1e-4)) * 0.5,
        asin(clamp(sphereN.y * 0.999, -0.999, 0.999))
    );

    vec3 surface;
    if (style == 1) {
        float bands = sin(surfUV.y * 14.0 + lu_planetFbm(surfUV * 4.0) * 2.5);
        float swirl = lu_planetFbm(surfUV * 6.0 + vec2(syncedTime * 0.005, 0.0));
        bands = bands * 0.5 + 0.5;
        vec3 cBand1 = vec3(0.85, 0.62, 0.40);
        vec3 cBand2 = vec3(0.55, 0.32, 0.20);
        vec3 cBand3 = vec3(0.95, 0.80, 0.55);
        surface = mix(cBand2, cBand1, bands);
        surface = mix(surface, cBand3, lu_clamp01(swirl * 1.2 - 0.3) * 0.6);
    } else if (style == 2) {
        float craters = lu_planetFbm(surfUV * 18.0);
        craters = pow(craters, 1.6);
        vec3 cRock1 = vec3(0.55, 0.45, 0.40);
        vec3 cRock2 = vec3(0.25, 0.18, 0.15);
        surface = mix(cRock2, cRock1, craters);
        float cracks = smoothstep(0.45, 0.5, lu_planetFbm(surfUV * 8.0));
        surface *= 1.0 - 0.6 * cracks;
    } else if (style == 3) {
        float bands = sin(surfUV.y * 11.0 + lu_planetFbm(surfUV * 3.0) * 1.8);
        bands = bands * 0.5 + 0.5;
        vec3 cBand1 = vec3(0.92, 0.82, 0.55);
        vec3 cBand2 = vec3(0.70, 0.55, 0.30);
        surface = mix(cBand2, cBand1, bands);
    } else if (style == 4) {
        // Ice / blue ocean world
        float continents = lu_planetFbm(surfUV * 5.0);
        vec3 cIce  = vec3(0.85, 0.92, 0.98);
        vec3 cSea  = vec3(0.15, 0.35, 0.60);
        surface = mix(cSea, cIce, smoothstep(0.45, 0.6, continents));
        float swirls = lu_planetFbm(surfUV * 10.0 + vec2(0.0, syncedTime * 0.003));
        surface += vec3(0.1, 0.15, 0.2) * (swirls - 0.5) * 0.4;
    } else {
        // Mars-like red world (style 5+)
        float terrain = lu_planetFbm(surfUV * 7.0);
        vec3 cRed1 = vec3(0.75, 0.32, 0.18);
        vec3 cRed2 = vec3(0.45, 0.18, 0.12);
        vec3 cPole = vec3(0.95, 0.88, 0.80);
        surface = mix(cRed2, cRed1, terrain);
        float poleMask = smoothstep(1.1, 1.35, abs(surfUV.y));
        surface = mix(surface, cPole, poleMask);
    }

    surface *= tintMult;

    vec3 nightAmbient = surface * 0.35 + vec3(0.06, 0.08, 0.14);
    vec3 lit = surface * (0.45 + 0.55 * terminator);
    vec3 disk = mix(nightAmbient, lit, terminator);
    disk *= 0.78 + 0.22 * nz;

    vec3 rimColor = vec3(0.30, 0.55, 0.95) * rim * (0.25 + 0.75 * NdotL);
    vec3 result = disk * diskMask + rimColor;

    // Saturn-style rings only for style 3
    if (style == 3) {
        vec2 ringUV = d;
        ringUV.y /= 0.32;
        float ringR = length(ringUV);
        float ringInner = radius * 1.25;
        float ringOuter = radius * 2.05;
        if (ringR > ringInner && ringR < ringOuter && r > radius * 0.95) {
            float t = (ringR - ringInner) / (ringOuter - ringInner);
            float ringBands = 0.4 + 0.6 * lu_planetFbm(vec2(t * 12.0, 0.0));
            float gap = smoothstep(0.40, 0.45, t) * (1.0 - smoothstep(0.50, 0.55, t));
            ringBands *= 1.0 - 0.85 * gap;
            float ringMask = smoothstep(0.0, 0.1, t) * (1.0 - smoothstep(0.85, 1.0, t));
            float ringLit = 0.4 + 0.6 * lu_clamp01(0.5 + 0.5 * lightDir.x);
            vec3 ringColor = vec3(0.85, 0.78, 0.60) * ringBands * ringMask * ringLit;
            ringColor *= 1.0 - diskMask;
            result += ringColor;
        }
    }

    return max(result, vec3(0.0));
}

vec3 lu_GetGiantPlanet(vec3 viewPos, float VdotU, float VdotS) {
    if (VdotU < 0.05) return vec3(0.0);

    vec3 wDir = normalize((gbufferModelViewInverse * vec4(viewPos, 0.0)).xyz);

    // Helper macro to build a world direction from azim/elev (degrees)
    #define LU_PD(azimDeg, elevDeg) normalize(vec3( \
        cos(radians(elevDeg)) * sin(radians(azimDeg)), \
        sin(radians(elevDeg)), \
        cos(radians(elevDeg)) * cos(radians(azimDeg))))

    float baseR = float(LU_PLANET_SIZE) * 0.012;
    vec3 result = vec3(0.0);

    // === Planet 1: Saturn-like (ringed) - main feature, large, SE high ===
    result += lu_RenderOnePlanet(wDir, LU_PD(135.0, 45.0), baseR * 1.20, 3, vec3(1.0));

    // === Planet 2: Jupiter-like (gas giant) - smaller, SW ===
    result += lu_RenderOnePlanet(wDir, LU_PD(225.0, 35.0), baseR * 0.85, 1, vec3(1.0));

    // === Planet 3: Rocky moon - small, north-east ===
    result += lu_RenderOnePlanet(wDir, LU_PD(45.0,  60.0), baseR * 0.55, 2, vec3(1.0));

    // === Planet 4: Ice/blue world - medium, NW ===
    result += lu_RenderOnePlanet(wDir, LU_PD(315.0, 30.0), baseR * 0.75, 4, vec3(1.0));

    // === Planet 5: Mars-like red - small, low east ===
    result += lu_RenderOnePlanet(wDir, LU_PD(90.0,  20.0), baseR * 0.65, 5, vec3(1.0));

    #undef LU_PD

    float skyVis = lu_clamp01(invRainFactor)
                 * lu_clamp01(lu_pow2(lu_pow2(invNoonFactor2)))
                 * lu_clamp01(1.0 - 0.85 * sunVisibility);
    result *= skyVis * (float(LU_PLANET_INTENSITY) * 0.01);

    return max(result, vec3(0.0));
}

//=======================================================================================
// END DIMENSION STARS MODULE
//=======================================================================================

/**
 * Generates End dimension stars with magical coloring
 * @param viewPos: View space position vector
 * @param VdotU: View direction dot up vector
 * @return: RGB End star color with magical enhancement
 */
vec3 lu_GetEnderStars(vec3 viewPos, float VdotU) {
    vec3 wpos = normalize((gbufferModelViewInverse * vec4(viewPos * 1000.0, 1.0)).xyz);

    vec3 starCoord = 0.65 * wpos / (abs(wpos.y) + length(wpos.xz));
    vec2 starCoord2 = starCoord.xz * 0.5;
    if (VdotU < 0.0) starCoord2 += 100.0;
    float starFactor = 1024.0;
    starCoord2 = floor(starCoord2 * starFactor) / starFactor;

    // Triple-layer End star generation
    float star = 1.0;
    star *= lu_GetStarNoise(starCoord2.xy);
    star *= lu_GetStarNoise(starCoord2.xy + 0.1);
    star *= lu_GetStarNoise(starCoord2.xy + 0.23);
    star = max(star - 0.6, 0.0);  // More visible End stars
    star *= star;

    // magical End star colors
    vec3 baseStarColor = endSkyColor * mix(
        vec3(3500.0, 2800.0, 4500.0),
        vec3(4200.0, 3500.0, 2200.0),
        lu_GetStarNoise(starCoord2.xy + 0.5)
    );
    vec3 enderStars = star * baseStarColor;

    // Apply directional visibility factors
    float VdotUM1 = abs(VdotU);
    float VdotUM2 = lu_pow2(1.0 - VdotUM1);
    enderStars *= VdotUM1 * VdotUM1 * (VdotUM2 + 0.015) + 0.015;

    return enderStars;
}

//=======================================================================================
// AURORA BOREALIS MODULE
//=======================================================================================

/**
 * Generates aurora borealis effects with configurable conditions
 * @param viewPos: View space position vector
 * @param VdotU: View direction dot up vector
 * @param dither: Temporal dithering value
 * @return: RGB aurora color contribution
 */
vec3 lu_GetAuroraBorealis(vec3 viewPos, float VdotU, float dither) {
    float visibility = lu_sqrt1(lu_clamp01(VdotU * 1.5 - 0.225)) - sunVisibility - rainFactor - maxBlindnessDarkness;
    visibility *= 1.0 - VdotU * 0.9;

    // Apply aurora visibility conditions
    #if LU_AURORA_CONDITION == 1 || LU_AURORA_CONDITION == 3
        visibility -= moonPhase;
    #endif
    #if LU_AURORA_CONDITION == 2 || LU_AURORA_CONDITION == 3
        visibility *= inSnowy;
    #endif
    #if LU_AURORA_CONDITION == 4
        visibility = max(visibility * inSnowy, visibility - moonPhase);
    #endif

    if (visibility > 0.0) {
        vec3 aurora = vec3(0.0);

        vec3 wpos = mat3(gbufferModelViewInverse) * viewPos;
             wpos.xz /= wpos.y;
        vec2 cameraPositionM = cameraPosition.xz * 0.0075;
             cameraPositionM.x += syncedTime * 0.04;

        // Quality-adaptive sample count
        #ifdef LU_DEFERRED1
            int sampleCount = 25;
            int sampleCountP = sampleCount + 5;
        #else
            int sampleCount = 10;
            int sampleCountP = sampleCount + 10;
        #endif

        float ditherM = dither + 5.0;
        float auroraAnimate = frameTimeCounter * 0.001;

        // Multi-layer aurora generation
        for (int i = 0; i < sampleCount; i++) {
            float current = lu_pow2((i + ditherM) / sampleCountP);

            vec2 planePos = wpos.xz * (0.8 + current) * 11.0 + cameraPositionM;

            #if LU_AURORA_STYLE == 1
                // Pixelated aurora style
                planePos = floor(planePos) * 0.0007;
                float noise = texture2D(noisetex, planePos).b;
                noise = lu_pow2(lu_pow2(lu_pow2(lu_pow2(1.0 - 2.0 * abs(noise - 0.5)))));
                noise *= lu_pow1_5(texture2D(noisetex, planePos * 100.0 + auroraAnimate).b);
            #else
                // Smooth aurora style
                planePos *= 0.0007;
                float noise = texture2D(noisetex, planePos).r;
                noise = lu_pow2(lu_pow2(lu_pow2(lu_pow2(1.0 - 2.0 * abs(noise - 0.5)))));
                noise *= texture2D(noisetex, planePos * 3.0 + auroraAnimate).b;
                noise *= texture2D(noisetex, planePos * 5.0 - auroraAnimate).b;
            #endif

            float currentM = 1.0 - current;
            aurora += noise * currentM * mix(
                vec3(7.0, 2.2, 12.0),
                vec3(6.0, 16.0, 12.0),
                lu_pow2(lu_pow2(currentM))
            );
        }

        #if LU_AURORA_STYLE == 1
            aurora *= 1.3;
        #else
            aurora *= 1.8;
        #endif

        #ifdef ATM_COLOR_MULTS
            aurora *= sqrtAtmColorMult;
        #endif

        return aurora * visibility / sampleCount;
    }

    return vec3(0.0);
}

//=======================================================================================
// NIGHT NEBULA MODULE
//=======================================================================================

#ifndef LU_HQ_NIGHT_NEBULA
    const int OCTAVE = 5;
#else
    const int OCTAVE = 8;
#endif
const float timescale = 5.0;
const float zoomScale = 3.5;
const vec4 CLOUD1_COL = vec4(0.41, 0.64, 0.97, 0.4);
const vec4 CLOUD2_COL = vec4(0.81, 0.55, 0.21, 0.2);
const vec4 CLOUD3_COL = vec4(0.51, 0.81, 0.98, 1.0);

float lu_sinM(float x) {
    return sin(mod(x, 2.0 * pi));
}

float lu_cosM(float x) {
    return cos(mod(x, 2.0 * pi));
}

float lu_rand(vec2 inCoord){
    return fract(lu_sinM(dot(inCoord, vec2(23.53, 44.0))) * 42350.45);
}

float lu_perlin(vec2 inCoord){
    vec2 i = floor(inCoord);
    vec2 j = fract(inCoord);
    vec2 coord = smoothstep(0.0, 1.0, j);

    float a = lu_rand(i);
    float b = lu_rand(i + vec2(1.0, 0.0));
    float c = lu_rand(i + vec2(0.0, 1.0));
    float d = lu_rand(i + vec2(1.0, 1.0));

    return mix(mix(a, b, coord.x), mix(c, d, coord.x), coord.y);
}

float lu_fbmCloud(vec2 inCoord, float minimum){
    float value = 0.0;
    float scale = 0.5;

    for (int i = 0; i < OCTAVE; i++){
        value += lu_perlin(inCoord) * scale;
        inCoord *= 2.0;
        scale *= 0.5;
    }

    return smoothstep(0.0, 1.0, (smoothstep(minimum, 1.0, value) - minimum) / (1.0 - minimum));
}

float lu_fbmCloud2(vec2 inCoord, float minimum){
    float value = 0.0;
    float scale = 0.5;

    for (int i = 0; i < OCTAVE; i++){
        value += lu_perlin(inCoord) * scale;
        inCoord *= 2.0;
        scale *= 0.5;
    }

    return (smoothstep(minimum, 1.0, value) - minimum) / (1.0 - minimum);
}

/**
 * Generates night nebula effects with fractal noise
 * @param viewPos: View space position vector
 * @param VdotU: View direction dot up vector
 * @param VdotS: View direction dot sun vector
 * @return: RGB nebula color contribution
 */
vec3 lu_GetNightNebula(vec3 viewPos, float VdotU, float VdotS) {
    float nebulaFactor = lu_pow2(lu_max0(VdotU) * lu_min1(nightFactor * 2.0)) * invRainFactor - maxBlindnessDarkness;
    if (nebulaFactor < 0.001) return vec3(0.0);

    vec2 UV = lu_GetStarCoord(viewPos, 0.75);
    float TIME = syncedTime * 0.003 + 15.0;

    float timescaled = TIME * timescale;
    vec2 zoomUV2 = vec2(
        zoomScale * UV.x + 0.03  * timescaled * lu_sinM(0.07 * timescaled),
        zoomScale * UV.y + 0.03  * timescaled * lu_cosM(0.06 * timescaled)
    );
    vec2 zoomUV3 = vec2(
        zoomScale * UV.x + 0.027 * timescaled * lu_sinM(0.07 * timescaled),
        zoomScale * UV.y + 0.025 * timescaled * lu_cosM(0.06 * timescaled)
    );
    vec2 zoomUV4 = vec2(
        zoomScale * UV.x + 0.021 * timescaled * lu_sinM(0.07 * timescaled),
        zoomScale * UV.y + 0.021 * timescaled * lu_cosM(0.07 * timescaled)
    );

    float tide = 0.05 * lu_sinM(TIME);
    float tide2 = 0.06 * lu_cosM(0.3 * TIME);

    // Multi-layer nebula texture generation
    vec4 nebulaTexture = vec4(vec3(0.0), 0.5 + 0.2 * lu_sinM(0.23 * TIME + UV.x - UV.y));
    nebulaTexture += lu_fbmCloud2(zoomUV3, 0.24 + tide) * CLOUD1_COL;
    nebulaTexture += lu_fbmCloud(zoomUV2 * 0.9, 0.33 - tide) * CLOUD2_COL;
    nebulaTexture = mix(nebulaTexture, CLOUD3_COL, lu_fbmCloud(vec2(0.9 * zoomUV4.x, 0.9 * zoomUV4.y), 0.25 + tide2));

    nebulaFactor *= 1.0 - lu_pow2(lu_pow2(lu_pow2(abs(VdotS))));
    nebulaTexture.a *= lu_min1(lu_pow2(lu_pow2(nebulaTexture.a))) * nebulaFactor;

    // Add star integration
    float starFactor = 1024.0;
    vec2 starCoord = floor(UV * 0.25 * starFactor) / starFactor;
    nebulaTexture.rgb *= 1.5 + 10.0 * lu_pow2(lu_max0(lu_GetStarNoise(starCoord) * lu_GetStarNoise(starCoord + 0.1) - 0.6));

    #if LU_NIGHT_NEBULA_I != 100
        #define LU_NIGHT_NEBULA_IM LU_NIGHT_NEBULA_I * 0.01
        nebulaTexture.a *= LU_NIGHT_NEBULA_IM;
    #endif

    #ifdef ATM_COLOR_MULTS
        nebulaTexture.rgb *= sqrtAtmColorMult;
    #endif

    return max(nebulaTexture.rgb * nebulaTexture.a, vec3(0.0));
}

//=======================================================================================
// LEGACY COMPATIBILITY
//=======================================================================================

// Maintain backward compatibility with existing shader code
#define LU_GetVolumetricFogRendering GetVolumetricFog
#define LU_GetVolumetricLightShafts GetVolumetricLight
#define LU_GetSky lu_GetSkyHorizon
#define LU_GetLowQualitySky lu_GetOptimizedSkyHorizon
#define LU_GetNightStars GetStars
#define LU_GetCelestialEffects GetAuroraBorealis
#define LU_GetWeatherEffects GetRainbow
#define LU_GetEnvironmentalFog DrawEnderBeams

#endif // LU_INCLUDE_ATMOSPHERIC_VOLUMETRIC_SYSTEM
