#version 330 core
out vec4 FragColor;

struct PRS_RES {
	vec3 PosWrld;
	vec2 TexCoord;
	vec3 Normal;
	vec3 Color;
	vec4 PosLgtVP[2];
};

in PRS_RES vs_res;

#define MAX_LIGHTS 9

// uniform for screen dimensions (integer numbers)
uniform ivec2 halfScreenSizePix;

// uniform for time delta
uniform float timeDelta;

// uniforms for lighting
uniform vec3 viewPos;

// rendering type
uniform int renderingType;
uniform int objectType;

uniform float tex_scale;
uniform float height_scale;


// light source properties
struct Light {
    int  type;  // 0 ambient, 1 directional, 2 point, 3 spot, 4 point_atn, 5 spot_atn
    vec3 direction;
    vec3 position;

    vec3 ambient;
    vec3 diffuse;
    vec3 specular;
	
    vec3 atn;
    //float atn_cnst;
    //float atn_lin;
    //float atn_quad;
    
    vec4 cutOffFOV;
    float cutoffInner;
    float cutOffSt;
    float cutOff;
    float FOV;

    vec2 farClip;
    //float farClip;
    //float farClipSq;
};

struct Material {
    vec3 ambient;
    vec3 diffuse;
    vec3 specular;
    vec3 roughness;

    sampler2D diffTex;
    sampler2D ambtTex;
    sampler2D specTex;
    sampler2D normTex;
    sampler2D dispTex;
    sampler2D rghnTex;
};

uniform Light light[MAX_LIGHTS];
uniform int lightNmb;
uniform int lgt_pt_adj;
uniform int lgt_spt_adj;
uniform int lightIndex;
uniform bool isBlinnPhong;
uniform sampler2D shadowMap;
uniform samplerCube shadowMapCube[4];
uniform samplerCube shadowMapCubeColors;
uniform samplerCube shadowMapCubeColorsMove;
uniform sampler2DArray shadowMapPara;
uniform sampler2D brdfLUT;
uniform int ptShdwMethod;

uniform Material mtrl[2];

uniform int cubeIndex;
uniform samplerCube skyboxTex;  // Texture from the skybox
uniform samplerCube envMapCube;

uniform bool isTextureArray;
uniform bool isExposure;
uniform float exposure;
uniform float gamma;

void calc_ambient( out vec3 ambient_out, vec3 light, vec3 material ){
    ambient_out = light * material;
}

void calc_diffuse( out vec3 diffuse_out, vec3 normal, vec3 pixel_pos, vec3 light_dir, vec3 light, vec3 material ){
    float diff  = max( dot( normal, -light_dir ), 0.0 );
    diffuse_out = light * diff * material;
}

void calc_specular( out vec3 specular_out, vec3 normal, vec3 view_dir, vec3 light_dir, vec3 light, vec3 material, float roughness ){
    float shiny = float( 1 << int( (1.0 - roughness) * 8.0 ) );
    float viewedReflect;
    if( isBlinnPhong ){
        vec3 halfwayDir = normalize( view_dir - light_dir );  
        viewedReflect   = max( dot( normal, halfwayDir ), 0.0 );
    } else {
        vec3 reflectDir = reflect( light_dir, normal );
        viewedReflect   = max( dot( view_dir, reflectDir ), 0.0 );
    }
    float spec   = pow( viewedReflect, shiny );
    specular_out = light * spec * material;
}

void calc_tbn_matrix( out mat3 tbn, vec3 normal, vec3 pixel_pos, vec2 pixel_texcoord ){
    // Calculate derivatives
    vec3 pos_dx  = dFdx(pixel_pos);
    vec3 pos_dy  = dFdy(pixel_pos);
    vec2 texC_dx = dFdx(pixel_texcoord);
    vec2 texC_dy = dFdy(pixel_texcoord);

    // Calculate tangent and bitangent
    vec3 tangent   = normalize(texC_dy.y * pos_dx - texC_dx.y * pos_dy);
    vec3 bitangent = normalize(texC_dx.x * pos_dy - texC_dy.x * pos_dx);

    // Construct TBN matrix
    tbn = mat3( tangent, bitangent, normal ); // normal interpolated vertex normal (after normalization)
}

vec2 parallax_mapping( vec2 texCoords, sampler2D depthMap, vec3 viewDir, float heightScale ){
    // Version 1 that does nothing other than sample an offset based on the viewDir
    //float height = texture( depthMap, texCoords ).r;     
    //return texCoords - viewDir.xy * ( height * heightScale );
    
    // Version 2 that does multiple samples and find the nearest intersection
    // number of depth layers
    const float minLayers = 8;
    const float maxLayers = 16;
    //float numLayers = mix(maxLayers, minLayers, abs(dot(vec3(0.0, 0.0, 1.0), viewDir)));
    float numLayers = mix( minLayers, maxLayers, max( dot( vec3(0.0, 0.0, 1.0), viewDir ), 0.0 ) );
    // calculate the size of each layer
    float layerDepth = 1.0 / numLayers;
    // depth of current layer
    float currentLayerDepth = 0.0;
    // the amount to shift the texture coordinates per layer (from vector P)
    vec2 P = viewDir.xy / viewDir.z * -heightScale; 
    vec2 deltaTexCoords = P / numLayers;
  
    // get initial values
    vec2  currentTexCoords     = texCoords;
    float currentDepthMapValue = texture(depthMap, currentTexCoords).r;
      
    while( currentLayerDepth < currentDepthMapValue ){
        // shift texture coordinates along direction of P
        currentTexCoords -= deltaTexCoords;
        // get depthmap value at current texture coordinates
        currentDepthMapValue = texture(depthMap, currentTexCoords).r;  
        // get depth of next layer
        currentLayerDepth += layerDepth;  
    }
    
    //return currentTexCoords;
    
    // Version 3 extension to version 2 that lerps between the two nearest depths
    // get texture coordinates before collision (reverse operations)
    //vec2 prevTexCoords = currentTexCoords + deltaTexCoords;

    // get depth after and before collision for linear interpolation
    //float afterDepth  = currentDepthMapValue - currentLayerDepth;
    //float beforeDepth = texture(depthMap, prevTexCoords).r - currentLayerDepth + layerDepth;
 
    // interpolation of texture coordinates
    //float weight = afterDepth / (afterDepth - beforeDepth);
    //vec2 finalTexCoords = prevTexCoords * weight + currentTexCoords * (1.0 - weight);

    //return finalTexCoords;

    // Contact refinement parallax from Andrea Riccardi at Unity
    // Based on the rough approximation, rolling back to the previous step along the ray.
    currentTexCoords    += deltaTexCoords;
    currentLayerDepth   -= layerDepth;
    currentDepthMapValue = texture(depthMap, currentTexCoords).r;

    // Adjust precision
    vec2 adjustedDelta       = deltaTexCoords * layerDepth;
    float adjustedLayerDepth = layerDepth * layerDepth;

    // Uses another loop with the same step numbers, this times only covers the distance between previous point and the rough intersection point.
    while( currentLayerDepth < currentDepthMapValue ){
        // shift texture coordinates along direction of P
        currentTexCoords -= adjustedDelta;
        // get depthmap value at current texture coordinates
        currentDepthMapValue = texture(depthMap, currentTexCoords).r;  
        // get depth of next layer
        currentLayerDepth += adjustedLayerDepth;  
    }
    return currentTexCoords;
}

