diff --git a/docs/opengl46-renderer.md b/docs/opengl46-renderer.md index df3eb8b9..575abfa6 100644 --- a/docs/opengl46-renderer.md +++ b/docs/opengl46-renderer.md @@ -173,13 +173,22 @@ scales can also be changed in the **Light clusters** debug window. light of the whole cab and weakened the shadows of the sun in it; now a share of that ambient light is kept for the light which bounces around the cab (`gfx.lights.cab.bounce`), and the sun shadows stay. With the probe of the cab (below) the light which bounces comes from the probe instead and nothing of the lamp is kept in the ambient light. +- **Lights of the cab model.** Light points (`FreeSpotLight`) of the cab model whose names start with `cablight` (any case) + are the lamps of the cab: they shine with its lamp switch (off, dimmed, half bright without 110 V), in place of the lamp + under the top of the cab, which a cab model gets only when it has no `cablight` of its own. Those whose names start with + `dashboardlight` are the lights of the desk: they shine with the dashboard light (`i-dashboardlight:` / + `dashboardlight_sw:`). Their colour, power, cone and the optional `Radius:`/`SoftFalloff:`/`CastShadows:` are those of the + light point (brightness `gfx.lights.freespot.intensity`, the self-illumination threshold doesn't matter), and they fade + in the daylight as the cab lamp does. Visibility counts: a light point under a submodel that is hidden doesn't shine. + Other light points of the cab only glare, as before. The lamps of models and compartments fade in from the light level at which scenery lamps switch on (`ls_Dark`) to full darkness; in daylight they are not gathered at all. The cab lamp and the lamps of scenery models nearest to the camera cast shadows (see below); the others (lamps of vehicles, compartments, farther scenery lamps) also light what is behind walls 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, the desk, the seats, +**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, 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 @@ -239,6 +248,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, `cablight`/`dashboardlight` light points) cast the shadows of the cab, a cube map each; 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/locallights.cpp b/rendering/common/locallights.cpp index d46aec47..4e401265 100644 --- a/rendering/common/locallights.cpp +++ b/rendering/common/locallights.cpp @@ -13,8 +13,10 @@ http://mozilla.org/MPL/2.0/. #include #include +#include #include #include +#include #include "utilities/Globals.h" #include "rendering/common/skyocclusion.h" @@ -38,6 +40,20 @@ float const LIGHT_RANGE_MAX{60.f}; // the lights of models and compartments fade in, as they would be lost in the daylight anyway float const LIGHT_DUSK_LUMINANCE{0.325f}; float const LIGHT_DAY_LUMINANCE{1.0f}; +// lights of the occupied cab, at most +std::size_t const CAB_LIGHTS_MAX{16}; +// kinds of the light points of a cab model, by the start of their names (any case): the lamps of the cab and the lights of the desk +std::uint8_t const CAB_LIGHT_LAMP{0x1}; +std::uint8_t const CAB_LIGHT_DASHBOARD{0x2}; + +std::uint8_t cab_light_kind(std::string const &Name) +{ + auto const starts = [&Name](std::string_view const Prefix) { + return (Name.size() >= Prefix.size()) && + std::equal(std::begin(Prefix), std::end(Prefix), std::begin(Name), [](char const Left, char const Right) { return Left == static_cast(std::tolower(static_cast(Right))); }); + }; + return starts("cablight") ? CAB_LIGHT_LAMP : starts("dashboardlight") ? CAB_LIGHT_DASHBOARD : 0; +} glm::vec3 to_linear(glm::vec3 const &Color) { @@ -113,6 +129,7 @@ void gfx::local_lights::gather(glm::dvec3 const &Camera, scene::basic_region &Re auto const start{std::chrono::steady_clock::now()}; m_lights.clear(); + m_cablights.clear(); m_cablampon = false; m_scenerycount = m_vehiclecount = m_compartmentcount = 0; @@ -124,7 +141,7 @@ void gfx::local_lights::gather(glm::dvec3 const &Camera, scene::basic_region &Re { if (Global.gfx_lights_cab) { - gather_cab_lamp(Geometry); + gather_cab_lights(Geometry); } // lamps are lost in the daylight; this also spares the work of gathering them in the middle of the day @@ -189,8 +206,10 @@ void gfx::local_lights::gather(glm::dvec3 const &Camera, scene::basic_region &Re void gfx::local_lights::reset() { m_lights.clear(); + m_cablights.clear(); m_cablampon = false; m_modellights.clear(); + m_cabkinds.clear(); m_cells.clear(); m_sections.clear(); m_models.clear(); @@ -304,44 +323,86 @@ void gfx::local_lights::gather_vehicle(TDynamicObject *Vehicle, glm::dvec3 const } } -void gfx::local_lights::gather_cab_lamp(gfx::geometrybank_manager const &Geometry) +void gfx::local_lights::gather_cab_lights(gfx::geometrybank_manager const &Geometry) { // the cab is drawn, Render_cab() conditions if (FreeFlyModeFlag || (false == Global.render_cab) || (simulation::Train == nullptr)) { return; } - auto const *vehicle{simulation::Train->Dynamic()}; + auto const *train{simulation::Train}; + auto const *vehicle{train->Dynamic()}; if ((vehicle == nullptr) || (false == vehicle->bDisplayCab) || (vehicle->mdKabina == nullptr) || (vehicle->mdKabina == vehicle->mdModel)) { return; } - if (vehicle->InteriorLightLevel <= 0.f) - { - return; - } - // as bright as the ambient light it used to add to the cab: lost in the daylight - auto const luminance{Global.fLuminance * gfx::vehicle_light_level(vehicle)}; - auto const level{vehicle->InteriorLightLevel * std::clamp(1.25f - static_cast(luminance), 0.f, 1.f)}; - if (level <= 0.f) - { - return; - } auto const &bounds{model_bounds(vehicle->mdKabina, Geometry)}; if (bounds.extent == glm::vec3{0.f}) { return; } - // under the ceiling, in the middle of the cab - auto const lamp{glm::vec3{bounds.center.x, std::max(bounds.center.y, bounds.center.y + bounds.extent.y - 0.25f), bounds.center.z}}; - m_cablamp = light{}; - m_cablamp.position = vehicle->GetWorldPosition(glm::dvec3{lamp}); + // as bright as the ambient light the lamp used to add to the cab: lost in the daylight. the lamps of the cab at the level of + // their switch (off, dimmed, with or without the 110 V), the lights of the desk with the dashboard light (i-dashboardlight:) + auto const luminance{Global.fLuminance * gfx::vehicle_light_level(vehicle)}; + auto const daylight{std::clamp(1.25f - static_cast(luminance), 0.f, 1.f)}; + auto const cablevel{std::max(vehicle->InteriorLightLevel, 0.f) * daylight}; + auto const dashboardlevel{(train->btDashboardLight.GetValue() ? 1.f : 0.f) * daylight}; + auto const kinds{cab_light_kinds(vehicle->mdKabina)}; + + if ((kinds != 0) && ((cablevel > 0.f) || (dashboardlevel > 0.f))) + { + // the light points of the cab model, in its space, the way Render_cab() sets it up + TSubModel::iInstance = reinterpret_cast(vehicle); + TSubModel::iVariantSeed = vehicle->variant_seed(); + TSubModel::fSquareDist = 0.f; + auto const count{m_lights.size()}; + m_walkcab = true; + m_walkcablevel = cablevel; + m_walkdashboardlevel = dashboardlevel; + walk(vehicle->mdKabina->Root, glm::mat4{1.f}, glm::dmat4{1.0}, Global.gfx_lights_freespot_intensity); + m_walkcab = false; + // within the cab: without shadows (gfx.lights.shadows) the light goes through its walls + auto const reach{std::max(2.f * glm::length(bounds.extent), 2.5f)}; + auto const rotation{glm::dmat3{vehicle->mMatrix}}; + for (auto idx{count}; idx < m_lights.size(); ++idx) + { + cab_light cablight; + cablight.source = m_lights[idx]; + cablight.local = glm::vec3{m_lights[idx].position}; + cablight.source.position = vehicle->GetWorldPosition(m_lights[idx].position); + auto const direction{glm::vec3{rotation * glm::dvec3{m_lights[idx].direction}}}; + cablight.source.direction = (glm::length2(direction) > 0.f ? glm::normalize(direction) : glm::vec3{0.f, -1.f, 0.f}); + cablight.source.range = std::min(cablight.source.range, reach); + m_cablights.emplace_back(cablight); + } + m_lights.resize(count); + } + + // the lamp of a cab model without lamps of its own: under the ceiling, in the middle of the cab + if (((kinds & CAB_LIGHT_LAMP) == 0) && (cablevel > 0.f)) + { + auto const lamp{glm::vec3{bounds.center.x, std::max(bounds.center.y, bounds.center.y + bounds.extent.y - 0.25f), bounds.center.z}}; + cab_light cablight; + cablight.local = lamp; + cablight.source.position = vehicle->GetWorldPosition(glm::dvec3{lamp}); + cablight.source.color = to_linear(vehicle->InteriorLight) * (cablevel * Global.gfx_lights_cab_intensity); + // within the cab: without shadows (gfx.lights.shadows) the lamp would light the track below and the body of the vehicle around it + cablight.source.range = std::clamp(glm::length(bounds.extent) * 1.25f, 2.5f, 8.f); + cablight.source.part = vehicle; + if (max_component(cablight.source.color) > 0.f) + { + m_cablights.emplace_back(cablight); + } + } + + // the brightest first: the first ones get the shadow maps, the brightest lights the probe of the cab + std::stable_sort(std::begin(m_cablights), std::end(m_cablights), [](cab_light const &Left, cab_light const &Right) { return max_component(Left.source.color) > max_component(Right.source.color); }); + if (m_cablights.size() > CAB_LIGHTS_MAX) + { + m_cablights.resize(CAB_LIGHTS_MAX); + } m_cablampvehicle = vehicle; - m_cablamplocal = lamp; - m_cablamp.color = to_linear(vehicle->InteriorLight) * (level * Global.gfx_lights_cab_intensity); - // within the cab: without shadows (gfx.lights.shadows) the lamp would light the track below and the body of the vehicle around it - m_cablamp.range = std::clamp(glm::length(bounds.extent) * 1.25f, 2.5f, 8.f); - m_cablampon = (max_component(m_cablamp.color) > 0.f); + m_cablampon = (false == m_cablights.empty()); } void gfx::local_lights::walk(TSubModel *Submodel, glm::mat4 const &Transform, glm::dmat4 const &Placement, float const Brightness) @@ -376,10 +437,20 @@ void gfx::local_lights::walk(TSubModel *Submodel, glm::mat4 const &Transform, gl } } -void gfx::local_lights::add_light_point(TSubModel const &Submodel, glm::mat4 const &Transform, glm::dmat4 const &Placement, float const Brightness) +void gfx::local_lights::add_light_point(TSubModel const &Submodel, glm::mat4 const &Transform, glm::dmat4 const &Placement, float Brightness) { + if (m_walkcab) + { + // the lights of the cab: with their switches, whatever the self-illumination threshold. other light points of the cab only glare + auto const kind{cab_light_kind(Submodel.pName)}; + Brightness *= (kind == CAB_LIGHT_LAMP ? m_walkcablevel : kind == CAB_LIGHT_DASHBOARD ? m_walkdashboardlevel : 0.f); + if ((Brightness <= 0.f) || (Submodel.f4Emision.a <= 0.f) || (Submodel.fVisible <= 0.f)) + { + return; + } + } // lit points: with self-illumination, past its threshold. the compartments of vehicles set it with their lights - if ((Submodel.f4Emision.a <= 0.f) || (Global.fLuminance >= Submodel.fLight) || (Submodel.fVisible <= 0.f)) + else if ((Submodel.f4Emision.a <= 0.f) || (Global.fLuminance >= Submodel.fLight) || (Submodel.fVisible <= 0.f)) { return; } @@ -411,6 +482,11 @@ void gfx::local_lights::add_light_point(TSubModel const &Submodel, glm::mat4 con lightpoint.owner = m_walkowner; lightpoint.part = &Submodel; } + else if (m_walkcab) + { + // the light of the cab, its shadow map kept by it + lightpoint.part = &Submodel; + } m_lights.emplace_back(lightpoint); } @@ -444,6 +520,35 @@ bool gfx::local_lights::has_light_points(TModel3d *Model) return found; } +std::uint8_t gfx::local_lights::cab_light_kinds(TModel3d *Model) +{ + auto lookup{m_cabkinds.find(Model)}; + if (lookup != m_cabkinds.end()) + { + return lookup->second; + } + std::uint8_t kinds{0}; + std::vector pending{Model->Root}; + while (false == pending.empty()) + { + auto const *submodel{pending.back()}; + pending.pop_back(); + for (; submodel != nullptr; submodel = submodel->Next) + { + if (submodel->eType == TP_FREESPOTLIGHT) + { + kinds |= cab_light_kind(submodel->pName); + } + if (submodel->Child != nullptr) + { + pending.emplace_back(submodel->Child); + } + } + } + m_cabkinds.emplace(Model, kinds); + return kinds; +} + gfx::local_lights::cell_data const &gfx::local_lights::cell_instances(scene::basic_cell &Cell, bool const Rebuild) { // light points are drawn in the translucent phase, so the instances which have them are on the translucent list of the cell @@ -522,7 +627,7 @@ bool gfx::local_lights::cab_bounds(TModel3d *Cab, gfx::geometrybank_manager cons std::string gfx::local_lights::info() const { return "local lights: " + std::to_string(m_lights.size()) + " (scenery " + std::to_string(m_scenerycount) + ", vehicles " + std::to_string(m_vehiclecount) + ", compartments " + - std::to_string(m_compartmentcount) + ")" + (m_cablampon ? " + cab lamp" : "") + ", placed " + std::to_string(m_placed) + ", over capacity " + std::to_string(m_dropped) + + std::to_string(m_compartmentcount) + ")" + (m_cablampon ? " + cab lights " + std::to_string(m_cablights.size()) : "") + ", placed " + std::to_string(m_placed) + ", over capacity " + std::to_string(m_dropped) + ", gather " + to_string(m_gathertime, 2) + " ms"; } @@ -531,14 +636,14 @@ void gfx::local_lights::draw_debug_ui() ImGui::TextUnformatted("local lights (need clustered lights)"); ImGui::Checkbox("light points of scenery models", &Global.gfx_lights_scenery); ImGui::Checkbox("light points and compartments of vehicles", &Global.gfx_lights_vehicles); - ImGui::Checkbox("cab lamp as a light", &Global.gfx_lights_cab); + ImGui::Checkbox("cab lamp as a light (cablight... / dashboardlight... light points of the cab)", &Global.gfx_lights_cab); ImGui::SliderFloat("light point brightness", &Global.gfx_lights_freespot_intensity, 0.f, 50.f); ImGui::SliderFloat("compartment brightness", &Global.gfx_lights_compartment_intensity, 0.f, 10.f); ImGui::SliderFloat("cab lamp brightness", &Global.gfx_lights_cab_intensity, 0.f, 10.f); ImGui::SliderFloat("cab ambient kept", &Global.gfx_lights_cab_bounce, 0.f, 1.f); ImGui::SliderFloat("range, m", &Global.gfx_lights_range, 0.f, 1000.f); - ImGui::Text("gathered %u: scenery %u, vehicles %u, compartments %u%s", static_cast(m_lights.size()), m_scenerycount, m_vehiclecount, m_compartmentcount, - m_cablampon ? ", cab lamp" : ""); + ImGui::Text("gathered %u: scenery %u, vehicles %u, compartments %u, cab %u", static_cast(m_lights.size()), m_scenerycount, m_vehiclecount, m_compartmentcount, + static_cast(m_cablights.size())); ImGui::Text("in the light buffer %u, over capacity %u", m_placed, m_dropped); ImGui::Text("gather %.2f ms (cpu)", m_gathertime); } diff --git a/rendering/common/locallights.h b/rendering/common/locallights.h index 97282bb4..590eff91 100644 --- a/rendering/common/locallights.h +++ b/rendering/common/locallights.h @@ -34,7 +34,9 @@ class TDynamicObject; // light along the -z axis of its submodel, with the cone of its glare. lamps of scenery models switched with Light_On / Light_Off // and the self-illumination of vehicle compartments (TDynamicObject::Sections) work through the same state // - the lit compartments of vehicles (low poly interior sections) without light points of their own: a point light in the middle -// - the lamp of the occupied cab: a point light under the top of the cab model, in place of the ambient light it used to add +// - the lights of the occupied cab, in place of the ambient light its lamp used to add: the light points of the cab model (mdKabina) +// named cablight... (the lamps of the cab, switched and dimmed with them) and dashboardlight... (the lights of the desk, switched +// with the dashboard light, i-dashboardlight:). a cab model without any cablight... gets a point light under its top instead // gathered once per frame around the camera, written to the light buffers by the renderers (Update_Lights()) namespace gfx { @@ -79,20 +81,28 @@ class local_lights { return m_lights; } - // the lamp of the occupied cab, when it is on and the cab is drawn; nullptr otherwise - light const *cab_lamp() const + // a light of the occupied cab: the light (part names it from frame to frame: the light point, or the vehicle for the lamp put + // under the top of the cab) and its position in the space of the cab model (mdKabina) + struct cab_light { - return m_cablampon ? &m_cablamp : nullptr; + light source; + glm::vec3 local{0.f}; + }; + // the lights of the occupied cab which are on, when the cab is drawn, the brightest first; empty otherwise + std::vector const &cab_lights() const + { + return m_cablights; } - // the vehicle of the cab lamp and the position of the lamp in the space of its cab model (mdKabina), when the lamp is on + // the brightest light of the cab, which lights its probe; nullptr when there is none + cab_light const *cab_principal() const + { + return m_cablights.empty() ? nullptr : &m_cablights.front(); + } + // the vehicle of the lights of the cab, when any of them is on TDynamicObject const *cab_lamp_vehicle() const { return m_cablampon ? m_cablampvehicle : nullptr; } - glm::vec3 const &cab_lamp_local() const - { - return m_cablamplocal; - } // the box of the cab model Cab (model space): its middle and half its size; false when the model has no geometry bool cab_bounds(TModel3d *Cab, gfx::geometrybank_manager const &Geometry, glm::vec3 &Center, glm::vec3 &Extent); // counts of the last pass which wrote the lights to the light buffer: placed, and left out for the lack of room @@ -124,21 +134,28 @@ class local_lights // lights of the light points of the submodel tree, gone through the way the render goes through it. Transform: model space of // Submodel's parent, Placement: model space to world space, Brightness: of a light point of white colour void walk(TSubModel *Submodel, glm::mat4 const &Transform, glm::dmat4 const &Placement, float const Brightness); - void add_light_point(TSubModel const &Submodel, glm::mat4 const &Transform, glm::dmat4 const &Placement, float const Brightness); + void add_light_point(TSubModel const &Submodel, glm::mat4 const &Transform, glm::dmat4 const &Placement, float Brightness); void gather_instance(TAnimModel *Instance, glm::dvec3 const &Camera, float const Brightness, unsigned int const Framestamp); void gather_vehicle(TDynamicObject *Vehicle, glm::dvec3 const &Camera, gfx::geometrybank_manager const &Geometry, float const Freespotbrightness, float const Compartmentbrightness); - void gather_cab_lamp(gfx::geometrybank_manager const &Geometry); + void gather_cab_lights(gfx::geometrybank_manager const &Geometry); bool has_light_points(TModel3d *Model); + // the kinds of the light points of a cab model by their names, CAB_LIGHT_LAMP | CAB_LIGHT_DASHBOARD (locallights.cpp) + std::uint8_t cab_light_kinds(TModel3d *Model); cell_data const &cell_instances(scene::basic_cell &Cell, bool const Rebuild); section_data const §ion(TModel3d *Model, TSubModel *Section, gfx::geometrybank_manager const &Geometry); section_data const &model_bounds(TModel3d *Model, gfx::geometrybank_manager const &Geometry); std::vector m_lights; - light m_cablamp; + std::vector m_cablights; bool m_cablampon{false}; TDynamicObject const *m_cablampvehicle{nullptr}; - glm::vec3 m_cablamplocal{0.f}; + // walk() goes through the cab model: only its light points of the cab and of the desk, at these levels (0-1) of their switches, + // whatever the self-illumination threshold + bool m_walkcab{false}; + float m_walkcablevel{0.f}; + float m_walkdashboardlevel{0.f}; std::unordered_map m_modellights; // whether the model has light points + std::unordered_map m_cabkinds; // the kinds of light points of cab models std::unordered_map m_cells; std::unordered_map m_sections; // compartments, by their submodel std::unordered_map m_models; // bounds of cab models diff --git a/rendering/common/localshadows.cpp b/rendering/common/localshadows.cpp index 266d1da3..f962a940 100644 --- a/rendering/common/localshadows.cpp +++ b/rendering/common/localshadows.cpp @@ -53,12 +53,12 @@ local_shadows::level_setup local_shadows::setup(int const Level) } } -void local_shadows::allocate(int const Size) +void local_shadows::allocate(int const Count, int const Size) { release(); - if (Size <= 0) + if ((Count <= 0) || (Size <= 0)) return; - if (false == gpu_allocate(Size)) + if (false == gpu_allocate(Count, Size)) { ErrorLog("lamp shadow map framebuffer setup failed, the lamps have no shadows"); release(); @@ -66,14 +66,18 @@ void local_shadows::allocate(int const Size) return; } m_size = Size; - WriteLog("lamp shadow maps: " + std::to_string(Size) + " x " + std::to_string(Size) + " cube"); + m_cabslots.assign(static_cast(Count), cab_slot{}); + WriteLog("cab light shadow maps: " + std::to_string(Count) + " x " + std::to_string(Size) + " x " + std::to_string(Size) + " cube (" + + std::to_string(Count * 6 * Size / 1024 * Size * 4 / 1024) + " MB)"); } void local_shadows::release() { gpu_release(); m_size = 0; - m_valid = false; + m_cabslots.clear(); + m_cabwanted.clear(); + m_cabdraws.clear(); } void local_shadows::allocate_lamps(int const Count, int const Size) @@ -129,59 +133,165 @@ void local_shadows::release_headlights() m_headlightswanted.clear(); } -bool local_shadows::update_wanted(TDynamicObject const *Vehicle, TModel3d const *Cab, glm::vec3 const &Lamp, unsigned int const Framestamp) +void local_shadows::update_cab(TDynamicObject const *Vehicle, TModel3d const *Cab, std::vector const &Lights, unsigned int const Framestamp) { + m_cabwanted.clear(); + m_cabdraws.clear(); auto const level{std::clamp(Global.gfx_lights_shadows, 0, 3)}; - if (level != m_level) + auto const limit{level > 0 ? std::clamp(Global.gfx_lights_shadows_cab, 0, cab_slots_max) : 0}; + if ((level != m_level) || (static_cast(limit) < m_cabslots.size())) { m_level = level; - auto const size{setup(level).size}; - if (size != m_size) - allocate(size); - m_valid = false; + release(); } - if ((m_size == 0) || (Vehicle == nullptr) || (Cab == nullptr)) + auto const forget = [this]() { + for (auto &slot : m_cabslots) + { + slot = cab_slot{}; + } + }; + if ((limit == 0) || (Vehicle == nullptr) || (Cab == nullptr)) { - // with the lamp off nothing reads the map; once it is on again the cab may have changed - m_valid = false; - return false; + // with the lights off nothing reads the maps; once they are on again the cab may have changed + forget(); + m_cabvehicle = nullptr; + m_cabmodel = nullptr; + return; } - if ((false == m_valid) || (Vehicle != m_vehicle) || (Cab != m_cab) || (Lamp != m_lamp)) + if ((Vehicle != m_cabvehicle) || (Cab != m_cabmodel)) { - m_valid = false; + forget(); + m_cabvehicle = Vehicle; + m_cabmodel = Cab; } - // the moving parts of the cab are followed every few frames - if (m_valid && (Framestamp - m_drawnframe < setup(level).interval)) + // the brightest lights which cast shadows, as many as there may be maps + std::vector picked; + for (auto const &light : Lights) { - return false; + if (picked.size() >= static_cast(limit)) + { + break; + } + if (light.source.cast_shadows && (light.source.range >= LAMP_RANGE_MIN)) + { + picked.emplace_back(&light); + } + } + if (picked.empty()) + { + return; + } + // the maps are made as they're needed, up to the limit: a cab with its lamp alone takes one + if (picked.size() > m_cabslots.size()) + { + allocate(static_cast(picked.size()), setup(level).size); + if (m_cabslots.empty()) + { + return; + } + } + 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)}; + if ((Slot.lamp != Light.local) || (Slot.nearplane != nearplane)) + { + Slot.valid = false; + } + Slot.lamp = Light.local; + 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; + for (auto const *light : picked) + { + auto placed{false}; + for (std::size_t idx = 0; idx < m_cabslots.size(); ++idx) + { + auto &slot{m_cabslots[idx]}; + if ((slot.part == light->source.part) && (slot.wantedframe != Framestamp)) + { + place(slot, idx, *light); + placed = true; + break; + } + } + if (false == placed) + { + unplaced.emplace_back(light); + } + } + for (auto const *light : unplaced) + { + for (std::size_t idx = 0; idx < m_cabslots.size(); ++idx) + { + auto &slot{m_cabslots[idx]}; + if (slot.wantedframe == Framestamp) + { + continue; + } + slot = cab_slot{}; + slot.part = light->source.part; + place(slot, idx, *light); + break; + } } - m_vehicle = Vehicle; - m_cab = Cab; - m_lamp = Lamp; - m_drawnframe = Framestamp; - return true; } -void local_shadows::begin() +void local_shadows::begin(std::size_t const Slot) { - m_cpustart = std::chrono::steady_clock::now(); - gpu_begin_cab(); + if (m_cabdraws.empty() || (Slot == m_cabdraws.front())) + { + // the first map of the frame + m_framedrawcalls = 0; + m_cpustart = std::chrono::steady_clock::now(); + } + m_cabdrawing = Slot; + gpu_begin_cab(Slot); } void local_shadows::end(std::uint32_t const Drawcalls) { gpu_end_cab(); - m_valid = true; - m_drawcalls = Drawcalls; + m_cabslots[m_cabdrawing].valid = true; + m_framedrawcalls += Drawcalls; ++m_updatecount; - auto const elapsed{std::chrono::duration(std::chrono::steady_clock::now() - m_cpustart).count()}; - m_cputime = (m_cputime == 0.0 ? elapsed : m_cputime * 0.9 + elapsed * 0.1); + if (m_cabdraws.empty() || (m_cabdrawing == m_cabdraws.back())) + { + // the last map of the frame + m_drawcalls = m_framedrawcalls; + auto const elapsed{std::chrono::duration(std::chrono::steady_clock::now() - m_cpustart).count()}; + m_cputime = (m_cputime == 0.0 ? elapsed : m_cputime * 0.9 + elapsed * 0.1); + } } -glm::vec4 local_shadows::weights() const +int local_shadows::cab_slot_of(TDynamicObject const *Vehicle, gfx::local_lights::cab_light const &Light) const +{ + if ((Vehicle == nullptr) || (Vehicle != m_cabvehicle)) + { + return -1; + } + for (auto const idx : m_cabwanted) + { + auto const &slot{m_cabslots[idx]}; + if ((slot.part == Light.source.part) && slot.valid && (slot.lamp == Light.local)) + { + return static_cast(idx); + } + } + return -1; +} + +glm::vec4 local_shadows::weights(std::size_t const Slot) const { // a face spans twice the distance from the lamp - return glm::vec4{static_cast(cab_slot), shadow_near, 2.f / static_cast(std::max(m_size, 1)), setup(m_level).radius}; + return glm::vec4{static_cast(Slot), m_cabslots[Slot].nearplane, 2.f / static_cast(std::max(m_size, 1)), setup(m_level).radius}; } void local_shadows::update_lamps(std::vector const &Lights, glm::dvec3 const &Camera, std::uint64_t const Version, unsigned int const Framestamp) @@ -596,8 +706,8 @@ std::string local_shadows::info() const std::string text; if (m_size != 0) { - text += "\nlamp shadows: " + std::to_string(m_size) + " cube, " + (m_valid ? "cab lamp" : "none") + ", " + std::to_string(m_drawcalls) + " drawcalls, cpu " + to_string(m_cputime, 2) + - " ms"; + text += "\ncab light shadows: " + std::to_string(m_cabwanted.size()) + " of " + std::to_string(m_cabslots.size()) + " " + std::to_string(m_size) + " cubes, " + + std::to_string(m_drawcalls) + " drawcalls, cpu " + to_string(m_cputime, 2) + " ms"; } if (false == m_slots.empty()) { @@ -630,9 +740,11 @@ void local_shadows::draw_debug_ui() { char const *levels[]{"no", "low (512, every 4th frame)", "medium (1024, every 2nd frame)", "high (1024, every frame, softer)"}; ImGui::Combo("lamp shadows (gfx.lights.shadows)", &Global.gfx_lights_shadows, levels, IM_ARRAYSIZE(levels)); + ImGui::SliderInt("cab lights with shadows (gfx.lights.shadows.cab)", &Global.gfx_lights_shadows_cab, 0, cab_slots_max); if (m_size != 0) { - ImGui::Text("cab: cube %d x %d, %s, drawn %u times, %u drawcalls, %.2f ms (cpu)", m_size, m_size, m_valid ? "cab lamp" : "no lamp", m_updatecount, m_drawcalls, m_cputime); + ImGui::Text("cab: %u of %u cubes %d x %d, drawn %u times, %u drawcalls, %.2f ms (cpu)", static_cast(m_cabwanted.size()), static_cast(m_cabslots.size()), m_size, + m_size, m_updatecount, m_drawcalls, m_cputime); } ImGui::SliderInt("scenery lamps with shadows (gfx.lights.shadows.lamps)", &Global.gfx_lights_shadows_lamps, 0, 32); ImGui::SliderFloat("their reach from the camera, m (.lamps.range)", &Global.gfx_lights_shadows_lamps_range, 0.f, 500.f); diff --git a/rendering/common/localshadows.h b/rendering/common/localshadows.h index 84b6dbb2..e0862003 100644 --- a/rendering/common/localshadows.h +++ b/rendering/common/localshadows.h @@ -28,9 +28,11 @@ namespace gfx // the shadow maps of the lamps (Global.gfx_lights_shadows), the part shared by the renderers (stage P4 of // claude/vulkan14-renderer-plan.md): which lamps and headlights get maps, when they are drawn, what the shading reads; the // backend (opengl46_local_shadows, the vulkan renderer) keeps the maps and draws to them through the gpu_* methods. -// - the lamp of the occupied cab, with the shadows cast by the cab (the model drawn by Render_cab()). the map is kept in the space -// of the cab model, centred on the lamp: 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 lamp, the cab or the level change; +// - 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; // - 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 @@ -51,38 +53,50 @@ class local_shadows public: // near plane of the maps, metres. surfaces closer to the lamp (its own fitting) cast no shadow static constexpr float shadow_near{0.15f}; - // layer of the map of the cab lamp in its array - static constexpr int cab_slot{0}; + // lights of the cab with shadow maps, at most + static constexpr int cab_slots_max{8}; local_shadows() = default; virtual ~local_shadows() = default; local_shadows(const local_shadows &) = delete; local_shadows &operator=(const local_shadows &) = delete; - // the cab lamp + // the lights of the cab - // whether the map of the cab lamp is to be drawn in this frame: Vehicle and Cab the occupied vehicle and its cab model, - // Lamp the position of the lamp in the space of the cab model; Vehicle nullptr when the lamp is off. (re)makes the map to - // the size of the level when the level changed - bool update_wanted(TDynamicObject const *Vehicle, TModel3d const *Cab, glm::vec3 const &Lamp, unsigned int const Framestamp); - // sets up the drawing to the map: the target, viewport, cleared faces, the state of the faces. the geometry is to be - // drawn with the programs of the cube maps and a modelview from the space of the model to the space of the map - void begin(); + // a light of the cab with a slot (a cube of the array of the cab) + struct cab_slot + { + void const *part{nullptr}; // the light it is, see gfx::local_lights::cab_light + glm::vec3 lamp{0.f}; // position of the light, in the space of the cab model + float nearplane{shadow_near}; // near plane of the map, see lamp_slot + bool valid{false}; // the map holds the shadows of the light where it is + unsigned int drawnframe{0}; // framestamp the map was last drawn in + unsigned int wantedframe{~0u}; // framestamp it was last among the lights given shadows + }; + // picks the lights of the cab which get shadows in this frame, and the maps to be drawn in it: Vehicle and Cab the occupied + // vehicle and its cab model (nullptr when the cab has no lights), Lights its lights, the brightest first. (re)makes the maps when + // the level, the size or the number wanted changed + void update_cab(TDynamicObject const *Vehicle, TModel3d const *Cab, std::vector const &Lights, unsigned int const Framestamp); + // the slots whose maps are to be drawn in this frame + std::vector const &cab_draws() const + { + return m_cabdraws; + } + cab_slot const &cab(std::size_t const Slot) const + { + return m_cabslots[Slot]; + } + // sets up the drawing to the map of the slot: the target, viewport, cleared faces, the state of the faces, its near plane. the + // geometry is to be drawn with the programs of the cube maps and a modelview from the space of the model to the space of the map + // (the light of the slot at the origin) + void begin(std::size_t const Slot); // ends the drawing to the map void end(std::uint32_t const Drawcalls); - // whether the map holds the shadows of the lamp of Vehicle, the occupied vehicle - bool ready(TDynamicObject const *Vehicle) const - { - return m_valid && (Vehicle != nullptr) && (Vehicle == m_vehicle); - } - // position of the lamp the map was drawn for, in the space of the cab model - glm::vec3 const &lamp() const - { - return m_lamp; - } - // the headlight_weights of the light which reads the map: layer, near plane, size of a texel one metre from the lamp, + // the slot of the light of the cab of Vehicle with its shadows ready, -1 if it has none + 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 - glm::vec4 weights() const; + glm::vec4 weights(std::size_t const Slot) const; // lamps of scenery models @@ -228,17 +242,17 @@ class local_shadows }; static level_setup setup(int const Level); - // what the backend does with the maps: makes them (false: they can't be made) and lets them go; the cab lamp: one cube, the - // lamps: 2 x Count cubes (the scenery parts, then the working copies), the headlights: Count layers - virtual bool gpu_allocate(int const Size) = 0; + // what the backend does with the maps: makes them (false: they can't be made) and lets them go; the lights of the cab: Count + // cubes, the lamps: 2 x Count cubes (the scenery parts, then the working copies), the headlights: Count layers + virtual bool gpu_allocate(int const Count, int const Size) = 0; virtual void gpu_release() = 0; virtual bool gpu_allocate_lamps(int const Count, int const Size) = 0; virtual void gpu_release_lamps() = 0; virtual bool gpu_allocate_headlights(int const Count, int const Size) = 0; virtual void gpu_release_headlights() = 0; - // the drawing to the map of the cab lamp, to a cube of the lamps (Vehicles: the working copy, from a copy of the scenery part - // Scenerycube; the scenery part cleared otherwise) and to a layer of the headlights - virtual void gpu_begin_cab() = 0; + // the drawing to the cube of a light of the cab (with the near plane of the slot), to a cube of the lamps (Vehicles: the working + // copy, from a copy of the scenery part Scenerycube; the scenery part cleared otherwise) and to a layer of the headlights + virtual void gpu_begin_cab(std::size_t const Slot) = 0; virtual void gpu_end_cab() = 0; virtual void gpu_begin_lamp(int const Scenerycube, int const Workingcube, bool const Vehicles) = 0; virtual void gpu_end_lamp() = 0; @@ -248,7 +262,7 @@ class local_shadows virtual void gpu_lamps_timer(bool const Begin) = 0; virtual void gpu_headlights_timer(bool const Begin) = 0; - void allocate(int const Size); + void allocate(int const Count, int const Size); void release(); void allocate_lamps(int const Count, int const Size); void release_lamps(); @@ -256,13 +270,14 @@ class local_shadows void release_headlights(); int m_level{-1}; // level the maps were made for - // cab lamp - int m_size{0}; // of a face of the map, 0 when there is none - bool m_valid{false}; // the map holds the shadows of the lamp of m_vehicle - TDynamicObject const *m_vehicle{nullptr}; - TModel3d const *m_cab{nullptr}; - glm::vec3 m_lamp{0.f}; - unsigned int m_drawnframe{0}; // framestamp the map was last drawn in + // lights of the cab: a cube per slot + int m_size{0}; // of a face of the maps, 0 when there are none + std::vector m_cabslots; + std::vector m_cabwanted; // slots of the lights picked in this frame + std::vector m_cabdraws; // and of those whose maps are drawn in it + std::size_t m_cabdrawing{0}; // slot begin() set up the drawing to + TDynamicObject const *m_cabvehicle{nullptr}; // the vehicle and the cab model the maps of the slots are of + TModel3d const *m_cabmodel{nullptr}; // scenery lamps: the scenery parts of the slots first, then their working copies int m_lampssize{0}; std::vector m_slots; @@ -282,8 +297,9 @@ class local_shadows std::vector> m_headlightcandidates; std::vector m_headlightpicked; // statistics - std::uint32_t m_drawcalls{0}; // of the last drawing of the cab map - std::uint32_t m_updatecount{0}; // drawings of the cab map so far + std::uint32_t m_drawcalls{0}; // of the drawings of the cab maps in the last frame with any + std::uint32_t m_framedrawcalls{0}; + std::uint32_t m_updatecount{0}; // drawings of the cab maps so far double m_cputime{0.0}; // milliseconds, smoothed std::chrono::steady_clock::time_point m_cpustart; std::uint32_t m_lampsscenery{0}; // scenery parts drawn so far diff --git a/rendering/common/scenewalk.h b/rendering/common/scenewalk.h index 5c2ca6ad..56aef992 100644 --- a/rendering/common/scenewalk.h +++ b/rendering/common/scenewalk.h @@ -567,6 +567,8 @@ class scene_walk bool m_previewing{false}; // the last lighting of the pass placed the cab lamp among the lights: the cab is lit by it, not by an addition to the ambient light bool m_cablamplit{false}; + // and the number of the lights of the cab it placed, the first ones of the light buffer after the sun + int m_cablamps{0}; // the colour pass reads the probe of the cab (gfx::cab_probe): the light of the cab lamp the cab reflects lights it again from the // probe, so the ambient light keeps no share of the lamp bool m_cabprobelit{false}; diff --git a/rendering/opengl46/opengl46localshadows.cpp b/rendering/opengl46/opengl46localshadows.cpp index 6c297397..61b0a3c0 100644 --- a/rendering/opengl46/opengl46localshadows.cpp +++ b/rendering/opengl46/opengl46localshadows.cpp @@ -24,8 +24,6 @@ float const SHADOW_NEAR{gfx::local_shadows::shadow_near}; // uniform locations of shadowcube.geom GLint const NEAR_LOCATION{0}; GLint const LAYER_LOCATION{1}; -// layer of the map of the cab lamp in the array -int const CAB_SLOT{gfx::local_shadows::cab_slot}; // cube map array of depth maps compared to the reference, one level, Cubes cubes of Size x Size faces GLuint make_cube_array(int const Size, int const Cubes) @@ -136,7 +134,7 @@ void opengl46_local_shadows::init() m_multidraw_program.reset(); m_alpha_multidraw_program.reset(); } - set_layer(CAB_SLOT * 6); + set_layer(0); // a shadow sampler with no depth texture to read from is undefined, also where the shading doesn't read it glCreateTextures(GL_TEXTURE_CUBE_MAP_ARRAY, 1, &m_empty); @@ -173,9 +171,9 @@ void opengl46_local_shadows::set_layer(int const Layer) glProgramUniform1i(*m_alpha_multidraw_program, LAYER_LOCATION, Layer); } -bool opengl46_local_shadows::gpu_allocate(int const Size) +bool opengl46_local_shadows::gpu_allocate(int const Count, int const Size) { - m_texture = make_cube_array(Size, CAB_SLOT + 1); + m_texture = make_cube_array(Size, Count); m_framebuffer = make_framebuffer(m_texture); return (m_framebuffer != 0); } @@ -235,21 +233,25 @@ void opengl46_local_shadows::bind() glBindTextureUnit(gl::HEADLIGHT_SHADOW_TEX, (m_headlightstexture != 0) ? m_headlightstexture : m_emptyheadlights); } -void opengl46_local_shadows::gpu_begin_cab() +void opengl46_local_shadows::gpu_begin_cab(std::size_t const Slot) { - m_timer.begin(); - set_layer(CAB_SLOT * 6); - set_near(SHADOW_NEAR); + if (Slot == m_cabdraws.front()) + m_timer.begin(); + set_layer(static_cast(Slot) * 6); + // the near plane of the light, which the shading reads the map with (weights()) + set_near(m_cabslots[Slot].nearplane); setup_drawing(m_framebuffer, m_size); float const farthest{0.f}; - glClearNamedFramebufferfv(m_framebuffer, GL_DEPTH, 0, &farthest); + glClearTexSubImage(m_texture, 0, 0, 0, static_cast(Slot) * 6, m_size, m_size, 6, GL_DEPTH_COMPONENT, GL_FLOAT, &farthest); } void opengl46_local_shadows::gpu_end_cab() { glEnable(GL_CULL_FACE); gl::framebuffer::unbind(); - m_timer.end(); + set_layer(0); + if (m_cabdrawing == m_cabdraws.back()) + m_timer.end(); } void opengl46_local_shadows::gpu_begin_lamp(int const Scenerycube, int const Workingcube, bool const Vehicles) @@ -274,7 +276,7 @@ void opengl46_local_shadows::gpu_end_lamp() { glEnable(GL_CULL_FACE); gl::framebuffer::unbind(); - set_layer(CAB_SLOT * 6); + set_layer(0); } void opengl46_local_shadows::gpu_lamps_timer(bool const Begin) diff --git a/rendering/opengl46/opengl46localshadows.h b/rendering/opengl46/opengl46localshadows.h index 2a7a1317..225e9e37 100644 --- a/rendering/opengl46/opengl46localshadows.h +++ b/rendering/opengl46/opengl46localshadows.h @@ -51,13 +51,13 @@ class opengl46_local_shadows : public gfx::local_shadows std::string info_times(); protected: - bool gpu_allocate(int const Size) override; + bool gpu_allocate(int const Count, int const Size) override; void gpu_release() override; bool gpu_allocate_lamps(int const Count, int const Size) override; void gpu_release_lamps() override; bool gpu_allocate_headlights(int const Count, int const Size) override; void gpu_release_headlights() override; - void gpu_begin_cab() override; + void gpu_begin_cab(std::size_t const Slot) override; void gpu_end_cab() override; void gpu_begin_lamp(int const Scenerycube, int const Workingcube, bool const Vehicles) override; void gpu_end_lamp() override; @@ -76,7 +76,7 @@ class opengl46_local_shadows : public gfx::local_shadows std::unique_ptr m_alpha_multidraw_program; GLuint m_empty{0}; // stand-in: a cube map array of 1x1 faces with the farthest depth GLuint m_emptyheadlights{0}; // the same, a 2d array of one 1x1 layer - // cab lamp: cube map array, one cube + // lights of the cab: cube map array, a cube per slot GLuint m_texture{0}; GLuint m_framebuffer{0}; gl::gpu_timer m_timer; diff --git a/rendering/opengl46/opengl46renderer.cpp b/rendering/opengl46/opengl46renderer.cpp index 382e11ef..347d6eff 100644 --- a/rendering/opengl46/opengl46renderer.cpp +++ b/rendering/opengl46/opengl46renderer.cpp @@ -1826,7 +1826,7 @@ void opengl46_renderer::Render_pass(viewport_config &vp, rendermode const Mode) setup_shadow_bind_map(); m_weather.render_view( OpenGLMatrices.data( GL_PROJECTION ), glm::mat4{ m_colorpass.pass_camera.modelview() }, m_colorpass.pass_camera.position(), fogtarget, - ( m_cablamplit && Global.gfx_lights_clustered && vp.clusters->valid() ) ? 1 : -1 ); + ( m_cablamplit && Global.gfx_lights_clustered && vp.clusters->valid() ) ? m_cablamps : 0 ); } m_weather.setup_pass( vp.main, fogtarget ); } @@ -6080,29 +6080,34 @@ void opengl46_renderer::Update_Lights(light_array &Lights, bool const Headlights size_t light_i = 1; m_lights_dropped = 0; - // the lights other than the headlights are only taken from the lists of the clusters. the lamp of the occupied cab goes first: - // it is next to the camera, and it is not to be the one left out of a full cluster + // the lights other than the headlights are only taken from the lists of the clusters. the lights of the occupied cab go first: + // they are next to the camera, and they are not to be the ones left out of a full cluster auto const locallights{Headlights && Clustered}; m_cablamplit = false; + m_cablamps = 0; if( locallights ) { - if( auto const *lamp{ m_locallights.cab_lamp() } ) { + auto const *vehicle{ m_locallights.cab_lamp_vehicle() }; + for( auto const &cablight : m_locallights.cab_lights() ) { + if( light_i >= gl::MAX_LIGHTS ) { + break; + } auto &light{ light_data.lights[ light_i++ ] }; - Write_Local_Light( light, *lamp, camera, mv ); - m_cablamplit = true; - auto const *vehicle{ m_locallights.cab_lamp_vehicle() }; - if( m_localshadows.ready( vehicle ) ) { - // view space to the space of the map: the space of the cab model, with the lamp at the origin. the vehicle is + Write_Local_Light( light, cablight.source, camera, mv ); + ++m_cablamps; + if( auto const slot{ m_localshadows.cab_slot_of( vehicle, cablight ) }; slot >= 0 ) { + // view space to the space of the map: the space of the cab model, with the light at the origin. the vehicle is // where it is now, the map stays right as it moves auto const toworld{ glm::dmat4{ glm::transpose( glm::mat3{ mv } ) } }; auto const tomap{ - glm::translate( glm::dmat4{ 1.0 }, -glm::dvec3{ m_localshadows.lamp() } ) + glm::translate( glm::dmat4{ 1.0 }, -glm::dvec3{ m_localshadows.cab( static_cast( slot ) ).lamp } ) * glm::inverse( vehicle->mMatrix ) * glm::translate( glm::dmat4{ 1.0 }, camera - vehicle->GetPosition() ) * toworld }; light.headlight_projection = glm::mat4{ tomap }; - light.headlight_weights = m_localshadows.weights(); + light.headlight_weights = m_localshadows.weights( static_cast( slot ) ); } } + m_cablamplit = ( m_cablamps > 0 ); } auto const headlightsstart{light_i}; @@ -6274,22 +6279,26 @@ void opengl46_renderer::Render_local_shadows() { // only the light buffers with the lamps read the maps auto const *vehicle{Global.gfx_lights_clustered ? m_locallights.cab_lamp_vehicle() : nullptr}; - if (false == m_localshadows.update_wanted(vehicle, (vehicle != nullptr ? vehicle->mdKabina : nullptr), m_locallights.cab_lamp_local(), m_framestamp)) + m_localshadows.update_cab(vehicle, (vehicle != nullptr ? vehicle->mdKabina : nullptr), m_locallights.cab_lights(), m_framestamp); + if (m_localshadows.cab_draws().empty()) return; glDebug("render lamp shadows"); - // the cab drawn by the shadow pass, with the programs of the cube map. the camera is put at the vehicle, which Render_cab() - // places the cab relative to, and the modelview takes the cab model to the space of the map + // the cab drawn by the shadow pass to the map of each light, with the programs of the cube map. the camera is put at the vehicle, + // which Render_cab() places the cab relative to, and the modelview takes the cab model to the space of the map m_renderpass.draw_mode = rendermode::shadows; m_renderpass.pass_camera.position() = vehicle->GetPosition(); - m_renderpass.draw_stats = {}; OpenGLMatrices.mode(GL_MODELVIEW); - OpenGLMatrices.load_matrix(glm::mat4{glm::translate(glm::dmat4{1.0}, -glm::dvec3{m_localshadows.lamp()}) * glm::inverse(vehicle->mMatrix)}); - m_localshadows.begin(); - m_cubeshadows = true; - Render_cab(vehicle, 0.0f, false); - m_cubeshadows = false; - m_localshadows.end(static_cast(m_renderpass.draw_stats.drawcalls)); + for (auto const slot : m_localshadows.cab_draws()) + { + m_renderpass.draw_stats = {}; + OpenGLMatrices.load_matrix(glm::mat4{glm::translate(glm::dmat4{1.0}, -glm::dvec3{m_localshadows.cab(slot).lamp}) * glm::inverse(vehicle->mMatrix)}); + m_localshadows.begin(slot); + m_cubeshadows = true; + Render_cab(vehicle, 0.0f, false); + m_cubeshadows = false; + m_localshadows.end(static_cast(m_renderpass.draw_stats.drawcalls)); + } m_renderpass = m_colorpass; glPolygonMode(GL_FRONT_AND_BACK, Global.bWireFrame ? GL_LINE : GL_FILL); } @@ -6311,15 +6320,16 @@ void opengl46_renderer::Render_cab_probe() glm::vec3 extent{0.f}; if ((vehicle != nullptr) && (false == m_locallights.cab_bounds(vehicle->mdKabina, m_geometry, center, extent))) vehicle = nullptr; - // the cab lamp lights the probe when it's a light of the frame - auto const *lamp{(vehicle != nullptr) && (m_locallights.cab_lamp_vehicle() == vehicle) ? m_locallights.cab_lamp() : nullptr}; + // the brightest light of the cab lights the probe when it's a light of the frame + auto const *principal{(vehicle != nullptr) && (m_locallights.cab_lamp_vehicle() == vehicle) ? m_locallights.cab_principal() : nullptr}; + auto const *lamp{principal != nullptr ? &principal->source : nullptr}; if (false == m_cabprobe.update_wanted(vehicle, (vehicle != nullptr ? vehicle->mdKabina : nullptr), center, extent, lamp, m_framestamp)) return; glDebug("render cab probe"); - // the lamp with its shadow map, when the map holds the shadows of the lamp where it is - auto const shadows{(lamp != nullptr) && m_localshadows.ready(vehicle) && (m_localshadows.lamp() == m_locallights.cab_lamp_local())}; - m_cabprobe.setup_drawing(scene_ubs, lamp, m_locallights.cab_lamp_local(), shadows, m_localshadows.weights()); + // the light with its shadow map, when the map holds the shadows of the light where it is + auto const slot{principal != nullptr ? m_localshadows.cab_slot_of(vehicle, *principal) : -1}; + m_cabprobe.setup_drawing(scene_ubs, lamp, (principal != nullptr ? principal->local : glm::vec3{0.f}), (slot >= 0), (slot >= 0 ? m_localshadows.weights(static_cast(slot)) : glm::vec4{0.f})); // the sun coming in through the windows, as the shading of the last frame got it m_cabprobe.setup_sun(scene_ubs, vehicle, -m_sunlight.direction, glm::vec3{light_data.lights[0].color} * light_data.lights[0].intensity, m_colorpass.pass_camera.position()); scene_ubo->update(scene_ubs); diff --git a/rendering/opengl46/opengl46weather.cpp b/rendering/opengl46/opengl46weather.cpp index dcbacb77..d3f40744 100644 --- a/rendering/opengl46/opengl46weather.cpp +++ b/rendering/opengl46/opengl46weather.cpp @@ -280,7 +280,7 @@ void opengl46_weather::prepare_view(glm::ivec2 const &Size, quality_level const m_view_level = Level; } -void opengl46_weather::render_view(glm::mat4 const &Projection, glm::mat4 const &View, glm::dvec3 const &Camera, glm::ivec2 const &Size, int const Cablamp) +void opengl46_weather::render_view(glm::mat4 const &Projection, glm::mat4 const &View, glm::dvec3 const &Camera, glm::ivec2 const &Size, int const Cablamps) { auto const level{gfx::weather_state::quality(Global.gfx_clouds, Global.gfx_fog_quality)}; prepare_view(Size, level); @@ -353,7 +353,7 @@ void opengl46_weather::render_view(glm::mat4 const &Projection, glm::mat4 const auto const sunshadows{Global.gfx_fog_shadows && Global.gfx_shadowmap_enabled}; auto const share{temporal ? std::clamp(Global.gfx_fog_temporal_share, 0.02f, 1.f) : 1.f}; glProgramUniform4f(*m_inject_program, 6, share, Global.gfx_fog_lights, sunshadows ? 1.f : 0.f, Global.gfx_fog_shadows ? 1.f : 0.f); - glProgramUniform1i(*m_inject_program, 7, Cablamp); + glProgramUniform1i(*m_inject_program, 7, Cablamps); glProgramUniform2f(*m_inject_program, 8, previousfog.slicing.x, previousfog.slicing.z); glProgramUniform2f(*m_inject_program, 9, jitter.x, jitter.y); m_inject_program->dispatch(groups(m_fog_grid.x), groups(m_fog_grid.y), static_cast(m_fog_grid.z)); diff --git a/rendering/opengl46/opengl46weather.h b/rendering/opengl46/opengl46weather.h index d31327d5..58ce3003 100644 --- a/rendering/opengl46/opengl46weather.h +++ b/rendering/opengl46/opengl46weather.h @@ -52,9 +52,9 @@ class opengl46_weather // moon, Keycolor: its colour as the shading gets it, Ambient: the ambient light of the sky, Fogcolor: the old colour of the fog void update(opengl46_sky const &Sky, glm::vec3 const &Keylight, glm::vec3 const &Keycolor, glm::vec3 const &Ambient, glm::vec3 const &Fogcolor, glm::dvec3 const &Camera); // the clouds and the fog volume of the colour pass of the main view. Projection and View (rotation, world axes to the view) - // of its camera, Size of the target the scene is drawn into, Cablamp the light of the cab lamp (it lights no fog) or -1. + // of its camera, Size of the target the scene is drawn into, Cablamps the number of the lights of the cab (they light no fog), from the light 1 on. // the light buffer, the light clusters and the shadow map of the pass are bound - void render_view(glm::mat4 const &Projection, glm::mat4 const &View, glm::dvec3 const &Camera, glm::ivec2 const &Size, int const Cablamp); + void render_view(glm::mat4 const &Projection, glm::mat4 const &View, glm::dvec3 const &Camera, glm::ivec2 const &Size, int const Cablamps); // the fog of a pass: the volume of the main view (after render_view() in the same frame), or along the ray void setup_pass(bool const Volume, glm::ivec2 const &Size); // the stars, in the sky pass. View: rotation of the camera of the pass diff --git a/rendering/vulkan14/vulkan14cabprobe.cpp b/rendering/vulkan14/vulkan14cabprobe.cpp index 89a404a3..726c4837 100644 --- a/rendering/vulkan14/vulkan14cabprobe.cpp +++ b/rendering/vulkan14/vulkan14cabprobe.cpp @@ -348,8 +348,9 @@ void vulkan14_renderer::render_cab_probe() { vehicle = nullptr; } - // the cab lamp lights the probe when it's a light of the frame - auto const *lamp{(vehicle != nullptr) && (m_locallights.cab_lamp_vehicle() == vehicle) ? m_locallights.cab_lamp() : nullptr}; + // the brightest light of the cab lights the probe when it's a light of the frame + auto const *principal{(vehicle != nullptr) && (m_locallights.cab_lamp_vehicle() == vehicle) ? m_locallights.cab_principal() : nullptr}; + auto const *lamp{principal != nullptr ? &principal->source : nullptr}; if (false == probe.update_wanted(vehicle, (vehicle != nullptr ? vehicle->mdKabina : nullptr), center, extent, lamp, m_framestamp)) { return; @@ -360,9 +361,9 @@ void vulkan14_renderer::render_cab_probe() return; } ctx.passes.begin_pass("cab probe"); - // the lamp with its shadow map, when the map holds the shadows of the lamp where it is - auto const shadows{(lamp != nullptr) && scene.lamps.ready(vehicle) && (scene.lamps.lamp() == m_locallights.cab_lamp_local())}; - probe.setup_drawing(scene_ubs, lamp, m_locallights.cab_lamp_local(), shadows, scene.lamps.weights()); + // the light with its shadow map, when the map holds the shadows of the light where it is + auto const slot{principal != nullptr ? scene.lamps.cab_slot_of(vehicle, *principal) : -1}; + probe.setup_drawing(scene_ubs, lamp, (principal != nullptr ? principal->local : glm::vec3{0.f}), (slot >= 0), (slot >= 0 ? scene.lamps.weights(static_cast(slot)) : glm::vec4{0.f})); // the sun coming in through the windows, as the shading of the last frame got it. the mask is read once it was copied probe.setup_sun(scene_ubs, vehicle, -m_sunlight.direction, glm::vec3{light_data.lights[0].color} * light_data.lights[0].intensity, m_colorpass.pass_camera.position()); if (probe.mask_map() == nullptr) diff --git a/rendering/vulkan14/vulkan14frame.cpp b/rendering/vulkan14/vulkan14frame.cpp index 0e4f2137..7b4d77c4 100644 --- a/rendering/vulkan14/vulkan14frame.cpp +++ b/rendering/vulkan14/vulkan14frame.cpp @@ -1629,7 +1629,7 @@ void vulkan14_renderer::render_viewport(std::size_t const Index) } if (vp.main) { - render_weather_view(OpenGLMatrices.data(GL_PROJECTION), glm::mat4{m_colorpass.pass_camera.modelview()}, m_colorpass.pass_camera.position(), fogtarget, (m_cablamplit && clustered) ? 1 : -1); + render_weather_view(OpenGLMatrices.data(GL_PROJECTION), glm::mat4{m_colorpass.pass_camera.modelview()}, m_colorpass.pass_camera.position(), fogtarget, (m_cablamplit && clustered) ? m_cablamps : 0); } m_weatherstate.setup_pass(vp.main, fogtarget); upload_weather_block(); @@ -3789,28 +3789,35 @@ void vulkan14_renderer::update_lights(light_array &Lights, bool const Headlights std::size_t light_i = 1; m_lights_dropped = 0; - // the lights other than the headlights are only taken from the lists of the clusters. the lamp of the occupied cab goes first + // the lights other than the headlights are only taken from the lists of the clusters. the lights of the occupied cab go first auto const locallights{Headlights && Clustered}; m_cablamplit = false; + m_cablamps = 0; if (locallights) { - if (auto const *lamp{m_locallights.cab_lamp()}) + auto const *vehicle{m_locallights.cab_lamp_vehicle()}; + auto const &lamps{m_context->scene.lamps}; + for (auto const &cablight : m_locallights.cab_lights()) { - auto &light{light_data.lights[light_i++]}; - write_local_light(light, *lamp, camera, mv); - m_cablamplit = true; - auto const *vehicle{m_locallights.cab_lamp_vehicle()}; - auto const &lamps{m_context->scene.lamps}; - if (lamps.ready(vehicle)) + if (light_i >= gl::MAX_LIGHTS) { - // view space to the space of the map: the space of the cab model, with the lamp at the origin. the vehicle is where it + break; + } + auto &light{light_data.lights[light_i++]}; + write_local_light(light, cablight.source, camera, mv); + ++m_cablamps; + if (auto const slot{lamps.cab_slot_of(vehicle, cablight)}; slot >= 0) + { + // view space to the space of the map: the space of the cab model, with the light at the origin. the vehicle is where it // is now, the map stays right as it moves auto const toworld{glm::dmat4{glm::transpose(glm::mat3{mv})}}; - auto const tomap{glm::translate(glm::dmat4{1.0}, -glm::dvec3{lamps.lamp()}) * glm::inverse(vehicle->mMatrix) * glm::translate(glm::dmat4{1.0}, camera - vehicle->GetPosition()) * toworld}; + auto const tomap{glm::translate(glm::dmat4{1.0}, -glm::dvec3{lamps.cab(static_cast(slot)).lamp}) * glm::inverse(vehicle->mMatrix) * + glm::translate(glm::dmat4{1.0}, camera - vehicle->GetPosition()) * toworld}; light.headlight_projection = glm::mat4{tomap}; - light.headlight_weights = lamps.weights(); + light.headlight_weights = lamps.weights(static_cast(slot)); } } + m_cablamplit = (m_cablamps > 0); } auto const headlightsstart{light_i}; for (auto const &scenelight : Lights.data) diff --git a/rendering/vulkan14/vulkan14lamps.cpp b/rendering/vulkan14/vulkan14lamps.cpp index 5cece737..92abdca7 100644 --- a/rendering/vulkan14/vulkan14lamps.cpp +++ b/rendering/vulkan14/vulkan14lamps.cpp @@ -110,26 +110,34 @@ void vulkan14_renderer::render_local_shadows() auto &lamps{m_context->scene.lamps}; // only the light buffers with the lamps read the maps auto const *vehicle{Global.gfx_lights_clustered ? m_locallights.cab_lamp_vehicle() : nullptr}; - if (false == lamps.update_wanted(vehicle, (vehicle != nullptr ? vehicle->mdKabina : nullptr), m_locallights.cab_lamp_local(), m_framestamp)) + lamps.update_cab(vehicle, (vehicle != nullptr ? vehicle->mdKabina : nullptr), m_locallights.cab_lights(), m_framestamp); + if (lamps.cab_draws().empty()) { return; } m_context->passes.begin_pass("lamp shadows"); - // the cab drawn by the shadow pass, with the programs of the cube map. the camera is put at the vehicle, which Render_cab() - // places the cab relative to, and the modelview takes the cab model to the space of the map + // the cab drawn by the shadow pass to the map of each light, with the programs of the cube map. the camera is put at the vehicle, + // which Render_cab() places the cab relative to, and the modelview takes the cab model to the space of the map m_renderpass.draw_mode = rendermode::shadows; m_renderpass.pass_camera.position() = vehicle->GetPosition(); - m_renderpass.draw_stats = {}; OpenGLMatrices.mode(GL_MODELVIEW); - OpenGLMatrices.load_matrix(glm::mat4{glm::translate(glm::dmat4{1.0}, -glm::dvec3{lamps.lamp()}) * glm::inverse(vehicle->mMatrix)}); - lamps.begin(); - begin_local_shadow_rendering(); - begin_local_shadow_target(); - m_cubeshadows = true; - Render_cab(vehicle, 0.0f, false); - m_cubeshadows = false; - end_local_shadow_rendering(); - lamps.end(static_cast(m_renderpass.draw_stats.drawcalls)); + auto const previousnear{scene_ubs.shadow_fit.y}; + for (auto const slot : lamps.cab_draws()) + { + m_renderpass.draw_stats = {}; + OpenGLMatrices.load_matrix(glm::mat4{glm::translate(glm::dmat4{1.0}, -glm::dvec3{lamps.cab(slot).lamp}) * glm::inverse(vehicle->mMatrix)}); + lamps.begin(slot); + // the near plane of the map, for shadowcube.vert, in the scene block begin_local_shadow_target() uploads + scene_ubs.shadow_fit.y = lamps.cab(slot).nearplane; + begin_local_shadow_rendering(); + begin_local_shadow_target(); + m_cubeshadows = true; + Render_cab(vehicle, 0.0f, false); + m_cubeshadows = false; + end_local_shadow_rendering(); + lamps.end(static_cast(m_renderpass.draw_stats.drawcalls)); + } + scene_ubs.shadow_fit.y = previousnear; m_renderpass = m_colorpass; m_context->passes.end_pass(); } diff --git a/rendering/vulkan14/vulkan14renderer.h b/rendering/vulkan14/vulkan14renderer.h index 38371e88..8feee541 100644 --- a/rendering/vulkan14/vulkan14renderer.h +++ b/rendering/vulkan14/vulkan14renderer.h @@ -461,7 +461,7 @@ class vulkan14_renderer : public gfx_renderer, public gfx::scene_walk void make_cloud_noise(); void upload_weather_block(); void update_weather(); - void render_weather_view(glm::mat4 const &Projection, glm::mat4 const &View, glm::dvec3 const &Camera, glm::ivec2 const &Size, int const Cablamp); + void render_weather_view(glm::mat4 const &Projection, glm::mat4 const &View, glm::dvec3 const &Camera, glm::ivec2 const &Size, int const Cablamps); void render_sky_weather(glm::mat4 const &Projection, bool const Mainview); // the ambient occlusion (vulkan14ao.cpp): the resources, and the occlusion of a viewport multiplied into its colour bool init_ao(std::string &Reason); diff --git a/rendering/vulkan14/vulkan14shadows.cpp b/rendering/vulkan14/vulkan14shadows.cpp index 1bdb6643..3bacda83 100644 --- a/rendering/vulkan14/vulkan14shadows.cpp +++ b/rendering/vulkan14/vulkan14shadows.cpp @@ -98,9 +98,9 @@ void local_shadow_maps::let_go(std::shared_ptr &Map, std::vector(m_size), 0x3f, true, true}; + m_target = draw_target{m_cab.get(), (Slot < m_cabviews.size()) ? m_cabviews[Slot] : VK_NULL_HANDLE, static_cast(m_size), 0x3f, true, true}; } void local_shadow_maps::gpu_begin_lamp(int const Scenerycube, int const Workingcube, bool const Vehicles) diff --git a/rendering/vulkan14/vulkan14shadows.h b/rendering/vulkan14/vulkan14shadows.h index 4cc42e0d..33799229 100644 --- a/rendering/vulkan14/vulkan14shadows.h +++ b/rendering/vulkan14/vulkan14shadows.h @@ -74,13 +74,13 @@ class local_shadow_maps : public gfx::local_shadows } protected: - bool gpu_allocate(int const Size) override; + bool gpu_allocate(int const Count, int const Size) override; void gpu_release() override; bool gpu_allocate_lamps(int const Count, int const Size) override; void gpu_release_lamps() override; bool gpu_allocate_headlights(int const Count, int const Size) override; void gpu_release_headlights() override; - void gpu_begin_cab() override; + void gpu_begin_cab(std::size_t const Slot) override; void gpu_end_cab() override {} void gpu_begin_lamp(int const Scenerycube, int const Workingcube, bool const Vehicles) override; void gpu_end_lamp() override {} diff --git a/rendering/vulkan14/vulkan14weather.cpp b/rendering/vulkan14/vulkan14weather.cpp index e3e59ecc..0079525e 100644 --- a/rendering/vulkan14/vulkan14weather.cpp +++ b/rendering/vulkan14/vulkan14weather.cpp @@ -114,12 +114,12 @@ struct inject_block glm::vec3 previous_camera; float pad1; glm::vec4 options; - std::int32_t cablamp; + std::int32_t cablamps; std::int32_t pad2; glm::vec2 previous_slicing; glm::vec2 jitter; }; -static_assert(offsetof(inject_block, cablamp) == 256, "the layout of the block of fog_inject.comp"); +static_assert(offsetof(inject_block, cablamps) == 256, "the layout of the block of fog_inject.comp"); struct integrate_constants { glm::ivec3 grid; @@ -460,9 +460,9 @@ void vulkan14_renderer::update_weather() } // the clouds and the fog volume of the colour pass of the main view (opengl46_weather::render_view()): Projection and View -// (rotation, world axes to the view) of its camera, Size of the target, Cablamp the light of the cab lamp or -1. the lights, the +// (rotation, world axes to the view) of its camera, Size of the target, Cablamps the number of the lights of the cab, from the light 1 on. the lights, the // clusters and the block of the scene of the pass are written, the shadow map drawn. outside a rendering -void vulkan14_renderer::render_weather_view(glm::mat4 const &Projection, glm::mat4 const &View, glm::dvec3 const &Camera, glm::ivec2 const &Size, int const Cablamp) +void vulkan14_renderer::render_weather_view(glm::mat4 const &Projection, glm::mat4 const &View, glm::dvec3 const &Camera, glm::ivec2 const &Size, int const Cablamps) { auto &ctx{*m_context}; auto &weather{ctx.scene.weather}; @@ -568,7 +568,7 @@ void vulkan14_renderer::render_weather_view(glm::mat4 const &Projection, glm::ma auto const sunshadows{Global.gfx_fog_shadows && Global.gfx_shadowmap_enabled}; auto const share{temporal ? std::clamp(Global.gfx_fog_temporal_share, 0.02f, 1.f) : 1.f}; block.options = glm::vec4{share, Global.gfx_fog_lights, sunshadows ? 1.f : 0.f, Global.gfx_fog_shadows ? 1.f : 0.f}; - block.cablamp = Cablamp; + block.cablamps = Cablamps; block.previous_slicing = glm::vec2{previousfog.slicing.x, previousfog.slicing.z}; block.jitter = glm::vec2{jitter.x, jitter.y}; auto const slice{ctx.ring.allocate(sizeof(block), ctx.device.caps().limits.minUniformBufferOffsetAlignment)}; diff --git a/shaders/ogl46/fog_inject.comp b/shaders/ogl46/fog_inject.comp index 443daece..9a804675 100644 --- a/shaders/ogl46/fog_inject.comp +++ b/shaders/ogl46/fog_inject.comp @@ -35,8 +35,8 @@ layout(location = 5) uniform vec3 previous_camera; // x: share of this frame (1 without history), y: brightness of the lamps in the fog (0: none), z: 1 when the sun casts shadows // into the fog, w: 1 when the headlights cast shadows into it layout(location = 6) uniform vec4 options; -// the light of the cab lamp, which lights no fog (the air of the cab is clear), or -1 -layout(location = 7) uniform int cablamp; +// the number of the lights of the cab, the lights 1 to cablamps, which light no fog (the air of the cab is clear) +layout(location = 7) uniform int cablamps; // x: near, y: slices / log((far + FOG_VOLUME_BIAS) / (near + FOG_VOLUME_BIAS)) of the previous frame layout(location = 8) uniform vec2 previous_slicing; // where in its cell the cell is sampled this frame across the view (0-1), as slicing.w is along the depth @@ -290,7 +290,7 @@ void main() for (uint i = 0U; i < range.count; i++) { uint index = cluster_light_indices[range.offset + i]; - if (int(index) == cablamp) + if ((index >= 1U) && (int(index) <= cablamps)) continue; partlamps += lamp_scattering(lights[index], partstart, raydirection, partspan, cellwidth, offset.z); } diff --git a/shaders/vk14/fog_inject.comp b/shaders/vk14/fog_inject.comp index 873a874e..d953e5b3 100644 --- a/shaders/vk14/fog_inject.comp +++ b/shaders/vk14/fog_inject.comp @@ -39,8 +39,8 @@ layout(std140, set = COMPUTE_SET, binding = 2) uniform kernel_block // x: share of this frame (1 without history), y: brightness of the lamps in the fog (0: none), z: 1 when the sun casts shadows // into the fog, w: 1 when the headlights cast shadows into it vec4 options; - // the light of the cab lamp, which lights no fog (the air of the cab is clear), or -1 - int cablamp; + // the number of the lights of the cab, the lights 1 to cablamps, which light no fog (the air of the cab is clear) + int cablamps; // x: near, y: slices / log((far + FOG_VOLUME_BIAS) / (near + FOG_VOLUME_BIAS)) of the previous frame vec2 previous_slicing; // where in its cell the cell is sampled this frame across the view (0-1), as slicing.w is along the depth @@ -295,7 +295,7 @@ void main() for (uint i = 0U; i < range.count; i++) { uint index = cluster_light_indices[range.offset + i]; - if (int(index) == cablamp) + if ((index >= 1U) && (int(index) <= cablamps)) continue; partlamps += lamp_scattering(lights[index], partstart, raydirection, partspan, cellwidth, offset.z); } diff --git a/utilities/Globals.cpp b/utilities/Globals.cpp index 8a8a125c..5a7916e1 100644 --- a/utilities/Globals.cpp +++ b/utilities/Globals.cpp @@ -1972,6 +1972,10 @@ global_settings::ConfigParse_gfx_sky( cParser &Parser, std::string_view const To ( level == "medium" || level == "yes" || level == "true" || level == "2" ) ? 2 : ( level == "high" || level == "3" ) ? 3 : 0; } + 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 ); } else if( Token == "gfx.lights.shadows.lamps" ) { Parser.getTokens( 1 ); Parser >> gfx_lights_shadows_lamps; @@ -2453,6 +2457,7 @@ global_settings::export_as_text( std::ostream &Output ) const { 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" } ); + export_as_text( Output, "gfx.lights.shadows.cab", gfx_lights_shadows_cab ); export_as_text( Output, "gfx.lights.shadows.lamps", gfx_lights_shadows_lamps ); export_as_text( Output, "gfx.lights.shadows.lamps.size", gfx_lights_shadows_lamps_size ); export_as_text( Output, "gfx.lights.shadows.lamps.range", gfx_lights_shadows_lamps_range ); diff --git a/utilities/Globals.h b/utilities/Globals.h index ed3f90a1..a1010262 100644 --- a/utilities/Globals.h +++ b/utilities/Globals.h @@ -538,6 +538,9 @@ struct global_settings { // shadows of the lamps (cube shadow maps), 0 none, 1 low, 2 medium, 3 high; see opengl46_local_shadows. for now the lamp of the // 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 }; // 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