/////////////////////////////////////
// Unbound Visual + PT v7.3 by EminGT //
/////////////////////////////////////

//Common//
#include "/lib/common.glsl"

#if LUT > 0
    #include "/lib/colors/lutColorGrading.glsl"
#endif

#include "/lib/colors/selectiveColorGrade.glsl"

//////////Fragment Shader//////////Fragment Shader//////////Fragment Shader//////////
#ifdef FRAGMENT_SHADER

noperspective in vec2 texCoord;

#if defined BLOOM_FOG || LENSFLARE_MODE > 0 && defined OVERWORLD || AUTO_EXPOSURE == 1 && defined OVERWORLD
    flat in vec3 upVec, sunVec;
#endif

#if SCENE_AUTO_EXPOSURE == 1
    #extension GL_ARB_shader_image_load_store : enable

    uniform sampler2D exposure_sampler;
    writeonly uniform image2D exposure_img;
#endif

//Pipeline Constants//

//Common Variables//
float pw = 1.0 / viewWidth;
float ph = 1.0 / viewHeight;

vec2 view = vec2(viewWidth, viewHeight);

#if defined BLOOM_FOG || LENSFLARE_MODE > 0 && defined OVERWORLD
    float SdotU = dot(sunVec, upVec);
    float sunFactor = SdotU < 0.0 ? clamp(SdotU + 0.375, 0.0, 0.75) / 0.75 : clamp(SdotU + 0.03125, 0.0, 0.0625) / 0.0625;
#endif

#if AUTO_EXPOSURE == 1 && defined OVERWORLD
    // How directly the camera is looking at the sun (1.0 = dead center, -1.0 = sun behind camera).
    // sunVec is already in view space, so the camera's forward direction is simply (0,0,-1).
    float sunGazeDot = -sunVec.z;
    // Sun has to be above the horizon for glare to happen (fades out around sunrise/sunset/night).
    float sunAboveHorizon = smoothstep(-0.15, 0.15, dot(sunVec, upVec));
    // Glare ramps up smoothly only within a cone of roughly 18 degrees around the sun.
    float sunGlare = smoothstep(0.75, 0.999, sunGazeDot) * sunAboveHorizon * eyeBrightnessM;
    float autoExposureMult = 1.0 - sunGlare * AUTO_EXPOSURE_STRENGTH;
#endif

#if EYE_ADAPTATION == 1
    // Real "pupil" eye adaptation: eyeBrightnessM is Minecraft's own light-level uniform,
    // already smoothed over ~5s by Iris (see shaders.properties). eyeBrightness is the
    // instant target. When they diverge (e.g. walking from a cave into daylight), the
    // instant value jumps immediately while the smoothed one lags - we use that gap to
    // temporarily over/under-expose the image, then let it settle, just like a real pupil.
    float eyeInstantBrightness = eyeBrightness.y / 240.0;
    float eyeAdaptDiff = eyeInstantBrightness - eyeBrightnessM; // >0: got brighter suddenly, <0: got darker suddenly
    float eyeAdaptationMult = clamp(1.0 + eyeAdaptDiff * EYE_ADAPTATION_STRENGTH, 1.0 - EYE_ADAPTATION_STRENGTH, 1.0 + EYE_ADAPTATION_STRENGTH * 1.5);
#endif

// Unified auto brightness/gamma/contrast signal: one combined value that drives every
// automatic adaptation below, so brightness, contrast and shadow gamma all react together
// instead of as separate disconnected effects - like a real eye adjusting to a scene.
#if AUTO_EXPOSURE == 1 && defined OVERWORLD
    #if EYE_ADAPTATION == 1
        float autoAdaptMult = autoExposureMult * eyeAdaptationMult;
    #else
        float autoAdaptMult = autoExposureMult;
    #endif
#else
    #if EYE_ADAPTATION == 1
        float autoAdaptMult = eyeAdaptationMult;
    #else
        float autoAdaptMult = 1.0;
    #endif
#endif

//Common Functions//
void LinearToRGB(inout vec3 color) {
    const vec3 k = vec3(0.055);
    color = mix((vec3(1.0) + k) * pow(color, vec3(1.0 / 2.4)) - k, 12.92 * color, lessThan(color, vec3(0.0031308)));
}

