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:
@@ -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),
|
||||
|
||||
@@ -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;
|
||||
|
||||
5
gl/ubo.h
5
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;
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 );
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user