diff --git a/docs/opengl46-renderer.md b/docs/opengl46-renderer.md index 4a2f72fd..df3eb8b9 100644 --- a/docs/opengl46-renderer.md +++ b/docs/opengl46-renderer.md @@ -70,6 +70,7 @@ existed. | `gfx.postfx.autoexposure.key` | `0.3` | average luminance of the image the exposure is set for | | `gfx.postfx.autoexposure.min` | `-1.0` | the most the exposure darkens a bright image, EV | | `gfx.postfx.autoexposure.max` | `0.5` | the most the exposure brightens a dark image, EV. Kept low on purpose: tunnels and the night are not meant to be brightened much | +| `gfx.postfx.autoexposure.max.cab` | `2.0` | the same seen from the cab by day: the eye takes to the light of the cab, which the bright windows would leave dark. It goes down to `gfx.postfx.autoexposure.max` with the daylight around the cab (the light level of the simulation, the occlusion of the sky): at night and in tunnels it's that one | | `gfx.postfx.autoexposure.speed.dark` | `1.0` | rate of adaptation to a darker image, 1/s | | `gfx.postfx.autoexposure.speed.bright` | `3.0` | rate of adaptation to a brighter image, 1/s | | `gfx.postfx.autoexposure.compensation` | `0.0` | fixed exposure correction, EV; applies also with the automatic exposure off | @@ -270,6 +271,10 @@ so its probe is drawn without the sun and again with it in the next frame. The c before (the shadow map); the ambient light is left out of the probe, the cab gets it as before. `gfx.cabprobe no` is the old way (the share of the lamp in the ambient light, the sky in the reflections, `gfx.interior.bounce` for the sun). +The parts of the cab of the `sunless` materials get no light of the sun; where it falls on them the ambient light is raised in the +proportion their shadow tone gives (at most four times), and their shadow (of the walls of the cab, of the clouds) leaves the +ambient light as it is. Before, the shadow darkened it to the shadow tone, so the whole cab went dark as in a tunnel. + **The low poly interiors** of the vehicles (and the cab when it has no probe) get the sun which comes in through their windows and bounces around them as a share of the light of the sun outside (`gfx.interior.bounce`), held back by the scenery and the clouds like the sun; the interior isn't known well enough for more. Both kinds of interiors take only a share of the ambient @@ -306,6 +311,14 @@ Things narrower than about three texels (poles, masts, fences) are left out. Fro and buildings towards it, under the ceiling what the rays reach under the vault from the portal (a low sun shines a little way into a tunnel). +With `gfx.skyvisibility.tunnels yes` (the default) all this is done only around the tracks with the `tunnel` flag (see the table): +the flag tells where a tunnel is, the geometry how dark it is and where it begins. + +A point is taken as under the scenery only where a ceiling is over it (a tunnel, a bridge, a roof). Under the top with no +ceiling over it, which a surface that is seen can't really be (the edges of platforms and walls the texels are blended over, +holes cut in the terrain, parts of models sunk in the ground), it's lit as at the top, in the open: no darkness of a tunnel +anywhere but under something. + The shading takes the ambient light, the reflection of the sky and the sun by them, wherever a surface is (the half of a train in the tunnel is dark, the other half in the light, with the passage over a few metres at the portal). The headlights, the lamps and the light of the cab are direct light and are not held back; the headlights fade in daylight by the light around the @@ -330,6 +343,7 @@ and the scenery is lit as in the open everywhere. | `gfx.skyvisibility` | `medium` | `no`/`low`/`medium`/`high` (or 0..3), see above | | `gfx.skyvisibility.sun yes/no` | `yes` | the sun is held back too (beyond the reach of the shadow map, and in the tunnels whatever the shadow map holds); `no`: the sun only by the shadow map | | `gfx.skyvisibility.fog yes/no` | `yes` | the light of the volumetric fog is held back too | +| `gfx.skyvisibility.tunnels yes/no` | `yes` | the occlusion only around the tracks with the `tunnel` flag: whole within 20 m of them, fading out by 35 m; there the geometry makes the tunnel dark (the portals, the passage at them, the half of a train in the tunnel), elsewhere the scenery is lit as in the open (no darkening between platforms, in cuttings, under shelters). `no`: everywhere, by the geometry alone | | `gfx.tunnelflags yes/no` | `no` | `yes`: the flags `tunnel`/`canyon` of the tracks darken the tracks and the vehicles on them, as before (on top of the occlusion) | All of them can be changed while the simulator runs, in the **Sky and exposure** debug window, which also shows views of the diff --git a/rendering/common/exposure.cpp b/rendering/common/exposure.cpp index 1d5c98ce..872ed94c 100644 --- a/rendering/common/exposure.cpp +++ b/rendering/common/exposure.cpp @@ -11,7 +11,12 @@ http://mozilla.org/MPL/2.0/. #include "rendering/common/exposure.h" #include "gl/ubo.h" +#include "rendering/common/skyocclusion.h" +#include "simulation/simulation.h" #include "utilities/Globals.h" +#include "utilities/utilities.h" +#include "vehicle/DynObj.h" +#include "vehicle/Train.h" namespace { @@ -29,6 +34,18 @@ float const WHITE_BALANCE_REFERENCE{6500.f}; // distance off the daylight locus (duv, cie 1960 ucs) of the tint at its ends (-1, 1) float const WHITE_BALANCE_TINT_DUV{0.02f}; +// how much of the daylight is around the camera in the cab of its vehicle, 0..1: 0 out of the cab, at night, in a tunnel. the eye +// seated in the cab takes to its light, which the image of the cab with the bright windows would leave dark +float cab_daylight() +{ + if (FreeFlyModeFlag || (false == Global.render_cab) || (simulation::Train == nullptr)) + return 0.f; + auto const *vehicle{simulation::Train->Dynamic()}; + if ((vehicle == nullptr) || (false == vehicle->bDisplayCab) || (vehicle->mdKabina == nullptr) || (vehicle->mdKabina == vehicle->mdModel)) + return 0.f; + return std::clamp(static_cast(Global.fLuminance) * gfx::vehicle_light_level(vehicle), 0.f, 1.f); +} + // chromaticity (cie xy) of the cie daylight of the correlated colour temperature Kelvin; the formula holds for 4000..25000 K glm::dvec2 daylight_chromaticity(double const Kelvin) { @@ -94,7 +111,9 @@ glm::vec4 exposure_settings::adaptation(double const Deltatime) glm::vec2 exposure_settings::exposure_range() { - return glm::vec2{std::exp2(Global.gfx_postfx_autoexposure_min), std::exp2(Global.gfx_postfx_autoexposure_max)}; + // seen from the cab by day the image may be brightened more (gfx.postfx.autoexposure.max.cab), by the daylight around the cab + auto const highest{std::max(Global.gfx_postfx_autoexposure_max, Global.gfx_postfx_autoexposure_max + (Global.gfx_postfx_autoexposure_max_cab - Global.gfx_postfx_autoexposure_max) * cab_daylight())}; + return glm::vec2{std::exp2(Global.gfx_postfx_autoexposure_min), std::exp2(highest)}; } std::vector exposure_settings::bloom_sizes(int const Width, int const Height) @@ -137,6 +156,7 @@ void exposure_settings::draw_debug_ui(float const *Readback) ImGui::SliderFloat("key", &Global.gfx_postfx_autoexposure_key, 0.02f, 2.f, "%.3f", ImGuiSliderFlags_Logarithmic); ImGui::SliderFloat("min EV", &Global.gfx_postfx_autoexposure_min, -4.f, 0.f); ImGui::SliderFloat("max EV", &Global.gfx_postfx_autoexposure_max, 0.f, 4.f); + ImGui::SliderFloat("max EV in the cab by day", &Global.gfx_postfx_autoexposure_max_cab, 0.f, 4.f); ImGui::SliderFloat("adaptation to dark (1/s)", &Global.gfx_postfx_autoexposure_speed_dark, 0.05f, 10.f); ImGui::SliderFloat("adaptation to bright (1/s)", &Global.gfx_postfx_autoexposure_speed_bright, 0.05f, 10.f); ImGui::SliderFloat("compensation EV", &Global.gfx_postfx_autoexposure_compensation, -4.f, 4.f); diff --git a/rendering/common/skyocclusion.cpp b/rendering/common/skyocclusion.cpp index e52776c4..f0b648e3 100644 --- a/rendering/common/skyocclusion.cpp +++ b/rendering/common/skyocclusion.cpp @@ -21,6 +21,7 @@ http://mozilla.org/MPL/2.0/. #include "utilities/Logs.h" #include "utilities/utilities.h" #include "vehicle/DynObj.h" +#include "world/Track.h" namespace gfx { @@ -60,6 +61,11 @@ float const COVER_SPAN{1.f}; float const SUN_SOFTNESS{0.6f}; // a key light moving more than this (the cosine of the angle) has the occlusion of the key light worked out anew double const KEYLIGHT_MOVED{0.99999}; // ~0.26 degree +// with Global.gfx_skyvisibility_tunnels: the occlusion is kept whole within this distance of a track with the tunnel flag and fades +// out by the second one, metres; the tracks are followed in steps of at most the third +float const TUNNEL_MASK_FULL{20.f}; +float const TUNNEL_MASK_REACH{35.f}; +double const TUNNEL_MASK_STEP{2.0}; sky_occlusion *g_current{nullptr}; @@ -117,7 +123,9 @@ glm::vec2 texel_occlusion(glm::vec4 const &Heights, glm::vec4 const &Occlusion, { auto const top{Heights.x}; auto const ceiling{Heights.y}; - auto const cover{std::clamp((top - Height - COVER_START) / COVER_SPAN, 0.f, 1.f)}; + // under the scenery only where a ceiling is over the point; under the top with nothing over it it's taken as at the top + auto const roofed{has_ceiling(ceiling) ? std::clamp(ceiling - Height + 1.f, 0.f, 1.f) : 0.f}; + auto const cover{std::clamp((top - Height - COVER_START) / COVER_SPAN, 0.f, 1.f) * roofed}; auto under{0.f}; if (has_ceiling(ceiling)) { @@ -126,7 +134,7 @@ glm::vec2 texel_occlusion(glm::vec4 const &Heights, glm::vec4 const &Occlusion, under = Occlusion.z + (Occlusion.y - Occlusion.z) * t; } auto const sky{Occlusion.x + (under - Occlusion.x) * cover}; - auto const suntop{smoothstep(-SUN_SOFTNESS, SUN_SOFTNESS, Height - (top + Occlusion.w))}; + auto const suntop{smoothstep(-SUN_SOFTNESS, SUN_SOFTNESS, std::max(Height, top - COVER_START) - (top + Occlusion.w))}; auto const sununder{smoothstep(-SUN_SOFTNESS, SUN_SOFTNESS, Heights.z - Height)}; return {sky, suntop + (sununder - suntop) * cover}; } @@ -236,6 +244,7 @@ void sky_occlusion::start(int const Level) m_side = side; m_level = Level; m_sun = Global.gfx_skyvisibility_sun; + m_tunnels = Global.gfx_skyvisibility_tunnels; auto const count{static_cast(m_layout.tiles * m_layout.tiles)}; m_slots.assign(count, slot{}); auto const texels{static_cast(side) * side}; @@ -257,6 +266,7 @@ void sky_occlusion::start(int const Level) m_rawceiling.assign(texels, NONE_CEILING); m_top.assign(texels, NONE_TOP); m_ceiling.assign(texels, NONE_CEILING); + m_mask.assign(texels, m_tunnels ? 0.f : 1.f); m_workertiles.assign(count, glm::ivec2{std::numeric_limits::min()}); m_workergenerations.assign(count, 0); m_workerraster.assign(count, false); @@ -295,6 +305,7 @@ void sky_occlusion::stop() m_rawceiling.clear(); m_top.clear(); m_ceiling.clear(); + m_mask.clear(); m_jobs.clear(); m_results.clear(); if (g_current == this) @@ -359,7 +370,7 @@ void sky_occlusion::apply(tile_result &Result) void sky_occlusion::update(scene::basic_region *Region, glm::dvec3 const &Camera, glm::vec3 const &Keylight) { auto const level{std::clamp(Global.gfx_skyvisibility, 0, 3)}; - if ((level != m_level) || ((m_side > 0) && (m_sun != Global.gfx_skyvisibility_sun))) + if ((level != m_level) || ((m_side > 0) && ((m_sun != Global.gfx_skyvisibility_sun) || (m_tunnels != Global.gfx_skyvisibility_tunnels)))) { stop(); if (level > 0) @@ -453,6 +464,8 @@ void sky_occlusion::update(scene::basic_region *Region, glm::dvec3 const &Camera job.generation = target.generation; target.signature = signature(Region, target.tile); gather(Region, target.tile, job.triangles); + if (m_tunnels) + gather_tunnels(Region, target.tile, job.tunnels); m_gathertime += std::chrono::duration(std::chrono::steady_clock::now() - gatherstart).count(); m_triangles = static_cast(job.triangles.size()); if (target.regather) @@ -601,6 +614,14 @@ std::uint64_t sky_occlusion::signature(scene::basic_region *Region, glm::ivec2 c continue; hash = hash_mix(hash, static_cast(cell.m_shapesopaque.size())); hash = hash_mix(hash, static_cast(cell.m_instancesopaque.size())); + if (m_tunnels) + { + for (auto const *path : cell.m_paths) + { + if ((path != nullptr) && (path->eEnvironment == e_tunnel)) + hash = hash_mix(hash, static_cast(reinterpret_cast(path))); + } + } for (auto &shape : cell.m_shapesopaque) { auto const &data{shape.data()}; @@ -718,6 +739,46 @@ void sky_occlusion::gather(scene::basic_region *Region, glm::ivec2 const &Tile, } } +void sky_occlusion::gather_tunnels(scene::basic_region *Region, glm::ivec2 const &Tile, std::vector &Points) +{ + auto const tilesize{static_cast(m_layout.tile)}; + auto const reach{static_cast(TUNNEL_MASK_REACH)}; + glm::dvec2 const corner{Tile.x * tilesize, Tile.y * tilesize}; + glm::dvec2 const low{corner - glm::dvec2{reach}}; + glm::dvec2 const high{corner + glm::dvec2{tilesize + reach}}; + glm::dvec3 const center{corner.x + 0.5 * tilesize, 0.0, corner.y + 0.5 * tilesize}; + std::vector done; + auto const &found{Region->sections(center, static_cast(tilesize * 0.75 + reach))}; + std::vector const sections{found.begin(), found.end()}; + for (auto *section : sections) + { + for (auto &cell : section->m_cells) + { + auto const &area{cell.area()}; + auto const radius{static_cast(area.radius)}; + if ((area.center.x + radius < low.x) || (area.center.x - radius > high.x) || (area.center.z + radius < low.y) || (area.center.z - radius > high.y)) + continue; + for (auto const *path : cell.m_paths) + { + if ((path == nullptr) || (path->eEnvironment != e_tunnel) || (path->CurrentSegment() == nullptr)) + continue; + if (std::find(done.begin(), done.end(), path) != done.end()) + continue; + done.push_back(path); + auto const &segment{*path->CurrentSegment()}; + auto const steps{std::max(1, static_cast(std::ceil(segment.GetLength() / TUNNEL_MASK_STEP)))}; + for (int step = 0; step <= steps; ++step) + { + auto const point{segment.FastGetPoint(static_cast(step) / steps)}; + if ((point.x < low.x) || (point.x > high.x) || (point.z < low.y) || (point.z > high.y)) + continue; + Points.emplace_back(static_cast(point.x - corner.x), static_cast(point.z - corner.y)); + } + } + } + } +} + void sky_occlusion::gather_model(TModel3d *Model, std::uint32_t const Alpha, glm::dmat4 const &Transform, glm::dvec2 const &Corner, glm::dvec2 const &Far, std::vector &Triangles) { if ((Model == nullptr) || (Model->Root == nullptr)) @@ -811,6 +872,7 @@ void sky_occlusion::work() std::fill_n(m_rawceiling.begin() + row, px, NONE_CEILING); std::fill_n(m_top.begin() + row, px, NONE_TOP); std::fill_n(m_ceiling.begin() + row, px, NONE_CEILING); + std::fill_n(m_mask.begin() + row, px, m_tunnels ? 0.f : 1.f); } } } @@ -897,6 +959,20 @@ void sky_occlusion::rasterize(raster_job &Job) auto const row{static_cast(z0 + z) * m_side + x0}; std::fill_n(m_rawtop.begin() + row, px, NONE_TOP); std::fill_n(m_rawceiling.begin() + row, px, NONE_CEILING); + // the mask of the tunnels: by the distance of the middle of a texel from the nearest point along a track with the flag + for (int x = 0; x < px; ++x) + { + auto weight{1.f}; + if (m_tunnels) + { + glm::vec2 const middle{(static_cast(x) + 0.5f) * texel, (static_cast(z) + 0.5f) * texel}; + auto nearest{TUNNEL_MASK_REACH * TUNNEL_MASK_REACH}; + for (auto const &point : Job.tunnels) + nearest = std::min(nearest, glm::dot(point - middle, point - middle)); + weight = 1.f - smoothstep(TUNNEL_MASK_FULL, TUNNEL_MASK_REACH, std::sqrt(nearest)); + } + m_mask[row + x] = weight; + } } for (auto const &entry : Job.triangles) { @@ -1137,7 +1213,9 @@ void sky_occlusion::occlude(int const Slot, tile_result &Result) auto const c0{ceiling[index]}; auto heights{NEUTRAL_HEIGHTS}; auto value{NEUTRAL_OCCLUSION}; - if (has_top(t0)) + // away from the tunnels (Global.gfx_skyvisibility_tunnels) the texel is in the open, fading into it at the edge of the mask + auto const weight{m_mask[static_cast(wrap(tile.y * px + z, m_side)) * m_side + wrap(tile.x * px + x, m_side)]}; + if (has_top(t0) && (weight > 0.f)) { heights.x = t0; auto const origin{t0 + ABOVE_TOP}; @@ -1213,6 +1291,22 @@ void sky_occlusion::occlude(int const Slot, tile_result &Result) } } } + if (weight <= 0.f) + { + heights = NEUTRAL_HEIGHTS; + value = NEUTRAL_OCCLUSION; + } + else if (weight < 1.f) + { + value.x = 1.f - (1.f - value.x) * weight; + value.y = 1.f - (1.f - value.y) * weight; + value.z = 1.f - (1.f - value.z) * weight; + if (weight < 0.5f) + { + heights.z = LIT_BELOW_ALWAYS; + value.w = LIT_ABOVE_ALWAYS; + } + } auto const target{static_cast(z) * px + x}; for (int c = 0; c < 4; ++c) { @@ -1253,6 +1347,7 @@ void sky_occlusion::draw_debug_ui() ImGui::Combo("occlusion of the sky by the scenery (gfx.skyvisibility)", &Global.gfx_skyvisibility, levels, IM_ARRAYSIZE(levels)); ImGui::Checkbox("the sun held back too (gfx.skyvisibility.sun)", &Global.gfx_skyvisibility_sun); ImGui::Checkbox("in the volumetric fog (gfx.skyvisibility.fog)", &Global.gfx_skyvisibility_fog); + ImGui::Checkbox("only around the tracks with the tunnel flag (gfx.skyvisibility.tunnels)", &Global.gfx_skyvisibility_tunnels); ImGui::Checkbox("environment flags of the tracks darken as before (gfx.tunnelflags)", &Global.gfx_tunnelflags); if (m_side <= 0) return; diff --git a/rendering/common/skyocclusion.h b/rendering/common/skyocclusion.h index 739a2bb7..2a9de973 100644 --- a/rendering/common/skyocclusion.h +++ b/rendering/common/skyocclusion.h @@ -142,6 +142,8 @@ class sky_occlusion int slot{0}; std::uint32_t generation{0}; std::vector triangles; + // points along the tracks with the tunnel flag over the tile and around it, x and z relative to the corner of the tile + std::vector tunnels; }; // what it gives back: the heights (four per texel) and the occlusion of the texels of a tile struct tile_result @@ -175,6 +177,8 @@ class sky_occlusion void gather(scene::basic_region *Region, glm::ivec2 const &Tile, std::vector &Triangles); void gather_model(TModel3d *Model, std::uint32_t const Alpha, glm::dmat4 const &Transform, glm::dvec2 const &Corner, glm::dvec2 const &Far, std::vector &Triangles); void gather_submodel(TSubModel *Submodel, std::uint32_t const Alpha, glm::dmat4 const &Transform, glm::dvec2 const &Corner, glm::dvec2 const &Far, std::vector &Triangles); + // the points along the tracks with the tunnel flag within the reach of the mask of the tunnels of a tile (gfx_skyvisibility_tunnels) + void gather_tunnels(scene::basic_region *Region, glm::ivec2 const &Tile, std::vector &Points); // what the scenery over a tile is made of, cheap to work out: a change of it means the tile is gathered again std::uint64_t signature(scene::basic_region *Region, glm::ivec2 const &Tile); // the values of a tile nothing is known of yet, in the textures and in the copy of the cpu @@ -191,6 +195,8 @@ class sky_occlusion layout m_layout; int m_level{0}; bool m_sun{true}; + // the occlusion only around the tracks with the tunnel flag, as at the start (Global.gfx_skyvisibility_tunnels) + bool m_tunnels{true}; std::vector m_slots; glm::ivec2 m_cameratile{0}; glm::dvec3 m_camera{0.0}; @@ -220,6 +226,9 @@ class sky_occlusion std::thread m_thread; // the maps of the thread: the top and the ceiling as rasterized, and opened std::vector m_rawtop, m_rawceiling, m_top, m_ceiling; + // how much of the occlusion a texel keeps, 0..1, by its distance from the tracks with the tunnel flag (1 everywhere without + // m_tunnels) + std::vector m_mask; // statistics std::uint32_t m_gathered{0}; diff --git a/shaders/ogl46/light_common.glsl b/shaders/ogl46/light_common.glsl index f15751d9..e5fa7862 100644 --- a/shaders/ogl46/light_common.glsl +++ b/shaders/ogl46/light_common.glsl @@ -825,7 +825,7 @@ vec3 cab_probe_reflection(vec3 surroundings, vec3 fragnormal, float roughness, v // Texturecolor is the albedo (the base colour of a metal), Specularity the specular level of a dielectric (0.5 is the // reflectance of 4% at normal incidence), Reflectivity how much of the reflected surroundings is the cubemap rather than the // ambient light, Shadowtone the share of the sun left in its shadow (1 for the materials which take no shadows). -// a Sunless surface (the interior of a cab) gets no light of the sun, its shadow only darkens the ambient light +// a Sunless surface (the interior of a cab) gets no light of the sun, where the sun falls it brightens the ambient light vec3 shade_surface(vec3 fragcolor, vec3 fragnormal, vec3 texturecolor, float reflectivity, float specularity, float shadowtone, bool sunless) { // Diffuse colour is applied once, to the albedo; the light terms below are not scaled by it again @@ -964,10 +964,12 @@ vec3 shade_surface(vec3 fragcolor, vec3 fragnormal, vec3 texturecolor, float ref float cubemap_shadow = 1.0; if (sunless) { - // the sun adds no light in the cab, where it would fall its shadow darkens the ambient light + // the sun adds no light of its own in the cab; where it falls the ambient light is raised as much as the shadow used to lower + // it (the share Shadowtone, at most 4 times), so the shadows of the walls of the cab and of the clouds leave the ambient light + // as it is instead of darkening the cab float diffuseamount = sun_NdotL * lights[0].intensity; - float darkness = (shadowtone < 1.0 ? max(shadow, 1.0 - cloudsun) : 0.0); - fragcolor = mix(fragcolor, fragcolor * shadowtone, clamp(diffuseamount * darkness, 0.0, 1.0)); + float sunlit = (shadowtone < 1.0 ? 1.0 - max(shadow, 1.0 - cloudsun) : 0.0); + fragcolor *= 1.0 + clamp(diffuseamount * sunlit, 0.0, 1.0) * (1.0 - shadowtone) / max(shadowtone, 0.25); } else cubemap_shadow = 1.0 - (1.0 - shadowtone) * shadow * 0.5; @@ -1003,7 +1005,7 @@ vec3 apply_lights(vec3 fragcolor, vec3 fragnormal, vec3 texturecolor, float refl return shade_surface(fragcolor, fragnormal, texturecolor, reflectivity, specularity, shadowtone, false); } -// the interior of a cab: no light of the sun, its shadow darkens the ambient light +// the interior of a cab: no light of the sun, where it falls it brightens the ambient light vec3 apply_lights_sunless(vec3 fragcolor, vec3 fragnormal, vec3 texturecolor, float reflectivity, float specularity, float shadowtone) { return shade_surface(fragcolor, fragnormal, texturecolor, reflectivity, specularity, shadowtone, true); diff --git a/shaders/ogl46/sky_occlusion.glsl b/shaders/ogl46/sky_occlusion.glsl index 34694f39..32092bb7 100644 --- a/shaders/ogl46/sky_occlusion.glsl +++ b/shaders/ogl46/sky_occlusion.glsl @@ -57,13 +57,17 @@ vec2 sky_occlusion_world(vec3 world, vec3 worldnormal) vec4 skydeep = textureGather(sky_occlusion_map, uv, 2); vec4 litabove = textureGather(sky_occlusion_map, uv, 3); vec4 height = vec4(point.y + sky_occlusion_origin.z); - vec4 cover = clamp((tops - height - SKY_OCCLUSION_COVER_START) / SKY_OCCLUSION_COVER_SPAN, 0.0, 1.0); + // a point is under the scenery only where a ceiling is over it (the vault of a tunnel, the deck of a bridge, a roof): under the top + // with nothing over it (the edge of a platform or a wall the texels are blended over, a hole cut in the terrain, a part of a + // model in the ground) it's taken as at the top, in the open + vec4 roofed = step(ceilings, vec4(5000.0)) * clamp(ceilings - height + 1.0, 0.0, 1.0); + vec4 cover = clamp((tops - height - SKY_OCCLUSION_COVER_START) / SKY_OCCLUSION_COVER_SPAN, 0.0, 1.0) * roofed; vec4 nearheight = ceilings - SKY_OCCLUSION_BELOW_CEILING; vec4 skyunder = mix(skydeep, skynear, clamp((height - deeps) / max(nearheight - deeps, vec4(1e-3)), 0.0, 1.0)); // no ceiling: the inside of what is solid skyunder *= step(ceilings, vec4(5000.0)); vec4 sky = mix(skytop, skyunder, cover); - vec4 suntop = smoothstep(vec4(-SKY_OCCLUSION_SUN_SOFTNESS), vec4(SKY_OCCLUSION_SUN_SOFTNESS), height - (tops + litabove)); + vec4 suntop = smoothstep(vec4(-SKY_OCCLUSION_SUN_SOFTNESS), vec4(SKY_OCCLUSION_SUN_SOFTNESS), max(height, tops - SKY_OCCLUSION_COVER_START) - (tops + litabove)); vec4 sununder = smoothstep(vec4(-SKY_OCCLUSION_SUN_SOFTNESS), vec4(SKY_OCCLUSION_SUN_SOFTNESS), litbelow - height); vec4 sun = mix(suntop, sununder, cover); return mix(vec2(dot(sky, weights), dot(sun, weights)), vec2(1.0), fade); @@ -81,7 +85,11 @@ float sky_occlusion_cover(vec3 world) if (false == sky_occlusion_texels(world.xz, uv, weights, fade)) return 0.0; vec4 tops = textureGather(sky_occlusion_height, uv, 0); - vec4 cover = clamp((tops - vec4(world.y + sky_occlusion_origin.z) - SKY_OCCLUSION_COVER_START) / SKY_OCCLUSION_COVER_SPAN, 0.0, 1.0); + vec4 ceilings = textureGather(sky_occlusion_height, uv, 1); + vec4 height = vec4(world.y + sky_occlusion_origin.z); + // under a ceiling only, as sky_occlusion_world() takes it + vec4 roofed = step(ceilings, vec4(5000.0)) * clamp(ceilings - height + 1.0, 0.0, 1.0); + vec4 cover = clamp((tops - height - SKY_OCCLUSION_COVER_START) / SKY_OCCLUSION_COVER_SPAN, 0.0, 1.0) * roofed; return dot(cover, weights) * (1.0 - fade); } diff --git a/shaders/vk14/light_common.glsl b/shaders/vk14/light_common.glsl index a4471af8..9a3ed0cc 100644 --- a/shaders/vk14/light_common.glsl +++ b/shaders/vk14/light_common.glsl @@ -838,7 +838,7 @@ vec3 cab_probe_reflection(vec3 surroundings, vec3 fragnormal, float roughness, v // Texturecolor is the albedo (the base colour of a metal), Specularity the specular level of a dielectric (0.5 is the // reflectance of 4% at normal incidence), Reflectivity how much of the reflected surroundings is the cubemap rather than the // ambient light, Shadowtone the share of the sun left in its shadow (1 for the materials which take no shadows). -// a Sunless surface (the interior of a cab) gets no light of the sun, its shadow only darkens the ambient light +// a Sunless surface (the interior of a cab) gets no light of the sun, where the sun falls it brightens the ambient light vec3 shade_surface(vec3 fragcolor, vec3 fragnormal, vec3 texturecolor, float reflectivity, float specularity, float shadowtone, bool sunless) { // Diffuse colour is applied once, to the albedo; the light terms below are not scaled by it again @@ -978,10 +978,12 @@ vec3 shade_surface(vec3 fragcolor, vec3 fragnormal, vec3 texturecolor, float ref float cubemap_shadow = 1.0; if (sunless) { - // the sun adds no light in the cab, where it would fall its shadow darkens the ambient light + // the sun adds no light of its own in the cab; where it falls the ambient light is raised as much as the shadow used to lower + // it (the share Shadowtone, at most 4 times), so the shadows of the walls of the cab and of the clouds leave the ambient light + // as it is instead of darkening the cab float diffuseamount = sun_NdotL * lights[0].intensity; - float darkness = (shadowtone < 1.0 ? max(shadow, 1.0 - cloudsun) : 0.0); - fragcolor = mix(fragcolor, fragcolor * shadowtone, clamp(diffuseamount * darkness, 0.0, 1.0)); + float sunlit = (shadowtone < 1.0 ? 1.0 - max(shadow, 1.0 - cloudsun) : 0.0); + fragcolor *= 1.0 + clamp(diffuseamount * sunlit, 0.0, 1.0) * (1.0 - shadowtone) / max(shadowtone, 0.25); } else cubemap_shadow = 1.0 - (1.0 - shadowtone) * shadow * 0.5; @@ -1017,7 +1019,7 @@ vec3 apply_lights(vec3 fragcolor, vec3 fragnormal, vec3 texturecolor, float refl return shade_surface(fragcolor, fragnormal, texturecolor, reflectivity, specularity, shadowtone, false); } -// the interior of a cab: no light of the sun, its shadow darkens the ambient light +// the interior of a cab: no light of the sun, where it falls it brightens the ambient light vec3 apply_lights_sunless(vec3 fragcolor, vec3 fragnormal, vec3 texturecolor, float reflectivity, float specularity, float shadowtone) { return shade_surface(fragcolor, fragnormal, texturecolor, reflectivity, specularity, shadowtone, true); diff --git a/shaders/vk14/sky_occlusion.glsl b/shaders/vk14/sky_occlusion.glsl index 893539b1..e86bcfc9 100644 --- a/shaders/vk14/sky_occlusion.glsl +++ b/shaders/vk14/sky_occlusion.glsl @@ -57,13 +57,17 @@ vec2 sky_occlusion_world(vec3 world, vec3 worldnormal) vec4 skydeep = textureGather(sky_occlusion_map, uv, 2); vec4 litabove = textureGather(sky_occlusion_map, uv, 3); vec4 height = vec4(point.y + sky_occlusion_origin.z); - vec4 cover = clamp((tops - height - SKY_OCCLUSION_COVER_START) / SKY_OCCLUSION_COVER_SPAN, 0.0, 1.0); + // a point is under the scenery only where a ceiling is over it (the vault of a tunnel, the deck of a bridge, a roof): under the top + // with nothing over it (the edge of a platform or a wall the texels are blended over, a hole cut in the terrain, a part of a + // model in the ground) it's taken as at the top, in the open + vec4 roofed = step(ceilings, vec4(5000.0)) * clamp(ceilings - height + 1.0, 0.0, 1.0); + vec4 cover = clamp((tops - height - SKY_OCCLUSION_COVER_START) / SKY_OCCLUSION_COVER_SPAN, 0.0, 1.0) * roofed; vec4 nearheight = ceilings - SKY_OCCLUSION_BELOW_CEILING; vec4 skyunder = mix(skydeep, skynear, clamp((height - deeps) / max(nearheight - deeps, vec4(1e-3)), 0.0, 1.0)); // no ceiling: the inside of what is solid skyunder *= step(ceilings, vec4(5000.0)); vec4 sky = mix(skytop, skyunder, cover); - vec4 suntop = smoothstep(vec4(-SKY_OCCLUSION_SUN_SOFTNESS), vec4(SKY_OCCLUSION_SUN_SOFTNESS), height - (tops + litabove)); + vec4 suntop = smoothstep(vec4(-SKY_OCCLUSION_SUN_SOFTNESS), vec4(SKY_OCCLUSION_SUN_SOFTNESS), max(height, tops - SKY_OCCLUSION_COVER_START) - (tops + litabove)); vec4 sununder = smoothstep(vec4(-SKY_OCCLUSION_SUN_SOFTNESS), vec4(SKY_OCCLUSION_SUN_SOFTNESS), litbelow - height); vec4 sun = mix(suntop, sununder, cover); return mix(vec2(dot(sky, weights), dot(sun, weights)), vec2(1.0), fade); @@ -81,7 +85,11 @@ float sky_occlusion_cover(vec3 world) if (false == sky_occlusion_texels(world.xz, uv, weights, fade)) return 0.0; vec4 tops = textureGather(sky_occlusion_height, uv, 0); - vec4 cover = clamp((tops - vec4(world.y + sky_occlusion_origin.z) - SKY_OCCLUSION_COVER_START) / SKY_OCCLUSION_COVER_SPAN, 0.0, 1.0); + vec4 ceilings = textureGather(sky_occlusion_height, uv, 1); + vec4 height = vec4(world.y + sky_occlusion_origin.z); + // under a ceiling only, as sky_occlusion_world() takes it + vec4 roofed = step(ceilings, vec4(5000.0)) * clamp(ceilings - height + 1.0, 0.0, 1.0); + vec4 cover = clamp((tops - height - SKY_OCCLUSION_COVER_START) / SKY_OCCLUSION_COVER_SPAN, 0.0, 1.0) * roofed; return dot(cover, weights) * (1.0 - fade); } diff --git a/utilities/Globals.cpp b/utilities/Globals.cpp index edd6fc55..dce8b21c 100644 --- a/utilities/Globals.cpp +++ b/utilities/Globals.cpp @@ -1889,6 +1889,7 @@ global_settings::ConfigParse_gfx_sky( cParser &Parser, std::string_view const To else if( Token == "gfx.postfx.autoexposure.key" ) { readvalue( gfx_postfx_autoexposure_key, 0.001f, 100.f ); } else if( Token == "gfx.postfx.autoexposure.min" ) { readvalue( gfx_postfx_autoexposure_min, -16.f, 0.f ); } else if( Token == "gfx.postfx.autoexposure.max" ) { readvalue( gfx_postfx_autoexposure_max, 0.f, 16.f ); } + else if( Token == "gfx.postfx.autoexposure.max.cab" ) { readvalue( gfx_postfx_autoexposure_max_cab, 0.f, 16.f ); } else if( Token == "gfx.postfx.autoexposure.speed.dark" ) { readvalue( gfx_postfx_autoexposure_speed_dark, 0.01f, 100.f ); } else if( Token == "gfx.postfx.autoexposure.speed.bright" ) { readvalue( gfx_postfx_autoexposure_speed_bright, 0.01f, 100.f ); } else if( Token == "gfx.postfx.autoexposure.compensation" ) { readvalue( gfx_postfx_autoexposure_compensation, -16.f, 16.f ); } @@ -1953,6 +1954,7 @@ global_settings::ConfigParse_gfx_sky( cParser &Parser, std::string_view const To 0; } else if( Token == "gfx.skyvisibility.sun" ) { readswitch( gfx_skyvisibility_sun ); } else if( Token == "gfx.skyvisibility.fog" ) { readswitch( gfx_skyvisibility_fog ); } + else if( Token == "gfx.skyvisibility.tunnels" ) { readswitch( gfx_skyvisibility_tunnels ); } else if( Token == "gfx.tunnelflags" ) { readswitch( gfx_tunnelflags ); } else if( Token == "gfx.lights.range" ) { readvalue( gfx_lights_range, 0.f, 2000.f ); } else if( Token == "gfx.lights.shadows" ) { @@ -2407,6 +2409,7 @@ global_settings::export_as_text( std::ostream &Output ) const { export_as_text( Output, "gfx.postfx.autoexposure.key", gfx_postfx_autoexposure_key ); export_as_text( Output, "gfx.postfx.autoexposure.min", gfx_postfx_autoexposure_min ); export_as_text( Output, "gfx.postfx.autoexposure.max", gfx_postfx_autoexposure_max ); + export_as_text( Output, "gfx.postfx.autoexposure.max.cab", gfx_postfx_autoexposure_max_cab ); export_as_text( Output, "gfx.postfx.autoexposure.speed.dark", gfx_postfx_autoexposure_speed_dark ); export_as_text( Output, "gfx.postfx.autoexposure.speed.bright", gfx_postfx_autoexposure_speed_bright ); export_as_text( Output, "gfx.postfx.autoexposure.compensation", gfx_postfx_autoexposure_compensation ); @@ -2440,6 +2443,7 @@ global_settings::export_as_text( std::ostream &Output ) const { export_as_text( Output, "gfx.skyvisibility", std::string{ gfx_skyvisibility == 3 ? "high" : gfx_skyvisibility == 2 ? "medium" : gfx_skyvisibility == 1 ? "low" : "no" } ); export_as_text( Output, "gfx.skyvisibility.sun", gfx_skyvisibility_sun ); export_as_text( Output, "gfx.skyvisibility.fog", gfx_skyvisibility_fog ); + export_as_text( Output, "gfx.skyvisibility.tunnels", gfx_skyvisibility_tunnels ); export_as_text( Output, "gfx.tunnelflags", gfx_tunnelflags ); export_as_text( Output, "gfx.lights.range", gfx_lights_range ); export_as_text( Output, "gfx.lights.shadows", std::string{ gfx_lights_shadows == 3 ? "high" : gfx_lights_shadows == 2 ? "medium" : gfx_lights_shadows == 1 ? "low" : "no" } ); diff --git a/utilities/Globals.h b/utilities/Globals.h index f4dff897..b91da60e 100644 --- a/utilities/Globals.h +++ b/utilities/Globals.h @@ -466,6 +466,7 @@ struct global_settings { float gfx_postfx_autoexposure_key { 0.3f }; // average luminance of the image the exposure is set for float gfx_postfx_autoexposure_min { -1.f }; // lowest exposure correction, EV (darkening of bright images) float gfx_postfx_autoexposure_max { 0.5f }; // highest exposure correction, EV (brightening of dark images, tunnels) + float gfx_postfx_autoexposure_max_cab { 2.f }; // the same seen from the cab by day: the eye takes to the light of the cab float gfx_postfx_autoexposure_speed_dark { 1.f }; // rate of adaptation to a darker image, 1/s float gfx_postfx_autoexposure_speed_bright { 3.f }; // rate of adaptation to a brighter image, 1/s float gfx_postfx_autoexposure_compensation { 0.f }; // fixed exposure correction, EV, applied with and without the adaptation @@ -523,6 +524,9 @@ struct global_settings { int gfx_skyvisibility { 2 }; bool gfx_skyvisibility_sun { true }; bool gfx_skyvisibility_fog { true }; + // the occlusion only around the tracks with the tunnel flag (the geometry there makes the tunnel dark, the portals, the passage at + // them); elsewhere the scenery is lit as in the open. off: everywhere + bool gfx_skyvisibility_tunnels { true }; // the environment flags of the tracks (tunnel, canyon) darken the tracks and the vehicles on them, as before the track T. off: the // renderers leave them alone and the light comes from the geometry (gfx_skyvisibility); the flags stay in the scenery files for // the sound and the driver