vec3 ACESFilmicTonemap(vec3 x) {
    // Narkowicz 2015 fitted approximation of the ACES Reference Rendering Transform.
    // Industry-standard filmic curve (used in Unreal/Unity/many film pipelines).
    const float a = 2.51, b = 0.03, c = 2.43, d = 0.59, e = 0.14;
    return clamp((x * (a * x + b)) / (x * (c * x + d) + e), 0.0, 1.0);
}

// Classic Hable/"Uncharted2" filmic curve (Hable, GDC 2010 - a widely published,
// public-domain-style tonemapping formula, independent of any specific shaderpack).
// Unlike the single-knob ACES fit above, its raw/mid/toe/shoulder shape exposes
// separate highlight-rolloff and shadow-lift controls, which is the "look" people
// usually mean by a classic filmic curve. autoContrast reuses the same auto-adapted
// contrast signal as the rest of DoCompTonemap so all three curve options react to
// eye adaptation consistently.
vec3 FilmicHighlightShadowTonemap(vec3 x, float autoContrast) {
    float A = mix(0.15, 0.22, autoContrast * 0.5);       // shoulder strength (tied to auto contrast)
    float B = 0.50;                                       // linear strength
    float C = TM_FILMIC_SHADOWS;                          // toe strength - shadow lift/crush
    float D = 0.20;                                       // toe numerator offset
    float E = TM_FILMIC_HIGHLIGHTS * 0.10;                 // shoulder numerator offset - highlight rolloff
    float F = 0.30;                                        // shoulder denominator offset

    vec3 curved  = ((x * (A * x + C * B) + D * E) / (x * (A * x + B) + D * F)) - E / F;
    // Re-normalize against the curve's response to a fixed reference white so exposure
    // stays consistent as TM_FILMIC_HIGHLIGHTS/SHADOWS are tweaked.
    const float whiteRef = 11.2;
    float whiteScale = 1.0 / (((whiteRef * (A * whiteRef + C * B) + D * E) / (whiteRef * (A * whiteRef + B) + D * F)) - E / F);

    return clamp(curved * whiteScale, 0.0, 1.0);
}

