diff --git a/Effects/cloud-impostor.eff b/Effects/cloud-impostor.eff new file mode 100644 index 000000000..13c675304 --- /dev/null +++ b/Effects/cloud-impostor.eff @@ -0,0 +1,168 @@ + + + Effects/cloud-impostor + + + + /sim/rendering/clouds3d-vis-range + /environment/terminator-relative-position-m + /sim/rendering/eye-altitude-m + /environment/cloud-self-shading + /environment/moonlight + /environment/air-pollution-norm + + + + + + /sim/rendering/shaders/skydome + /sim/rendering/clouds3d-enable + + 1.0 + + + + + + + true + + 0.5 0.5 0.5 1.0 + 0.5 0.5 0.5 1.0 + off + + + greater + 0.01 + + smooth + + src-alpha + one-minus-src-alpha + + + false + + + 9 + DepthSortedBin + + + 0 + texture[0]/type + texture[0]/image + texture[0]/filter + texture[0]/wrap-s + texture[0]/wrap-t + + + + Shaders/cloud-impostor-lightfield.vert + Shaders/cloud-static-lightfield.frag + + + baseTexture + sampler-2d + 0 + + + range + float + range + + + terminator + float + terminator + + + altitude + float + altitude + + + cloud_self_shading + float + cloud_self_shading + + + moonlight + float + moonlight + + + air_pollution + float + air_pollution + + true + + + + + + /sim/rendering/clouds3d-enable + + 1.0 + + + + + + + true + + 0.5 0.5 0.5 1.0 + 0.5 0.5 0.5 1.0 + off + + + greater + 0.01 + + smooth + + src-alpha + one-minus-src-alpha + + + false + + + 9 + DepthSortedBin + + + 0 + texture[0]/type + texture[0]/image + texture[0]/filter + texture[0]/wrap-s + texture[0]/wrap-t + + + + Shaders/cloud-static.vert + Shaders/cloud-static.frag + + + baseTexture + sampler-2d + 0 + + + terminator + float + terminator + + + altitude + float + altitude + + true + + + diff --git a/Nasal/local_weather/cloud_definitions.nas b/Nasal/local_weather/cloud_definitions.nas index c204ccccf..4765380dd 100644 --- a/Nasal/local_weather/cloud_definitions.nas +++ b/Nasal/local_weather/cloud_definitions.nas @@ -536,6 +536,27 @@ else if (type=="Noctilucent") { else if (rn > 0.25) {path = "Models/Weather/noctilucent9.xml";} else {path = "Models/Weather/noctilucent10.xml";} } +else if (type=="Impostor sheet") { + if (subtype=="Nimbus") { + if (rn>0.0) {path = "Models/Weather/impostor_nimbus.xml";} + } + else if (subtype=="broken") { + if (rn>0.5) {path = "Models/Weather/impostor_broken1.xml";} + else if (rn>0.0) {path = "Models/Weather/impostor_broken2.xml";} + } + else if (subtype=="scattered") { + if (rn>0.6) {path = "Models/Weather/impostor_scattered1.xml";} + else if (rn>0.4) {path = "Models/Weather/impostor_scattered2.xml";} + else if (rn > 0.2) {path = "Models/Weather/impostor_few1.xml";} + else {path = "Models/Weather/impostor_few2.xml";} + } + else if (subtype=="few") { + if (rn>0.7) {path = "Models/Weather/impostor_few1.xml";} + else if (rn>0.4) {path = "Models/Weather/impostor_few2.xml";} + else if (rn>0.3) {path = "Models/Weather/impostor_scattered2.xml";} + else {path = "void";} + } + } else if (type == "Cirrocumulus (cloudlet)") { cloudAssembly = local_weather.cloud.new(type, subtype); diff --git a/Nasal/local_weather/compat_layer.nas b/Nasal/local_weather/compat_layer.nas index bee026932..655d0da6c 100644 --- a/Nasal/local_weather/compat_layer.nas +++ b/Nasal/local_weather/compat_layer.nas @@ -20,6 +20,7 @@ # setWindSmoothly to set the wind gradually across a second # smooth_wind_loop (helper function for setWindSmoothly) # create_cloud to place a single cloud into the scenery +# create_impostor to place an impostor sheet mimicking far clouds into the scene # create_cloud_array to place clouds from storage arrays into the scenery # get_elevation to get the terrain elevation at given coordinates # get_elevation_vector to get terrain elevation at given coordinate vector @@ -542,7 +543,36 @@ if (local_weather.dynamics_flag == 1) } +########################################################### +# place an impostor sheet +########################################################### +var create_impostor = func(path, lat, long, alt, heading) { + +var n = props.globals.getNode("local-weather/clouds", 1); +var model_number = n.getNode("model-placement-index").getValue(); +var m = props.globals.getNode("models", 1); + for (var i = model_number; 1; i += 1) + if (m.getChild("model", i, 0) == nil) + break; +var model = m.getChild("model", i, 1); +n.getNode("model-placement-index").setValue(i); + + +model.getNode("path", 1).setValue(path); +model.getNode("latitude-deg", 1).setValue(lat); +model.getNode("longitude-deg", 1).setValue(long); +model.getNode("elevation-ft", 1).setValue(alt); +model.getNode("heading-deg", 1).setValue(local_weather.wind.cloudlayer[0]+180.0); +model.getNode("speed-kt",1).setValue(local_weather.wind.cloudlayer[1]); +model.getNode("load", 1).remove(); + + +var imp = weather_tile_management.cloudImpostor.new(model); +append(weather_tile_management.cloudImpostorArray,imp); + + +} ########################################################### diff --git a/Nasal/local_weather/local_weather.nas b/Nasal/local_weather/local_weather.nas index 61a3a6e82..094639ab4 100644 --- a/Nasal/local_weather/local_weather.nas +++ b/Nasal/local_weather/local_weather.nas @@ -1687,6 +1687,9 @@ settimer ( func { setsize(weather_dynamics.cloudQuadtrees,0);},0.1); # to avoid setsize(effectVolumeArray,0); n_effectVolumeArray = 0; +# remove any impostors + +weather_tile_management.remove_impostors(); # clear any wxradar echos @@ -3676,6 +3679,12 @@ if (compat_layer.features.can_disable_environment ==1) local_weather.setDefaultCloudsOff(); +# read max. visibility range and set far camera clipping + +max_vis_range = math.exp(getprop(lw~"config/aux-max-vis-range-m")); +setprop(lw~"config/max-vis-range-m",max_vis_range); +if (max_vis_range>120000.0){setprop("/sim/rendering/camera-group/zfar",max_vis_range);} + # now see if we need to presample the terrain if ((presampling_flag == 1) and (getprop(lw~"tmp/presampling-status") == "idle")) @@ -3972,6 +3981,8 @@ local_weather.init_sea_colors(); # start the mask loop #local_weather.init_mask(); +# create impostors - this should only happen when sufficiently high in air +weather_tile_management.create_impostors(); # weather_tile_management.watchdog_loop(); diff --git a/Nasal/local_weather/weather_tile_management.nas b/Nasal/local_weather/weather_tile_management.nas index ab9162aa4..b4f3f3dde 100644 --- a/Nasal/local_weather/weather_tile_management.nas +++ b/Nasal/local_weather/weather_tile_management.nas @@ -14,6 +14,8 @@ # create_neighbours to initialize the 8 neighbours of the initial tile # buffer_loop to manage the buffering of faraway clouds in an array # housekeeping_loop to shift clouds from the scenery into the buffer +# remove_impostors to delete a ring of impostors to mimick distant clouds +# create_impostors to create a ring of impostors to mimick distant clouds # watchdog loop (debug helping structure) # calc_geo to get local Cartesian geometry for latitude conversion # get_lat to get latitude from Cartesian coordinates @@ -27,6 +29,7 @@ # cloud to provide the data hash for the new cloud rendering system # cloudBuffer to store a cloud in a Nasal buffer, to provide methods to move it # cloudScenery to store info for clouds in scenery, to provide methods to move and evolve them +# cloudImpostor to provide the hash data for an impostor cloud sheet ################################### @@ -876,6 +879,11 @@ if (system_rotation_angle > 0.0) create_neighbour(lat, lon, 9, alpha); rotate_tile_scheme(system_rotation_angle); } + +# ready the system for creating impostors + +impostor_trigger = 1; + } @@ -1190,6 +1198,57 @@ if (getprop(lw~"housekeeping-loop-flag") ==1) {settimer( func {housekeeping_loop } +############################### +# impostor handline routines +############################### + +var impostor_trigger = 0; + +var impostor_type_map = {"low_pressure_core" : "Nimbus", "low_pressure" : "broken", "low_pressure_border": "broken", "high_pressure_border" : "scattered", "high_pressure" : "few", "high_pressure_core": "few"}; + +var remove_impostors = func { + +foreach (entry;cloudImpostorArray) + { + entry.removeNodes(); + } +setsize(cloudImpostorArray,0); +} + +var create_impostors = func { + +var visibility = getprop("/environment/visibility-m"); +var cloud_range = getprop("/sim/rendering/clouds3d-vis-range"); + +if ((visibility < 80000.0) or (cloud_range < 70000.0)) {return;} + +if (visibility < cloud_range) {var range = visibility;} +else {var range = cloud_range;} + +var n = int ((range - 60000.0)/40000.0); + +var lat = getprop(lw~"tiles/tile[4]/latitude-deg"); +var lon = getprop(lw~"tiles/tile[4]/longitude-deg"); +var alpha = getprop(lw~"tiles/tile[4]/orientation-deg"); +var code = getprop(lw~"tiles/tile[4]/code"); +var index = getprop(lw~"tiles/tile[4]/tile-index"); +var alt_offset = getprop(lw~"tmp/tile-alt-offset-ft"); +var alt = weather_dynamics.tile_convective_altitude[index-1] + 1000.0 + alt_offset; + +if (contains(impostor_type_map,code)) {var type = impostor_type_map[code];} +else {var type = "scattered";} + +if (local_weather.debug_output_flag == 1) + { + printf("Creating impostor ring..."); + print(lat, " ", lon, " ", alt, " ", alpha, " ", type, " ", n); + } + +weather_tiles.create_impostor_ring(lat, lon, alt, alpha, type, n); +} + + + ############################### # watchdog loop for debugging ############################### @@ -1369,6 +1428,21 @@ var cloudBuffer = { }; +var cloudImpostorArray = []; + +var cloudImpostor = { + new: func(modelNode) { + var c = { parents: [cloudImpostor] }; + c.modelNode = modelNode; + return c; + }, + removeNodes: func { + me.modelNode.remove(); + }, +}; + + + var cloudSceneryArray = []; var n_cloudSceneryArray = 0; diff --git a/Nasal/local_weather/weather_tiles.nas b/Nasal/local_weather/weather_tiles.nas index 77576174d..49fabeca1 100644 --- a/Nasal/local_weather/weather_tiles.nas +++ b/Nasal/local_weather/weather_tiles.nas @@ -78,6 +78,15 @@ else # without worker threads, tile generation is complete at this point {props.globals.getNode(lw~"tiles").getChild("tile",dir_index).getNode("generated-flag").setValue(2);} +# generate impostor ring if applicable + +if (impostor_trigger == 1) + { + weather_tile_management.remove_impostors(); + weather_tile_management.create_impostors(); + impostor_trigger = 0; + } + } @@ -4418,6 +4427,38 @@ for (var i=0; i<2; i=i+1) } +var create_impostor_ring = func (lat, lon, alt, alpha, type, n) { + +var path = local_weather.select_cloud_model("Impostor sheet", type); +var phi = alpha * math.pi/180.0; + +var limit = 4 + 2 * n; + +for (var i = 0; i< limit+1; i=i+1) + { + for (var j = 0; j 6000.0) + { + x=x*1000.0; y=y*1000.0; + path = local_weather.select_cloud_model("Impostor sheet", type); + if (path != "void") + { + var rnd = rand(); + if (rnd > 0.75) {alpha = alpha + 90.0;} + compat_layer.create_impostor(path, lat + get_lat(x,y,phi), lon+get_lon(x,y,phi), alt ,alpha); + } + } + } + } + + +} + + ################### # helper functions ################### diff --git a/Shaders/cloud-impostor-lightfield.vert b/Shaders/cloud-impostor-lightfield.vert new file mode 100644 index 000000000..0992cfd65 --- /dev/null +++ b/Shaders/cloud-impostor-lightfield.vert @@ -0,0 +1,146 @@ +// -*-C++-*- +#version 120 + +varying float fogFactor; +varying vec3 hazeColor; + +uniform float terminator; +uniform float altitude; +uniform float cloud_self_shading; +uniform float moonlight; +uniform float air_pollution; +uniform float range; + +const float shade = 1.0; +const float cloud_height = 1000.0; +const float EarthRadius = 5800000.0; + +// light_func is a generalized logistic function fit to the light intensity as a function +// of scaled terminator position obtained from Flightgear core + +float light_func (in float x, in float a, in float b, in float c, in float d, in float e) +{ +x = x-0.5; + +// use the asymptotics to shorten computations +if (x > 30.0) {return e;} +if (x < -15.0) {return 0.03;} + + +return e / pow((1.0 + a * exp(-b * (x-c)) ),(1.0/d)); +} + +void main(void) +{ + + vec3 shadedFogColor = vec3 (0.65, 0.67, 0.78); + vec3 moonLightColor = vec3 (0.095, 0.095, 0.15) * moonlight; + + gl_TexCoord[0] = gl_TextureMatrix[0] * gl_MultiTexCoord0; + //gl_TexCoord[0] = gl_MultiTexCoord0 + vec4(textureIndexX, textureIndexY, 0.0, 0.0); + vec4 ep = gl_ModelViewMatrixInverse * vec4(0.0,0.0,0.0,1.0); + vec4 l = gl_ModelViewMatrixInverse * vec4(0.0,0.0,1.0,1.0); + vec3 u = normalize(ep.xyz - l.xyz); + + gl_Position = vec4(0.0, 0.0, 0.0, 1.0); + gl_Position.x = gl_Vertex.x; + gl_Position.y += gl_Vertex.y; + gl_Position.z += gl_Vertex.z; + gl_Position.xyz += gl_Color.xyz; + + + // Determine a lighting normal based on the vertex position from the + // center of the cloud, so that sprite on the opposite side of the cloud to the sun are darker. + float n = dot(normalize(-gl_LightSource[0].position.xyz), + normalize(mat3x3(gl_ModelViewMatrix) * (- gl_Position.xyz)));; + + // Determine the position - used for fog and shading calculations + vec3 ecPosition = vec3(gl_ModelViewMatrix * gl_Position); + float fogCoord = abs(ecPosition.z); + float fract = smoothstep(0.0, cloud_height, gl_Position.z + cloud_height); + + vec3 relVector = gl_Position.xyz - ep.xyz; + gl_Position = gl_ModelViewProjectionMatrix * gl_Position; + + // Light at the final position + + // first obtain normal to sun position + + vec3 lightFull = (gl_ModelViewMatrixInverse * gl_LightSource[0].position).xyz; + vec3 lightHorizon = normalize(vec3(lightFull.x,lightFull.y, 0.0)); + + // yprime is the distance of the vertex into sun direction, corrected for altitude + //float vertex_alt = max(altitude * 0.30480 + relVector.z,100.0); + float vertex_alt = altitude + relVector.z; + float yprime = -dot(relVector, lightHorizon); + float yprime_alt = yprime -sqrt(2.0 * EarthRadius * vertex_alt); + + // compute the light at the position + vec4 light_diffuse; + + float lightArg = (terminator-yprime_alt)/100000.0; + + light_diffuse.b = light_func(lightArg -1.2 * air_pollution, 1.330e-05, 0.264, 2.227, 1.08e-05, 1.0); + light_diffuse.g = light_func(lightArg -0.6 * air_pollution, 3.931e-06, 0.264, 3.827, 7.93e-06, 1.0); + light_diffuse.r = light_func(lightArg, 8.305e-06, 0.161, 3.827, 3.04e-05, 1.0); + light_diffuse.a = 1.0; + + //float light_intensity = light_func(lightArg, 8.305e-06, 0.161, 3.827, 3.04e-05, 1.0); + //vec4 light_diffuse = vec4 (0.57, 0.57, 0.9, 1.0); + //light_diffuse.rgb = light_intensity * light_diffuse.rgb; + + // two times terminator width governs how quickly light fades into shadow + float terminator_width = 200000.0; + float earthShade = 0.9 * smoothstep(terminator_width+ terminator, -terminator_width + terminator, yprime_alt) + 0.1; + + //float intensity = length(light_diffuse.rgb); + float intensity = (1.0 - (0.5 * (1.0 - earthShade))) * length(light_diffuse.rgb); + //light_diffuse.rgb = intensity * normalize(mix(light_diffuse.rgb, shadedFogColor, (1.0 - smoothstep(0.5,0.9, cloud_self_shading )))); + light_diffuse.rgb = intensity * normalize(mix(light_diffuse.rgb, shadedFogColor, (1.0 - smoothstep(0.5,0.9, cloud_self_shading )))); + if (earthShade < 0.6) + { + intensity = length(light_diffuse.rgb); + light_diffuse.rgb = intensity * normalize(mix(light_diffuse.rgb, shadedFogColor, 1.0 -smoothstep(0.1, 0.6,earthShade ) )); + } + + // Determine the shading of the sprite based on its vertical position and position relative to the sun. + // n = min(smoothstep(-0.5, 0.0, n), fract); +// Determine the shading based on a mixture from the backlight to the front + //vec4 backlight = light_diffuse * shade; + + gl_FrontColor = light_diffuse;//mix(backlight, light_diffuse, n); + //gl_FrontColor += gl_FrontLightModelProduct.sceneColor; + + // As we get within 100m of the sprite, it is faded out. Equally at large distances it also fades out. + gl_FrontColor.a = min(smoothstep(100.0, 250.0, fogCoord), 1.0 - smoothstep(0.9 * range, range, fogCoord)); + // During the day, noctilucent clouds are invisible + //gl_FrontColor.a = gl_FrontColor.a * (1.0 - smoothstep(3.0,5.0,lightArg)); + + + // Fog doesn't affect rain as much as other objects. + //fogFactor = exp( -gl_Fog.density * fogCoord * 0.4); + //fogFactor = clamp(fogFactor, 0.0, 1.0); + +float fadeScale = 0.05 + 0.2 * log(fogCoord/1000.0); + if (fadeScale < 0.05) fadeScale = 0.05; + fogFactor = exp( -gl_Fog.density * 0.5* fogCoord * fadeScale); + + hazeColor = light_diffuse.rgb; + hazeColor.r = hazeColor.r * 0.83; + hazeColor.g = hazeColor.g * 0.9; + + // in sunset or sunrise conditions, do extra shading of clouds + + + + + + + + //hazeColor = hazeColor * earthShade; + //gl_FrontColor.rgb = gl_FrontColor.rgb * earthShade; + gl_FrontColor.rgb = gl_FrontColor.rgb + moonLightColor * (1.0 - smoothstep(0.4, 0.5, earthShade)); + hazeColor.rgb = hazeColor.rgb + moonLightColor * (1.0 - smoothstep(0.4, 0.5, earthShade)); + gl_BackColor = gl_FrontColor; + +} diff --git a/gui/dialogs/rendering.xml b/gui/dialogs/rendering.xml index 05d1f2617..3209a8dcf 100644 --- a/gui/dialogs/rendering.xml +++ b/gui/dialogs/rendering.xml @@ -616,7 +616,7 @@ cloud-vis-range 1000.0 - 45000.0 + 150000.0 /sim/rendering/clouds3d-vis-range dialog-apply