#version 120 // Atmospheric scattering shader for flightgear // Written by Lauri Peltonen (Zan) // Implementation of O'Neil's algorithm varying vec3 rayleigh; varying vec3 mie; varying vec3 eye; varying vec3 hazeColor; varying float ct; //varying float cosphi; varying float delta_z; varying float alt; varying float earthShade; uniform float overcast; uniform float saturation; uniform float visibility; uniform float avisibility; uniform float scattering; const float EarthRadius = 5800000.0; float miePhase(in float cosTheta, in float g) { float g2 = g*g; float a = 1.5 * (1.0 - g2); float b = (2.0 + g2); float c = 1.0 + cosTheta*cosTheta; float d = pow(1.0 + g2 - 2.0 * g * cosTheta, 0.6667); return (a*c) / (b*d); } float rayleighPhase(in float cosTheta) { //return 1.5 * (1.0 + cosTheta*cosTheta); return 1.5 * (2.0 + 0.5*cosTheta*cosTheta); } void main() { float cosTheta = dot(normalize(eye), gl_LightSource[0].position.xyz); // position of the horizon line float lAltitude = alt + delta_z; float radiusEye = EarthRadius + alt; float radiusLayer = EarthRadius + lAltitude; float cthorizon; float ctterrain; if (radiusEye > radiusLayer) cthorizon = -sqrt(radiusEye * radiusEye - radiusLayer * radiusLayer)/radiusEye; else cthorizon = sqrt(radiusLayer * radiusLayer - radiusEye * radiusEye)/radiusLayer; ctterrain = -sqrt(radiusEye * radiusEye - EarthRadius * EarthRadius)/radiusEye; vec3 color = rayleigh * rayleighPhase(cosTheta); color += mie * miePhase(cosTheta, -0.8); vec3 black = vec3(0.0,0.0,0.0); float ovc = overcast; float sat = 1.0 - ((1.0 - saturation) * 2.0); if (sat < 0.3) sat = 0.3; // float wscale = 1.732; // an overexposure filter, the log() seems to be pretty expensive though // if (color.x > 0.8) color.x = 0.8 + 0.8* log(color.x/0.8); // if (color.y > 0.8) color.y = 0.8 + 0.8* log(color.y/0.8); // if (color.z > 0.8) color.z = 0.8 + 0.8* log(color.z/0.8); // a different exposure filter //color.x = 1.0 - exp(-1.3 * color.x); //color.y = 1.0 - exp(-1.3 * color.y); //color.z = 1.0 - exp(-1.3 * color.z); if (color.x > 0.58) color.x = 1.0 - exp(-1.5 * color.x); if (color.y > 0.58) color.y = 1.0 - exp(-1.5 * color.y); if (color.z > 0.58) color.z = 1.0 - exp(-1.5 * color.z); // reduce the whiteout near the horizon generated by the single scattering approximation //if (ct > cthorizon) color = mix(color, black ,smoothstep(0.2+cthorizon, -0.2+cthorizon, ct)); //else color = mix (color, black, smoothstep(0.2+cthorizon,-0.2+cthorizon, cthorizon)); // fog computations for a ground haze layer, extending from zero to lAltitude float transmission; float vAltitude; float delta_zv; float costheta = ct; float vis = min(visibility, avisibility); // hack - in an effect volume the visibility only may be reduced, so we take care here //if (avisibility < visibility){vis = avisibility;} if (delta_z > 0.0) // we're inside the layer { if (costheta>0.0 + ctterrain) // looking up, view ray intersecting upper layer edge { transmission = exp(-min((delta_z/max(costheta,0.1)),25000.0)/vis); //transmission = 1.0; vAltitude = min(vis * costheta, delta_z); delta_zv = delta_z - vAltitude; } else // looking down, view range intersecting terrain (which may not be drawn) { transmission = exp(alt/vis/costheta); vAltitude = min(-vis * costheta, alt); delta_zv = delta_z + vAltitude; } } else // we see the layer from above { if (costheta < 0.0 + cthorizon) { transmission = exp(-min(lAltitude/abs(costheta),25000.0)/vis); transmission = transmission * exp(-alt/avisibility/abs(costheta)); transmission = 1.0 - (1.0 - transmission) * smoothstep(0+cthorizon, -0.02+cthorizon, costheta); vAltitude = min(lAltitude, -vis * costheta); delta_zv = vAltitude; } else { transmission = 1.0; delta_zv = 0.0; } } // combined intensity reduction by cloud shading and fog self-shading, corrected for Weber-Fechner perception law //float scattering = ground_scattering + (1.0 - ground_scattering) * smoothstep(avisibility, 1.5 * avisibility, -alt/costheta); float eqColorFactor = 1.0 - 0.1 * delta_zv/vis - (1.0 -scattering); // there's always residual intensity, we should never be driven to zero if (eqColorFactor < 0.2) eqColorFactor = 0.2; // postprocessing of haze color vec3 hColor = hazeColor; // high altitude desaturation float intensity = length(hColor); hColor = intensity * normalize (mix(hColor, intensity * vec3 (1.0,1.0,1.0), 0.7* smoothstep(5000.0, 50000.0, alt))); // blue hue hColor.x = 0.83 * hColor.x; hColor.y = 0.9 * hColor.y; // further blueshift when in shadow, either cloud shadow, or self-shadow or Earth shadow, dependent on indirect // light float fade_out = max(0.65 - 0.3 *overcast, 0.45); intensity = length(hColor); hColor = intensity * normalize(mix(hColor, 1.5* vec3 (0.45, 0.6, 0.8), 1.0 -smoothstep(0.25, fade_out,earthShade) )); hColor = intensity * normalize(mix(hColor, 2.0 * vec3 (0.55, 0.6, 0.8), (1.0 - smoothstep(0.3,0.8,eqColorFactor)))); hColor = hColor * earthShade; // accounting for overcast and saturation color = ovc * mix(color, hazeColor * earthShade ,smoothstep(-0.1+ctterrain, 0.0+ctterrain, ct)) + (1-ovc) * color; color = sat * color + (1.0 - sat) * mix(color, black, smoothstep(0.4+cthorizon,0.2+cthorizon,ct)); // the terrain below the horizon gets drawn in one optical thickness vec3 terrainHazeColor = eqColorFactor * hColor; color = mix(color, terrainHazeColor ,smoothstep(0.01 + ctterrain, 0.0+ctterrain, ct)); // mix fog the skydome with the right amount of haze color = transmission * color + (1.0-transmission) * eqColorFactor * hColor; gl_FragColor = vec4(color, 1.0); gl_FragDepth = 0.1; }