mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-17 02:49:19 +02:00
Cast shadows of vehicle headlights from the scene in their beam in the GL 4.6 renderer
This commit is contained in:
@@ -148,6 +148,27 @@ into the cab only through the windows. Each such lamp has a cube shadow map in w
|
||||
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.
|
||||
|
||||
**Shadows of headlights** (with `gfx.lights.shadows` above `no`): the headlights of the vehicles cast the shadows of what is
|
||||
in their beam, other vehicles, signals, poles, platforms, the walls of a cutting. The beam of a set of headlights (one end of
|
||||
a vehicle) is projected from behind the vehicle, as a narrow cone along it which starts at its end; the shadow map is drawn
|
||||
with that same projection, so it covers the beam and nothing else, and the shadows fall the way the beam does (nearly along
|
||||
the vehicle). In the map is what lies within `gfx.lights.shadows.headlights.range` ahead of the vehicle (the terrain,
|
||||
tracks, models, the other vehicles and the occupied cab, only their back faces, as in the shadows of the sun), not the
|
||||
vehicle itself; past that reach things cast no shadow of the headlights, but what is behind a nearer one is still in its
|
||||
shadow. Maps go to the most important headlights (the nearest and brightest, as the light buffer orders them),
|
||||
`gfx.lights.shadows.headlights` sets of them, of those whose beam is in view within that reach. By day, when the beam isn't
|
||||
seen, nothing is drawn. A map keeps the place the projector was in when it was drawn, so an older one still holds right for
|
||||
what doesn't move:
|
||||
|
||||
| Level | Map | Drawn, vehicle moving / standing | Edges |
|
||||
|---|---|---|---|
|
||||
| `low` (1) | 1024 x 1024, 4 MB | every 2nd / 8th frame | hard (2x2 filter) |
|
||||
| `medium` (2) | 1024 x 1024, 4 MB | every frame / every 4th | soft, 4 samples |
|
||||
| `high` (3) | 2048 x 2048, 16 MB | every frame / every 2nd | softer, 8 samples |
|
||||
|
||||
Drawing a map is a shadow pass of the scene in the beam (draw calls in proportion to what is in it, on the CPU like the
|
||||
shadows of the sun, see `headlight shadows:` in Shift+F12); with the vehicle on the move it is drawn every frame from `medium` on.
|
||||
|
||||
| Entry | Default | Meaning |
|
||||
|---|---|---|
|
||||
| `gfx.lights.scenery yes/no` | `yes` | light points of scenery models light their surroundings |
|
||||
@@ -163,6 +184,8 @@ Lamps of vehicles and lit compartments cast no shadows. The memory taken is 2 ma
|
||||
| `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 |
|
||||
| `gfx.lights.shadows.headlights` | `2` | 0..4: how many sets of headlights (vehicle ends) cast shadows; 0: none |
|
||||
| `gfx.lights.shadows.headlights.range` | `250` | 10..1000: distance ahead of the vehicle within which things cast the shadows of its headlights, metres |
|
||||
|
||||
### Models drawn from the GPU
|
||||
|
||||
@@ -223,7 +246,10 @@ editor goes through the first `dynamiclights` headlights, the reflections throug
|
||||
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.
|
||||
(`scenery lamp shadows:`). The count and both ranges can be changed in the window too. The `headlight shadows:` row gives
|
||||
the headlights with maps (of the slots, how many ready), the size of the maps, how many were drawn so far, and the draw
|
||||
calls and cpu time of the last frame which drew any, with their GPU time in the timings (`headlight shadows:`); the count
|
||||
and the reach can be changed in the window.
|
||||
- **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),
|
||||
|
||||
@@ -61,6 +61,8 @@ void gl::glsl_common_setup()
|
||||
"#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" +
|
||||
"#define HEADLIGHT_SHADOW_TEX " + std::to_string(HEADLIGHT_SHADOW_TEX) + "\n" +
|
||||
"#define MAX_HEADLIGHT_SHADOWS " + std::to_string(MAX_HEADLIGHT_SHADOWS) + "U\n" +
|
||||
R"STRING(
|
||||
const uint LIGHT_SPOT = 0U;
|
||||
const uint LIGHT_POINT = 1U;
|
||||
@@ -196,6 +198,9 @@ void gl::glsl_common_setup()
|
||||
vec4 wiper_pos;
|
||||
vec4 wiper_timer_out;
|
||||
vec4 wiper_timer_return;
|
||||
// shadow maps of the headlights, see gl::scene_ubs
|
||||
mat4 headlight_shadow[MAX_HEADLIGHT_SHADOWS];
|
||||
vec4 headlight_shadow_params[MAX_HEADLIGHT_SHADOWS];
|
||||
};
|
||||
|
||||
#ifdef STAGE_VERTEX
|
||||
|
||||
13
gl/ubo.h
13
gl/ubo.h
@@ -62,6 +62,9 @@ namespace gl
|
||||
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;
|
||||
// shadow maps of the headlights of vehicles (2d array, depth compared; opengl46_local_shadows), and how many of them there may be
|
||||
const size_t HEADLIGHT_SHADOW_TEX = MAX_TEXTURES + HELPER_TEXTURES + 3;
|
||||
const size_t MAX_HEADLIGHT_SHADOWS = 4;
|
||||
|
||||
struct scene_ubs
|
||||
{
|
||||
@@ -74,9 +77,14 @@ namespace gl
|
||||
glm::vec4 wiper_pos;
|
||||
glm::vec4 wiper_timer_out;
|
||||
glm::vec4 wiper_timer_return;
|
||||
// view space of the pass to the space of the shadow map of a set of headlights (HEADLIGHTS lights with headlight_weights.w
|
||||
// above 0 read the map w - 1): x, y 0..1 across the map, z / w the depth of the map, w the distance along the axis of the
|
||||
// projector. and x: size of a texel of the map one metre from the projector, y: radius of the filter in texels
|
||||
glm::mat4 headlight_shadow[MAX_HEADLIGHT_SHADOWS];
|
||||
glm::vec4 headlight_shadow_params[MAX_HEADLIGHT_SHADOWS];
|
||||
};
|
||||
|
||||
static_assert(sizeof(scene_ubs) == 400, "bad size of ubs");
|
||||
static_assert(sizeof(scene_ubs) == 400 + 80 * MAX_HEADLIGHT_SHADOWS, "bad size of ubs");
|
||||
|
||||
const size_t MAX_PARAMS = 3;
|
||||
|
||||
@@ -140,7 +148,8 @@ namespace gl
|
||||
float intensity;
|
||||
float ambient;
|
||||
|
||||
// HEADLIGHTS: view space to the space of the beam texture, and the weights of its channels.
|
||||
// HEADLIGHTS: view space to the space of the beam texture, and the weights of its channels; w: 1 + the shadow map of the
|
||||
// headlights (scene_ubs::headlight_shadow), 0 when they have none.
|
||||
// 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. the map is in the array of
|
||||
|
||||
@@ -48,6 +48,22 @@ GLuint make_cube_array(int const Size, int const Cubes)
|
||||
return texture;
|
||||
}
|
||||
|
||||
// 2d array of depth maps compared to the reference, one level, Layers layers of Size x Size
|
||||
GLuint make_array(int const Size, int const Layers)
|
||||
{
|
||||
GLuint texture{0};
|
||||
glCreateTextures(GL_TEXTURE_2D_ARRAY, 1, &texture);
|
||||
glTextureStorage3D(texture, 1, GL_DEPTH_COMPONENT32F, Size, Size, Layers);
|
||||
// one level, as the cube maps above
|
||||
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);
|
||||
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)
|
||||
{
|
||||
@@ -85,8 +101,11 @@ opengl46_local_shadows::~opengl46_local_shadows()
|
||||
{
|
||||
release();
|
||||
release_lamps();
|
||||
release_headlights();
|
||||
if (m_empty != 0)
|
||||
glDeleteTextures(1, &m_empty);
|
||||
if (m_emptyheadlights != 0)
|
||||
glDeleteTextures(1, &m_emptyheadlights);
|
||||
}
|
||||
|
||||
void opengl46_local_shadows::init()
|
||||
@@ -114,6 +133,10 @@ void opengl46_local_shadows::init()
|
||||
glTextureParameteri(m_empty, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL);
|
||||
float const farthest{0.f};
|
||||
glClearTexImage(m_empty, 0, GL_DEPTH_COMPONENT, GL_FLOAT, &farthest);
|
||||
m_emptyheadlights = make_array(1, 1);
|
||||
glTextureParameteri(m_emptyheadlights, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTextureParameteri(m_emptyheadlights, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glClearTexImage(m_emptyheadlights, 0, GL_DEPTH_COMPONENT, GL_FLOAT, &farthest);
|
||||
}
|
||||
|
||||
void opengl46_local_shadows::set_layer(int const Layer)
|
||||
@@ -127,13 +150,13 @@ opengl46_local_shadows::level_setup opengl46_local_shadows::setup(int const Leve
|
||||
switch (Level)
|
||||
{
|
||||
case 1:
|
||||
return {512, 4, 0.f, 1};
|
||||
return {512, 4, 0.f, 1, 1024, 2, 8};
|
||||
case 2:
|
||||
return {1024, 2, 1.f, 1};
|
||||
return {1024, 2, 1.f, 1, 1024, 1, 4};
|
||||
case 3:
|
||||
return {1024, 1, 2.f, 2};
|
||||
return {1024, 1, 2.f, 2, 2048, 1, 2};
|
||||
default:
|
||||
return {0, 0, 0.f, 0};
|
||||
return {0, 0, 0.f, 0, 0, 0, 0};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -201,10 +224,47 @@ void opengl46_local_shadows::release_lamps()
|
||||
m_wanted.clear();
|
||||
}
|
||||
|
||||
void opengl46_local_shadows::allocate_headlights(int const Count, int const Size)
|
||||
{
|
||||
release_headlights();
|
||||
if ((Count <= 0) || (Size <= 0))
|
||||
return;
|
||||
m_headlightstexture = make_array(Size, Count);
|
||||
glCreateFramebuffers(1, &m_headlightsframebuffer);
|
||||
glNamedFramebufferTextureLayer(m_headlightsframebuffer, GL_DEPTH_ATTACHMENT, m_headlightstexture, 0, 0);
|
||||
glNamedFramebufferDrawBuffer(m_headlightsframebuffer, GL_NONE);
|
||||
glNamedFramebufferReadBuffer(m_headlightsframebuffer, GL_NONE);
|
||||
if (glCheckNamedFramebufferStatus(m_headlightsframebuffer, GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
|
||||
{
|
||||
ErrorLog("headlight shadow map framebuffer setup failed, the headlights have no shadows");
|
||||
release_headlights();
|
||||
Global.gfx_lights_shadows_headlights = 0;
|
||||
return;
|
||||
}
|
||||
m_headlightssize = Size;
|
||||
m_headlightslots.assign(static_cast<std::size_t>(Count), headlight_slot{});
|
||||
WriteLog("headlight shadow maps: " + std::to_string(Count) + " x " + std::to_string(Size) + " x " + std::to_string(Size) + " (" +
|
||||
std::to_string(Count * Size / 1024 * Size * 4 / 1024) + " MB)");
|
||||
}
|
||||
|
||||
void opengl46_local_shadows::release_headlights()
|
||||
{
|
||||
if (m_headlightsframebuffer != 0)
|
||||
glDeleteFramebuffers(1, &m_headlightsframebuffer);
|
||||
if (m_headlightstexture != 0)
|
||||
glDeleteTextures(1, &m_headlightstexture);
|
||||
m_headlightsframebuffer = 0;
|
||||
m_headlightstexture = 0;
|
||||
m_headlightssize = 0;
|
||||
m_headlightslots.clear();
|
||||
m_headlightswanted.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);
|
||||
glBindTextureUnit(gl::HEADLIGHT_SHADOW_TEX, (m_headlightstexture != 0) ? m_headlightstexture : m_emptyheadlights);
|
||||
}
|
||||
|
||||
bool opengl46_local_shadows::update_wanted(TDynamicObject const *Vehicle, TModel3d const *Cab, glm::vec3 const &Lamp, unsigned int const Framestamp)
|
||||
@@ -466,6 +526,154 @@ glm::vec4 opengl46_local_shadows::lamp_weights(std::size_t const Slot) const
|
||||
return glm::vec4{static_cast<float>(cube), SHADOW_NEAR, 2.f / static_cast<float>(std::max(m_lampssize, 1)), setup(m_level).radius};
|
||||
}
|
||||
|
||||
void opengl46_local_shadows::update_headlights(std::vector<light_array::light_record const *> const &Lights, unsigned int const Framestamp)
|
||||
{
|
||||
m_headlightswanted.clear();
|
||||
auto const level{std::clamp(Global.gfx_lights_shadows, 0, 3)};
|
||||
auto const count{level > 0 ? std::clamp(Global.gfx_lights_shadows_headlights, 0, static_cast<int>(gl::MAX_HEADLIGHT_SHADOWS)) : 0};
|
||||
auto const size{count > 0 ? setup(level).headlightsize : 0};
|
||||
if ((static_cast<std::size_t>(count) != m_headlightslots.size()) || (size != m_headlightssize))
|
||||
{
|
||||
allocate_headlights(count, size);
|
||||
}
|
||||
if (m_headlightslots.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
// sets which already have a slot keep it
|
||||
std::vector<light_array::light_record const *> unplaced;
|
||||
for (auto const *light : Lights)
|
||||
{
|
||||
if (m_headlightswanted.size() + unplaced.size() >= m_headlightslots.size())
|
||||
{
|
||||
break;
|
||||
}
|
||||
auto placed{false};
|
||||
for (std::size_t idx = 0; idx < m_headlightslots.size(); ++idx)
|
||||
{
|
||||
auto &slot{m_headlightslots[idx]};
|
||||
if ((slot.owner == light->owner) && (slot.index == light->index) && (slot.wantedframe != Framestamp))
|
||||
{
|
||||
slot.wantedframe = Framestamp;
|
||||
m_headlightswanted.emplace_back(idx);
|
||||
placed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (false == placed)
|
||||
{
|
||||
unplaced.emplace_back(light);
|
||||
}
|
||||
}
|
||||
// the others take the slots of sets no longer picked
|
||||
for (auto const *light : unplaced)
|
||||
{
|
||||
for (std::size_t idx = 0; idx < m_headlightslots.size(); ++idx)
|
||||
{
|
||||
auto &slot{m_headlightslots[idx]};
|
||||
if (slot.wantedframe == Framestamp)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
slot = headlight_slot{};
|
||||
slot.owner = light->owner;
|
||||
slot.index = light->index;
|
||||
slot.wantedframe = Framestamp;
|
||||
m_headlightswanted.emplace_back(idx);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void opengl46_local_shadows::reset_headlights()
|
||||
{
|
||||
for (auto &slot : m_headlightslots)
|
||||
{
|
||||
slot = headlight_slot{};
|
||||
}
|
||||
m_headlightswanted.clear();
|
||||
}
|
||||
|
||||
bool opengl46_local_shadows::headlight_wanted(std::size_t const Slot, bool const Moved, unsigned int const Framestamp) const
|
||||
{
|
||||
auto const &slot{m_headlightslots[Slot]};
|
||||
auto const level{setup(m_level)};
|
||||
return (false == slot.drawn) || (Framestamp - slot.drawnframe >= (Moved ? level.headlightmoving : level.headlightstill));
|
||||
}
|
||||
|
||||
void opengl46_local_shadows::begin_headlight(std::size_t const Slot, glm::dvec3 const &Projector, glm::mat3 const &Rotation, glm::mat4 const &Projection, glm::vec3 const &Direction, unsigned int const Framestamp)
|
||||
{
|
||||
auto &slot{m_headlightslots[Slot]};
|
||||
slot.drawn = true;
|
||||
slot.drawnframe = Framestamp;
|
||||
slot.projector = Projector;
|
||||
slot.rotation = Rotation;
|
||||
slot.projection = Projection;
|
||||
slot.direction = Direction;
|
||||
++m_headlightsdrawn;
|
||||
|
||||
gl::framebuffer::unbind();
|
||||
glNamedFramebufferTextureLayer(m_headlightsframebuffer, GL_DEPTH_ATTACHMENT, m_headlightstexture, 0, static_cast<GLint>(Slot));
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, m_headlightsframebuffer);
|
||||
glViewport(0, 0, m_headlightssize, m_headlightssize);
|
||||
float const farthest{0.f};
|
||||
glClearNamedFramebufferfv(m_headlightsframebuffer, GL_DEPTH, 0, &farthest);
|
||||
glDisable(GL_BLEND);
|
||||
glDepthMask(GL_TRUE);
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
// as in the shadows of the sun: the back faces, so the lit faces (the ground most of all, which the beam skims) don't shadow themselves
|
||||
glEnable(GL_CULL_FACE);
|
||||
glCullFace(GL_FRONT);
|
||||
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
|
||||
}
|
||||
|
||||
void opengl46_local_shadows::end_headlight(std::uint32_t const Drawcalls)
|
||||
{
|
||||
gl::framebuffer::unbind();
|
||||
m_headlightsframedrawcalls += Drawcalls;
|
||||
}
|
||||
|
||||
void opengl46_local_shadows::headlights_timer_begin()
|
||||
{
|
||||
m_headlightsframedrawcalls = 0;
|
||||
m_headlightscpustart = std::chrono::steady_clock::now();
|
||||
m_headlightstimer.begin();
|
||||
m_headlightstiming = true;
|
||||
}
|
||||
|
||||
void opengl46_local_shadows::headlights_timer_end()
|
||||
{
|
||||
if (false == m_headlightstiming)
|
||||
{
|
||||
return;
|
||||
}
|
||||
m_headlightstimer.end();
|
||||
m_headlightstiming = false;
|
||||
m_headlightsdrawcalls = m_headlightsframedrawcalls;
|
||||
auto const elapsed{std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - m_headlightscpustart).count()};
|
||||
m_headlightscputime = (m_headlightscputime == 0.0 ? elapsed : m_headlightscputime * 0.9 + elapsed * 0.1);
|
||||
}
|
||||
|
||||
int opengl46_local_shadows::headlight_map(TDynamicObject const *Owner, int const Index) const
|
||||
{
|
||||
for (auto const idx : m_headlightswanted)
|
||||
{
|
||||
auto const &slot{m_headlightslots[idx]};
|
||||
if ((slot.owner == Owner) && (slot.index == Index))
|
||||
{
|
||||
return (slot.drawn ? static_cast<int>(idx) : -1);
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
glm::vec4 opengl46_local_shadows::headlight_params(std::size_t const Slot) const
|
||||
{
|
||||
// a map spans 2 tan(fov / 2) one metre from the projector, and the projection holds 1 / tan(fov / 2)
|
||||
auto const &projection{m_headlightslots[Slot].projection};
|
||||
return glm::vec4{2.f / (std::max(projection[1][1], 1e-6f) * static_cast<float>(std::max(m_headlightssize, 1))), setup(m_level).radius, 0.f, 0.f};
|
||||
}
|
||||
|
||||
std::string opengl46_local_shadows::info_times()
|
||||
{
|
||||
std::string text;
|
||||
@@ -473,6 +681,8 @@ std::string opengl46_local_shadows::info_times()
|
||||
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";
|
||||
if (auto const time = m_headlightstimer.result())
|
||||
text += " headlight shadows: " + to_string(static_cast<double>(*time) / 1000000.0, 3) + " ms\n";
|
||||
return text;
|
||||
}
|
||||
|
||||
@@ -497,6 +707,17 @@ std::string opengl46_local_shadows::info() const
|
||||
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";
|
||||
}
|
||||
if (false == m_headlightslots.empty())
|
||||
{
|
||||
auto ready{0};
|
||||
for (auto const idx : m_headlightswanted)
|
||||
{
|
||||
ready += (m_headlightslots[idx].drawn ? 1 : 0);
|
||||
}
|
||||
text += "\nheadlight shadows: " + std::to_string(m_headlightswanted.size()) + " of " + std::to_string(m_headlightslots.size()) + " slots (" + std::to_string(ready) + " ready), " +
|
||||
std::to_string(m_headlightssize) + " map, drawn " + std::to_string(m_headlightsdrawn) + " times, " + std::to_string(m_headlightsdrawcalls) + " drawcalls, cpu " +
|
||||
to_string(m_headlightscputime, 2) + " ms";
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
@@ -512,6 +733,16 @@ void opengl46_local_shadows::draw_debug_ui()
|
||||
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.find("\nscenery lamp shadows:")};
|
||||
auto const end{(line == std::string::npos) ? std::string::npos : text.find('\n', line + 1)};
|
||||
if (line != std::string::npos)
|
||||
ImGui::TextUnformatted(text.substr(line + 1, end == std::string::npos ? std::string::npos : end - line - 1).c_str());
|
||||
}
|
||||
ImGui::SliderInt("headlights with shadows (gfx.lights.shadows.headlights)", &Global.gfx_lights_shadows_headlights, 0, static_cast<int>(gl::MAX_HEADLIGHT_SHADOWS));
|
||||
ImGui::SliderFloat("their reach ahead of the vehicle, m (.headlights.range)", &Global.gfx_lights_shadows_headlights_range, 10.f, 1000.f);
|
||||
if (false == m_headlightslots.empty())
|
||||
{
|
||||
auto const text{info()};
|
||||
auto const line{text.rfind('\n')};
|
||||
|
||||
@@ -19,6 +19,7 @@ http://mozilla.org/MPL/2.0/.
|
||||
#include "gl/shader.h"
|
||||
#include "gl/query.h"
|
||||
#include "rendering/opengl46/opengl46locallights.h"
|
||||
#include "rendering/lightarray.h"
|
||||
|
||||
class TDynamicObject;
|
||||
class TModel3d;
|
||||
@@ -35,7 +36,13 @@ class TModel3d;
|
||||
// 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
|
||||
// lamps when its linear is 1 and from the one of the cab lamp otherwise.
|
||||
// - the headlights of the vehicles (Global.gfx_lights_shadows_headlights sets of them, the most important ones): a plain map,
|
||||
// drawn from the projector of the beam with its projection (the beam is a narrow frustum aimed along the vehicle, see
|
||||
// opengl46_renderer::Headlight_Projector()), with what is within Global.gfx_lights_shadows_headlights_range ahead of the
|
||||
// vehicle, but not the vehicle itself. the map keeps the projector it was drawn with, in world space: one a few frames old is
|
||||
// still right for what doesn't move. read by headlight_beam_shadow() in light_common.glsl, for the headlights with the map in their
|
||||
// headlight_weights.w (1 + the layer) and the projection to it in gl::scene_ubs::headlight_shadow
|
||||
class opengl46_local_shadows
|
||||
{
|
||||
public:
|
||||
@@ -132,6 +139,52 @@ class opengl46_local_shadows
|
||||
// the headlight_weights of the light of the lamp in the slot, see weights()
|
||||
glm::vec4 lamp_weights(std::size_t const Slot) const;
|
||||
|
||||
// headlights of vehicles
|
||||
|
||||
// a set of headlights (one end of a vehicle) with a slot
|
||||
struct headlight_slot
|
||||
{
|
||||
TDynamicObject const *owner{nullptr}; // light_array::light_record::owner and index
|
||||
int index{-1};
|
||||
unsigned int wantedframe{~0u}; // framestamp it was last among the headlights given shadows
|
||||
bool drawn{false}; // the map holds the shadows of the headlights
|
||||
unsigned int drawnframe{0};
|
||||
// the projector the map was drawn with: world space position, world axes to its axes, projection
|
||||
glm::dvec3 projector{0.0};
|
||||
glm::mat3 rotation{1.f};
|
||||
glm::mat4 projection{1.f};
|
||||
glm::vec3 direction{0.f}; // of the light when the map was drawn
|
||||
};
|
||||
// picks the headlights which get shadows in this frame: Lights, the most important first, as many as there are slots
|
||||
// (Global.gfx_lights_shadows_headlights). a set keeps its slot while it is picked. (re)makes the maps when their count or size changed
|
||||
void update_headlights(std::vector<light_array::light_record const *> const &Lights, unsigned int const Framestamp);
|
||||
// forgets the headlights: the vehicles may be about to go (a scenery being loaded)
|
||||
void reset_headlights();
|
||||
// slots of the headlights picked in this frame
|
||||
std::vector<std::size_t> const &headlights() const
|
||||
{
|
||||
return m_headlightswanted;
|
||||
}
|
||||
headlight_slot const &headlight(std::size_t const Slot) const
|
||||
{
|
||||
return m_headlightslots[Slot];
|
||||
}
|
||||
// whether the map of the slot is to be drawn in this frame: one never drawn, then every few frames by the level, more often
|
||||
// while the headlights Moved (with the vehicle)
|
||||
bool headlight_wanted(std::size_t const Slot, bool const Moved, unsigned int const Framestamp) const;
|
||||
// sets up the drawing to the map of the slot (cleared), drawn from the projector given (see headlight_slot). the geometry is
|
||||
// to be drawn by the shadow pass with this projection, back faces only
|
||||
void begin_headlight(std::size_t const Slot, glm::dvec3 const &Projector, glm::mat3 const &Rotation, glm::mat4 const &Projection, glm::vec3 const &Direction, unsigned int const Framestamp);
|
||||
// ends the drawing to the map of a set of headlights, leaves the default framebuffer bound
|
||||
void end_headlight(std::uint32_t const Drawcalls);
|
||||
// gpu timer of the drawing of the maps of the headlights in this frame
|
||||
void headlights_timer_begin();
|
||||
void headlights_timer_end();
|
||||
// the slot of the headlights with their map ready in this frame, -1 if there is none
|
||||
int headlight_map(TDynamicObject const *Owner, int const Index) const;
|
||||
// the headlight_shadow_params of the map of the slot, see gl::scene_ubs
|
||||
glm::vec4 headlight_params(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;
|
||||
@@ -139,25 +192,32 @@ class opengl46_local_shadows
|
||||
void draw_debug_ui();
|
||||
|
||||
private:
|
||||
// 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
|
||||
// size, refresh interval (frames), filter radius (texels, 0: the 2x2 filter of the hardware) of a level, the number of
|
||||
// scenery parts of lamp maps drawn per frame, and the size and refresh intervals (frames, while the vehicle moves and while
|
||||
// it doesn't) of the maps of headlights
|
||||
struct level_setup
|
||||
{
|
||||
int size;
|
||||
unsigned int interval;
|
||||
float radius;
|
||||
int sceneryperframe;
|
||||
int headlightsize;
|
||||
unsigned int headlightmoving;
|
||||
unsigned int headlightstill;
|
||||
};
|
||||
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 allocate_headlights(int const Count, int const Size);
|
||||
void release_headlights();
|
||||
void set_layer(int const Layer);
|
||||
|
||||
std::unique_ptr<gl::program> m_program;
|
||||
std::unique_ptr<gl::program> m_alpha_program;
|
||||
GLuint m_empty{0}; // stand-in: a cube map array of 1x1 faces with the farthest depth
|
||||
GLuint m_emptyheadlights{0}; // the same, a 2d array of one 1x1 layer
|
||||
int m_level{-1}; // level the maps were made for
|
||||
// cab lamp
|
||||
GLuint m_texture{0}; // cube map array, one cube
|
||||
@@ -179,6 +239,15 @@ class opengl46_local_shadows
|
||||
int m_sceneryleft{0}; // scenery parts which may still be drawn in this frame
|
||||
gl::gpu_timer m_lampstimer;
|
||||
bool m_lampstiming{false};
|
||||
// headlights: 2d array, a layer per slot
|
||||
GLuint m_headlightstexture{0};
|
||||
GLuint m_headlightsframebuffer{0};
|
||||
int m_headlightssize{0};
|
||||
std::vector<headlight_slot> m_headlightslots;
|
||||
std::vector<std::size_t> m_headlightswanted;
|
||||
gl::gpu_timer m_headlightstimer;
|
||||
bool m_headlightstiming{false};
|
||||
std::chrono::steady_clock::time_point m_headlightscpustart;
|
||||
// statistics
|
||||
std::uint32_t m_drawcalls{0}; // of the last drawing of the cab map
|
||||
std::uint32_t m_updatecount{0}; // drawings of the cab map so far
|
||||
@@ -189,4 +258,8 @@ class opengl46_local_shadows
|
||||
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
|
||||
std::uint32_t m_headlightsdrawn{0}; // maps of headlights drawn so far
|
||||
std::uint32_t m_headlightsdrawcalls{0}; // in the drawings of the maps of headlights in the last frame with any
|
||||
std::uint32_t m_headlightsframedrawcalls{0};
|
||||
double m_headlightscputime{0.0}; // milliseconds per frame, smoothed
|
||||
};
|
||||
|
||||
@@ -637,6 +637,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();
|
||||
m_localshadows.reset_headlights();
|
||||
}
|
||||
// generate new frame
|
||||
opengl_texture::reset_unit_cache();
|
||||
@@ -988,6 +989,7 @@ void opengl46_renderer::Render_pass(viewport_config &vp, rendermode const Mode)
|
||||
{
|
||||
Render_local_shadows();
|
||||
Render_lamp_shadows();
|
||||
Render_headlight_shadows();
|
||||
}
|
||||
|
||||
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
|
||||
@@ -3006,6 +3008,9 @@ void opengl46_renderer::Render(cell_sequence::iterator First, cell_sequence::ite
|
||||
{
|
||||
for (auto *dynamic : path->Dynamics)
|
||||
{
|
||||
// the vehicle of the headlights a map is drawn for would cover all of it
|
||||
if (dynamic == m_shadowskipvehicle)
|
||||
continue;
|
||||
Render(dynamic);
|
||||
}
|
||||
}
|
||||
@@ -6434,54 +6439,40 @@ void opengl46_renderer::Update_Lights(light_array &Lights, bool const Headlights
|
||||
light->ambient = headlight_config.ambient;
|
||||
light->intensity = headlight_config.intensity;
|
||||
// headlights-to-world projection
|
||||
// NOTE: we strip transformations from camera projections to remove jitter that occurs
|
||||
// with large (and unneded as we only need the offset) transformations back and forth
|
||||
auto const worldcam{ glm::mat3{ m_renderpass.pass_camera.modelview() } };
|
||||
{
|
||||
// headlight projection is 1 km long box aligned with vehicle rotation and aimed slightly downwards
|
||||
opengl_camera headlights;
|
||||
auto const &ownerdimensions{ scenelight.owner->MoverParameters->Dim };
|
||||
auto const up{ static_cast<glm::dvec3>( scenelight.owner->VectorUp() ) };
|
||||
auto const size{ static_cast<float>( std::max( ownerdimensions.W, ownerdimensions.H ) * 1.0 ) }; // ensure square ratio
|
||||
auto const cone{ 5.f };
|
||||
auto const offset{ 75.f * ( 5.f / cone ) };
|
||||
headlights.position() =
|
||||
scenelight.owner->GetPosition()
|
||||
- glm::dvec3(scenelight.direction * offset)
|
||||
+ up * ( size * 0.5 );
|
||||
/*
|
||||
headlights.projection() = ortho_projection(
|
||||
-size, size,
|
||||
-size, size,
|
||||
ownerdimensions.L * 0.5 - 0.5, 1000.0f );
|
||||
*/
|
||||
headlights.projection() = perspective_projection_raw(
|
||||
glm::radians( cone ),
|
||||
1.0,
|
||||
ownerdimensions.L * 0.5 + offset - 0.25, 1000.0f );
|
||||
glm::dmat4 viewmatrix{ 1.0 };
|
||||
viewmatrix *=
|
||||
glm::lookAt(
|
||||
headlights.position(),
|
||||
headlights.position()
|
||||
- up * 1.0
|
||||
+ glm::dvec3{ scenelight.direction * 1000.f },
|
||||
glm::dvec3{ 0.f, 1.f, 0.f } );
|
||||
headlights.modelview() = viewmatrix;
|
||||
glm::dvec3 projector;
|
||||
glm::mat3 lightcam;
|
||||
glm::mat4 depthproj;
|
||||
float nearplane, fieldofview;
|
||||
Headlight_Projector( scenelight, projector, lightcam, depthproj, nearplane, fieldofview );
|
||||
// calculate world->headlight space projection matrix
|
||||
glm::mat4 const depthproj{ headlights.projection() };
|
||||
// NOTE: we strip transformations from camera projections to remove jitter that occurs
|
||||
// with large (and unneded as we only need the offset) transformations back and forth
|
||||
auto const lightcam{ glm::mat3{ headlights.modelview() } };
|
||||
auto const worldcam{ glm::mat3{ m_renderpass.pass_camera.modelview() } };
|
||||
|
||||
light->headlight_projection =
|
||||
coordmove
|
||||
* depthproj
|
||||
* glm::translate(
|
||||
glm::mat4{ lightcam },
|
||||
glm::vec3{ m_renderpass.pass_camera.position() - headlights.position() } )
|
||||
glm::vec3{ m_renderpass.pass_camera.position() - projector } )
|
||||
* glm::mat4{ glm::inverse( worldcam ) };
|
||||
}
|
||||
// headlights weights
|
||||
light->headlight_weights = { scenelight.state, 0.f };
|
||||
// the shadow map of the headlights, if they have one: the same projection, from where the projector was when it was drawn
|
||||
if( auto const shadowmap{ m_localshadows.headlight_map( scenelight.owner, scenelight.index ) }; shadowmap >= 0 ) {
|
||||
auto const &slot{ m_localshadows.headlight( static_cast<std::size_t>( shadowmap ) ) };
|
||||
scene_ubs.headlight_shadow[ shadowmap ] =
|
||||
coordmove
|
||||
* slot.projection
|
||||
* glm::translate(
|
||||
glm::mat4{ slot.rotation },
|
||||
glm::vec3{ m_renderpass.pass_camera.position() - slot.projector } )
|
||||
* glm::mat4{ glm::inverse( worldcam ) };
|
||||
scene_ubs.headlight_shadow_params[ shadowmap ] = m_localshadows.headlight_params( static_cast<std::size_t>( shadowmap ) );
|
||||
light->headlight_weights.w = static_cast<float>( shadowmap + 1 );
|
||||
}
|
||||
|
||||
++light_i;
|
||||
}
|
||||
@@ -6704,6 +6695,159 @@ void opengl46_renderer::Render_lamp_shadow(std::size_t const Slot, bool const Ve
|
||||
glPolygonMode(GL_FRONT_AND_BACK, Global.bWireFrame ? GL_LINE : GL_FILL);
|
||||
}
|
||||
|
||||
void opengl46_renderer::Headlight_Projector(light_array::light_record const &Light, glm::dvec3 &Position, glm::mat3 &Rotation, glm::mat4 &Projection, float &Near, float &Fieldofview)
|
||||
{
|
||||
auto const &ownerdimensions{Light.owner->MoverParameters->Dim};
|
||||
auto const up{static_cast<glm::dvec3>(Light.owner->VectorUp())};
|
||||
auto const size{static_cast<float>(std::max(ownerdimensions.W, ownerdimensions.H) * 1.0)}; // ensure square ratio
|
||||
auto const cone{5.f};
|
||||
auto const offset{75.f * (5.f / cone)};
|
||||
Position = Light.owner->GetPosition() - glm::dvec3(Light.direction * offset) + up * (size * 0.5);
|
||||
Near = static_cast<float>(ownerdimensions.L * 0.5 + offset - 0.25);
|
||||
Fieldofview = glm::radians(cone);
|
||||
/*
|
||||
Projection = ortho_projection(
|
||||
-size, size,
|
||||
-size, size,
|
||||
ownerdimensions.L * 0.5 - 0.5, 1000.0f );
|
||||
*/
|
||||
Projection = perspective_projection_raw(Fieldofview, 1.0, Near, 1000.0f);
|
||||
glm::dmat4 viewmatrix{1.0};
|
||||
viewmatrix *= glm::lookAt(Position, Position - up * 1.0 + glm::dvec3{Light.direction * 1000.f}, glm::dvec3{0.f, 1.f, 0.f});
|
||||
Rotation = glm::mat3{glm::mat4{viewmatrix}};
|
||||
}
|
||||
|
||||
void opengl46_renderer::Render_headlight_shadows()
|
||||
{
|
||||
// the headlights which shine, the most important first (in the order of Update_Lights()), whose beam may be seen within the
|
||||
// reach of the shadows
|
||||
m_headlightshadowpicked.clear();
|
||||
auto const count{(std::clamp(Global.gfx_lights_shadows, 0, 3) > 0) ? std::clamp(Global.gfx_lights_shadows_headlights, 0, static_cast<int>(gl::MAX_HEADLIGHT_SHADOWS)) : 0};
|
||||
if (count > 0)
|
||||
{
|
||||
m_headlightshadowcandidates.clear();
|
||||
auto const camera{m_colorpass.pass_camera.position()};
|
||||
auto const range{static_cast<double>(Global.gfx_lights_shadows_headlights_range)};
|
||||
for (auto const &light : simulation::Lights.data)
|
||||
{
|
||||
if (light.intensity == 0.f)
|
||||
continue;
|
||||
// as in Update_Lights(): by day the light around takes the beam down to nothing
|
||||
auto luminance{static_cast<float>(Global.fLuminance)};
|
||||
if (light.owner->fShade > 0.f)
|
||||
luminance *= light.owner->fShade;
|
||||
auto const beam{light.color - glm::vec3{luminance}};
|
||||
if (std::max(beam.r, std::max(beam.g, beam.b)) <= 0.f)
|
||||
continue;
|
||||
// the part of the beam the shadows are cast in: from the end of the vehicle to the reach ahead of it
|
||||
auto const &dimensions{light.owner->MoverParameters->Dim};
|
||||
auto const radius{range * 0.5 + std::max(dimensions.W, dimensions.H) + 2.0};
|
||||
auto const centre{light.position + glm::dvec3{light.direction} * (range * 0.5 + 2.0)};
|
||||
if (glm::length(centre - camera) - radius > range)
|
||||
continue;
|
||||
if (false == m_colorpass.pass_camera.visible(scene::bounding_area{centre, static_cast<float>(radius)}))
|
||||
continue;
|
||||
m_headlightshadowcandidates.emplace_back(static_cast<float>(glm::length2(camera - light.position)) / light.intensity, &light);
|
||||
}
|
||||
auto const picked{std::min(m_headlightshadowcandidates.size(), static_cast<std::size_t>(count))};
|
||||
std::partial_sort(std::begin(m_headlightshadowcandidates), std::begin(m_headlightshadowcandidates) + picked, std::end(m_headlightshadowcandidates),
|
||||
[](auto const &Left, auto const &Right) { return Left.first < Right.first; });
|
||||
for (std::size_t idx = 0; idx < picked; ++idx)
|
||||
{
|
||||
m_headlightshadowpicked.emplace_back(m_headlightshadowcandidates[idx].second);
|
||||
}
|
||||
}
|
||||
m_localshadows.update_headlights(m_headlightshadowpicked, m_framestamp);
|
||||
// the maps may have been made anew, in place of the ones bound for this frame
|
||||
m_localshadows.bind();
|
||||
auto const &slots{m_localshadows.headlights()};
|
||||
if (slots.empty())
|
||||
return;
|
||||
|
||||
// the occupied cab, as in Render_lamp_shadows()
|
||||
TDynamicObject *cab{nullptr};
|
||||
if ((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 : slots)
|
||||
{
|
||||
auto const &slot{m_localshadows.headlight(idx)};
|
||||
light_array::light_record const *light{nullptr};
|
||||
for (auto const *picked : m_headlightshadowpicked)
|
||||
{
|
||||
if ((picked->owner == slot.owner) && (picked->index == slot.index))
|
||||
{
|
||||
light = picked;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (light == nullptr)
|
||||
continue;
|
||||
glm::dvec3 projector;
|
||||
glm::mat3 rotation;
|
||||
glm::mat4 projection;
|
||||
float nearplane, fieldofview;
|
||||
Headlight_Projector(*light, projector, rotation, projection, nearplane, fieldofview);
|
||||
// a vehicle on the move needs its map anew, the things around it and the vehicles passing by only now and then
|
||||
auto const moved{(glm::length(projector - slot.projector) > 0.01) || (glm::dot(light->direction, slot.direction) < 0.99999f)};
|
||||
if (false == m_localshadows.headlight_wanted(idx, moved, m_framestamp))
|
||||
continue;
|
||||
if (false == timing)
|
||||
{
|
||||
m_localshadows.headlights_timer_begin();
|
||||
timing = true;
|
||||
}
|
||||
Render_headlight_shadow(idx, *light, cab);
|
||||
}
|
||||
if (timing)
|
||||
m_localshadows.headlights_timer_end();
|
||||
}
|
||||
|
||||
void opengl46_renderer::Render_headlight_shadow(std::size_t const Slot, light_array::light_record const &Light, TDynamicObject *Cab)
|
||||
{
|
||||
glDebug("render headlight shadows");
|
||||
glm::dvec3 projector;
|
||||
glm::mat3 rotation;
|
||||
glm::mat4 projection;
|
||||
float nearplane, fieldofview;
|
||||
Headlight_Projector(Light, projector, rotation, projection, nearplane, fieldofview);
|
||||
auto const reach{nearplane + Global.gfx_lights_shadows_headlights_range};
|
||||
// the shadow pass from the projector of the beam, with its projection. 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 = reach;
|
||||
m_renderpass.draw_stats = {};
|
||||
auto &camera{m_renderpass.pass_camera};
|
||||
camera.position() = projector;
|
||||
camera.modelview() = glm::mat4{glm::dmat4{glm::dmat3{rotation}} * glm::translate(glm::dmat4{1.0}, -projector)};
|
||||
camera.projection() = projection;
|
||||
// the projection goes on to the infinity, what the shadows are cast by ends at their reach
|
||||
camera.update_frustum(glm::perspective(fieldofview, 1.f, nearplane, reach));
|
||||
setup_matrices();
|
||||
auto const colorprojection{scene_ubs.projection};
|
||||
scene_ubs.projection = projection;
|
||||
scene_ubo->update(scene_ubs);
|
||||
|
||||
m_localshadows.begin_headlight(Slot, projector, rotation, projection, Light.direction, m_framestamp);
|
||||
m_shadowskipvehicle = Light.owner;
|
||||
Render(simulation::Region);
|
||||
if ((Cab != nullptr) && (Cab != Light.owner) && (glm::length(Cab->GetPosition() - projector) <= reach + Cab->radius()))
|
||||
{
|
||||
Render_cab(Cab, 0.0f, false);
|
||||
}
|
||||
m_shadowskipvehicle = nullptr;
|
||||
m_localshadows.end_headlight(static_cast<std::uint32_t>(m_renderpass.draw_stats.drawcalls));
|
||||
scene_ubs.projection = colorprojection;
|
||||
scene_ubo->update(scene_ubs);
|
||||
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");
|
||||
|
||||
@@ -308,6 +308,14 @@ class opengl46_renderer : public gfx_renderer {
|
||||
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);
|
||||
// picks the headlights which get shadow maps in this frame and draws the maps which need it
|
||||
void Render_headlight_shadows();
|
||||
// the map of the headlights in the slot: the scene within reach ahead of them (and Cab, the occupied cab), without their vehicle
|
||||
void Render_headlight_shadow(std::size_t const Slot, light_array::light_record const &Light, TDynamicObject *Cab);
|
||||
// the projector of the beam of a set of headlights: a narrow frustum along the vehicle, from behind it, its near plane at the
|
||||
// end of the vehicle. the beam texture is projected with it (Update_Lights()) and the shadow map of the headlights drawn with it.
|
||||
// Position in world space, Rotation from world axes to its axes; Near is the distance of the near plane from the projector
|
||||
void Headlight_Projector(light_array::light_record const &Light, glm::dvec3 &Position, glm::mat3 &Rotation, glm::mat4 &Projection, float &Near, float &Fieldofview);
|
||||
bool Render(world_environment *Environment);
|
||||
void Render(scene::basic_region *Region);
|
||||
void Render(section_sequence::iterator First, section_sequence::iterator Last);
|
||||
@@ -556,6 +564,11 @@ class opengl46_renderer : public gfx_renderer {
|
||||
// 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;
|
||||
// the shadow pass leaves this vehicle out (the one the headlights of the map being drawn belong to)
|
||||
TDynamicObject const *m_shadowskipvehicle = nullptr;
|
||||
// headlights which may get shadow maps in this frame, and their importance
|
||||
std::vector<std::pair<float, light_array::light_record const *>> m_headlightshadowcandidates;
|
||||
std::vector<light_array::light_record const *> m_headlightshadowpicked;
|
||||
|
||||
double m_precipitationrotation;
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@ uniform sampler2D headlightmap;
|
||||
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;
|
||||
// shadow maps of the headlights of vehicles, see gl::HEADLIGHT_SHADOW_TEX and gl::scene_ubs::headlight_shadow
|
||||
layout(binding = HEADLIGHT_SHADOW_TEX) uniform sampler2DArrayShadow headlight_shadowmap;
|
||||
|
||||
#include <envmapping.glsl>
|
||||
#include <conversion.glsl>
|
||||
@@ -198,6 +200,52 @@ vec2 calc_dir_light(light_s light, vec3 fragnormal)
|
||||
return calc_light(light_dir, fragnormal);
|
||||
}
|
||||
|
||||
// samples of the soft shadows of the lamps and headlights: a disc, rotated per pixel
|
||||
const vec2 SHADOW_DISC[8] = vec2[](vec2(-0.326, -0.406), vec2(-0.840, -0.074), vec2(-0.696, 0.457), vec2(-0.203, 0.621),
|
||||
vec2(0.962, -0.195), vec2(0.473, -0.480), vec2(0.519, 0.767), vec2(0.185, -0.893));
|
||||
|
||||
float shadow_disc_angle()
|
||||
{
|
||||
return 6.2831853 * fract(52.9829189 * fract(dot(gl_FragCoord.xy, vec2(0.06711056, 0.00583715))));
|
||||
}
|
||||
|
||||
// share of the beam of headlights with a shadow map (Map, headlight_weights.w - 1 of the light) held back before the fragment,
|
||||
// 0..1. the map was drawn from the projector of the beam: x and y of the projection across it, z / w the depth (near plane over
|
||||
// the distance along the axis, as the reverse z of the scene), w the distance. the comparison gives 1 where the map holds a
|
||||
// value at least as large as the reference: a surface closer to the projector
|
||||
float headlight_beam_shadow(uint map, vec3 fragnormal, vec3 light_dir)
|
||||
{
|
||||
mat4 tomap = headlight_shadow[map];
|
||||
vec4 params = headlight_shadow_params[map];
|
||||
// a texel of the map is as large as this here. the point is moved along the normal by about a texel, and the reference towards
|
||||
// the projector by a part of one. only the back faces are in the map, which leaves the lit faces little to shadow themselves with
|
||||
float texel = params.x * max((tomap * vec4(f_pos.xyz, 1.0)).w, 0.0);
|
||||
float NdotL = clamp(dot(fragnormal, light_dir), 0.0, 1.0);
|
||||
vec4 position = tomap * vec4(f_pos.xyz + fragnormal * (texel * (0.5 + 1.5 * (1.0 - NdotL))), 1.0);
|
||||
if (position.w <= 0.0)
|
||||
return 0.0;
|
||||
vec2 coords = position.xy / position.w;
|
||||
if (any(lessThan(coords, vec2(0.0))) || any(greaterThan(coords, vec2(1.0))))
|
||||
return 0.0;
|
||||
// nearer to the projector than its near plane (the vehicle itself) the reference is past anything in the map
|
||||
float reference = position.z / max(position.w - texel * 1.5, 1e-3);
|
||||
float layer = float(map);
|
||||
float radius = params.y;
|
||||
if (radius <= 0.0)
|
||||
return texture(headlight_shadowmap, vec4(coords, layer, reference));
|
||||
float angle = shadow_disc_angle();
|
||||
vec2 rotation = vec2(cos(angle), sin(angle));
|
||||
float spread = radius / float(textureSize(headlight_shadowmap, 0).x);
|
||||
int count = (radius > 1.5) ? 8 : 4;
|
||||
float shadow = 0.0;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
vec2 offset = vec2(SHADOW_DISC[i].x * rotation.x - SHADOW_DISC[i].y * rotation.y, SHADOW_DISC[i].x * rotation.y + SHADOW_DISC[i].y * rotation.x) * spread;
|
||||
shadow += texture(headlight_shadowmap, vec4(coords + offset, layer, reference));
|
||||
}
|
||||
return shadow / float(count);
|
||||
}
|
||||
|
||||
// dpdx and dpdy are the change of the view space position of the fragment from a pixel to the next one. the beam texture is
|
||||
// sampled with gradients worked out from them instead of the ones of the hardware: those are undefined where the fragments of
|
||||
// a quad do not go through the same lights, which is what happens at the edges of the clusters
|
||||
@@ -230,6 +278,9 @@ vec2 calc_headlights(light_s light, vec3 fragnormal, vec3 dpdx, vec3 dpdy)
|
||||
vec2 dbeamdy = (dheadlightposdy.xy - beamcoords * dheadlightposdy.w) / headlightpos.w;
|
||||
vec3 lights = textureGrad(headlightmap, beamcoords, dbeamdx, dbeamdy).rgb * light.headlight_weights.rgb;
|
||||
float lightintensity = max(max(lights.r, lights.g), lights.b);
|
||||
// headlights with a shadow map: what is in the way holds the beam back
|
||||
if ((light.headlight_weights.w > 0.5) && (lightintensity > 0.0) && (part.x > 0.0))
|
||||
lightintensity *= 1.0 - headlight_beam_shadow(uint(light.headlight_weights.w - 0.5), fragnormal, light_dir);
|
||||
return part * atten * lightintensity;
|
||||
}
|
||||
|
||||
@@ -272,16 +323,14 @@ float local_light_shadow(light_s light, vec3 fragnormal)
|
||||
// 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)));
|
||||
vec3 up = cross(normal, side);
|
||||
float angle = 6.2831853 * fract(52.9829189 * fract(dot(gl_FragCoord.xy, vec2(0.06711056, 0.00583715))));
|
||||
float angle = shadow_disc_angle();
|
||||
vec2 rotation = vec2(cos(angle), sin(angle));
|
||||
float spread = texel * radius;
|
||||
const vec2 disc[8] = vec2[](vec2(-0.326, -0.406), vec2(-0.840, -0.074), vec2(-0.696, 0.457), vec2(-0.203, 0.621),
|
||||
vec2(0.962, -0.195), vec2(0.473, -0.480), vec2(0.519, 0.767), vec2(0.185, -0.893));
|
||||
int count = (radius > 1.5) ? 8 : 4;
|
||||
float shadow = 0.0;
|
||||
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;
|
||||
vec2 offset = vec2(SHADOW_DISC[i].x * rotation.x - SHADOW_DISC[i].y * rotation.y, SHADOW_DISC[i].x * rotation.y + SHADOW_DISC[i].y * rotation.x) * spread;
|
||||
shadow += local_light_shadow_tap(position + side * offset.x + up * offset.y, layer, light.headlight_weights.y, bias, lamps);
|
||||
}
|
||||
return shadow / float(count);
|
||||
|
||||
@@ -1639,6 +1639,11 @@ global_settings::ConfigParse_gfx_sky( cParser &Parser, std::string_view const To
|
||||
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 if( Token == "gfx.lights.shadows.headlights" ) {
|
||||
Parser.getTokens( 1 );
|
||||
Parser >> gfx_lights_shadows_headlights;
|
||||
gfx_lights_shadows_headlights = std::clamp( gfx_lights_shadows_headlights, 0, 4 ); }
|
||||
else if( Token == "gfx.lights.shadows.headlights.range" ) { readvalue( gfx_lights_shadows_headlights_range, 10.f, 1000.f ); }
|
||||
else { return false; }
|
||||
|
||||
return true;
|
||||
@@ -1921,6 +1926,8 @@ global_settings::export_as_text( std::ostream &Output ) const {
|
||||
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, "gfx.lights.shadows.headlights", gfx_lights_shadows_headlights );
|
||||
export_as_text( Output, "gfx.lights.shadows.headlights.range", gfx_lights_shadows_headlights_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 );
|
||||
|
||||
@@ -357,6 +357,11 @@ struct global_settings {
|
||||
int gfx_lights_shadows_lamps_size { 512 };
|
||||
float gfx_lights_shadows_lamps_range { 100.f };
|
||||
float gfx_lights_shadows_vehicles_range { 50.f };
|
||||
// shadows of the headlights of vehicles (a map drawn with the projector of the beam), with gfx_lights_shadows above 0: for how
|
||||
// many sets of headlights at once (the most important ones, 0-4), and the distance ahead of the vehicle within which things
|
||||
// cast them, metres
|
||||
int gfx_lights_shadows_headlights { 2 };
|
||||
float gfx_lights_shadows_headlights_range { 250.f };
|
||||
|
||||
float ui_fontsize = 13.0f;
|
||||
float ui_scale = 1.0f;
|
||||
|
||||
Reference in New Issue
Block a user