void calc_light_dir_and_atn( out vec3 lightDir, out float atn_amb, out float atn_ds, vec3 frag_pos, Light light ){
    lightDir = vec3( 0.0, -1.0, 0.0 );
    atn_amb  = 1.0f;
    atn_ds   = 1.0f;

    float amb_intensity  = 1.0f;
    float spot_intensity = 1.0f;

    if( light.type == 0 ){
        atn_ds   = 0.0f;
    } else if( light.type == 1 ){
        lightDir = light.direction;  // lightDir and light.direction only equal because the directional light has no distance component
        lightDir = normalize( lightDir );
    } else {
        lightDir = frag_pos - light.position;
        float distSq = lightDir.x * lightDir.x + lightDir.y * lightDir.y + lightDir.z * lightDir.z;
        // (farClip.y = farClipSq)
        if( distSq < light.farClip.y ){
            float distance = sqrt( distSq );
            vec3 lightDir_norm = lightDir / distance;
            if( light.type > 3 ){
                // distance based attenuation (atn.x = atn_cst, atn.y = atn_lnr, atn.z = atn_quad)
                atn_amb        = 1.0 / ( light.atn.x + ( light.atn.y + ( light.atn.z * distance ) ) * distance );
                atn_ds         = atn_amb;
            }

            if( light.type == 3 || light.type == 5 ){
                // spotlight (soft edges)
                // The lightDir_norm is the position relative to the vsPosWrld while the light[lgt_spt_adj].direction is the pointing direction
                // (cutOffFOV.y = cutOffSt, cutOffFOV.z = cutOff, cutOffFOV.w = FOV)
                float theta    = dot( -lightDir_norm, normalize( -light.direction ) );
                spot_intensity = clamp( ( theta - light.cutOffFOV.z ) / ( light.cutOffFOV.y - light.cutOffFOV.z ), 0.0, 1.0 );
                amb_intensity  = clamp( ( theta - light.cutOffFOV.w ) / ( light.cutOffFOV.y - light.cutOffFOV.w ), 0.0, 1.0 );
                //spot_intensity = clamp( ( theta - light.cutOff ) / ( light.cutOffSt - light.cutOff ), 0.0, 1.0 );
                //amb_intensity  = clamp( ( theta - light.FOV    ) / ( light.cutOffSt - light.FOV    ), 0.0, 1.0 );
                atn_ds  *= spot_intensity;
                atn_amb *= amb_intensity;
            }
            lightDir = lightDir_norm;
        } else {
            atn_amb = 0.0;
            atn_ds  = 0.0;
        }
    }
}

float ShadowCalculation( vec4 posSrcLgtVP, int shdwNmb ){
    vec3 projCoords = posSrcLgtVP.xyz / posSrcLgtVP.w; // perform perspective divide
    projCoords      = projCoords * 0.5 + 0.5; // transform to [0,1] range
    float shadow    = 0.0;  // keep the shadow at 0.0 when outside the far_plane region of the light's frustum.
    if( projCoords.z <= 1.0 ){
        // get closest depth value from light's perspective (using [0,1] range fragPosLight as coords)
        float closestDepth = texture(shadowMap, projCoords.xy)[shdwNmb]; 
        float currentDepth = projCoords.z; // get depth of current fragment from light's perspective
        shadow             = (currentDepth - 0.0001) > closestDepth  ? 1.0 : 0.0;
    }
    return shadow;
}

float ShadowCalculationCubemap( vec3 posWrld, Light light, int lgt ){
    vec3 fragToLight   = posWrld - light.position; // get vector between fragment position and light position
    float currentDepth = length( fragToLight ); // now get current linear depth as the length between the fragment and light position
    //float closestDepth = texture( shadowMapCubeStatic, fragToLight )[lgt];
    float closestDepth;
    if( ptShdwMethod == 0 ){
        closestDepth = texture( shadowMapCube[lgt], fragToLight ).r;
    } else if( ptShdwMethod == 1 ){
        closestDepth = texture( shadowMapCubeColorsMove, fragToLight )[lgt];
    } else {
        // calculate the front paraboloid map texture coordinates
        vec3 Pos = fragToLight.xyz;
        Pos     /= currentDepth;

        if( fragToLight.z >= 0.0 )
        {
            //generate texture coords for the front hemisphere
            Pos.z   = 1.0 + Pos.z;
            Pos.xy /= Pos.z;
            Pos.x   = -Pos.x;
            Pos.xy *= -0.5;
            Pos.xy += 0.5;
            closestDepth = texture( shadowMapPara, vec3( Pos.xy, 0 ) )[lgt];
        }
        else
        {
            //generate texture coords for the rear hemisphere
            Pos.z   = 1.0 - Pos.z;
            Pos.xy /= Pos.z;
            Pos.xy *= -0.5;
            Pos.xy += 0.5;
            closestDepth = texture( shadowMapPara, vec3( Pos.xy, 1 ) )[lgt];
        }
    }
    closestDepth      *= light.farClip.x; // closestDepth is currently in linear range between [0,1]. Re-transform back to original value (farClip.x = farClip)
    return (currentDepth - 0.01) > closestDepth ? 1.0 : 0.0;
}

