17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-28 21:37:31 +01:00

Hold the sky back only around tunnels and keep the cab lit by day

This commit is contained in:
2026-10-28 14:01:37 +01:00
parent e8a90507f0
commit 413cebed9c
10 changed files with 187 additions and 21 deletions

View File

@@ -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

View File

@@ -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<float>(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<glm::ivec2> 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);

View File

@@ -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<std::size_t>(m_layout.tiles * m_layout.tiles)};
m_slots.assign(count, slot{});
auto const texels{static_cast<std::size_t>(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<int>::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<double, std::milli>(std::chrono::steady_clock::now() - gatherstart).count();
m_triangles = static_cast<std::uint32_t>(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<std::uint64_t>(cell.m_shapesopaque.size()));
hash = hash_mix(hash, static_cast<std::uint64_t>(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<std::uint64_t>(reinterpret_cast<std::uintptr_t>(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<glm::vec2> &Points)
{
auto const tilesize{static_cast<double>(m_layout.tile)};
auto const reach{static_cast<double>(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<TTrack const *> done;
auto const &found{Region->sections(center, static_cast<float>(tilesize * 0.75 + reach))};
std::vector<scene::basic_section *> 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<double>(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<int>(std::ceil(segment.GetLength() / TUNNEL_MASK_STEP)))};
for (int step = 0; step <= steps; ++step)
{
auto const point{segment.FastGetPoint(static_cast<double>(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<float>(point.x - corner.x), static_cast<float>(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<triangle> &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<std::size_t>(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<float>(x) + 0.5f) * texel, (static_cast<float>(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<std::size_t>(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<std::size_t>(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;

View File

@@ -142,6 +142,8 @@ class sky_occlusion
int slot{0};
std::uint32_t generation{0};
std::vector<triangle> 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<glm::vec2> 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<triangle> &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<triangle> &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<triangle> &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<glm::vec2> &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<slot> 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<float> 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<float> m_mask;
// statistics
std::uint32_t m_gathered{0};

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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" } );

View File

@@ -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