void DoCompTonemap(inout vec3 color, float autoAdaptMult) {
    color = TM_EXPOSURE * color;

    float initialLuminance = GetLuminance(color);

    // Auto brightness + auto contrast + auto gamma, unified: when the eye dims the image
    // (autoAdaptMult < 1, e.g. looking straight at the sun), contrast is nudged up so the
    // dimmed picture still looks punchy and focused instead of flat/washed out. When the eye
    // boosts the image for a dark area (autoAdaptMult > 1), contrast is nudged down and shadow
    // detail is lifted more (see autoDarkLiftMix below), so dark corners stay readable and
    // natural instead of crushing to black - mimicking real-eye adaptation as one system.
    // ("auto jangan ngubah warna" fix): konstan, tidak digerakkan autoAdaptMult,
    // supaya exposure/gamma tidak menggeser cor/colour balance saat adaptasi mata.
    float autoContrast = TM_CONTRAST;

    #if TONEMAP_STYLE == 1
        // --- ACES Filmic ---
        vec3 colorOut = pow(color, vec3(autoContrast)); // TM_CONTRAST still acts as a contrast/gamma control pre-curve
        colorOut = ACESFilmicTonemap(colorOut);
        LinearToRGB(colorOut);
    #elif TONEMAP_STYLE == 2
        // --- Filmic Highlights/Shadows (Hable/Uncharted2-style) ---
        vec3 colorOut = FilmicHighlightShadowTonemap(color, autoContrast);
        LinearToRGB(colorOut);
    #else
        // Lottes tonemap modified for Unbound Visual + PT v7.3
        // Lottes 2016, "Advanced Techniques and Optimization of HDR Color Pipelines"
        // http://32ipi028l5q82yhj72224m8j.wpengine.netdna-cdn.com/wp-content/uploads/2016/03/GdcVdrLottes.pdf
        float colorMax = max(color.r, max(color.g, color.b));

        vec3 a      = vec3(autoContrast); // General Contrast (auto-adjusted)
        vec3 d      = vec3(1.0); // Roll-off control
        vec3 hdrMax = vec3(8.0); // Maximum input brightness
        vec3 midIn  = vec3(0.25); // Input middle gray
        vec3 midOut = vec3(0.25); // Output middle gray

        vec3 a_d = a * d;
        vec3 hdrMaxA = pow(hdrMax, a);
        vec3 hdrMaxAD = pow(hdrMax, a_d);
        vec3 midInA = pow(midIn, a);
        vec3 midInAD = pow(midIn, a_d);
        vec3 HM1 = hdrMaxA * midOut;
        vec3 HM2 = hdrMaxAD - midInAD;

        vec3 b = (-midInA + HM1) / (HM2 * midOut);
        vec3 c = (hdrMaxAD * midInA - HM1 * midInAD) / (HM2 * midOut);

        vec3 colorOut = pow(color, a) / (pow(color, a_d) * b + c);

        LinearToRGB(colorOut);
    #endif

    // Remove tonemapping from darker colors for better readability
    // ("shadow terlalu gelap" fix): wider start + higher mix so more of the
    // shadow range gets the readable/lifted gamma curve instead of the raw
    // ACES toe crushing it to near-black.
    const float darkLiftStart = 0.20;
    const float darkLiftMix = 0.70;
    // Auto gamma: lift shadow detail further when the eye is compensating for a dark area
    // (autoAdaptMult > 1), less when it's dimming for a bright area (autoAdaptMult < 1).
    // ("auto jangan ngubah warna" fix): dibuat konstan agar tidak menggeser warna.
    float autoDarkLiftMix = clamp(darkLiftMix, 0.0, 1.0);
    float darkLift = smoothstep(darkLiftStart, 0.0, initialLuminance);
    vec3 smoothColor = pow(color, vec3(1.0 / 2.2));
    colorOut = mix(colorOut, smoothColor, darkLift * autoDarkLiftMix * max0(0.55 - abs(1.05 - autoContrast)) / 0.55);
    
    // Path to White
    const float wpInputCurveStart = 0.0;
    float wpInputCurveMax = TM_HDR_HEADROOM; // Increase this value to reduce the effect of white path (more HDR highlight headroom before clipping to white)
    float modifiedLuminance = pow(initialLuminance / wpInputCurveMax, 2.0 - TM_WHITE_PATH) * wpInputCurveMax;
    float whitePath = smoothstep(wpInputCurveStart, wpInputCurveMax, modifiedLuminance);
    colorOut = mix(colorOut, vec3(1.0), whitePath);

    // Desaturate dark colors
    const float dpInputCurveStart = 0.1;
    const float dpInputCurveMax = 0.0;
    float desaturatePath = smoothstep(dpInputCurveStart, dpInputCurveMax, initialLuminance);
    colorOut = mix(colorOut, vec3(GetLuminance(colorOut)), desaturatePath * TM_DARK_DESATURATION);

    colorOut = clamp01(colorOut);

    // Manual display gamma calibration (monitor-brightness knob), applied
    // last in display-referred space, after the tone curve's own
    // linear->display encode. Kept as a separate pass so it never
    // interacts with/duplicates the LinearToRGB encode above - it only
    // nudges the ALREADY-encoded image brighter/darker, exactly like a
    // TV/monitor gamma control, not a second color-science pass.
    colorOut = pow(colorOut, vec3(1.0 / MANUAL_GAMMA));

    color = colorOut;
}

void DoBSLColorSaturation(inout vec3 color) {
    float saturationFactor = T_SATURATION;

    float grayVibrance = (color.r + color.g + color.b) / 3.0;
    float graySaturation = grayVibrance;
    if (saturationFactor < 1.00) graySaturation = dot(color, vec3(0.299, 0.587, 0.114));

    float mn = min(color.r, min(color.g, color.b));
    float mx = max(color.r, max(color.g, color.b));
    float sat = (1.0 - (mx - mn)) * (1.0 - mx) * grayVibrance * 5.0;
    vec3 lightness = vec3((mn + mx) * 0.5);

    color = mix(color, mix(color, lightness, 1.0 - T_VIBRANCE), sat);
    color = mix(color, lightness, (1.0 - lightness) * (2.0 - T_VIBRANCE) / 2.0 * abs(T_VIBRANCE - 1.0));

    //Highlight saturation rolloff - tames the neon/overshoot look the plain saturation boost
    //used to cause on bright pixels, by fading saturationFactor back towards 1.0 as brightness climbs.
    float rolloffStrength = HIGHLIGHT_SATURATION_ROLLOFF * 0.01;
    float highlightMask = clamp(mx * 2.0 - 1.0, 0.0, 1.0); // ramps 0 -> 1 as max channel goes 0.5 -> 1.0
    highlightMask *= highlightMask;
    float rolloffSaturation = mix(saturationFactor, 1.0, highlightMask * rolloffStrength);

    color = color * rolloffSaturation - graySaturation * (rolloffSaturation - 1.0);

    //Safety clamp - the previous unclamped output could overshoot into clipped, neon-looking highlights
    color = clamp01(color);
}