float DistributionGGX(vec3 N, vec3 H, float roughness)
{
    float a = roughness*roughness;
    float a2 = a*a;
    float NdotH = max(dot(N, H), 0.0);
    float NdotH2 = NdotH*NdotH;

    float nom   = a2;
    float denom = (NdotH2 * (a2 - 1.0) + 1.0);
    denom = 3.14159265359 * denom * denom;

    return nom / denom;
}
// ----------------------------------------------------------------------------
float GeometrySchlickGGX(float NdotV, float roughness)
{
    float r = (roughness + 1.0);
    float k = (r*r) / 8.0;

    float nom   = NdotV;
    float denom = NdotV * (1.0 - k) + k;

    return nom / denom;
}
// ----------------------------------------------------------------------------
float GeometrySmith(vec3 N, vec3 V, vec3 L, float roughness)
{
    float NdotV = max(dot(N, V), 0.0);
    float NdotL = max(dot(N, L), 0.0);
    float ggx2 = GeometrySchlickGGX(NdotV, roughness);
    float ggx1 = GeometrySchlickGGX(NdotL, roughness);

    return ggx1 * ggx2;
}
// ----------------------------------------------------------------------------
vec3 fresnelSchlick(float cosTheta, vec3 F0)
{
    return F0 + (1.0 - F0) * pow(clamp(1.0 - cosTheta, 0.0, 1.0), 5.0);
}

vec3 sample_env( int envNmb, vec3 dir ){
    if(      envNmb == 0 ){ return texture( skyboxTex, dir ).rgb; }
    //else if( envNmb == 1 ){ return texture( shadowMapCubeStatic, dir ).rgb; }
    //else if( envNmb == 2 ){ return texture( shadowMapCubeMove, dir ).rgb; }
    //else if( envNmb == 3 ){ return texture( envMapCube, dir ).rgb; }
    else if( envNmb == 1 ){ return texture( envMapCube, dir ).rgb; }
    else if( envNmb == 2 ){ float depth = texture( shadowMapCube[0], dir ).r; return vec3( depth, depth, depth ); }
    else if( envNmb == 3 ){ float depth = texture( shadowMapCube[1], dir ).r; return vec3( depth, depth, depth ); }
    else if( envNmb == 4 ){ float depth = texture( shadowMapCube[2], dir ).r; return vec3( depth, depth, depth ); }
    else if( envNmb == 5 ){ float depth = texture( shadowMapCube[3], dir ).r; return vec3( depth, depth, depth ); }
}

