diff --git a/docs/opengl46-renderer.md b/docs/opengl46-renderer.md index f9e28eff..b55290a9 100644 --- a/docs/opengl46-renderer.md +++ b/docs/opengl46-renderer.md @@ -116,8 +116,9 @@ scales can also be changed in the **Light clusters** debug window. which bounces around the cab (`gfx.lights.cab.bounce`), and the sun shadows stay. 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. Of these lights only the cab lamp casts shadows (see below); the others -also light what is behind walls within their reach, which is kept short for compartments. +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, the levers and the walls (light leaves the cab only through the windows, glass doesn't stop it). They come from a cube @@ -133,6 +134,20 @@ cab once more. Nothing is drawn with the lamp off (in the daylight it is). The l | `medium` (2, default) | 1024 x 1024 per face, 24 MB | every 2nd frame | soft, 4 samples | | `high` (3) | 1024 x 1024 per face, 24 MB | every frame | softer, 8 samples | +**Shadows of scenery lamps** (with `gfx.lights.shadows` above `no`): the street, platform and other lamps of scenery models +nearest to the camera (`gfx.lights.shadows.lamps` of them, within `gfx.lights.shadows.lamps.range` of the camera) cast the +shadows of the scenery around them, of the vehicles near the camera and of the occupied cab: light from a lamp outside comes +into the cab only through the windows. Each such lamp has a cube shadow map in world axes around it, in two parts: +- the scenery (terrain, tracks, models; not the model of the lamp itself, which would shadow everything), drawn once when the + lamp gets its map and again only when the scene changes (events showing or hiding models, the editor). At most one map is + drawn per frame (two at `high`), so lamps coming into reach get their shadows over a few frames; +- a copy of the scenery with the vehicles within `gfx.lights.shadows.vehicles.range` of the camera (and the cab) added, + made every few frames (as for the cab lamp) while any of them is within the reach of the lamp. A vehicle farther from + the camera casts no shadow of the lamps; with no vehicle near a lamp nothing is drawn for it. + +Lamps of vehicles and lit compartments cast no shadows. The memory taken is 2 maps per lamp: with the defaults (8 lamps, +512 x 512 per face) about 100 MB of video memory. + | Entry | Default | Meaning | |---|---|---| | `gfx.lights.scenery yes/no` | `yes` | light points of scenery models light their surroundings | @@ -143,7 +158,11 @@ cab once more. Nothing is drawn with the lamp off (in the daylight it is). The l | `gfx.lights.cab.intensity` | `1.5` | brightness of the cab lamp | | `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, see above | +| `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.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 | +| `gfx.lights.shadows.vehicles.range` | `50` | distance from the camera within which vehicles (and the occupied cab) cast the shadows of scenery lamps, metres; 0: only the scenery does | ### Changed option @@ -182,7 +201,10 @@ editor goes through the first `dynamiclights` headlights, the reflections throug scenery models, vehicles and compartments, and the cab lamp), how many were placed in the light buffer of the main view (those that may light something in view) and did not fit, and the cpu time of gathering them. The `lamp shadows:` row gives the size of the cube map, whether it holds the cab lamp, the draw calls and cpu time of its last drawing, and the - timings the GPU time of drawing it (`lamp shadows:`); the level can be changed in the window. + timings the GPU time of drawing it (`lamp shadows:`); the level can be changed in the window. The `scenery lamp shadows:` + row gives the lamps with maps (of the slots, how many ready, how many with vehicles), how many scenery parts and vehicle + copies were drawn so far, and the draw calls and cpu time of the last frame which drew any; the timings show their GPU time + (`scenery lamp shadows:`). The count and both ranges can be changed in the window too. - **Sky and exposure** window (next to the Light clusters window): the switches and scales above, the share of the atmosphere, the colours of the sun, ambient light and fog in use, and the measured luminance and exposure. Shift+F12 shows the GPU time of `atmosphere:` (look-up tables, only when the sun moved or the weather changed), diff --git a/gl/glsl_common.cpp b/gl/glsl_common.cpp index c7b74748..1eb8f162 100644 --- a/gl/glsl_common.cpp +++ b/gl/glsl_common.cpp @@ -57,6 +57,7 @@ void gl::glsl_common_setup() "#define ATMOSPHERE_SKYVIEW_UNIT " + std::to_string(ATMOSPHERE_SKYVIEW_UNIT) + "\n" + "#define SKY_VIEW_TEX " + std::to_string(SKY_VIEW_TEX) + "\n" + "#define LOCAL_SHADOW_TEX " + std::to_string(LOCAL_SHADOW_TEX) + "\n" + + "#define LOCAL_SHADOW_LAMPS_TEX " + std::to_string(LOCAL_SHADOW_LAMPS_TEX) + "\n" + R"STRING( const uint LIGHT_SPOT = 0U; const uint LIGHT_POINT = 1U; diff --git a/gl/ubo.h b/gl/ubo.h index bcfbec5e..ef1b6839 100644 --- a/gl/ubo.h +++ b/gl/ubo.h @@ -60,6 +60,8 @@ namespace gl const size_t SKY_VIEW_TEX = MAX_TEXTURES + HELPER_TEXTURES + 0; // cube shadow maps of the lamps (cube map array, depth compared; opengl46_local_shadows). bound directly, like the one above const size_t LOCAL_SHADOW_TEX = MAX_TEXTURES + HELPER_TEXTURES + 1; + // the same for the lamps of scenery models (a texture of its own: the maps have another size) + const size_t LOCAL_SHADOW_LAMPS_TEX = MAX_TEXTURES + HELPER_TEXTURES + 2; struct scene_ubs { @@ -141,7 +143,8 @@ namespace gl // HEADLIGHTS: view space to the space of the beam texture, and the weights of its channels. // SPOT and POINT (lamps): view space to the space of the cube shadow map of the lamp (origin at the lamp), and // x: layer of the cube map array the map is in, below 0 when the lamp has no shadows; y: near plane of the map; - // z: size of a texel of the map one metre from the lamp; w: radius of the filter, in texels + // z: size of a texel of the map one metre from the lamp; w: radius of the filter, in texels. the map is in the array of + // the scenery lamps (LOCAL_SHADOW_LAMPS_TEX) when linear is 1, in the one of the cab lamp (LOCAL_SHADOW_TEX) otherwise glm::mat4 headlight_projection; glm::vec4 headlight_weights; }; diff --git a/rendering/opengl46/opengl46locallights.cpp b/rendering/opengl46/opengl46locallights.cpp index b4429b53..d38d84d0 100644 --- a/rendering/opengl46/opengl46locallights.cpp +++ b/rendering/opengl46/opengl46locallights.cpp @@ -220,7 +220,9 @@ void opengl46_local_lights::gather_instance(TAnimModel *Instance, glm::dvec3 con auto const count{m_lights.size()}; auto const placement{glm::translate(glm::dmat4{1.0}, Instance->location()) * glm::dmat4{Instance->rotation_scale()}}; + m_walkowner = Instance; walk(Instance->pModel->Root, glm::mat4{1.f}, placement, Brightness); + m_walkowner = nullptr; m_scenerycount += static_cast(m_lights.size() - count); } @@ -401,6 +403,11 @@ void opengl46_local_lights::add_light_point(TSubModel const &Submodel, glm::mat4 lightpoint.cos_outer = std::clamp(Submodel.fCosFalloffAngle, -1.f, 0.999f); lightpoint.cos_inner = std::clamp(std::max(Submodel.fCosHotspotAngle, lightpoint.cos_outer + 0.05f), lightpoint.cos_outer + 0.001f, 1.f); lightpoint.range = light_range(color); + if (m_walkowner != nullptr) + { + lightpoint.owner = m_walkowner; + lightpoint.part = &Submodel; + } m_lights.emplace_back(lightpoint); } diff --git a/rendering/opengl46/opengl46locallights.h b/rendering/opengl46/opengl46locallights.h index 82b71e11..acb84dad 100644 --- a/rendering/opengl46/opengl46locallights.h +++ b/rendering/opengl46/opengl46locallights.h @@ -47,6 +47,10 @@ class opengl46_local_lights float cos_outer{-1.f}; // cosine of the half angle of the cone; not above 0: the light is not limited to a cone float cos_inner{-1.f}; // cosine of the half angle within which the light is at its full brightness; above cos_outer float range{0.f}; // distance at which the light ends, metres + // light point of a scenery model: the instance and the submodel it comes from, which name the lamp from frame to frame + // (opengl46_local_shadows keeps the shadow maps of scenery lamps by them). nullptr for the other lights + void const *owner{nullptr}; + void const *part{nullptr}; }; opengl46_local_lights() = default; @@ -125,6 +129,7 @@ class opengl46_local_lights std::unordered_map m_cells; std::unordered_map m_sections; // compartments, by their submodel std::unordered_map m_models; // bounds of cab models + TAnimModel const *m_walkowner{nullptr}; // scenery instance whose light points walk() goes through; nullptr for vehicles bool m_editorseen{false}; // the editor was on in the last frame: the instance lists of the cells may have changed // statistics std::uint32_t m_scenerycount{0}; diff --git a/rendering/opengl46/opengl46localshadows.cpp b/rendering/opengl46/opengl46localshadows.cpp index 64fd265d..5a00146f 100644 --- a/rendering/opengl46/opengl46localshadows.cpp +++ b/rendering/opengl46/opengl46localshadows.cpp @@ -26,12 +26,65 @@ 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{0}; +// a lamp which moved farther than this (an animated submodel) has its scenery drawn anew, metres +double const LAMP_MOVED{0.01}; +// lamps with a shorter reach aren't worth a map, metres +float const LAMP_RANGE_MIN{1.f}; + +// 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) +{ + GLuint texture{0}; + glCreateTextures(GL_TEXTURE_CUBE_MAP_ARRAY, 1, &texture); + glTextureStorage3D(texture, 1, GL_DEPTH_COMPONENT32F, Size, Size, 6 * Cubes); + // one level: the lookups need no derivatives, also in the loops over the lights the fragments of a quad don't go through together + glTextureParameteri(texture, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTextureParameteri(texture, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTextureParameteri(texture, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTextureParameteri(texture, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTextureParameteri(texture, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE); + // 1 (shadow) where the map holds n / d at least as large as the reference: a surface at most as far from the lamp + glTextureParameteri(texture, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL); + return texture; +} + +// framebuffer drawing to all layers of the depth texture (the geometry shader picks the face); 0 if it can't be made +GLuint make_framebuffer(GLuint const Texture) +{ + GLuint framebuffer{0}; + glCreateFramebuffers(1, &framebuffer); + glNamedFramebufferTexture(framebuffer, GL_DEPTH_ATTACHMENT, Texture, 0); + glNamedFramebufferDrawBuffer(framebuffer, GL_NONE); + glNamedFramebufferReadBuffer(framebuffer, GL_NONE); + if (glCheckNamedFramebufferStatus(framebuffer, GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) + { + glDeleteFramebuffers(1, &framebuffer); + return 0; + } + return framebuffer; +} + +// the state of the drawing to a cube map +void setup_drawing(GLuint const Framebuffer, int const Size) +{ + // the framebuffer is bound past the cache of gl::framebuffer, which the end of the drawing resets + gl::framebuffer::unbind(); + glBindFramebuffer(GL_FRAMEBUFFER, Framebuffer); + glViewport(0, 0, Size, Size); + glDisable(GL_BLEND); + glDepthMask(GL_TRUE); + glEnable(GL_DEPTH_TEST); + // one sided walls (of cabs, of buildings) face the lamp, they are to cast shadows as well + glDisable(GL_CULL_FACE); + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); +} } // namespace opengl46_local_shadows::~opengl46_local_shadows() { release(); + release_lamps(); if (m_empty != 0) glDeleteTextures(1, &m_empty); } @@ -50,8 +103,7 @@ void opengl46_local_shadows::init() } glProgramUniform1f(*m_program, NEAR_LOCATION, SHADOW_NEAR); glProgramUniform1f(*m_alpha_program, NEAR_LOCATION, SHADOW_NEAR); - glProgramUniform1i(*m_program, LAYER_LOCATION, CAB_SLOT * 6); - glProgramUniform1i(*m_alpha_program, LAYER_LOCATION, CAB_SLOT * 6); + set_layer(CAB_SLOT * 6); // 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); @@ -64,18 +116,24 @@ void opengl46_local_shadows::init() glClearTexImage(m_empty, 0, GL_DEPTH_COMPONENT, GL_FLOAT, &farthest); } +void opengl46_local_shadows::set_layer(int const Layer) +{ + glProgramUniform1i(*m_program, LAYER_LOCATION, Layer); + glProgramUniform1i(*m_alpha_program, LAYER_LOCATION, Layer); +} + opengl46_local_shadows::level_setup opengl46_local_shadows::setup(int const Level) { switch (Level) { case 1: - return {512, 4, 0.f}; + return {512, 4, 0.f, 1}; case 2: - return {1024, 2, 1.f}; + return {1024, 2, 1.f, 1}; case 3: - return {1024, 1, 2.f}; + return {1024, 1, 2.f, 2}; default: - return {0, 0, 0.f}; + return {0, 0, 0.f, 0}; } } @@ -84,23 +142,9 @@ void opengl46_local_shadows::allocate(int const Size) release(); if (Size <= 0) return; - glCreateTextures(GL_TEXTURE_CUBE_MAP_ARRAY, 1, &m_texture); - glTextureStorage3D(m_texture, 1, GL_DEPTH_COMPONENT32F, Size, Size, 6 * (CAB_SLOT + 1)); - // one level: the lookups need no derivatives, also in the loops over the lights the fragments of a quad don't go through together - glTextureParameteri(m_texture, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTextureParameteri(m_texture, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTextureParameteri(m_texture, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTextureParameteri(m_texture, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTextureParameteri(m_texture, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE); - // 1 (shadow) where the map holds n / d at least as large as the reference: a surface at most as far from the lamp - glTextureParameteri(m_texture, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL); - - glCreateFramebuffers(1, &m_framebuffer); - // all layers: the geometry shader picks the face - glNamedFramebufferTexture(m_framebuffer, GL_DEPTH_ATTACHMENT, m_texture, 0); - glNamedFramebufferDrawBuffer(m_framebuffer, GL_NONE); - glNamedFramebufferReadBuffer(m_framebuffer, GL_NONE); - if (glCheckNamedFramebufferStatus(m_framebuffer, GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) + m_texture = make_cube_array(Size, CAB_SLOT + 1); + m_framebuffer = make_framebuffer(m_texture); + if (m_framebuffer == 0) { ErrorLog("lamp shadow map framebuffer setup failed, the lamps have no shadows"); release(); @@ -123,9 +167,44 @@ void opengl46_local_shadows::release() m_valid = false; } +void opengl46_local_shadows::allocate_lamps(int const Count, int const Size) +{ + release_lamps(); + if ((Count <= 0) || (Size <= 0)) + return; + // the scenery parts and the working copies + m_lampstexture = make_cube_array(Size, 2 * Count); + m_lampsframebuffer = make_framebuffer(m_lampstexture); + if (m_lampsframebuffer == 0) + { + ErrorLog("scenery lamp shadow map framebuffer setup failed, the scenery lamps have no shadows"); + release_lamps(); + Global.gfx_lights_shadows_lamps = 0; + return; + } + m_lampssize = Size; + m_slots.assign(static_cast(Count), lamp_slot{}); + WriteLog("scenery lamp shadow maps: " + std::to_string(Count) + " x " + std::to_string(Size) + " x " + std::to_string(Size) + " cube (" + + std::to_string(2 * Count * 6 * Size / 1024 * Size * 4 / 1024) + " MB)"); +} + +void opengl46_local_shadows::release_lamps() +{ + if (m_lampsframebuffer != 0) + glDeleteFramebuffers(1, &m_lampsframebuffer); + if (m_lampstexture != 0) + glDeleteTextures(1, &m_lampstexture); + m_lampsframebuffer = 0; + m_lampstexture = 0; + m_lampssize = 0; + m_slots.clear(); + m_wanted.clear(); +} + void opengl46_local_shadows::bind() { glBindTextureUnit(gl::LOCAL_SHADOW_TEX, (m_texture != 0) ? m_texture : m_empty); + glBindTextureUnit(gl::LOCAL_SHADOW_LAMPS_TEX, (m_lampstexture != 0) ? m_lampstexture : m_empty); } bool opengl46_local_shadows::update_wanted(TDynamicObject const *Vehicle, TModel3d const *Cab, glm::vec3 const &Lamp, unsigned int const Framestamp) @@ -165,16 +244,8 @@ void opengl46_local_shadows::begin() { m_cpustart = std::chrono::steady_clock::now(); m_timer.begin(); - // the framebuffer is bound past the cache of gl::framebuffer, which end() resets - gl::framebuffer::unbind(); - glBindFramebuffer(GL_FRAMEBUFFER, m_framebuffer); - glViewport(0, 0, m_size, m_size); - glDisable(GL_BLEND); - glDepthMask(GL_TRUE); - glEnable(GL_DEPTH_TEST); - // one sided walls of the cab face the lamp, they are to cast shadows as well - glDisable(GL_CULL_FACE); - glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + set_layer(CAB_SLOT * 6); + setup_drawing(m_framebuffer, m_size); float const farthest{0.f}; glClearNamedFramebufferfv(m_framebuffer, GL_DEPTH, 0, &farthest); } @@ -197,27 +268,253 @@ glm::vec4 opengl46_local_shadows::weights() const return glm::vec4{static_cast(CAB_SLOT), SHADOW_NEAR, 2.f / static_cast(std::max(m_size, 1)), setup(m_level).radius}; } +void opengl46_local_shadows::update_lamps(std::vector const &Lights, glm::dvec3 const &Camera, std::uint64_t const Version, unsigned int const Framestamp) +{ + m_frame = Framestamp; + m_wanted.clear(); + auto const level{std::clamp(Global.gfx_lights_shadows, 0, 3)}; + auto const count{level > 0 ? Global.gfx_lights_shadows_lamps : 0}; + auto const size{count > 0 ? Global.gfx_lights_shadows_lamps_size : 0}; + if ((static_cast(count) != m_slots.size()) || (size != m_lampssize)) + { + allocate_lamps(count, size); + } + if (m_slots.empty()) + { + return; + } + m_sceneryleft = setup(level).sceneryperframe; + auto const reach{static_cast(Global.gfx_lights_shadows_lamps_range)}; + // the lights come the most important first: the first ones within reach get the slots + std::vector picked; + for (auto const &light : Lights) + { + if (picked.size() >= m_slots.size()) + { + break; + } + if ((light.owner == nullptr) || (light.range < LAMP_RANGE_MIN) || (glm::length(light.position - Camera) > reach)) + { + continue; + } + picked.emplace_back(&light); + } + // lamps which already have a slot keep it + std::vector unplaced; + for (auto const *light : picked) + { + auto placed{false}; + for (std::size_t idx = 0; idx < m_slots.size(); ++idx) + { + auto &slot{m_slots[idx]}; + if ((slot.owner == light->owner) && (slot.part == light->part) && (slot.wantedframe != Framestamp)) + { + if (glm::length(slot.position - light->position) > LAMP_MOVED) + { + slot.scenery = false; + slot.vehicles = false; + } + slot.position = light->position; + slot.range = light->range; + slot.wantedframe = Framestamp; + m_wanted.emplace_back(idx); + placed = true; + break; + } + } + if (false == placed) + { + unplaced.emplace_back(light); + } + } + // the others take the slots of lamps no longer picked + for (auto const *light : unplaced) + { + for (std::size_t idx = 0; idx < m_slots.size(); ++idx) + { + auto &slot{m_slots[idx]}; + if (slot.wantedframe == Framestamp) + { + continue; + } + slot = lamp_slot{}; + slot.owner = light->owner; + slot.part = light->part; + slot.position = light->position; + slot.range = light->range; + slot.wantedframe = Framestamp; + m_wanted.emplace_back(idx); + break; + } + } + // the scene changed (events, the editor): the scenery parts are out of date + for (auto &slot : m_slots) + { + if (slot.scenery && (slot.version != Version)) + { + slot.scenery = false; + slot.vehicles = false; + } + } +} + +void opengl46_local_shadows::reset_lamps() +{ + for (auto &slot : m_slots) + { + slot = lamp_slot{}; + } + m_wanted.clear(); +} + +bool opengl46_local_shadows::scenery_wanted(std::size_t const Slot) +{ + if (m_slots[Slot].scenery || (m_sceneryleft <= 0)) + { + return false; + } + --m_sceneryleft; + return true; +} + +bool opengl46_local_shadows::vehicles_wanted(std::size_t const Slot, unsigned int const Framestamp) const +{ + auto const &slot{m_slots[Slot]}; + return slot.scenery && ((false == slot.vehicles) || (Framestamp - slot.vehiclesframe >= setup(m_level).interval)); +} + +void opengl46_local_shadows::begin_lamp(std::size_t const Slot, bool const Vehicles, std::uint64_t const Version, unsigned int const Framestamp) +{ + auto &slot{m_slots[Slot]}; + auto const scenerycube{static_cast(Slot)}; + auto const workingcube{static_cast(m_slots.size() + Slot)}; + if (Vehicles) + { + // the vehicles go over the scenery + glCopyImageSubData(m_lampstexture, GL_TEXTURE_CUBE_MAP_ARRAY, 0, 0, 0, scenerycube * 6, m_lampstexture, GL_TEXTURE_CUBE_MAP_ARRAY, 0, 0, 0, workingcube * 6, m_lampssize, m_lampssize, 6); + slot.vehicles = true; + slot.vehiclesframe = Framestamp; + ++m_lampsvehicles; + } + else + { + float const farthest{0.f}; + glClearTexSubImage(m_lampstexture, 0, 0, 0, scenerycube * 6, m_lampssize, m_lampssize, 6, GL_DEPTH_COMPONENT, GL_FLOAT, &farthest); + slot.scenery = true; + slot.version = Version; + slot.vehicles = false; + ++m_lampsscenery; + } + set_layer((Vehicles ? workingcube : scenerycube) * 6); + setup_drawing(m_lampsframebuffer, m_lampssize); +} + +void opengl46_local_shadows::end_lamp(std::uint32_t const Drawcalls) +{ + glEnable(GL_CULL_FACE); + gl::framebuffer::unbind(); + set_layer(CAB_SLOT * 6); + m_lampsframedrawcalls += Drawcalls; +} + +void opengl46_local_shadows::lamps_timer_begin() +{ + m_lampsframedrawcalls = 0; + m_cpustart = std::chrono::steady_clock::now(); + m_lampstimer.begin(); + m_lampstiming = true; +} + +void opengl46_local_shadows::lamps_timer_end() +{ + if (false == m_lampstiming) + { + return; + } + m_lampstimer.end(); + m_lampstiming = false; + m_lampsdrawcalls = m_lampsframedrawcalls; + auto const elapsed{std::chrono::duration(std::chrono::steady_clock::now() - m_cpustart).count()}; + m_lampscputime = (m_lampscputime == 0.0 ? elapsed : m_lampscputime * 0.9 + elapsed * 0.1); +} + +opengl46_local_shadows::lamp_slot const *opengl46_local_shadows::lamp(opengl46_local_lights::light const &Light, std::size_t &Slot) const +{ + if (Light.owner == nullptr) + { + return nullptr; + } + for (auto const idx : m_wanted) + { + auto const &slot{m_slots[idx]}; + if ((slot.owner == Light.owner) && (slot.part == Light.part)) + { + if (false == slot.scenery) + { + return nullptr; + } + Slot = idx; + return &slot; + } + } + return nullptr; +} + +glm::vec4 opengl46_local_shadows::lamp_weights(std::size_t const Slot) const +{ + auto const cube{m_slots[Slot].vehicles ? m_slots.size() + Slot : Slot}; + return glm::vec4{static_cast(cube), SHADOW_NEAR, 2.f / static_cast(std::max(m_lampssize, 1)), setup(m_level).radius}; +} + std::string opengl46_local_shadows::info_times() { std::string text; if (auto const time = m_timer.result()) text += " lamp shadows: " + to_string(static_cast(*time) / 1000000.0, 3) + " ms\n"; + if (auto const time = m_lampstimer.result()) + text += " scenery lamp shadows: " + to_string(static_cast(*time) / 1000000.0, 3) + " ms\n"; return text; } std::string opengl46_local_shadows::info() const { - if (m_size == 0) - return ""; - return "\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"; + 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"; + } + if (false == m_slots.empty()) + { + auto withvehicles{0}; + auto ready{0}; + for (auto const idx : m_wanted) + { + ready += (m_slots[idx].scenery ? 1 : 0); + withvehicles += (m_slots[idx].vehicles ? 1 : 0); + } + text += "\nscenery lamp shadows: " + std::to_string(m_wanted.size()) + " of " + std::to_string(m_slots.size()) + " slots (" + std::to_string(ready) + " ready, " + + std::to_string(withvehicles) + " with vehicles), drawn " + std::to_string(m_lampsscenery) + " scenery + " + std::to_string(m_lampsvehicles) + " vehicles, " + + std::to_string(m_lampsdrawcalls) + " drawcalls, cpu " + to_string(m_lampscputime, 2) + " ms"; + } + return text; } void opengl46_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)); - if (m_size == 0) - return; - ImGui::Text("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); + 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::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); + ImGui::SliderFloat("vehicles casting them, from the camera, m (.vehicles.range)", &Global.gfx_lights_shadows_vehicles_range, 0.f, 500.f); + if (false == m_slots.empty()) + { + auto const text{info()}; + auto const line{text.rfind('\n')}; + ImGui::TextUnformatted(text.c_str() + (line == std::string::npos ? 0 : line + 1)); + } } diff --git a/rendering/opengl46/opengl46localshadows.h b/rendering/opengl46/opengl46localshadows.h index 0d716be1..4d2540b1 100644 --- a/rendering/opengl46/opengl46localshadows.h +++ b/rendering/opengl46/opengl46localshadows.h @@ -14,20 +14,28 @@ http://mozilla.org/MPL/2.0/. #include #include #include +#include #include "gl/shader.h" #include "gl/query.h" +#include "rendering/opengl46/opengl46locallights.h" class TDynamicObject; class TModel3d; -// cube shadow maps of the lamps of the opengl 4.6 renderer (Global.gfx_lights_shadows). for now one map, of 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 six faces are drawn at once, by a geometry shader (shadowcube.vert/.geom), so the cab costs one draw call -// per submodel, as in the colour pass. the depth is n / d (d the distance along the axis of the face), see local_light_shadow() -// in light_common.glsl, which reads the map for the light with the layer in its headlight_weights.x +// cube shadow maps of the lamps of the opengl 4.6 renderer (Global.gfx_lights_shadows): +// - 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 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 +// the camera (within Global.gfx_lights_shadows_vehicles_range) and the occupied cab added, made every few frames while there +// are any by the lamp. the shading reads the working copy then, the scenery part otherwise. +// the six faces of a map are drawn at once, by a geometry shader (shadowcube.vert/.geom), so a model costs one draw call per +// submodel, as in the colour pass. the depth is n / d (d the distance along the axis of the face), see local_light_shadow() in +// light_common.glsl, which reads the map for the light with the layer in its headlight_weights.x, from the array of scenery +// lamps when its linear is 1 and from the one of the cab lamp otherwise class opengl46_local_shadows { public: @@ -36,10 +44,19 @@ class opengl46_local_shadows opengl46_local_shadows(const opengl46_local_shadows &) = delete; opengl46_local_shadows &operator=(const opengl46_local_shadows &) = delete; - // creates the programs and the stand-in map. throws gl::shader_exception + // creates the programs and the stand-in maps. throws gl::shader_exception void init(); - // puts the map (or the stand-in, which shadows nothing) on its texture unit, gl::LOCAL_SHADOW_TEX. once per context and frame + // puts the maps (or the stand-ins, which shadow nothing) on their texture units, gl::LOCAL_SHADOW_TEX and + // gl::LOCAL_SHADOW_LAMPS_TEX. once per context and frame void bind(); + // shadow program of the cube maps, of the alpha tested materials when Alpha + gl::program &program(bool const Alpha) + { + return Alpha ? *m_alpha_program : *m_program; + } + + // the cab lamp + // 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 @@ -49,11 +66,6 @@ class opengl46_local_shadows void begin(); // ends the drawing to the map, leaves the default framebuffer bound and the culling of faces on void end(std::uint32_t const Drawcalls); - // shadow program of the cube maps, of the alpha tested materials when Alpha - gl::program &program(bool const Alpha) - { - return Alpha ? *m_alpha_program : *m_program; - } // whether the map holds the shadows of the lamp of Vehicle, the occupied vehicle bool ready(TDynamicObject const *Vehicle) const { @@ -67,6 +79,59 @@ class opengl46_local_shadows // the headlight_weights of the light which reads the map: layer, near plane, size of a texel one metre from the lamp, // radius of the filter in texels glm::vec4 weights() const; + + // lamps of scenery models + + // a lamp with a slot + struct lamp_slot + { + void const *owner{nullptr}; // the light it is, see opengl46_local_lights::light + void const *part{nullptr}; + glm::dvec3 position{0.0}; // world space + float range{0.f}; // of the light, metres + unsigned int wantedframe{~0u}; // framestamp it was last among the lamps given shadows + bool scenery{false}; // the scenery part of the map is drawn + std::uint64_t version{0}; // of the scene the scenery part was drawn for + bool vehicles{false}; // the working copy is drawn, with vehicles, and is what the shading reads + unsigned int vehiclesframe{0}; // framestamp the working copy was last drawn in + }; + // picks the lamps which get shadows in this frame from the gathered lights (the most important first): the scenery lamps + // within reach of Camera. a lamp keeps its slot while it is picked; one which got a slot or moved, and all of them when the + // scene changes (Version), need their scenery drawn. (re)makes the maps when their count or size changed + void update_lamps(std::vector const &Lights, glm::dvec3 const &Camera, std::uint64_t const Version, unsigned int const Framestamp); + // forgets the lamps: the scene they come from is about to be replaced (a scenery being loaded) + void reset_lamps(); + // slots of the lamps picked in this frame + std::vector const &lamps() const + { + return m_wanted; + } + lamp_slot const &slot(std::size_t const Slot) const + { + return m_slots[Slot]; + } + // whether the scenery part of the map of the slot is to be drawn in this frame. a few per frame at most, by the level + bool scenery_wanted(std::size_t const Slot); + // whether the working copy of the slot, with vehicles, is to be drawn in this frame (every few frames, by the level) + bool vehicles_wanted(std::size_t const Slot, unsigned int const Framestamp) const; + // the slot has no vehicles by it: the shading reads the scenery part + void no_vehicles(std::size_t const Slot) + { + m_slots[Slot].vehicles = false; + } + // sets up the drawing to the scenery part (cleared) or to the working copy (a copy of the scenery part) of the map of the slot. + // the geometry is to be drawn with program() and a modelview to world axes centred on the lamp + void begin_lamp(std::size_t const Slot, bool const Vehicles, std::uint64_t const Version, unsigned int const Framestamp); + // ends the drawing to the map of a lamp + void end_lamp(std::uint32_t const Drawcalls); + // gpu timer of the drawing of the maps of the lamps in this frame + void lamps_timer_begin(); + void lamps_timer_end(); + // the slot of the lamp the light is, if it has its shadows ready, nullptr otherwise + lamp_slot const *lamp(opengl46_local_lights::light const &Light, std::size_t &Slot) const; + // the headlight_weights of the light of the lamp in the slot, see weights() + glm::vec4 lamp_weights(std::size_t const Slot) const; + // lines for the gpu times and statistics of the debug panel std::string info_times(); std::string info() const; @@ -74,33 +139,54 @@ class opengl46_local_shadows void draw_debug_ui(); private: - // size, refresh interval (frames) and filter radius (texels, 0: the 2x2 filter of the hardware) of a level + // size, refresh interval (frames), filter radius (texels, 0: the 2x2 filter of the hardware) of a level, and the number of + // scenery parts of lamp maps drawn per frame struct level_setup { int size; unsigned int interval; float radius; + int sceneryperframe; }; static level_setup setup(int const Level); void allocate(int const Size); void release(); + void allocate_lamps(int const Count, int const Size); + void release_lamps(); + void set_layer(int const Layer); std::unique_ptr m_program; std::unique_ptr m_alpha_program; - GLuint m_texture{0}; // cube map array, one cube GLuint m_empty{0}; // stand-in: a cube map array of 1x1 faces with the farthest depth + int m_level{-1}; // level the maps were made for + // cab lamp + GLuint m_texture{0}; // cube map array, one cube GLuint m_framebuffer{0}; int m_size{0}; // of a face of m_texture, 0 when there is none - int m_level{-1}; // level the map was made for bool m_valid{false}; // m_texture holds the map 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 gl::gpu_timer m_timer; + // scenery lamps: cube map array, the scenery parts of the slots first, then their working copies + GLuint m_lampstexture{0}; + GLuint m_lampsframebuffer{0}; + int m_lampssize{0}; + std::vector m_slots; + std::vector m_wanted; + unsigned int m_frame{0}; // framestamp of the last update_lamps() + int m_sceneryleft{0}; // scenery parts which may still be drawn in this frame + gl::gpu_timer m_lampstimer; + bool m_lampstiming{false}; // statistics - std::uint32_t m_drawcalls{0}; // of the last drawing of the map - std::uint32_t m_updatecount{0}; // drawings of the map so far + 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 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 + std::uint32_t m_lampsvehicles{0}; // working copies drawn so far + std::uint32_t m_lampsdrawcalls{0}; // in the drawings of the lamp maps in the last frame with any + std::uint32_t m_lampsframedrawcalls{0}; + double m_lampscputime{0.0}; // milliseconds per frame, smoothed }; diff --git a/rendering/opengl46/opengl46renderer.cpp b/rendering/opengl46/opengl46renderer.cpp index 46c6b4ad..0606eaa5 100644 --- a/rendering/opengl46/opengl46renderer.cpp +++ b/rendering/opengl46/opengl46renderer.cpp @@ -633,6 +633,7 @@ bool opengl46_renderer::Render() { // a scenery is being loaded: the cells, instances and models the lights were taken from may be gone m_locallights.reset(); + m_localshadows.reset_lamps(); } // generate new frame opengl_texture::reset_unit_cache(); @@ -644,6 +645,9 @@ bool opengl46_renderer::Render() ++m_instancerecordsversion; m_nodestateversion = scene::node_state_version; } + // scenery lamps which get shadows in this frame. their maps hold the scenery as of the version of the instance records + if (simulation::is_ready && (false == Global.gfx_skiprendering)) + m_localshadows.update_lamps(m_locallights.lights(), Global.pCamera.Pos, m_instancerecordsversion, m_framestamp); // instanced draws of the frame place their matrices in the next of the instance buffers, the one used the longest ago m_instancebufferindex = ( m_instancebufferindex + 1 ) % m_instancebuffers.size(); m_instancebufferfill = 1; @@ -967,7 +971,10 @@ void opengl46_renderer::Render_pass(viewport_config &vp, rendermode const Mode) } if (vp.main) + { Render_local_shadows(); + Render_lamp_shadows(); + } glClearColor(0.0f, 0.0f, 0.0f, 0.0f); @@ -2966,14 +2973,18 @@ void opengl46_renderer::Render(cell_sequence::iterator First, cell_sequence::ite for (auto *instance : cell->m_instancesopaque) { if( instance->m_instanceable ) { continue; } // already handled via bucket + // the model of a lamp would shadow everything in the map of the lamp + if( instance == m_cubeskipinstance ) { continue; } Render(instance); } - // opaque parts of vehicles - for (auto *path : cell->m_paths) - { - for (auto *dynamic : path->Dynamics) + // opaque parts of vehicles. the kept scenery part of the map of a lamp is drawn without them + if( false == m_cubescenery ) { + for (auto *path : cell->m_paths) { - Render(dynamic); + for (auto *dynamic : path->Dynamics) + { + Render(dynamic); + } } } break; @@ -6278,6 +6289,7 @@ void opengl46_renderer::Update_Lights(light_array &Lights, bool const Headlights m_lights_lit = static_cast(light_i - headlightsstart); // lamps of models and vehicles, the most important first, those which may be seen lighting something in this view if( locallights ) { + auto const toworld{ glm::dmat4{ glm::transpose( glm::mat3{ mv } ) } }; uint32_t placed{ 0 }; uint32_t dropped{ 0 }; for( auto const &local : m_locallights.lights() ) { @@ -6288,7 +6300,16 @@ void opengl46_renderer::Update_Lights(light_array &Lights, bool const Headlights ++dropped; continue; } - Write_Local_Light( light_data.lights[ light_i++ ], local, camera, mv ); + auto &light{ light_data.lights[ light_i++ ] }; + Write_Local_Light( light, local, camera, mv ); + std::size_t slot; + if( auto const *lamp{ m_localshadows.lamp( local, slot ) } ) { + // view space to the space of the map: world axes with the lamp at the origin + light.headlight_projection = glm::mat4{ glm::translate( glm::dmat4{ 1.0 }, camera - lamp->position ) * toworld }; + light.headlight_weights = m_localshadows.lamp_weights( slot ); + // the map is in the array of the scenery lamps + light.linear = 1.0f; + } ++placed; } m_locallights.placed( placed, dropped ); @@ -6372,6 +6393,118 @@ void opengl46_renderer::Render_local_shadows() glPolygonMode(GL_FRONT_AND_BACK, Global.bWireFrame ? GL_LINE : GL_FILL); } +void opengl46_renderer::Render_lamp_shadows() +{ + auto const &lamps{m_localshadows.lamps()}; + if (lamps.empty() || (false == Global.gfx_lights_clustered)) + return; + + // vehicles near the camera cast the shadows of the lamps, and the occupied cab + m_lampshadowvehicles.clear(); + auto const vehiclesrange{static_cast(Global.gfx_lights_shadows_vehicles_range)}; + if (vehiclesrange > 0.0) + { + for (auto *vehicle : simulation::Vehicles.sequence()) + { + if (glm::length(vehicle->GetPosition() - Global.pCamera.Pos) <= vehiclesrange + vehicle->radius()) + m_lampshadowvehicles.emplace_back(vehicle); + } + } + TDynamicObject *cab{nullptr}; + if ((vehiclesrange > 0.0) && (false == FreeFlyModeFlag) && Global.render_cab && (simulation::Train != nullptr)) + { + cab = simulation::Train->Dynamic(); + if ((cab != nullptr) && ((false == cab->bDisplayCab) || (cab->mdKabina == nullptr) || (cab->mdKabina == cab->mdModel))) + cab = nullptr; + } + + auto timing{false}; + for (auto const idx : lamps) + { + auto const &slot{m_localshadows.slot(idx)}; + if (m_localshadows.scenery_wanted(idx)) + { + if (false == timing) + { + m_localshadows.lamps_timer_begin(); + timing = true; + } + Render_lamp_shadow(idx, false, nullptr); + } + if (false == slot.scenery) + continue; + // the vehicles by the lamp, within its reach + m_lampshadowslot.clear(); + for (auto *vehicle : m_lampshadowvehicles) + { + if (glm::length(vehicle->GetPosition() - slot.position) <= slot.range + vehicle->radius()) + m_lampshadowslot.emplace_back(vehicle); + } + auto *slotcab{(cab != nullptr) && (glm::length(cab->GetPosition() - slot.position) <= slot.range + cab->radius()) ? cab : nullptr}; + if (m_lampshadowslot.empty() && (slotcab == nullptr)) + { + m_localshadows.no_vehicles(idx); + continue; + } + if (false == m_localshadows.vehicles_wanted(idx, m_framestamp)) + continue; + if (false == timing) + { + m_localshadows.lamps_timer_begin(); + timing = true; + } + Render_lamp_shadow(idx, true, slotcab); + } + if (timing) + m_localshadows.lamps_timer_end(); +} + +void opengl46_renderer::Render_lamp_shadow(std::size_t const Slot, bool const Vehicles, TDynamicObject *Cab) +{ + auto const &slot{m_localshadows.slot(Slot)}; + glDebug("render lamp shadows"); + // the shadow pass from the lamp, in world axes: the camera of the pass is the lamp, its frustum the box of its reach. the + // distances which pick the details of the models are measured from the real camera, as in the shadows of the sun + m_renderpass = m_colorpass; + m_renderpass.draw_mode = rendermode::shadows; + m_renderpass.draw_range = slot.range; + m_renderpass.draw_stats = {}; + auto &camera{m_renderpass.pass_camera}; + camera.position() = slot.position; + camera.modelview() = glm::mat4{glm::translate(glm::dmat4{1.0}, -slot.position)}; + auto const reach{slot.range}; + camera.projection() = glm::ortho(-reach, reach, -reach, reach, -reach, reach); + camera.update_frustum(); + OpenGLMatrices.mode(GL_MODELVIEW); + OpenGLMatrices.load_matrix(glm::mat4{1.f}); + + m_localshadows.begin_lamp(Slot, Vehicles, m_instancerecordsversion, m_framestamp); + m_cubeshadows = true; + if (Vehicles) + { + for (auto *vehicle : m_lampshadowslot) + { + Render(vehicle); + } + if (Cab != nullptr) + { + Render_cab(Cab, 0.0f, false); + } + } + else + { + m_cubescenery = true; + m_cubeskipinstance = static_cast(slot.owner); + Render(simulation::Region); + m_cubeskipinstance = nullptr; + m_cubescenery = false; + } + m_cubeshadows = false; + m_localshadows.end_lamp(static_cast(m_renderpass.draw_stats.drawcalls)); + m_renderpass = m_colorpass; + glPolygonMode(GL_FRONT_AND_BACK, Global.bWireFrame ? GL_LINE : GL_FILL); +} + void opengl46_renderer::Update_Clusters(viewport_config &vp, glm::ivec2 const &Targetsize) { glDebug("Update_Clusters"); diff --git a/rendering/opengl46/opengl46renderer.h b/rendering/opengl46/opengl46renderer.h index f7e759c4..4c22980a 100644 --- a/rendering/opengl46/opengl46renderer.h +++ b/rendering/opengl46/opengl46renderer.h @@ -303,6 +303,10 @@ class opengl46_renderer : public gfx_renderer { bool Render_reflections(viewport_config &vp); // draws the cube shadow maps of the lamps which need it in this frame (the cab lamp: the cab) void Render_local_shadows(); + // draws the parts of the cube shadow maps of the scenery lamps which need it in this frame + void Render_lamp_shadows(); + // one part of the map of a scenery lamp: the scenery, or the vehicles by it (and Cab, the occupied cab) over a copy of it + void Render_lamp_shadow(std::size_t const Slot, bool const Vehicles, TDynamicObject *Cab); bool Render(world_environment *Environment); void Render(scene::basic_region *Region); void Render(section_sequence::iterator First, section_sequence::iterator Last); @@ -514,6 +518,12 @@ class opengl46_renderer : public gfx_renderer { opengl46_local_shadows m_localshadows; // the shadow pass draws to a cube shadow map, with the programs of m_localshadows bool m_cubeshadows = false; + // the shadow pass draws the kept scenery part of the map of a lamp: no vehicles, and not the model of the lamp itself + bool m_cubescenery = false; + TAnimModel const *m_cubeskipinstance = nullptr; + // vehicles near the camera which cast the shadows of scenery lamps, and those of them by the lamp being drawn + std::vector m_lampshadowvehicles; + std::vector m_lampshadowslot; double m_precipitationrotation; diff --git a/shaders/light_common.glsl b/shaders/light_common.glsl index d4e44d38..9086d532 100644 --- a/shaders/light_common.glsl +++ b/shaders/light_common.glsl @@ -4,6 +4,8 @@ uniform sampler2DArrayShadow shadowmap; uniform sampler2D headlightmap; // cube shadow maps of the lamps, see gl::LOCAL_SHADOW_TEX and opengl46_local_shadows layout(binding = LOCAL_SHADOW_TEX) uniform samplerCubeArrayShadow local_shadowmap; +// the same for the lamps of scenery models, the lights with linear 1 +layout(binding = LOCAL_SHADOW_LAMPS_TEX) uniform samplerCubeArrayShadow local_shadowmap_lamps; #include #include @@ -234,12 +236,14 @@ vec2 calc_headlights(light_s light, vec3 fragnormal, vec3 dpdx, vec3 dpdy) // closest distance (squared) the light of a lamp is computed for: nearer the inverse square would grow without a limit const float LOCAL_LIGHT_MIN_DISTANCE2 = 0.01; -// one comparison with the cube shadow map at the point of the space of the map, moved towards the lamp by Bias (metres) -float local_light_shadow_tap(vec3 position, float layer, float near, float bias) +// one comparison with the cube shadow map at the point of the space of the map, moved towards the lamp by Bias (metres). the +// map is in the array of the scenery lamps when Lamps, in the one of the cab lamp otherwise +float local_light_shadow_tap(vec3 position, float layer, float near, float bias, bool lamps) { vec3 axes = abs(position); float major = max(axes.x, max(axes.y, axes.z)); - return texture(local_shadowmap, vec4(position, layer), near / max(major - bias, near)); + float reference = near / max(major - bias, near); + return lamps ? texture(local_shadowmap_lamps, vec4(position, layer), reference) : texture(local_shadowmap, vec4(position, layer), reference); } // share of the light of a lamp with a cube shadow map (headlight_weights.x is its layer) which reaches the fragment, 0..1. @@ -261,8 +265,9 @@ float local_light_shadow(light_s light, vec3 fragnormal) float bias = texel * 1.5; float layer = light.headlight_weights.x; float radius = light.headlight_weights.w; + bool lamps = (light.linear > 0.5); if (radius <= 0.0) - return local_light_shadow_tap(position, layer, light.headlight_weights.y, bias); + return local_light_shadow_tap(position, layer, light.headlight_weights.y, bias, lamps); // a disc of samples in the plane of the surface, each compared with its own distance: points of the surface itself, which // samples spread across the direction to the lamp would not be at grazing angles. rotated per pixel to turn the banding into noise vec3 side = normalize(cross(normal, (abs(normal.y) < 0.99) ? vec3(0.0, 1.0, 0.0) : vec3(1.0, 0.0, 0.0))); @@ -277,7 +282,7 @@ float local_light_shadow(light_s light, vec3 fragnormal) for (int i = 0; i < count; i++) { vec2 offset = vec2(disc[i].x * rotation.x - disc[i].y * rotation.y, disc[i].x * rotation.y + disc[i].y * rotation.x) * spread; - shadow += local_light_shadow_tap(position + side * offset.x + up * offset.y, layer, light.headlight_weights.y, bias); + shadow += local_light_shadow_tap(position + side * offset.x + up * offset.y, layer, light.headlight_weights.y, bias, lamps); } return shadow / float(count); } diff --git a/utilities/Globals.cpp b/utilities/Globals.cpp index 1beeaa1e..1ba3da8d 100644 --- a/utilities/Globals.cpp +++ b/utilities/Globals.cpp @@ -1616,6 +1616,16 @@ 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.lamps" ) { + Parser.getTokens( 1 ); + Parser >> gfx_lights_shadows_lamps; + gfx_lights_shadows_lamps = std::clamp( gfx_lights_shadows_lamps, 0, 32 ); } + else if( Token == "gfx.lights.shadows.lamps.size" ) { + Parser.getTokens( 1 ); + Parser >> gfx_lights_shadows_lamps_size; + gfx_lights_shadows_lamps_size = std::clamp( gfx_lights_shadows_lamps_size, 128, 2048 ); } + else if( Token == "gfx.lights.shadows.lamps.range" ) { readvalue( gfx_lights_shadows_lamps_range, 0.f, 1000.f ); } + else if( Token == "gfx.lights.shadows.vehicles.range" ) { readvalue( gfx_lights_shadows_vehicles_range, 0.f, 1000.f ); } else { return false; } return true; @@ -1892,6 +1902,10 @@ global_settings::export_as_text( std::ostream &Output ) const { export_as_text( Output, "gfx.lights.cab.bounce", gfx_lights_cab_bounce ); 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.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 ); + export_as_text( Output, "gfx.lights.shadows.vehicles.range", gfx_lights_shadows_vehicles_range ); export_as_text( Output, "python.enabled", python_enabled ); export_as_text( Output, "python.threadedupload", python_threadedupload ); export_as_text( Output, "python.uploadmain", python_uploadmain ); diff --git a/utilities/Globals.h b/utilities/Globals.h index 354cb488..5d9e5ec2 100644 --- a/utilities/Globals.h +++ b/utilities/Globals.h @@ -346,6 +346,13 @@ 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 }; + // 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 + int gfx_lights_shadows_lamps { 8 }; + int gfx_lights_shadows_lamps_size { 512 }; + float gfx_lights_shadows_lamps_range { 100.f }; + float gfx_lights_shadows_vehicles_range { 50.f }; float ui_fontsize = 13.0f; float ui_scale = 1.0f;