// -*-C++-*- #version 120 // Shader that takes a list of GL_POINTS and draws a light (point-sprite like // texture, more accurately a light halo) at the given point. This shader // provides support for light animations like blinking, time period handling // for lights on only during night time or in low visiblity and directional // lighting. // // The actual rendering code is heavily based on an existing implementation // found at: // FGData commit 9355d464c175bd5d51ba32527180ed4e94e86fbb // Shaders/surface-lights-ALS.frag // with minor modifications for readability and tuning. // // Licence: GPL v2+ // Written by Fahim Dalvi, January 2021 uniform sampler2D texture; uniform float visibility; uniform float avisibility; uniform float hazeLayerAltitude; uniform float eye_alt; uniform float terminator; uniform bool use_IR_vision; uniform bool use_night_vision; varying vec3 relativePosition; varying vec2 rawPosition; varying float apparentSize; varying float haloSize; varying float lightSize; varying float lightIntensity; float alt; float Noise2D(in vec2 coord, in float wavelength); float fog_func (in float targ) { float fade_mix; // for large altitude > 30 km, we switch to some component of quadratic distance fading to // create the illusion of improved visibility range targ = 1.25 * targ * smoothstep(0.04,0.06,targ); // need to sync with the distance to which terrain is drawn if (alt < 30000.0) { return exp(-targ - targ * targ * targ * targ); } else if (alt < 50000.0) { fade_mix = (alt - 30000.0)/20000.0; return fade_mix * exp(-targ*targ - pow(targ,4.0)) + (1.0 - fade_mix) * exp(-targ - pow(targ,4.0)); } else { return exp(- targ * targ - pow(targ,4.0)); } } float light_sprite (in vec2 coord, in float transmission, in float noise) { // Center the texture coordinates at (0,0) coord.s = coord.s - 0.5; coord.t = coord.t - 0.5; // Radius of the current pixel from the center of the light ranging from 0 to 1 float r = length(coord) * 2; // If the light is too small, return constant intensity if (apparentSize<1.3) {return 0.08;} // Calculate the rays (star-shaped structure) around the light // These are randomized for every light based on `noise` float angle = noise * 6.2832; float sinphi = dot(vec2 (sin(angle),cos(angle)), normalize(coord)); float sinterm = sin(mod((sinphi-3.0) * (sinphi-3.0),6.2832)); float ray = 0.0; if (sinterm == 0.0) { ray = 0.0; } else { ray = sinterm * sinterm * sinterm * sinterm * sinterm * sinterm * sinterm * sinterm * sinterm * sinterm; } ray *= 0.2 * exp(-4 * pow(r, 2.5)); float fogEffect = (1.0-smoothstep(0.4, 0.8, transmission)); float halo = 0.2 * exp(-4.0 * pow(r, 2.5)); float base = exp(-4 * pow(r * haloSize, 2.5)); // Combine: // base: the central disc of the light // halo: the faint discs around the light // ray: star-like structures around the disk float intensity = clamp(ray + base + halo, 0.0, 1.0) + 0.1 * fogEffect * (1.0-smoothstep(0.3, 0.6, r)); return intensity; } void main() { float dist = length(relativePosition); float delta_z = hazeLayerAltitude - eye_alt; float transmission; float vAltitude; float delta_zv; float H; float distance_in_layer; float transmission_arg; if (use_IR_vision) {discard;} float noise = Noise2D(rawPosition.xy ,1.0); // angle with horizon float ct = dot(vec3(0.0, 0.0, 1.0), relativePosition)/dist; // we solve the geometry what part of the light path is attenuated normally and what is through the haze layer if (delta_z > 0.0) // we're inside the layer { if (ct < 0.0) { // we look down distance_in_layer = dist; vAltitude = min(distance_in_layer,min(visibility, avisibility)) * ct; delta_zv = delta_z - vAltitude; } else { // we may look through upper layer edge H = dist * ct; if (H > delta_z) { distance_in_layer = dist/H * delta_z; } else { distance_in_layer = dist; } vAltitude = min(distance_in_layer,visibility) * ct; delta_zv = delta_z - vAltitude; } } else { // we see the layer from above, delta_z < 0.0 H = dist * -ct; if (H < (-delta_z)) { // we don't see into the layer at all, aloft visibility is the only fading distance_in_layer = 0.0; delta_zv = 0.0; } else { vAltitude = H + delta_z; distance_in_layer = vAltitude/H * dist; vAltitude = min(distance_in_layer,visibility) * (-ct); delta_zv = vAltitude; } } // ground haze cannot be thinner than aloft visibility in the model, // so we need to use aloft visibility otherwise transmission_arg = (dist-distance_in_layer)/avisibility; if (visibility < avisibility) { transmission_arg = transmission_arg + (distance_in_layer/visibility); } else { transmission_arg = transmission_arg + (distance_in_layer/avisibility); } transmission = fog_func(transmission_arg); float lightArg = terminator/100000.0; float attenuationScale = 1.0 + 20.0 * (1.0 -smoothstep(-15.0, 0.0, lightArg)); float dist_att = exp(-dist/200.0/lightSize/attenuationScale); float intensity = light_sprite(gl_TexCoord[0].st, transmission, noise); vec3 light_color = gl_Color.rgb; if (use_night_vision) { light_color.rgb = vec3 (0.0, 1.0, 0.0); } light_color = mix(light_color, vec3 (1.0, 1.0, 1.0), 0.5 * intensity * intensity); gl_FragColor = vec4 (clamp(light_color.rgb, 0.0, 1.0), intensity * transmission * dist_att); }