//=== Custom Look: Warm Midtones + Dark Blue Shadows ===
// Tweak these 4 values to taste:
// ("terlalu coklat/kontras/gelap" fix): these 4 were stacked on top of
// TM_CONTRAST + the tonemap curve itself, applied to literally every pixel
// every frame - grass, dirt, wood, skin, all got the same +warm push, and
// every shadow got an extra multiply-darken ON TOP of the tonemapper's own
// ("cahaya tegas, vivid, warm, tajam - mirip shader Voyager" request):
// raised back up from the 0.03 "nearly off" value above, but only to 0.35
// (not back to the original strong value that was dialed back for reading
// "too brown"/too much shadow contrast) - a clearly visible warm push
// without swinging back to that earlier problem.
// ("sore oren/merah banget" fix): lowered from 0.35 so the warm midtone
            // push no longer over-amps the already-warm sunset cast.
// ("cahaya matahari vivid/tidak warm oranye" fix): diturunkan ke 0.05 supaya
// warna midtone tetap netral/putih bersih, bukan ada bias oranye di atasnya.
const float WARM_MIDTONE_STRENGTH = 0.05; // 0.0 = off, 1.0 = strong warm push in midtones
const float SHADOW_TINT_RANGE     = 0.12; // how far up the luminance range the shadow tint reaches
const float SHADOW_BLACK_STRENGTH = 0.02; // "layer 1": crush/density added into shadows (pure black)
const float SHADOW_BLUE_STRENGTH  = 0.03; // "layer 2": dark blue tint added on top of the black layer

void DoWarmMidtoneBlueShadowGrade(inout vec3 color) {
    float lum = GetLuminance(color);

    //--- 1) Warm Midtones ---
    // Only pushes warmth in the mid-luminance band, leaves deep shadows and bright
    // highlights untouched so the effect reads as "midtones", not a global white-balance shift.
    float midMask = smoothstep(0.05, 0.35, lum) * (1.0 - smoothstep(0.55, 0.85, lum));
    const vec3 warmMul = vec3(1.000, 1.000, 1.000); // neutralized - no warm push (was 1.030/1.012/0.970)
    color = mix(color, color * warmMul, midMask * WARM_MIDTONE_STRENGTH);

    //--- 2) Dark Blue Shadows: 2-layer overlay (Black then Dark Blue) ---
    float shadowMask = 1.0 - smoothstep(0.0, SHADOW_TINT_RANGE, lum);

    // Layer 1 - Black (multiply-style): adds weight/density to the shadows first,
    // like laying down a black solid under a "Multiply" blend mode.
    vec3 blackLayer = color * color;
    color = mix(color, blackLayer, shadowMask * SHADOW_BLACK_STRENGTH);

    // Layer 2 - Dark Blue (linear-add/"Screen"-style): a low-value, blue-leaning color
    // added on top. Kept very dark and low-saturation on purpose so shadows read as
    // "dark blue", not "blue" - it should never fight for attention against the black layer.
    const vec3 darkBlueLayer = vec3(0.018, 0.025, 0.045); // B > G > R, cool blue without brown
    color += darkBlueLayer * shadowMask * SHADOW_BLUE_STRENGTH;

    color = clamp01(color);
}

