diff --git a/docs/opengl46-renderer.md b/docs/opengl46-renderer.md index d3a49ede..4589e56f 100644 --- a/docs/opengl46-renderer.md +++ b/docs/opengl46-renderer.md @@ -187,7 +187,9 @@ shadows (see below); the others (lamps of vehicles, compartments, farther scener within their reach, which is kept short for compartments. **Shadows of the cab lamp** (`gfx.lights.shadows`): the cab casts shadows in the light of its lamp (and of the brightest -`gfx.lights.shadows.cab` lights of the cab model, each with a map of its own; the maps are made as they're needed), the desk, the seats, +`gfx.lights.shadows.cab` lights of the cab model, each with a map of its own, made as they're needed; the other lights of the +cab take the map of the closest light with one; for the light points the near plane is 3 cm, or their `Radius:`, so the desk +right under its lights holds their light back; a few maps are redrawn per frame), the desk, the seats, the levers and the walls (light leaves the cab only through the windows, glass doesn't stop it). They come from a cube shadow map around the lamp, drawn with the opaque parts of the cab model only. The map is kept in the space of the cab, so it stays right as the vehicle moves; it is drawn anew every few frames, for the moving parts of the cab, and at once when @@ -247,7 +249,7 @@ shadows of the sun, see `headlight shadows:` in Shift+F12); with the vehicle on | `gfx.lights.cab.bounce` | `0.3` | 0..1: share of the former ambient light of the cab lamp kept with the lamp on | | `gfx.lights.range` | `300` | distance from the camera the lamps of models and vehicles are gathered from, metres | | `gfx.lights.shadows` | `medium` | `no`/`low`/`medium`/`high` (or 0..3): shadows of the cab lamp and the scenery lamps, see above | -| `gfx.lights.shadows.cab` | `4` | 0..8: how many lights of the cab (the brightest; the lamp or the light points of the cab model) cast the shadows of the cab, a cube map each; 0: none | +| `gfx.lights.shadows.cab` | `8` | 0..16: how many lights of the cab (the brightest; the lamp or the light points of the cab model) have a cube shadow map of their own; the others take the map of the closest light with one (within 1 m); 0: none | | `gfx.lights.shadows.lamps` | `8` | 0..32: how many scenery lamps nearest to the camera cast shadows; 0: none | | `gfx.lights.shadows.lamps.size` | `512` | 128..2048: size of a face of the shadow map of a scenery lamp | | `gfx.lights.shadows.lamps.range` | `100` | distance from the camera within which scenery lamps cast shadows, metres | diff --git a/rendering/common/localshadows.cpp b/rendering/common/localshadows.cpp index f962a940..87fc056f 100644 --- a/rendering/common/localshadows.cpp +++ b/rendering/common/localshadows.cpp @@ -35,6 +35,18 @@ float lamp_near(gfx::local_lights::light const &Light) { return std::clamp(Light.radius, local_shadows::shadow_near, std::max(local_shadows::shadow_near, Light.range * 0.5f)); } +// the same for a light of the cab, which is often right by what it lights and what holds its light back (the lights of the desk +// under its edge): only what is within a few centimetres, or within its sphere, casts no shadow +float const CAB_SHADOW_NEAR{0.03f}; +float cab_near(gfx::local_lights::light const &Light) +{ + return std::clamp(Light.radius, CAB_SHADOW_NEAR, std::max(CAB_SHADOW_NEAR, Light.range * 0.5f)); +} +// lights of the cab with a shorter reach aren't worth a map of their own, metres +float const CAB_RANGE_MIN{0.25f}; +// a light of the cab without a map of its own takes the shadows of the map of the closest light within this, metres: the desk, the +// walls are where they are for both +float const CAB_SHARE_DISTANCE{1.0f}; } // namespace @@ -172,7 +184,7 @@ void local_shadows::update_cab(TDynamicObject const *Vehicle, TModel3d const *Ca { break; } - if (light.source.cast_shadows && (light.source.range >= LAMP_RANGE_MIN)) + if (light.source.cast_shadows && (light.source.range >= CAB_RANGE_MIN)) { picked.emplace_back(&light); } @@ -191,7 +203,8 @@ void local_shadows::update_cab(TDynamicObject const *Vehicle, TModel3d const *Ca } } auto const place = [&](cab_slot &Slot, std::size_t const Index, gfx::local_lights::cab_light const &Light) { - auto const nearplane{lamp_near(Light.source)}; + // the lamp put under the top of a cab model without lights of its own keeps the near plane it had + auto const nearplane{Light.source.part == static_cast(Vehicle) ? lamp_near(Light.source) : cab_near(Light.source)}; if ((Slot.lamp != Light.local) || (Slot.nearplane != nearplane)) { Slot.valid = false; @@ -200,12 +213,6 @@ void local_shadows::update_cab(TDynamicObject const *Vehicle, TModel3d const *Ca Slot.nearplane = nearplane; Slot.wantedframe = Framestamp; m_cabwanted.emplace_back(Index); - // the moving parts of the cab are followed every few frames - if ((false == Slot.valid) || (Framestamp - Slot.drawnframe >= setup(level).interval)) - { - Slot.drawnframe = Framestamp; - m_cabdraws.emplace_back(Index); - } }; // lights which already have a slot keep it, the others take the slots of lights no longer picked std::vector unplaced; @@ -242,6 +249,33 @@ void local_shadows::update_cab(TDynamicObject const *Vehicle, TModel3d const *Ca break; } } + // the maps to be drawn: those which don't hold their light yet, at once, and those of the moving parts of the cab, every few + // frames, a few of them per frame (the ones drawn longest ago first): a cab may have many lights + std::vector refresh; + for (auto const idx : m_cabwanted) + { + auto const &slot{m_cabslots[idx]}; + if (false == slot.valid) + { + m_cabdraws.emplace_back(idx); + } + else if (Framestamp - slot.drawnframe >= setup(level).interval) + { + refresh.emplace_back(idx); + } + } + auto const refreshes{static_cast(level >= 3 ? 4 : 2)}; + if (refresh.size() > refreshes) + { + std::partial_sort(std::begin(refresh), std::begin(refresh) + refreshes, std::end(refresh), + [this, Framestamp](std::size_t const Left, std::size_t const Right) { return Framestamp - m_cabslots[Left].drawnframe > Framestamp - m_cabslots[Right].drawnframe; }); + refresh.resize(refreshes); + } + m_cabdraws.insert(std::end(m_cabdraws), std::begin(refresh), std::end(refresh)); + for (auto const idx : m_cabdraws) + { + m_cabslots[idx].drawnframe = Framestamp; + } } void local_shadows::begin(std::size_t const Slot) @@ -277,15 +311,28 @@ int local_shadows::cab_slot_of(TDynamicObject const *Vehicle, gfx::local_lights: { return -1; } + auto closest{-1}; + auto closestdistance{CAB_SHARE_DISTANCE}; for (auto const idx : m_cabwanted) { auto const &slot{m_cabslots[idx]}; - if ((slot.part == Light.source.part) && slot.valid && (slot.lamp == Light.local)) + if (false == slot.valid) + { + continue; + } + if ((slot.part == Light.source.part) && (slot.lamp == Light.local)) { return static_cast(idx); } + // a light without a map of its own takes the one closest to it + auto const distance{glm::length(slot.lamp - Light.local)}; + if (Light.source.cast_shadows && (distance <= closestdistance)) + { + closest = static_cast(idx); + closestdistance = distance; + } } - return -1; + return closest; } glm::vec4 local_shadows::weights(std::size_t const Slot) const diff --git a/rendering/common/localshadows.h b/rendering/common/localshadows.h index e0862003..bc50f1da 100644 --- a/rendering/common/localshadows.h +++ b/rendering/common/localshadows.h @@ -31,8 +31,9 @@ namespace gfx // - the lights of the occupied cab (gfx::local_lights::cab_lights(): its lamps, the lights of its desk), the brightest // Global.gfx_lights_shadows_cab of them, with the shadows cast by the cab (the model drawn by Render_cab()). the map of a light is // kept in the space of the cab model, centred on the light: it stays right while the vehicle moves and turns, and is drawn anew only -// every few frames (for the moving parts of the cab: levers, doors, wipers) or when the light, the cab or the level change. a light -// keeps its map (slot) while it's among those picked; +// every few frames (for the moving parts of the cab: levers, doors, wipers; a few maps per frame) or when the light, the cab or the +// level change. a light keeps its map (slot) while it's among those picked; the other lights of the cab take the map of the +// closest light with one. the near plane of these maps is a few centimetres: the desk right under its lights holds their light back; // - the lamps of scenery models closest to the camera (Global.gfx_lights_shadows_lamps of them, within // Global.gfx_lights_shadows_lamps_range). the map of such a lamp is kept in world axes, centred on the lamp, and has two parts: // the scenery (drawn once, when the lamp gets its slot or the scene changes) and a working copy of it, with the vehicles near @@ -54,7 +55,7 @@ class local_shadows // near plane of the maps, metres. surfaces closer to the lamp (its own fitting) cast no shadow static constexpr float shadow_near{0.15f}; // lights of the cab with shadow maps, at most - static constexpr int cab_slots_max{8}; + static constexpr int cab_slots_max{16}; local_shadows() = default; virtual ~local_shadows() = default; @@ -92,7 +93,8 @@ class local_shadows void begin(std::size_t const Slot); // ends the drawing to the map void end(std::uint32_t const Drawcalls); - // the slot of the light of the cab of Vehicle with its shadows ready, -1 if it has none + // the slot of the light of the cab of Vehicle with its shadows ready, -1 if it has none. a light without a map of its own (past + // the number of maps, or too weak for one) takes the map of the closest light within a metre, whose shadows are close to its own int cab_slot_of(TDynamicObject const *Vehicle, gfx::local_lights::cab_light const &Light) const; // the headlight_weights of the light which reads the map of the slot: layer, near plane, size of a texel one metre from the lamp, // radius of the filter in texels diff --git a/utilities/Globals.cpp b/utilities/Globals.cpp index 5a7916e1..b1fcee94 100644 --- a/utilities/Globals.cpp +++ b/utilities/Globals.cpp @@ -1975,7 +1975,7 @@ global_settings::ConfigParse_gfx_sky( cParser &Parser, std::string_view const To else if( Token == "gfx.lights.shadows.cab" ) { Parser.getTokens( 1 ); Parser >> gfx_lights_shadows_cab; - gfx_lights_shadows_cab = std::clamp( gfx_lights_shadows_cab, 0, 8 ); } + gfx_lights_shadows_cab = std::clamp( gfx_lights_shadows_cab, 0, 16 ); } else if( Token == "gfx.lights.shadows.lamps" ) { Parser.getTokens( 1 ); Parser >> gfx_lights_shadows_lamps; diff --git a/utilities/Globals.h b/utilities/Globals.h index a1010262..58f27dd0 100644 --- a/utilities/Globals.h +++ b/utilities/Globals.h @@ -539,8 +539,8 @@ struct global_settings { // occupied cab, its shadows cast by the cab int gfx_lights_shadows { 2 }; // with gfx_lights_shadows above 0: how many lights of the occupied cab (its lamps, the lights of its desk) at most have shadows - // cast by the cab, the brightest ones (0-8) - int gfx_lights_shadows_cab { 4 }; + // cast by the cab, the brightest ones (0-16); the others take the shadows of the map of the closest light with one + int gfx_lights_shadows_cab { 8 }; // shadows of the lamps of scenery models (cube maps of the scenery around them, kept; vehicles added as they pass), with // gfx_lights_shadows above 0: how many lamps at once, the size of a face of their maps, the distance from the camera within // which lamps get shadows and within which vehicles (and the cab) cast them