#ifndef INCLUDE_LIGHT_AND_AMBIENT_COLORS
    #define INCLUDE_LIGHT_AND_AMBIENT_COLORS

    // --- Blackbody Radiation (Planckian locus) -> RGB ---
    // Tanner Helland / Neil Bartlett analytic approximation, valid ~1000K-12000K.
    // Used to derive sun/light color continuously from a color temperature instead
    // of a fixed, hand-picked RGB triplet.
    vec3 KelvinToRGB(float kelvin) {
        float temp = kelvin * 0.01;
        float r, g, b;

        r = temp <= 66.0 ? 255.0 : 329.698727446 * pow(temp - 60.0, -0.1332047592);

        g = temp <= 66.0
            ? 99.4708025861 * log(temp) - 161.1195681661
            : 288.1221695283 * pow(temp - 60.0, -0.0755148492);

        b = temp >= 66.0 ? 255.0 : (temp <= 19.0 ? 0.0 : 138.5177312231 * log(temp - 10.0) - 305.0447927307);

        return clamp(vec3(r, g, b) / 255.0, 0.0, 1.0);
    }

    // Maps sun elevation proxy (noonFactor: 0 = horizon, 1 = zenith) to a color temperature.
    // Non-linear ramp: real solar color temperature climbs fast just above the horizon
    // then plateaus near noon, so a simple linear mix under-sells the golden-hour warmth.
    float GetSunKelvin(float noonF) {
        float t = pow(clamp(noonF, 0.0, 1.0), 0.45);
        return mix(float(SUN_KELVIN_SUNSET), float(SUN_KELVIN_NOON), t);
    }

    // Rayleigh scattering removes blue light fastest, red slowest (~1/wavelength^4).
    // As the sun gets lower, direct light travels through more atmosphere ("airmass"),
    // so blue gets filtered out progressively more -> physically-motivated reddening,
    // continuous with sun angle instead of a fixed hardcoded sunset color.
    vec3 GetRayleighExtinctionTint(float noonF) {
        const vec3 rayleighK = vec3(0.25, 0.55, 1.0) * RAYLEIGH_MIE_STRENGTH; // R, G, B relative scattering rates
        float airmass    = 1.0 / clamp(noonF, 0.15, 1.0); // ~1/sin(elevation), grows near the horizon
        vec3 extinction  = exp(-airmass * rayleighK);
        vec3 refExtinction = exp(-1.0 * rayleighK); // reference at airmass = 1 (zenith/noon) so noon stays neutral
        vec3 tint = extinction / refExtinction;
        return tint / GetLuminance(tint); // normalize so this only shifts hue, never brightness
    }

    // Pulls a color toward its own luminance (neutral gray) by `amount`
    // (0 = untouched, 1 = fully neutral) while keeping the exact same
    // brightness - used to tone down the night/moonlight color's blue bias
    // without darkening or brightening the night.
    vec3 DesaturateTowardGray(vec3 c, float amount) {
        return mix(c, vec3(GetLuminance(c)), clamp(amount, 0.0, 1.0));
    }

    #if defined OVERWORLD
        #ifndef COMPOSITE1
            vec3 noonClearLightColor = vec3(0.65, 0.55, 0.375) * 2.05; //ground and cloud color
        #else
            vec3 noonClearLightColor = vec3(0.4, 0.75, 1.3); //light shaft color
        #endif
        // WHITE BALANCE FIX ("shadow biru, matahari oranye" mismatch):
        // noonClearAmbientColor used to be pure pow(skyColor, 0.75)*0.72 with
        // ZERO warm tilt at full noon - the sunset warm tilt below only
        // ramps in as the sun gets low. Meanwhile direct light is warmed at
        // EVERY time of day by both GetSunKelvin() (~5800K at noon) and the
        // LIGHT_NOON_R/G/B multiplier. Net result: at midday, direct-lit
        // surfaces read warm/orange while anything in open-sky shadow reads
        // straight sky-blue with barely any correction, since the later
        // uniform lightColorMult pass (mainLighting.glsl) scales both terms
        // by the same factor and can't fix a hue MISMATCH between them, only
        // brightness. A permanent-but-mild warm tilt (kept close to neutral
        // luminance: 1.12/1.00/0.90 averages ~1.007) keeps ambient believably
        // cool/blue under open sky (physically correct - shadows ARE cooler
        // than direct sun) while closing enough of the gap that it stops
        // reading as two different white balances in the same shot. The
        // existing sunset-only tilt further below still layers an extra,
        // stronger warm push on top as the sun gets low.
        vec3 noonClearAmbientColor = pow(skyColor, vec3(0.75)) * 0.72 * vec3(1.12, 1.00, 0.90);

        #ifndef COMPOSITE1
            vec3 sunsetClearLightColor = pow(vec3(0.48, 0.46, 0.38), vec3(1.0 + invNoonFactor * 0.3)) * 4.0; //ground and cloud color
        #else
            vec3 sunsetClearLightColor = pow(vec3(0.46, 0.44, 0.38), vec3(1.0 + invNoonFactor * 0.3)) * 5.5; //light shaft color
        #endif
        // ("ambient pagi/sore kelihatan biru gelap" fix): warm-up kan tilt AMBIENT ke
        // emas/merah ketika matahari rendah (invNoonFactor besar), supaya golden
        // hour tidak berubah jadi biru gelap.
        vec3 sunsetClearAmbientColor = noonClearAmbientColor * vec3(1.35, 0.92, 0.62) * 0.95;

        // ("moonlight biru total" fix) desaturate the night colors toward
        // neutral white/gray - see MOONLIGHT_DESATURATE in common.glsl.
        // Applied inline in the declaration itself (a bare re-assignment
        // statement isn't legal GLSL at this global/file scope - it broke
        // the shader compile) so every downstream user (direct light,
        // ambient, GI's solar/moon injection, etc.) sees the same
        // corrected color.
        #if !defined COMPOSITE1 && !defined DEFERRED1
            vec3 nightClearLightColor = DesaturateTowardGray(2.2 * vec3(0.06, 0.08, 0.18) * (0.3 + vsBrightness * 0.3), MOONLIGHT_DESATURATE); //ground color
        #elif defined DEFERRED1
            vec3 nightClearLightColor = DesaturateTowardGray(2.2 * vec3(0.05, 0.08, 0.18), MOONLIGHT_DESATURATE); //cloud color
        #else
            vec3 nightClearLightColor = DesaturateTowardGray(vec3(0.10, 0.14, 0.28), MOONLIGHT_DESATURATE); //light shaft color
        #endif
        vec3 nightClearAmbientColor = DesaturateTowardGray(2.2 * vec3(0.04, 0.07, 0.15) * (1.2 + vsBrightness * 0.5), MOONLIGHT_DESATURATE);

        #ifdef SPECIAL_BIOME_WEATHER
            vec3 drlcSnowM = inSnowy * vec3(-0.06, 0.0, 0.04);
            vec3 drlcDryM = inDry * vec3(0.01, -0.035, -0.06);
        #else
            vec3 drlcSnowM = vec3(0.0), drlcDryM = vec3(0.0);
        #endif
        #if RAIN_STYLE == 2
            vec3 drlcRainMP = vec3(-0.03, 0.0, 0.02);
            #ifdef SPECIAL_BIOME_WEATHER
                vec3 drlcRainM = inRainy * drlcRainMP;
            #else
                vec3 drlcRainM = drlcRainMP;
            #endif
        #else
            vec3 drlcRainM = vec3(0.0);
        #endif
        vec3 dayRainLightColor   = vec3(0.21, 0.16, 0.13) * 0.72 + noonFactor * vec3(0.0, 0.02, 0.06)
                                 + drlcRainM + drlcSnowM + drlcDryM;
        vec3 dayRainAmbientColor = vec3(0.2, 0.2, 0.25) * (1.55 + 0.5 * vsBrightness);

        // Same night desaturation applied to the rainy-night variant (inline
        // in the declaration - see note above), so the fix is consistent
        // whether or not it's raining.
        vec3 nightRainLightColor   = DesaturateTowardGray(vec3(0.03, 0.035, 0.05) * (0.5 + 0.5 * vsBrightness), MOONLIGHT_DESATURATE);
        vec3 nightRainAmbientColor = DesaturateTowardGray(vec3(0.16, 0.20, 0.3) * (0.75 + 0.6 * vsBrightness), MOONLIGHT_DESATURATE);

        #ifndef COMPOSITE1
            float noonFactorDM = noonFactor; //ground and cloud factor
        #else
            float noonFactorDM = noonFactor * noonFactor; //light shaft factor
        #endif
        vec3 dayLightColorEnvelope = mix(sunsetClearLightColor, noonClearLightColor, noonFactorDM); // original brightness envelope - kept identical
        float dayLightLum = GetLuminance(dayLightColorEnvelope);

        #ifndef COMPOSITE1
            // Ground/cloud direct light: drive hue from blackbody Kelvin + Rayleigh airmass extinction,
            // but keep the exact same luminance as the original hand-tuned envelope above.
            vec3 kelvinTintRaw = KelvinToRGB(GetSunKelvin(noonFactorDM));
            vec3 kelvinTint = kelvinTintRaw / GetLuminance(kelvinTintRaw);
            vec3 rayleighTint = GetRayleighExtinctionTint(noonFactorDM);
            vec3 dayLightColor = kelvinTint * rayleighTint * dayLightLum;
        #else
            // Light-shaft variant keeps its original distinct (intentionally cool/hazy) look untouched.
            vec3 dayLightColor = dayLightColorEnvelope;
        #endif
        vec3 dayAmbientColor = mix(sunsetClearAmbientColor, noonClearAmbientColor, noonFactorDM);

        vec3 clearLightColor   = mix(nightClearLightColor, dayLightColor, sunVisibility2);
        vec3 clearAmbientColor = mix(nightClearAmbientColor, dayAmbientColor, sunVisibility2);

        float rainShadowVisReduce = 0.0
            #ifdef SUN_MOON_DURING_RAIN
                #ifdef SPECIAL_BIOME_WEATHER
                    + 0.2 * inSnowy + 0.2 * inDry
                #elif RAIN_STYLE == 2
                    + 0.2
                #endif
            #else
                + 0.4
            #endif
        ;

        vec3 rainLightColor   = mix(nightRainLightColor, dayRainLightColor * (1.0 - rainShadowVisReduce), sunVisibility2) * 2.5;
        vec3 rainAmbientColor = mix(nightRainAmbientColor, dayRainAmbientColor * (1.0 + rainShadowVisReduce), sunVisibility2);

        vec3 lightColor   = mix(clearLightColor, rainLightColor, rainFactor);
        vec3 ambientColor = mix(clearAmbientColor, rainAmbientColor, rainFactor);
    #elif defined NETHER
        vec3 lightColor   = vec3(0.0);
        vec3 ambientColor = (netherColor + 0.5 * lavaLightColor) * (0.9 + 0.45 * vsBrightness);
    #elif defined END
        vec3 endLightColor = vec3(0.68, 0.51, 1.07);
        vec3 endOrangeCol = vec3(1.0, 0.3, 0.0);
        float endLightBalancer = 0.2 * vsBrightness;
        vec3 lightColor    = endLightColor * (0.35 - endLightBalancer);
        vec3 ambientColor  = endLightColor * (0.2 + endLightBalancer);
    #endif

#endif //INCLUDE_LIGHT_AND_AMBIENT_COLORS