mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-28 18:07:29 +01:00
Cast shadows of vehicle headlights from the scene in their beam in the GL 4.6 renderer
This commit is contained in:
@@ -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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user