#if BLOOM_ENABLED == 1
    vec2 rescale = max(vec2(viewWidth, viewHeight) / vec2(1920.0, 1080.0), vec2(1.0));
    vec3 GetBloomTile(float lod, vec2 coord, vec2 offset) {
        float scale = exp2(lod);
        vec2 bloomCoord = coord / scale + offset;
        bloomCoord = clamp(bloomCoord, offset, 1.0 / scale + offset);

        vec3 bloom = texture2D(colortex3, bloomCoord / rescale).rgb;
        bloom *= bloom;
        bloom *= bloom;
        return bloom * 128.0;
    }

    void DoBloom(inout vec3 color, vec2 coord, float dither, float lViewPos) {
        vec3 blur1 = GetBloomTile(2.0, coord, vec2(0.0      , 0.0   ));
        vec3 blur2 = GetBloomTile(3.0, coord, vec2(0.0      , 0.26  ));
        vec3 blur3 = GetBloomTile(4.0, coord, vec2(0.135    , 0.26  ));

        vec3 blur = (blur1 * 3.0 + blur2 * 2.0 + blur3 * 1.0) * 0.16;

        float bloomStrength = BLOOM_STRENGTH;

        #if defined BLOOM_FOG && defined NETHER && defined BORDER_FOG
            float farM = min(renderDistance, NETHER_VIEW_LIMIT); // consistency9023HFUE85JG
            float netherBloom = lViewPos / clamp(farM, 96.0, 256.0);
            netherBloom *= netherBloom;
            netherBloom *= netherBloom;
            netherBloom = 1.0 - exp(-8.0 * netherBloom);
            netherBloom *= 1.0 - maxBlindnessDarkness;
            bloomStrength = mix(bloomStrength * 0.7, bloomStrength * 1.8, netherBloom);
        #endif

        color = mix(color, blur, bloomStrength);
        //color += blur * bloomStrength * (ditherFactor.x + ditherFactor.y);
    }
#endif

//Includes//
#ifdef BLOOM_FOG
    #include "/lib/atmospherics/fog/bloomFog.glsl"
#endif

#if BLOOM_ENABLED == 1
    #include "/lib/util/dither.glsl"
#endif

#if LENSFLARE_MODE > 0 && defined OVERWORLD
    #include "/lib/misc/lensFlare.glsl"
#endif