void main(){
    vec2 texCoord = vs_res.TexCoord;
    if( objectType == 0 ){
        FragColor = vec4( light[lightNmb].diffuse, 1.0 );
    } else {
        if( texture( mtrl[0].diffTex, texCoord ).a < 0.05 ){
            discard;
        } else {
            // Implementation rendering a depth map texture (first channel only for all the channels)
            if( renderingType == 0 ){
                float near  = 0.1; 
                float far   = 100.0; 
                vec3 z     = texture( mtrl[0].diffTex, texCoord ).rgb * 2.0 - 1.0; // back to NDC 
                vec3 depth = sqrt( (2.0 * near ) / (far + near - z * (far - near)) );
                FragColor = vec4( depth, 1.0 );
                //FragColor = vec4( depthColor, depthColor, depthColor, 1.0 );
            } else if( renderingType == 1 ){
                // First implementation of the example using only the first texture
                if( !isTextureArray ){
                    FragColor = texture( mtrl[0].diffTex, texCoord );
                } else {
                    FragColor = texture( shadowMapPara, vec3( texCoord, ptShdwMethod ) );
                }
            } else if( renderingType == 2 ){
                // Second implementation of the example using texture and color inputs
                FragColor = texture( mtrl[0].diffTex, texCoord ) * vec4( vs_res.Color, 1.0 );
            } else if( renderingType == 3 ){
                // Third implmentation of the example linearly interpolating between both textures
                FragColor = mix( texture( mtrl[0].diffTex, texCoord ), texture( mtrl[1].diffTex, texCoord ), 0.5 );
            } else if( renderingType == 4 ){
                // Fourth implmentation of the example with colored object defined by vec3 and a light
                // It's not really "diffuse" lighting, diffuse is just the default for "what color is this light?"
                FragColor = vec4( light[lgt_spt_adj].diffuse * mtrl[0].diffuse, 1.0 );
            } else if( renderingType == 5 ){
                // Fifth implementation of the example with only ambient lighting
                vec3 ambient;
                calc_ambient( ambient, light[lgt_spt_adj].ambient, mtrl[0].ambient );
                FragColor    = vec4( ambient, 1.0 );
            } else {
                // Implementations after this are going to use the direction the light is pointing relative to the fragment
                vec3 lightDir, ambient, diffuse, specular;
                float atn_amb, atn_ds;
                diffuse  = vec3( 0.0, 0.0, 0.0 );
                specular = vec3( 0.0, 0.0, 0.0 );

                vec3 normal_norm = normalize( vs_res.Normal );
                if(!gl_FrontFacing){ normal_norm = -normal_norm; }
                vec3 viewDir_norm     = normalize( viewPos - vs_res.PosWrld );

                calc_light_dir_and_atn( lightDir, atn_amb, atn_ds, vs_res.PosWrld, light[lgt_spt_adj] );
                if( renderingType < 12 ||  ( renderingType > 17 && renderingType < 20 ) ){
                    float shadow = ShadowCalculation( vs_res.PosLgtVP[0], 0 );
                    atn_ds *= ( 1.0 - shadow );
                }
                vec3 result = vec3( 0.0, 0.0, 0.0 );

                if( renderingType == 6 ){
                    // Sixth implementation of the example with ambient and diffuse lighting
                    calc_ambient( ambient, light[lgt_spt_adj].ambient, mtrl[0].ambient );
                    if( atn_ds > 0.0 ){
                        calc_diffuse( diffuse, normal_norm, vs_res.PosWrld, lightDir, light[lgt_spt_adj].diffuse, mtrl[0].diffuse );
                    }
                    result += vec3( ambient * atn_amb + ( diffuse + specular ) * atn_ds );
                    FragColor = vec4( result, 1.0 );
                    //FragColor     = vec4( ambient * atn_amb + diffuse * atn_ds, 1.0 );
                    //FragColor     = vec4( vs_res.Normal, 1.0 );
                } else if( renderingType == 7 ){
                    // Seventh implementation of the example with ambient, diffuse, and specular lighting (Phong Lighting)
                    calc_ambient( ambient, light[lgt_spt_adj].ambient, mtrl[0].ambient );
                    if( atn_ds > 0.0 ){
                        calc_diffuse( diffuse, normal_norm, vs_res.PosWrld, lightDir, light[lgt_spt_adj].diffuse, mtrl[0].diffuse );
                        calc_specular( specular, normal_norm, viewDir_norm, lightDir, light[lgt_spt_adj].specular, mtrl[0].specular, mtrl[0].roughness.x );
                    }
                    result += vec3( ambient * atn_amb + ( diffuse + specular ) * atn_ds );
                    FragColor = vec4( result, 1.0 );
                    //FragColor = vec4( ambient * atn_amb + ( diffuse + specular ) * atn_ds, 1.0 );
                } else if( renderingType == 8 ){
                    // Eighth implementation of the example with ambient, diffuse, specular lighting (Phong Lighting), and textures
                    // blend all the textures
                    vec3 ambtTex = mix( texture( mtrl[0].ambtTex, texCoord ).rgb,  texture( mtrl[1].ambtTex, texCoord ).rgb, 0.5 );
                    vec3 diffTex = mix( texture( mtrl[0].diffTex, texCoord ).rgb, texture( mtrl[1].diffTex,  texCoord ).rgb, 0.5 );
                    vec3 specTex = mix( texture( mtrl[0].specTex, texCoord ).rgb,  texture( mtrl[1].specTex, texCoord ).rgb, 0.5 );

                    calc_ambient( ambient, light[lgt_spt_adj].ambient, (mtrl[0].ambient * ambtTex.r) );
                    if( atn_ds > 0.0 ){
                        calc_diffuse( diffuse, normal_norm, vs_res.PosWrld, lightDir, light[lgt_spt_adj].diffuse, (mtrl[0].diffuse * diffTex) );
                        calc_specular( specular, normal_norm, viewDir_norm, lightDir, light[lgt_spt_adj].specular, (mtrl[0].specular * specTex), mtrl[0].roughness.x );
                    }
                    result += vec3( ambient * atn_amb + ( diffuse + specular ) * atn_ds );
                    FragColor = vec4( result, 1.0 );
                    //FragColor = vec4( ambient * atn_amb + ( diffuse + specular ) * atn_ds, 1.0 );
                } else if( renderingType == 9 ){
                    // Nineth implementation of the example for ambient, diffuse, specular lighting (Phong Lighting), texture, and normal maps
                    // Sample normal map, get normal vector, then normalize and remap (* 2 - 1)
                    vec3 normalMapNormal1 = normalize( texture( mtrl[0].normTex, texCoord ).rgb * 2.0 - 1.0 );

                    // Construct TBN matrix
                    mat3 tbn;
                    calc_tbn_matrix( tbn, normal_norm, vs_res.PosWrld, texCoord );

                    // Transform the normal
                    vec3 worldNormal1 = normalize( tbn * normalMapNormal1 );

                    vec3 ambtTex = texture( mtrl[0].ambtTex, texCoord ).rgb;
                    vec3 diffTex = texture( mtrl[0].diffTex, texCoord ).rgb;
                    vec3 specTex = texture( mtrl[0].specTex, texCoord ).rgb;

                    calc_ambient( ambient, light[lgt_spt_adj].ambient, (mtrl[0].ambient * ambtTex.r) );
                    if( atn_ds > 0.0 ){
                        calc_diffuse( diffuse, worldNormal1, vs_res.PosWrld, lightDir, light[lgt_spt_adj].diffuse, (mtrl[0].diffuse * diffTex) );
                        calc_specular( specular, worldNormal1, viewDir_norm, lightDir, light[lgt_spt_adj].specular, (mtrl[0].specular * specTex.r), mtrl[0].roughness.x );
                    }
                    result += vec3( ambient * atn_amb + ( diffuse + specular ) * atn_ds );
                    FragColor = vec4( result, 1.0 );
                    //FragColor = vec4( ambient * atn_amb + ( diffuse + specular ) * atn_ds, 1.0 );
                } else if( renderingType == 10 ){
                    // Tenth implementation of the example with ambient, diffuse, specular lighting (Phong Lighting), texture, and normal maps
                    // This implementation (vs 11 below) takes two textures, blends them together, and then uses blended normals and textures
                    // Sample normal map, get normal vector, then normalize and remap (* 2 - 1)
                    vec3 normalMapNormal1 = normalize( texture( mtrl[0].normTex, texCoord ).rgb * 2.0 - 1.0 );
                    vec3 normalMapNormal2 = normalize( texture( mtrl[1].normTex, texCoord ).rgb * 2.0 - 1.0 );

                    // Construct TBN matrix
                    mat3 tbn;
                    calc_tbn_matrix( tbn, normal_norm, vs_res.PosWrld, texCoord );

                    // Transform the normal
                    vec3 worldNormal1 = normalize( tbn * normalMapNormal1 );
                    vec3 worldNormal2 = normalize( tbn * normalMapNormal2 );

                    vec3 pixelNormal = normalize( mix( worldNormal1, worldNormal2, 0.5 ) );

                    // blending two textures together and using an average worldNormal based on both at the pixel (pixelNormal)
                    vec3 ambtTex = mix( texture( mtrl[0].ambtTex, texCoord ).rgb,  texture( mtrl[1].ambtTex, texCoord ).rgb, 0.5 );
                    vec3 diffTex = mix( texture( mtrl[0].diffTex, texCoord ).rgb, texture( mtrl[1].diffTex,  texCoord ).rgb, 0.5 );
                    vec3 specTex = mix( texture( mtrl[0].specTex, texCoord ).rgb,  texture( mtrl[1].specTex, texCoord ).rgb, 0.5 );

                    calc_ambient( ambient, light[lgt_spt_adj].ambient, (mtrl[0].ambient * ambtTex.r) );
                    if( atn_ds > 0.0 ){
                        calc_diffuse( diffuse, pixelNormal, vs_res.PosWrld, lightDir, light[lgt_spt_adj].diffuse, (mtrl[0].diffuse * diffTex) );
                        calc_specular( specular, pixelNormal, viewDir_norm, lightDir, light[lgt_spt_adj].specular, (mtrl[0].specular * specTex.r), mtrl[0].roughness.x );
                    }
                    result += vec3( ambient * atn_amb + ( diffuse + specular ) * atn_ds );
                    FragColor = vec4( result, 1.0 );
                    //FragColor = vec4( ambient * atn_amb + ( diffuse + specular ) * atn_ds, 1.0 );
                } else if( renderingType == 11 ){
                    // Eleventh implementation of the example with ambient, diffuse, specular lighting (Phong Lighting), texture, and normal maps
                    // This implementation handles diffuse and specular seperately for each texture
                    // Sample normal map, get normal vector, then normalize and remap (* 2 - 1)
                    vec3 normalMapNormal1 = normalize( texture( mtrl[0].normTex, texCoord ).rgb * 2.0 - 1.0 );
                    vec3 normalMapNormal2 = normalize( texture( mtrl[1].normTex, texCoord ).rgb * 2.0 - 1.0 );

                    // Construct TBN matrix
                    mat3 tbn;
                    calc_tbn_matrix( tbn, normal_norm, vs_res.PosWrld, texCoord );

                    // Transform the normal
                    vec3 worldNormal1 = normalize( tbn * normalMapNormal1 );
                    vec3 worldNormal2 = normalize( tbn * normalMapNormal2 );

                    // blend the textures for ambient, diffuse and specular handle each texture independently
                    vec3 ambtTex  = mix( texture( mtrl[0].ambtTex,  texCoord ).rgb, texture( mtrl[1].ambtTex,  texCoord ).rgb, 0.5 );
                    vec3 diffTex1 = texture( mtrl[0].diffTex, texCoord ).rgb;
                    vec3 diffTex2 = texture( mtrl[1].diffTex, texCoord ).rgb;
                    vec3 specTex1 = texture( mtrl[0].specTex, texCoord ).rgb;
                    vec3 specTex2 = texture( mtrl[1].specTex, texCoord ).rgb;

                    vec3 ambient, diffuse1, diffuse2, specular1, specular2;
                    
                    calc_ambient( ambient, light[lgt_spt_adj].ambient, (mtrl[0].ambient * ambtTex.r) );
                    if( atn_ds > 0.0 ){
                        calc_diffuse( diffuse1, worldNormal1, vs_res.PosWrld, lightDir, light[lgt_spt_adj].diffuse, (mtrl[0].diffuse * diffTex1) );
                        calc_diffuse( diffuse2, worldNormal2, vs_res.PosWrld, lightDir, light[lgt_spt_adj].diffuse, (mtrl[0].diffuse * diffTex2) );
                        calc_specular( specular1, worldNormal1, viewDir_norm, lightDir, light[lgt_spt_adj].specular, (mtrl[0].specular * specTex1.r), mtrl[0].roughness.x );
                        calc_specular( specular2, worldNormal2, viewDir_norm, lightDir, light[lgt_spt_adj].specular, (mtrl[0].specular * specTex2.r), mtrl[0].roughness.x );
                    }
                    result += vec3( ambient * atn_amb + ( mix( diffuse1, diffuse2, 0.5 ) + mix( specular1, specular2, 0.5 ) ) * atn_ds );
                    FragColor = vec4( result, 1.0 );
                    //FragColor = vec4( ambient * atn_amb + ( mix( diffuse1, diffuse2, 0.5 ) + mix( specular1, specular2, 0.5 ) ) * atn_ds, 1.0 );
                } else if( renderingType == 12 ){
                    // Twelfth implementation of the example with multiple light sources
                    // Lights and surfaces have ambient, diffuse, specular lighting (Phong Lighting), and textures
                    // Sample normal map, get normal vector, then normalize and remap (* 2 - 1)
                    vec3 normalMapNormal1 = normalize( texture( mtrl[0].normTex, texCoord ).rgb * 2.0 - 1.0 );

                    // Construct TBN matrix
                    mat3 tbn;
                    calc_tbn_matrix( tbn, normal_norm, vs_res.PosWrld, texCoord );

                    // Transform the normal
                    vec3 worldNormal1 = normalize( tbn * normalMapNormal1 );

                    vec3 ambtTex = texture( mtrl[0].ambtTex, texCoord ).rgb;
                    vec3 diffTex = texture( mtrl[0].diffTex, texCoord ).rgb;
                    vec3 specTex = texture( mtrl[0].specTex, texCoord ).rgb;

                    int light_st, light_end;
                    if( lightIndex == 0 ){
                        light_st = 1; light_end = lightNmb;
                    } else {
                        light_st = lightIndex; light_end = light_st+1;
                    }

                    for( int i = light_st; i < light_end; i++ ){
                        calc_light_dir_and_atn( lightDir, atn_amb, atn_ds, vs_res.PosWrld, light[i] );
                        if( light[i].type == 4 ){
                            float shadow = ShadowCalculationCubemap( vs_res.PosWrld, light[i], i-lgt_pt_adj );
                            atn_ds *= ( 1.0 - shadow );
                        }
                        if( light[i].type == 5 ){
                            int lgt_spt  = i-lgt_spt_adj;
                            float shadow = ShadowCalculation( vs_res.PosLgtVP[lgt_spt], lgt_spt );
                            atn_ds *= ( 1.0 - shadow );
                        }
                        calc_ambient( ambient, light[i].ambient, (mtrl[0].ambient * ambtTex.r) );
                        if( atn_ds > 0.0 ){
                            calc_diffuse( diffuse, worldNormal1, vs_res.PosWrld, lightDir, light[i].diffuse, (mtrl[0].diffuse * diffTex) );
                            calc_specular( specular, worldNormal1, viewDir_norm, lightDir, light[i].specular, (mtrl[0].specular * specTex.r), mtrl[0].roughness.x );
                        }
                            
                        result += vec3( ambient * atn_amb + ( diffuse + specular ) * atn_ds );
                    }
                    FragColor = vec4( result, 1.0 );
                } else if( renderingType == 13 ){
                    // Thirteenth implementation that renders the depth of the pixels on screen
                    // Normal depth used by the shader is only gl_FragCoord.z
                    // Linearized depth is (2.0 * near ) / (far + near - z * (far - near))
                    // This implementation then takes the sqrt to accentuate those values 
                    float near  = 0.1; 
                    float far   = 100.0; 
                    float z     = gl_FragCoord.z * 2.0 - 1.0; // back to NDC 
                    float depth = sqrt( (2.0 * near ) / (far + near - z * (far - near)) );
                    FragColor = vec4( vec3( depth ), 1.0 );
                } else if( renderingType == 14 ){
                    // Fourteenth implementation that renders smooth reflection on surfaces from the skybox cubemap
                    // Similar to the specular code, except that this takes the view direction, reflects it
                    // and then samples from the cubemap texture
                    vec3 reflectDir = reflect( -viewDir_norm, normal_norm );
                    FragColor = vec4( sample_env( cubeIndex, reflectDir ), 1.0 );
                } else if( renderingType == 15 ){
                    // Fifteenth implementation that renders reflections from the cubemap with normal maps applied
                    // Sample normal map, get normal vector, then normalize and remap (* 2 - 1)
                    vec3 normalMapNormal = normalize( texture( mtrl[0].normTex, texCoord ).rgb * 2.0 - 1.0 );

                    // Construct TBN matrix
                    mat3 tbn;
                    calc_tbn_matrix( tbn, normal_norm, vs_res.PosWrld, texCoord );

                    // Transform the normal
                    vec3 worldNormal = normalize( tbn * normalMapNormal );
                    vec3 reflectDir  = reflect( -viewDir_norm, worldNormal );
                    FragColor = vec4( sample_env( cubeIndex, reflectDir ), 1.0 );
                } else if( renderingType == 16 ){
                    // Sixteenth implementation that renders smooth refraction thru surfaces from the skybox cubemap
                    // Very similar to fourteen except it uses the refract function vs the reflect function
                    // Implements only single layer refraction rather than multi-layer
                    float snell_ratio = 1.00 / 1.52;  // air to glass ratio
                    vec3 refractDir = refract( -viewDir_norm, normal_norm, snell_ratio );
                    FragColor = vec4( sample_env( cubeIndex, refractDir ), 1.0 );
                } else if( renderingType == 17 ){
                    // Seventeenth implementation that renders refraction thru surfaces with a normal map
                    float snell_ratio = 1.00 / 1.52;  // air to glass ratio

                    // Sample normal map, get normal vector, then normalize and remap (* 2 - 1)
                    vec3 normalMapNormal = normalize( texture( mtrl[0].normTex, texCoord ).rgb * 2.0 - 1.0 );

                    // Construct TBN matrix
                    mat3 tbn;
                    calc_tbn_matrix( tbn, normal_norm, vs_res.PosWrld, texCoord );

                    // Transform the normal
                    vec3 worldNormal = normalize( tbn * normalMapNormal );
                    vec3 refractDir = refract( -viewDir_norm, worldNormal, snell_ratio );
                    FragColor = vec4( sample_env( cubeIndex, refractDir ), 1.0 );
                } else if( renderingType == 18 ){
                    // Eighteenth implementation that renders multiple window views
                    // Calculate an angle for the view
                    float timeDeltaMlt = timeDelta * 27.9120741831915;
                    float viewAngle    = atan( ( gl_FragCoord.y - halfScreenSizePix.y ), ( gl_FragCoord.x - halfScreenSizePix.x ) );

                    if( ( viewAngle < -1.247197551 + timeDeltaMlt ) || ( viewAngle > 2.941592654 + timeDeltaMlt ) ){
                        // blend all the textures
                        vec3 ambtTex = mix( texture( mtrl[0].ambtTex, texCoord ).rgb,  texture( mtrl[1].ambtTex, texCoord ).rgb, 0.5 );
                        vec3 diffTex = mix( texture( mtrl[0].diffTex, texCoord ).rgb, texture( mtrl[1].diffTex,  texCoord ).rgb, 0.5 );
                        vec3 specTex = mix( texture( mtrl[0].specTex, texCoord ).rgb,  texture( mtrl[1].specTex, texCoord ).rgb, 0.5 );

                        calc_ambient( ambient, light[lgt_spt_adj].ambient, (mtrl[0].ambient * ambtTex.r) );
                        if( atn_ds > 0.0 ){
                            calc_diffuse( diffuse, normal_norm, vs_res.PosWrld, lightDir, light[lgt_spt_adj].diffuse, (mtrl[0].diffuse * diffTex) );
                            calc_specular( specular, normal_norm, viewDir_norm, lightDir, light[lgt_spt_adj].specular, (mtrl[0].specular * specTex), mtrl[0].roughness.x );
                        }
                        result += vec3( ambient * atn_amb + ( diffuse + specular ) * atn_ds );
                            
                        if( gl_FrontFacing ){ FragColor = vec4( 1.0, 0.2, 0.2, 1.0 ) * vec4( result, 1.0 ); }
                        else                { FragColor = vec4( 0.2, 1.0, 1.0, 1.0 ) * vec4( result, 1.0 ); }
                    } else if( ( viewAngle > 0.847197551 + timeDeltaMlt ) && ( viewAngle < 2.941592654 + timeDeltaMlt ) ){
                        float snell_ratio = 1.00 / 1.52;  // air to glass ratio

                        // Sample normal map, get normal vector, then normalize and remap (* 2 - 1)
                        vec3 normalMapNormal = normalize( texture( mtrl[0].normTex, texCoord ).rgb * 2.0 - 1.0 );

                        // Construct TBN matrix
                        mat3 tbn;
                        calc_tbn_matrix( tbn, normal_norm, vs_res.PosWrld, texCoord );

                        // Transform the normal
                        vec3 worldNormal = normalize( tbn * normalMapNormal );
                        vec3 refractDir = refract( -viewDir_norm, worldNormal, snell_ratio );
                        
                        if( gl_FrontFacing ){ FragColor = vec4( 0.2, 1.0, 0.2, 1.0 ) * vec4( sample_env( cubeIndex, refractDir ), 1.0 ); }
                        else                { FragColor = vec4( 1.0, 0.2, 1.0, 1.0 ) * vec4( sample_env( cubeIndex, refractDir ), 1.0 ); }
                    } else { 
                        float near  = 0.1; 
                        float far   = 100.0; 
                        float z     = gl_FragCoord.z * 2.0 - 1.0; // back to NDC 
                        float depth = sqrt( (2.0 * near ) / (far + near - z * (far - near)) );
                        
                        if( gl_FrontFacing ){ FragColor = vec4( 0.2, 0.2, 1.0, 1.0 ) * vec4( vec3( depth ), 1.0 ); }
                        else                { FragColor = vec4( 1.0, 1.0, 0.2, 1.0 ) * vec4( vec3( depth ), 1.0 ); }
                    }
                } else if( renderingType == 19 ){
                    // Ninteenth implementation of the example for ambient, diffuse, specular lighting (Phong Lighting), texture, normal, and displacement maps

                    // Construct TBN matrix
                    mat3 tbn, tbn_T;
                    calc_tbn_matrix( tbn, normal_norm, vs_res.PosWrld, texCoord );
                    //tbn_T = transpose( tbn );
                    //vec3 view_dir_tngt = tbn_T * viewDir_norm;
                    vec3 view_dir_tngt = viewDir_norm * tbn;
                    vec2 texCoord_parallax = parallax_mapping( texCoord, mtrl[0].dispTex, view_dir_tngt, height_scale );
                    //if( texCoord_parallax.x > 1.0 || texCoord_parallax.y > 1.0 || texCoord_parallax.x < 0.0 || texCoord_parallax.y < 0.0 )
                    //    discard;
                    //FragColor = texture( mtrl[0].diffTex, texCoord_parallax );

                    // Transform the normal from the normal map to world space
                    vec3 normalMapNormal1 = normalize( texture( mtrl[0].normTex, texCoord_parallax ).rgb * 2.0 - 1.0 );
                    vec3 worldNormal1 = normalize( tbn * normalMapNormal1 );

                    vec3 ambtTex = texture( mtrl[0].ambtTex, texCoord_parallax ).rgb;
                    vec3 diffTex = texture( mtrl[0].diffTex, texCoord_parallax ).rgb;
                    vec3 specTex = texture( mtrl[0].specTex, texCoord_parallax ).rgb;

                    calc_ambient( ambient, light[lgt_spt_adj].ambient, (mtrl[0].ambient * ambtTex.r) );
                    if( atn_ds > 0.0 ){
                        calc_diffuse( diffuse, worldNormal1, vs_res.PosWrld, lightDir, light[lgt_spt_adj].diffuse, (mtrl[0].diffuse * diffTex) );
                        calc_specular( specular, worldNormal1, viewDir_norm, lightDir, light[lgt_spt_adj].specular, (mtrl[0].specular * specTex.r), mtrl[0].roughness.x );
                    }
                    //FragColor = vec4( ambient * atn_amb + ( diffuse + specular ) * atn_ds, 1.0 );
                    result += vec3( ambient * atn_amb + ( diffuse + specular ) * atn_ds );
                    
                    int light_st, light_end;
                    if( lightIndex == 0 ){
                        light_st = 1; light_end = lightNmb;
                    } else {
                        light_st = lightIndex; light_end = light_st+1;
                    }

                    for( int i = light_st; i < light_end; i++ ){
                        calc_light_dir_and_atn( lightDir, atn_amb, atn_ds, vs_res.PosWrld, light[i] );
                        if( light[i].type == 4 ){
                            float shadow = ShadowCalculationCubemap( vs_res.PosWrld, light[i], i-lgt_pt_adj );
                            atn_ds      *= ( 1.0 - shadow );
                        }
                        if( light[i].type == 5 ){
                            int lgt_spt  = i-lgt_spt_adj;
                            float shadow = ShadowCalculation( vs_res.PosLgtVP[lgt_spt], lgt_spt );
                            atn_ds      *= ( 1.0 - shadow );
                        }
                        calc_ambient( ambient, light[i].ambient, (mtrl[0].ambient * ambtTex.r) );
                        if( atn_ds > 0.0 ){
                            calc_diffuse( diffuse, worldNormal1, vs_res.PosWrld, lightDir, light[i].diffuse, (mtrl[0].diffuse * diffTex) );
                            calc_specular( specular, worldNormal1, viewDir_norm, lightDir, light[i].specular, (mtrl[0].specular * specTex.r), mtrl[0].roughness.x );
                        }
                            
                        result += vec3( ambient * atn_amb + ( diffuse + specular ) * atn_ds );
                    }
                    FragColor = vec4( result, 1.0 );
                } else if( renderingType == 20 ){
                    // Construct TBN matrix
                    mat3 tbn;
                    calc_tbn_matrix( tbn, normal_norm, vs_res.PosWrld, texCoord );
                    //vec3 view_dir_tngt = viewDir_norm * tbn;
                    //texCoord           = parallax_mapping( texCoord, mtrl[0].dispTex, view_dir_tngt, height_scale );

                    // material parameters
                    float ambientMtrl   = mtrl[0].ambient.r;
                    vec3 diffuseMtrl    = mtrl[0].diffuse;
                    float metallicMtrl  = mtrl[0].specular.r;
                    float roughnessMtrl = mtrl[0].roughness.r;

                    float ambtTex = texture( mtrl[0].ambtTex, texCoord ).r;  // used to be ambtTex (rgb)
                    vec3 diffTex  = texture( mtrl[0].diffTex, texCoord ).rgb;
                    float specTex = texture( mtrl[0].specTex, texCoord ).r;  // used to be specTex (rgb)
                    float rghnTex = texture( mtrl[0].rghnTex, texCoord ).r;

                    ambientMtrl   *= ambtTex;
                    diffuseMtrl   *= diffTex;
                    metallicMtrl  *= specTex;
                    roughnessMtrl *= rghnTex;

                    diffuseMtrl = pow( diffuseMtrl, vec3(2.2) );

                    // Transform the normal from the normal map to world space
                    vec3 normalMapNormal = normalize( texture( mtrl[0].normTex, texCoord ).rgb * 2.0 - 1.0 );
                    vec3 N = normalize( tbn * normalMapNormal );
                    vec3 V = viewDir_norm;
                    vec3 R = reflect(-V, N);

                    float NdotV = max(dot( N, V ), 0.0);
                    float OneMNdotV = clamp(1.0 - NdotV, 0.0, 1.0);
                    float OMNdVSq   = OneMNdotV * OneMNdotV;

                    // calculate reflectance at normal incidence; if dia-electric (like plastic) use F0 
                    // of 0.04 and if it's a metal, use the albedo color as F0 (metallic workflow)    
                    vec3 F0 = vec3(0.04); 
                    F0 = mix(F0, diffuseMtrl, metallicMtrl);

                    float rSq  = roughnessMtrl*roughnessMtrl;
                    float r4th = rSq*rSq;
                    float k    = (rSq + roughnessMtrl + roughnessMtrl + 1.0) * 0.125;

                    float LightIntensityMult = 8.0;

                    // reflectance equation
                    vec3 Lo = vec3(0.0);
                    for( int i = 0; i < lightNmb; i++ ){
                        vec3 L = light[i].position - vs_res.PosWrld;
                        float distSq = L.x * L.x + L.y * L.y + L.z * L.z;
                        float attenuation = 1.0 / (distSq);
                        if( light[i].type == 4 ){
                            float shadow = ShadowCalculationCubemap( vs_res.PosWrld, light[i], i-lgt_pt_adj );
                            attenuation *= ( 1.0 - shadow );
                        }
                        if( light[i].type == 5 ){
                            int lgt_spt  = i-lgt_spt_adj;
                            float shadow = ShadowCalculation( vs_res.PosLgtVP[lgt_spt], lgt_spt );
                            attenuation *= ( 1.0 - shadow );
                        }
                        if( attenuation > 0.0 ){
                            // calculate per-light radiance
                            L *= sqrt(attenuation);
                            vec3 H = normalize(V + L);
                            float HdotV = max(dot( H, V ), 0.0);
                            float NdotH = max(dot( N, H ), 0.0);
                            float NdotL = max(dot( N, L ), 0.0);
                            vec3 radiance = light[i].diffuse * attenuation;

                            float OneMHdotV = clamp(1.0 - HdotV, 0.0, 1.0);
                            float OMHdVSq   = OneMHdotV * OneMHdotV;

                            float NdotHSq  = NdotH*NdotH;
                            float NDFdenom = (NdotHSq * (r4th - 1.0) + 1.0);

                            float ggx1 = NdotV / (NdotV * (1.0 - k) + k);
                            float ggx2 = NdotL / (NdotL * (1.0 - k) + k);

                            // Cook-Torrance BRDF
                            float NDF = r4th / ( NDFdenom * NDFdenom );   
                            float GS  = ggx1 * ggx2;
                            vec3  kS  = F0 + (1.0 - F0) * OMHdVSq * OMHdVSq * OneMHdotV;
                            
                            vec3 specular = (NDF * GS * kS) / (4.0 * NdotV * NdotL + 0.0001);
                            
                            // for energy conservation, the diffuse and specular light can't
                            // be above 1.0 (unless the surface emits light); to preserve this
                            // relationship the diffuse component (kD) should equal 1.0 - kS.
                            vec3 kD = vec3(1.0) - kS;
                            // multiply kD by the inverse metalness such that only non-metals 
                            // have diffuse lighting, or a linear blend if partly metal (pure metals
                            // have no diffuse light).
                            kD *= 1.0 - metallicMtrl;	  

                            // add to outgoing radiance Lo
                            Lo += (kD * diffuseMtrl + specular) * radiance * LightIntensityMult * NdotL / 3.14159265359;
                        }
                    }
                    // ambient lighting (we now use IBL as the ambient term)
                    
                    vec3 kSAmbt = F0 + ( max( vec3( 1.0 - roughnessMtrl ), F0 ) - F0 ) * OMNdVSq * OMNdVSq * OneMNdotV;
                    vec3 kDAmbt = 1.0 - kSAmbt;
                    kDAmbt *= 1.0 - metallicMtrl;	  
                    
                    // sample both the pre-filter map and the BRDF lut and combine them together as per the Split-Sum approximation to get the IBL specular part.
                    vec3 irradiance;
                    const float MAX_REFLECTION_LOD = 10.0;
                    vec3 prefilteredColor; 
                    if( cubeIndex == 0 ){
                        irradiance       = textureLod( skyboxTex, N, 9 ).rgb;
                        prefilteredColor = textureLod( skyboxTex, R,  roughnessMtrl * MAX_REFLECTION_LOD ).rgb;
                    //} else if( cubeIndex == 1 ){
                    //    irradiance       = textureLod( shadowMapCubeStatic, N, 9 ).rgb;
                    //    prefilteredColor = textureLod( shadowMapCubeStatic, R,  roughnessMtrl * MAX_REFLECTION_LOD ).rgb;
                    //} else if( cubeIndex == 2 ){
                    //    irradiance       = textureLod( shadowMapCubeMove, N, 9 ).rgb;
                    //    prefilteredColor = textureLod( shadowMapCubeMove, R,  roughnessMtrl * MAX_REFLECTION_LOD ).rgb;
                    //} else if( cubeIndex == 3 ){
                    //    irradiance       = textureLod( envMapCube, N, 9 ).rgb;
                    //    prefilteredColor = textureLod( envMapCube, R,  roughnessMtrl * MAX_REFLECTION_LOD ).rgb;
                    //}
                    } else if( cubeIndex == 1 ){
                        irradiance       = textureLod( envMapCube, N, 9 ).rgb;
                        prefilteredColor = textureLod( envMapCube, R,  roughnessMtrl * MAX_REFLECTION_LOD ).rgb;
                    } else if( cubeIndex == 2 ){
                        irradiance       = textureLod( shadowMapCube[0], N, 9 ).rgb;
                        prefilteredColor = textureLod( shadowMapCube[0], R,  roughnessMtrl * MAX_REFLECTION_LOD ).rgb;
                    } else if( cubeIndex == 3 ){
                        irradiance       = textureLod( shadowMapCube[1], N, 9 ).rgb;
                        prefilteredColor = textureLod( shadowMapCube[1], R,  roughnessMtrl * MAX_REFLECTION_LOD ).rgb;
                    } else if( cubeIndex == 4 ){
                        irradiance       = textureLod( shadowMapCube[2], N, 9 ).rgb;
                        prefilteredColor = textureLod( shadowMapCube[2], R,  roughnessMtrl * MAX_REFLECTION_LOD ).rgb;
                    } else if( cubeIndex == 5 ){
                        irradiance       = textureLod( shadowMapCube[3], N, 9 ).rgb;
                        prefilteredColor = textureLod( shadowMapCube[3], R,  roughnessMtrl * MAX_REFLECTION_LOD ).rgb;
                    }
                    vec3 diffuseAmbt  = irradiance * diffuseMtrl * 0.5;
                    vec2 brdf         = texture( brdfLUT, vec2( NdotV, roughnessMtrl ) ).rg;
                    vec3 specularAmbt = prefilteredColor * ( kSAmbt * brdf.x + brdf.y );
                    vec3 ambient      = (kDAmbt * diffuseAmbt + specularAmbt) * ambientMtrl;

                    // ambient lighting (note that the next IBL tutorial will replace 
                    // this ambient lighting with environment lighting).
                    //vec3 ambient = vec3(0.03) * diffuseMtrl * ambientMtrl;
                    
                    vec3 color = Lo + ambient;
                    
                    color = color / (color + vec3(1.0));
                    // gamma correct
                    color = pow(color, vec3(1.0/2.2));

                    FragColor = vec4( color, 1.0 );
                } else if( renderingType == 21 ){
                    FragColor = vec4( vs_res.PosWrld, 1.0 );
                } else if( renderingType == 22 ){
                    FragColor = vec4( vs_res.TexCoord, 1.0, 1.0 );
                } else if( renderingType == 23 ){
                    FragColor = vec4( vs_res.Normal, 1.0 );
                } else if( renderingType == 24 ){
                    FragColor = vec4( vs_res.Color, 1.0 );
                } else if( renderingType == 25 ){
                    FragColor = vec4( vs_res.PosLgtVP[0] );
                }
            }
        }
    }

    if( isExposure ){
        // exposure tone mapping
        FragColor = vec4( vec3(1.0) - exp(-FragColor.rgb * exposure ), 1.0 );
        //FragColor = vec4( sqrt(FragColor.rgb), 1.0 );
    }

    if( gamma != 1.0 ){
        // gamma correction 
        FragColor = vec4( pow( FragColor.rgb, vec3( 1.0 / gamma ) ), 1.0 );
    }
}