mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-11-04 19:59:25 +01: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,
|
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.
|
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 |
|
| Entry | Default | Meaning |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `gfx.lights.scenery yes/no` | `yes` | light points of scenery models light their surroundings |
|
| `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.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.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.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
|
### 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:`
|
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
|
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
|
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
|
- **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.
|
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),
|
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 SKY_VIEW_TEX " + std::to_string(SKY_VIEW_TEX) + "\n" +
|
||||||
"#define LOCAL_SHADOW_TEX " + std::to_string(LOCAL_SHADOW_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 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(
|
R"STRING(
|
||||||
const uint LIGHT_SPOT = 0U;
|
const uint LIGHT_SPOT = 0U;
|
||||||
const uint LIGHT_POINT = 1U;
|
const uint LIGHT_POINT = 1U;
|
||||||
@@ -196,6 +198,9 @@ void gl::glsl_common_setup()
|
|||||||
vec4 wiper_pos;
|
vec4 wiper_pos;
|
||||||
vec4 wiper_timer_out;
|
vec4 wiper_timer_out;
|
||||||
vec4 wiper_timer_return;
|
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
|
#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;
|
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)
|
// 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;
|
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
|
struct scene_ubs
|
||||||
{
|
{
|
||||||
@@ -74,9 +77,14 @@ namespace gl
|
|||||||
glm::vec4 wiper_pos;
|
glm::vec4 wiper_pos;
|
||||||
glm::vec4 wiper_timer_out;
|
glm::vec4 wiper_timer_out;
|
||||||
glm::vec4 wiper_timer_return;
|
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;
|
const size_t MAX_PARAMS = 3;
|
||||||
|
|
||||||
@@ -140,7 +148,8 @@ namespace gl
|
|||||||
float intensity;
|
float intensity;
|
||||||
float ambient;
|
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
|
// 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;
|
// 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
|
// 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;
|
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
|
// 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 make_framebuffer(GLuint const Texture)
|
||||||
{
|
{
|
||||||
@@ -85,8 +101,11 @@ opengl46_local_shadows::~opengl46_local_shadows()
|
|||||||
{
|
{
|
||||||
release();
|
release();
|
||||||
release_lamps();
|
release_lamps();
|
||||||
|
release_headlights();
|
||||||
if (m_empty != 0)
|
if (m_empty != 0)
|
||||||
glDeleteTextures(1, &m_empty);
|
glDeleteTextures(1, &m_empty);
|
||||||
|
if (m_emptyheadlights != 0)
|
||||||
|
glDeleteTextures(1, &m_emptyheadlights);
|
||||||
}
|
}
|
||||||
|
|
||||||
void opengl46_local_shadows::init()
|
void opengl46_local_shadows::init()
|
||||||
@@ -114,6 +133,10 @@ void opengl46_local_shadows::init()
|
|||||||
glTextureParameteri(m_empty, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL);
|
glTextureParameteri(m_empty, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL);
|
||||||
float const farthest{0.f};
|
float const farthest{0.f};
|
||||||
glClearTexImage(m_empty, 0, GL_DEPTH_COMPONENT, GL_FLOAT, &farthest);
|
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)
|
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)
|
switch (Level)
|
||||||
{
|
{
|
||||||
case 1:
|
case 1:
|
||||||
return {512, 4, 0.f, 1};
|
return {512, 4, 0.f, 1, 1024, 2, 8};
|
||||||
case 2:
|
case 2:
|
||||||
return {1024, 2, 1.f, 1};
|
return {1024, 2, 1.f, 1, 1024, 1, 4};
|
||||||
case 3:
|
case 3:
|
||||||
return {1024, 1, 2.f, 2};
|
return {1024, 1, 2.f, 2, 2048, 1, 2};
|
||||||
default:
|
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();
|
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()
|
void opengl46_local_shadows::bind()
|
||||||
{
|
{
|
||||||
glBindTextureUnit(gl::LOCAL_SHADOW_TEX, (m_texture != 0) ? m_texture : m_empty);
|
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::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)
|
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};
|
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 opengl46_local_shadows::info_times()
|
||||||
{
|
{
|
||||||
std::string text;
|
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";
|
text += " lamp shadows: " + to_string(static_cast<double>(*time) / 1000000.0, 3) + " ms\n";
|
||||||
if (auto const time = m_lampstimer.result())
|
if (auto const time = m_lampstimer.result())
|
||||||
text += " scenery lamp shadows: " + to_string(static_cast<double>(*time) / 1000000.0, 3) + " ms\n";
|
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;
|
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(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";
|
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;
|
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("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);
|
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())
|
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 text{info()};
|
||||||
auto const line{text.rfind('\n')};
|
auto const line{text.rfind('\n')};
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ http://mozilla.org/MPL/2.0/.
|
|||||||
#include "gl/shader.h"
|
#include "gl/shader.h"
|
||||||
#include "gl/query.h"
|
#include "gl/query.h"
|
||||||
#include "rendering/opengl46/opengl46locallights.h"
|
#include "rendering/opengl46/opengl46locallights.h"
|
||||||
|
#include "rendering/lightarray.h"
|
||||||
|
|
||||||
class TDynamicObject;
|
class TDynamicObject;
|
||||||
class TModel3d;
|
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
|
// 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
|
// 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
|
// 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
|
class opengl46_local_shadows
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@@ -132,6 +139,52 @@ class opengl46_local_shadows
|
|||||||
// the headlight_weights of the light of the lamp in the slot, see weights()
|
// the headlight_weights of the light of the lamp in the slot, see weights()
|
||||||
glm::vec4 lamp_weights(std::size_t const Slot) const;
|
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
|
// lines for the gpu times and statistics of the debug panel
|
||||||
std::string info_times();
|
std::string info_times();
|
||||||
std::string info() const;
|
std::string info() const;
|
||||||
@@ -139,25 +192,32 @@ class opengl46_local_shadows
|
|||||||
void draw_debug_ui();
|
void draw_debug_ui();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// size, refresh interval (frames), filter radius (texels, 0: the 2x2 filter of the hardware) of a level, and the number of
|
// 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
|
// 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
|
struct level_setup
|
||||||
{
|
{
|
||||||
int size;
|
int size;
|
||||||
unsigned int interval;
|
unsigned int interval;
|
||||||
float radius;
|
float radius;
|
||||||
int sceneryperframe;
|
int sceneryperframe;
|
||||||
|
int headlightsize;
|
||||||
|
unsigned int headlightmoving;
|
||||||
|
unsigned int headlightstill;
|
||||||
};
|
};
|
||||||
static level_setup setup(int const Level);
|
static level_setup setup(int const Level);
|
||||||
void allocate(int const Size);
|
void allocate(int const Size);
|
||||||
void release();
|
void release();
|
||||||
void allocate_lamps(int const Count, int const Size);
|
void allocate_lamps(int const Count, int const Size);
|
||||||
void release_lamps();
|
void release_lamps();
|
||||||
|
void allocate_headlights(int const Count, int const Size);
|
||||||
|
void release_headlights();
|
||||||
void set_layer(int const Layer);
|
void set_layer(int const Layer);
|
||||||
|
|
||||||
std::unique_ptr<gl::program> m_program;
|
std::unique_ptr<gl::program> m_program;
|
||||||
std::unique_ptr<gl::program> m_alpha_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_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
|
int m_level{-1}; // level the maps were made for
|
||||||
// cab lamp
|
// cab lamp
|
||||||
GLuint m_texture{0}; // cube map array, one cube
|
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
|
int m_sceneryleft{0}; // scenery parts which may still be drawn in this frame
|
||||||
gl::gpu_timer m_lampstimer;
|
gl::gpu_timer m_lampstimer;
|
||||||
bool m_lampstiming{false};
|
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
|
// statistics
|
||||||
std::uint32_t m_drawcalls{0}; // of the last drawing of the cab map
|
std::uint32_t m_drawcalls{0}; // of the last drawing of the cab map
|
||||||
std::uint32_t m_updatecount{0}; // drawings of the cab map so far
|
std::uint32_t m_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_lampsdrawcalls{0}; // in the drawings of the lamp maps in the last frame with any
|
||||||
std::uint32_t m_lampsframedrawcalls{0};
|
std::uint32_t m_lampsframedrawcalls{0};
|
||||||
double m_lampscputime{0.0}; // milliseconds per frame, smoothed
|
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
|
// a scenery is being loaded: the cells, instances and models the lights were taken from may be gone
|
||||||
m_locallights.reset();
|
m_locallights.reset();
|
||||||
m_localshadows.reset_lamps();
|
m_localshadows.reset_lamps();
|
||||||
|
m_localshadows.reset_headlights();
|
||||||
}
|
}
|
||||||
// generate new frame
|
// generate new frame
|
||||||
opengl_texture::reset_unit_cache();
|
opengl_texture::reset_unit_cache();
|
||||||
@@ -988,6 +989,7 @@ void opengl46_renderer::Render_pass(viewport_config &vp, rendermode const Mode)
|
|||||||
{
|
{
|
||||||
Render_local_shadows();
|
Render_local_shadows();
|
||||||
Render_lamp_shadows();
|
Render_lamp_shadows();
|
||||||
|
Render_headlight_shadows();
|
||||||
}
|
}
|
||||||
|
|
||||||
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
|
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)
|
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);
|
Render(dynamic);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6434,54 +6439,40 @@ void opengl46_renderer::Update_Lights(light_array &Lights, bool const Headlights
|
|||||||
light->ambient = headlight_config.ambient;
|
light->ambient = headlight_config.ambient;
|
||||||
light->intensity = headlight_config.intensity;
|
light->intensity = headlight_config.intensity;
|
||||||
// headlights-to-world projection
|
// 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
|
// headlight projection is 1 km long box aligned with vehicle rotation and aimed slightly downwards
|
||||||
opengl_camera headlights;
|
glm::dvec3 projector;
|
||||||
auto const &ownerdimensions{ scenelight.owner->MoverParameters->Dim };
|
glm::mat3 lightcam;
|
||||||
auto const up{ static_cast<glm::dvec3>( scenelight.owner->VectorUp() ) };
|
glm::mat4 depthproj;
|
||||||
auto const size{ static_cast<float>( std::max( ownerdimensions.W, ownerdimensions.H ) * 1.0 ) }; // ensure square ratio
|
float nearplane, fieldofview;
|
||||||
auto const cone{ 5.f };
|
Headlight_Projector( scenelight, projector, lightcam, depthproj, nearplane, fieldofview );
|
||||||
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;
|
|
||||||
// calculate world->headlight space projection matrix
|
// 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 =
|
light->headlight_projection =
|
||||||
coordmove
|
coordmove
|
||||||
* depthproj
|
* depthproj
|
||||||
* glm::translate(
|
* glm::translate(
|
||||||
glm::mat4{ lightcam },
|
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 ) };
|
* glm::mat4{ glm::inverse( worldcam ) };
|
||||||
}
|
}
|
||||||
// headlights weights
|
// headlights weights
|
||||||
light->headlight_weights = { scenelight.state, 0.f };
|
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;
|
++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);
|
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)
|
void opengl46_renderer::Update_Clusters(viewport_config &vp, glm::ivec2 const &Targetsize)
|
||||||
{
|
{
|
||||||
glDebug("Update_Clusters");
|
glDebug("Update_Clusters");
|
||||||
|
|||||||
@@ -308,6 +308,14 @@ class opengl46_renderer : public gfx_renderer {
|
|||||||
void Render_lamp_shadows();
|
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
|
// 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);
|
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);
|
bool Render(world_environment *Environment);
|
||||||
void Render(scene::basic_region *Region);
|
void Render(scene::basic_region *Region);
|
||||||
void Render(section_sequence::iterator First, section_sequence::iterator Last);
|
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
|
// 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_lampshadowvehicles;
|
||||||
std::vector<TDynamicObject *> m_lampshadowslot;
|
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;
|
double m_precipitationrotation;
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ uniform sampler2D headlightmap;
|
|||||||
layout(binding = LOCAL_SHADOW_TEX) uniform samplerCubeArrayShadow local_shadowmap;
|
layout(binding = LOCAL_SHADOW_TEX) uniform samplerCubeArrayShadow local_shadowmap;
|
||||||
// the same for the lamps of scenery models, the lights with linear 1
|
// the same for the lamps of scenery models, the lights with linear 1
|
||||||
layout(binding = LOCAL_SHADOW_LAMPS_TEX) uniform samplerCubeArrayShadow local_shadowmap_lamps;
|
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 <envmapping.glsl>
|
||||||
#include <conversion.glsl>
|
#include <conversion.glsl>
|
||||||
@@ -198,6 +200,52 @@ vec2 calc_dir_light(light_s light, vec3 fragnormal)
|
|||||||
return calc_light(light_dir, 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
|
// 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
|
// 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
|
// 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;
|
vec2 dbeamdy = (dheadlightposdy.xy - beamcoords * dheadlightposdy.w) / headlightpos.w;
|
||||||
vec3 lights = textureGrad(headlightmap, beamcoords, dbeamdx, dbeamdy).rgb * light.headlight_weights.rgb;
|
vec3 lights = textureGrad(headlightmap, beamcoords, dbeamdx, dbeamdy).rgb * light.headlight_weights.rgb;
|
||||||
float lightintensity = max(max(lights.r, lights.g), lights.b);
|
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;
|
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
|
// 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 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);
|
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));
|
vec2 rotation = vec2(cos(angle), sin(angle));
|
||||||
float spread = texel * radius;
|
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;
|
int count = (radius > 1.5) ? 8 : 4;
|
||||||
float shadow = 0.0;
|
float shadow = 0.0;
|
||||||
for (int i = 0; i < count; i++)
|
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);
|
shadow += local_light_shadow_tap(position + side * offset.x + up * offset.y, layer, light.headlight_weights.y, bias, lamps);
|
||||||
}
|
}
|
||||||
return shadow / float(count);
|
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 ); }
|
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.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.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; }
|
else { return false; }
|
||||||
|
|
||||||
return true;
|
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.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.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.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.enabled", python_enabled );
|
||||||
export_as_text( Output, "python.threadedupload", python_threadedupload );
|
export_as_text( Output, "python.threadedupload", python_threadedupload );
|
||||||
export_as_text( Output, "python.uploadmain", python_uploadmain );
|
export_as_text( Output, "python.uploadmain", python_uploadmain );
|
||||||
|
|||||||
@@ -357,6 +357,11 @@ struct global_settings {
|
|||||||
int gfx_lights_shadows_lamps_size { 512 };
|
int gfx_lights_shadows_lamps_size { 512 };
|
||||||
float gfx_lights_shadows_lamps_range { 100.f };
|
float gfx_lights_shadows_lamps_range { 100.f };
|
||||||
float gfx_lights_shadows_vehicles_range { 50.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_fontsize = 13.0f;
|
||||||
float ui_scale = 1.0f;
|
float ui_scale = 1.0f;
|
||||||
|
|||||||
Reference in New Issue
Block a user