//Program//
void main() {
    vec3 color = texture2D(colortex0, texCoord).rgb;

    #if SCENE_AUTO_EXPOSURE == 1
        // Scene auto exposure: measures how bright the actual rendered frame
        // is (not just the player's light level), and gently nudges exposure
        // to compensate - looking at bright sky/windows dims down, looking
        // into a dark corner/cave brightens up.
        float sceneExposureMult = 1.0;
        {
            // The main HDR color buffer already has mipmaps generated for
            // other effects (DoF, reflection roughness) elsewhere in this
            // pack, so sampling a very downsampled mip level gives an
            // approximate box-filtered average of the whole visible frame
            // in a single texture fetch - no separate reduction pass needed.
            const float SCENE_AUTO_EXPOSURE_MIP = 8.0;
            const float SCENE_AUTO_EXPOSURE_TARGET = 0.30; // approximate "middle grey" reference in this engine's linear scale

            vec3 avgColor = textureLod(colortex0, vec2(0.5), SCENE_AUTO_EXPOSURE_MIP).rgb;
            float avgLuma = GetLuminance(avgColor);

            float rawMult = SCENE_AUTO_EXPOSURE_TARGET / max(avgLuma, 0.02);
            rawMult = clamp(rawMult, SCENE_AUTO_EXPOSURE_MIN, SCENE_AUTO_EXPOSURE_MAX);

            // "Not aggressive": only apply a fraction of the full
            // correction - 0 strength would leave the image untouched,
            // 1.0 would fully normalize to the target level every frame.
            float desiredMult = mix(1.0, rawMult, SCENE_AUTO_EXPOSURE_STRENGTH);

            // Ease toward the desired value instead of snapping to it, using
            // last frame's result from the persistent 1x1 buffer, so panning
            // across a bright window or dark doorway fades in/out smoothly.
            float prevMult = texelFetch(exposure_sampler, ivec2(0, 0), 0).r;
            if (prevMult < 0.01) prevMult = 1.0; // first-frame fallback, buffer starts at 0
            sceneExposureMult = mix(prevMult, desiredMult, SCENE_AUTO_EXPOSURE_SPEED);

            // Only a single fragment invocation writes the persistent value
            // back, to avoid every pixel racing to write the same texel.
            if (gl_FragCoord.x < 1.0 && gl_FragCoord.y < 1.0) {
                imageStore(exposure_img, ivec2(0, 0), vec4(sceneExposureMult, 0.0, 0.0, 0.0));
            }
        }
        autoAdaptMult *= sceneExposureMult;
    #endif
    
    #if defined BLOOM_FOG || LENSFLARE_MODE > 0 && defined OVERWORLD
        float z0 = texture2D(depthtex0, texCoord).r;
        vec4 screenPos = vec4(texCoord, z0, 1.0);
        vec4 viewPos = gbufferProjectionInverse * (screenPos * 2.0 - 1.0);
        viewPos /= viewPos.w;
        float lViewPos = length(viewPos.xyz);

        #if defined DISTANT_HORIZONS || defined VOXY
            #ifdef DISTANT_HORIZONS
                float z0lod = texelFetch(dhDepthTex, texelCoord, 0).r;
                vec4 screenPosLod = vec4(texCoord, z0lod, 1.0);
                vec4 viewPosLod = dhProjectionInverse * (screenPosLod * 2.0 - 1.0);
            #elif defined VOXY
                float z0lod = texelFetch(vxDepthTexTrans, texelCoord, 0).r;
                vec4 screenPosLod = vec4(texCoord, z0lod, 1.0);
                vec4 viewPosLod = vxProjInv * (screenPosLod * 2.0 - 1.0);
            #endif
            viewPosLod /= viewPosLod.w;
            lViewPos = min(lViewPos, length(viewPosLod.xyz));
        #endif
    #else
        float lViewPos = 0.0;
    #endif

    float dither = texture2DLod(noisetex, texCoord * view / 128.0, 0.0).b;
    #ifdef TAA
        dither = fract(dither + goldenRatio * mod(float(frameCounter), 3600.0));
    #endif

    #ifdef BLOOM_FOG
        color /= GetBloomFog(lViewPos);
    #endif

    #if BLOOM_ENABLED == 1
        DoBloom(color, texCoord, dither, lViewPos);
    #endif

    #ifdef COLORGRADING
        color =
            pow(color.r, GR_RC) * vec3(GR_RR, GR_RG, GR_RB) +
            pow(color.g, GR_GC) * vec3(GR_GR, GR_GG, GR_GB) +
            pow(color.b, GR_BC) * vec3(GR_BR, GR_BG, GR_BB);
        color *= 0.01;
    #endif

    color *= autoAdaptMult;

    #if VIGNETTE == 1
        color *= SCG_Vignette(texCoord, frameTimeCounter, blindness, darknessFactor);
    #endif

    DoCompTonemap(color, autoAdaptMult);

    #if defined GREEN_SCREEN_LIME || SELECT_OUTLINE == 4
        int materialMaskInt = int(texelFetch(colortex6, texelCoord, 0).g * 255.1);
    #endif

    #ifdef GREEN_SCREEN_LIME
        if (materialMaskInt == 240) { // Green Screen Lime Blocks
            color = vec3(0.0, 1.0, 0.0);
        }
    #endif

    #if SELECT_OUTLINE == 4
        if (materialMaskInt == 252) { // Versatile Selection Outline
            float colorMF = 1.0 - dot(color, vec3(0.25, 0.45, 0.1));
            colorMF = smoothstep1(smoothstep1(smoothstep1(smoothstep1(smoothstep1(colorMF)))));
            color = mix(color, 3.0 * (color + 0.2) * vec3(colorMF * SELECT_OUTLINE_I), 0.3);
        }
    #endif

    #if LENSFLARE_MODE > 0 && defined OVERWORLD
        DoLensFlare(color, viewPos.xyz, dither);
    #endif

    DoBSLColorSaturation(color);

    DoWarmMidtoneBlueShadowGrade(color);

    #if SELECTIVE_COLOR_GRADE == 1
        DoSelectiveColorGrade(color);
    #endif

    #if LUT > 0
        ApplyLUT(color);
    #endif

    /* DRAWBUFFERS:3 */
    gl_FragData[0] = vec4(color, 1.0);
}

#endif

//////////Vertex Shader//////////Vertex Shader//////////Vertex Shader//////////
#ifdef VERTEX_SHADER

noperspective out vec2 texCoord;

#if defined BLOOM_FOG || LENSFLARE_MODE > 0 && defined OVERWORLD || AUTO_EXPOSURE == 1 && defined OVERWORLD
    flat out vec3 upVec, sunVec;
#endif

//Attributes//

//Common Variables//

//Common Functions//

//Includes//

//Program//
void main() {
    gl_Position = ftransform();

    texCoord = (gl_TextureMatrix[0] * gl_MultiTexCoord0).xy;

    #if defined BLOOM_FOG || LENSFLARE_MODE > 0 && defined OVERWORLD || AUTO_EXPOSURE == 1 && defined OVERWORLD
        upVec = normalize(gbufferModelView[1].xyz);
        sunVec = GetSunVector();
    #endif
}

#endif
