17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-17 05:09:20 +02:00

Cast shadows of scenery lamps from the scenery, nearby vehicles and the cab in the GL 4.6 renderer

This commit is contained in:
2026-10-16 11:32:45 +02:00
parent 06d7b9e9f0
commit 2f0a06b98e
12 changed files with 665 additions and 75 deletions

View File

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

View File

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

View File

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

View File

@@ -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<std::uint32_t>(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);
}

View File

@@ -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<scene::basic_cell const *, cell_data> m_cells;
std::unordered_map<TSubModel const *, section_data> m_sections; // compartments, by their submodel
std::unordered_map<TModel3d const *, section_data> 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};

View File

@@ -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<std::size_t>(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<float>(CAB_SLOT), SHADOW_NEAR, 2.f / static_cast<float>(std::max(m_size, 1)), setup(m_level).radius};
}
void opengl46_local_shadows::update_lamps(std::vector<opengl46_local_lights::light> 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<std::size_t>(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<double>(Global.gfx_lights_shadows_lamps_range)};
// the lights come the most important first: the first ones within reach get the slots
std::vector<opengl46_local_lights::light const *> 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<opengl46_local_lights::light const *> 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<int>(Slot)};
auto const workingcube{static_cast<int>(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<double, std::milli>(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<float>(cube), SHADOW_NEAR, 2.f / static_cast<float>(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<double>(*time) / 1000000.0, 3) + " ms\n";
if (auto const time = m_lampstimer.result())
text += " scenery lamp shadows: " + to_string(static_cast<double>(*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));
}
}

View File

@@ -14,20 +14,28 @@ http://mozilla.org/MPL/2.0/.
#include <cstdint>
#include <memory>
#include <string>
#include <vector>
#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<opengl46_local_lights::light> 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<std::size_t> 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<gl::program> m_program;
std::unique_ptr<gl::program> 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<lamp_slot> m_slots;
std::vector<std::size_t> 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
};

View File

@@ -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<uint32_t>(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<double>(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<TAnimModel const *>(slot.owner);
Render(simulation::Region);
m_cubeskipinstance = nullptr;
m_cubescenery = false;
}
m_cubeshadows = false;
m_localshadows.end_lamp(static_cast<std::uint32_t>(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");

View File

@@ -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<TDynamicObject *> m_lampshadowvehicles;
std::vector<TDynamicObject *> m_lampshadowslot;
double m_precipitationrotation;

View File

@@ -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 <envmapping.glsl>
#include <conversion.glsl>
@@ -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);
}

View File

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

View File

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