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

Cast shadows of the cab lamp from the cab in the GL 4.6 renderer

This commit is contained in:
2026-10-16 02:43:27 +02:00
parent 4d3a401aae
commit 06d7b9e9f0
15 changed files with 568 additions and 8 deletions

View File

@@ -333,8 +333,10 @@ void opengl46_local_lights::gather_cab_lamp(gfx::geometrybank_manager const &Geo
auto const lamp{glm::vec3{bounds.center.x, std::max(bounds.center.y, bounds.center.y + bounds.extent.y - 0.25f), bounds.center.z}};
m_cablamp = light{};
m_cablamp.position = vehicle->GetWorldPosition(glm::dvec3{lamp});
m_cablampvehicle = vehicle;
m_cablamplocal = lamp;
m_cablamp.color = to_linear(vehicle->InteriorLight) * (level * Global.gfx_lights_cab_intensity);
// within the cab: with no shadows the lamp would light the track below and the body of the vehicle around it
// within the cab: without shadows (gfx.lights.shadows) the lamp would light the track below and the body of the vehicle around it
m_cablamp.range = std::clamp(glm::length(bounds.extent) * 1.25f, 2.5f, 8.f);
m_cablampon = (max_component(m_cablamp.color) > 0.f);
}

View File

@@ -69,6 +69,15 @@ class opengl46_local_lights
{
return m_cablampon ? &m_cablamp : nullptr;
}
// the vehicle of the cab lamp and the position of the lamp in the space of its cab model (mdKabina), when the lamp is on
TDynamicObject const *cab_lamp_vehicle() const
{
return m_cablampon ? m_cablampvehicle : nullptr;
}
glm::vec3 const &cab_lamp_local() const
{
return m_cablamplocal;
}
// counts of the last pass which wrote the lights to the light buffer: placed, and left out for the lack of room
void placed(std::uint32_t const Placed, std::uint32_t const Dropped)
{
@@ -110,6 +119,8 @@ class opengl46_local_lights
std::vector<light> m_lights;
light m_cablamp;
bool m_cablampon{false};
TDynamicObject const *m_cablampvehicle{nullptr};
glm::vec3 m_cablamplocal{0.f};
std::unordered_map<TModel3d const *, bool> m_modellights; // whether the model has light points
std::unordered_map<scene::basic_cell const *, cell_data> m_cells;
std::unordered_map<TSubModel const *, section_data> m_sections; // compartments, by their submodel

View File

@@ -0,0 +1,223 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
#include "stdafx.h"
#include "rendering/opengl46/opengl46localshadows.h"
#include "gl/framebuffer.h"
#include "gl/ubo.h"
#include "utilities/Globals.h"
#include "utilities/Logs.h"
#include "utilities/utilities.h"
namespace
{
// near plane of the maps, metres. surfaces closer to the lamp (its own fitting) cast no shadow
float const SHADOW_NEAR{0.15f};
// uniform locations of shadowcube.geom
GLint const NEAR_LOCATION{0};
GLint const LAYER_LOCATION{1};
// layer of the map of the cab lamp in the array
int const CAB_SLOT{0};
} // namespace
opengl46_local_shadows::~opengl46_local_shadows()
{
release();
if (m_empty != 0)
glDeleteTextures(1, &m_empty);
}
void opengl46_local_shadows::init()
{
gl::shader vertex("shadowcube.vert");
gl::shader geometry("shadowcube.geom");
{
gl::shader fragment("shadowmap.frag");
m_program = std::make_unique<gl::program>(std::vector<std::reference_wrapper<const gl::shader>>{vertex, geometry, fragment});
}
{
gl::shader fragment("alphashadowmap.frag");
m_alpha_program = std::make_unique<gl::program>(std::vector<std::reference_wrapper<const gl::shader>>{vertex, geometry, fragment});
}
glProgramUniform1f(*m_program, NEAR_LOCATION, SHADOW_NEAR);
glProgramUniform1f(*m_alpha_program, NEAR_LOCATION, SHADOW_NEAR);
glProgramUniform1i(*m_program, LAYER_LOCATION, CAB_SLOT * 6);
glProgramUniform1i(*m_alpha_program, LAYER_LOCATION, CAB_SLOT * 6);
// a shadow sampler with no depth texture to read from is undefined, also where the shading doesn't read it
glCreateTextures(GL_TEXTURE_CUBE_MAP_ARRAY, 1, &m_empty);
glTextureStorage3D(m_empty, 1, GL_DEPTH_COMPONENT32F, 1, 1, 6);
glTextureParameteri(m_empty, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTextureParameteri(m_empty, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTextureParameteri(m_empty, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE);
glTextureParameteri(m_empty, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL);
float const farthest{0.f};
glClearTexImage(m_empty, 0, GL_DEPTH_COMPONENT, GL_FLOAT, &farthest);
}
opengl46_local_shadows::level_setup opengl46_local_shadows::setup(int const Level)
{
switch (Level)
{
case 1:
return {512, 4, 0.f};
case 2:
return {1024, 2, 1.f};
case 3:
return {1024, 1, 2.f};
default:
return {0, 0, 0.f};
}
}
void opengl46_local_shadows::allocate(int const Size)
{
release();
if (Size <= 0)
return;
glCreateTextures(GL_TEXTURE_CUBE_MAP_ARRAY, 1, &m_texture);
glTextureStorage3D(m_texture, 1, GL_DEPTH_COMPONENT32F, Size, Size, 6 * (CAB_SLOT + 1));
// one level: the lookups need no derivatives, also in the loops over the lights the fragments of a quad don't go through together
glTextureParameteri(m_texture, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTextureParameteri(m_texture, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTextureParameteri(m_texture, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTextureParameteri(m_texture, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTextureParameteri(m_texture, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE);
// 1 (shadow) where the map holds n / d at least as large as the reference: a surface at most as far from the lamp
glTextureParameteri(m_texture, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL);
glCreateFramebuffers(1, &m_framebuffer);
// all layers: the geometry shader picks the face
glNamedFramebufferTexture(m_framebuffer, GL_DEPTH_ATTACHMENT, m_texture, 0);
glNamedFramebufferDrawBuffer(m_framebuffer, GL_NONE);
glNamedFramebufferReadBuffer(m_framebuffer, GL_NONE);
if (glCheckNamedFramebufferStatus(m_framebuffer, GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
{
ErrorLog("lamp shadow map framebuffer setup failed, the lamps have no shadows");
release();
Global.gfx_lights_shadows = 0;
return;
}
m_size = Size;
WriteLog("lamp shadow maps: " + std::to_string(Size) + " x " + std::to_string(Size) + " cube");
}
void opengl46_local_shadows::release()
{
if (m_framebuffer != 0)
glDeleteFramebuffers(1, &m_framebuffer);
if (m_texture != 0)
glDeleteTextures(1, &m_texture);
m_framebuffer = 0;
m_texture = 0;
m_size = 0;
m_valid = false;
}
void opengl46_local_shadows::bind()
{
glBindTextureUnit(gl::LOCAL_SHADOW_TEX, (m_texture != 0) ? m_texture : m_empty);
}
bool opengl46_local_shadows::update_wanted(TDynamicObject const *Vehicle, TModel3d const *Cab, glm::vec3 const &Lamp, unsigned int const Framestamp)
{
auto const level{std::clamp(Global.gfx_lights_shadows, 0, 3)};
if (level != m_level)
{
m_level = level;
auto const size{setup(level).size};
if (size != m_size)
allocate(size);
m_valid = false;
}
if ((m_size == 0) || (Vehicle == nullptr) || (Cab == nullptr))
{
// with the lamp off nothing reads the map; once it is on again the cab may have changed
m_valid = false;
return false;
}
if ((false == m_valid) || (Vehicle != m_vehicle) || (Cab != m_cab) || (Lamp != m_lamp))
{
m_valid = false;
}
// the moving parts of the cab are followed every few frames
if (m_valid && (Framestamp - m_drawnframe < setup(level).interval))
{
return false;
}
m_vehicle = Vehicle;
m_cab = Cab;
m_lamp = Lamp;
m_drawnframe = Framestamp;
return true;
}
void opengl46_local_shadows::begin()
{
m_cpustart = std::chrono::steady_clock::now();
m_timer.begin();
// the framebuffer is bound past the cache of gl::framebuffer, which end() resets
gl::framebuffer::unbind();
glBindFramebuffer(GL_FRAMEBUFFER, m_framebuffer);
glViewport(0, 0, m_size, m_size);
glDisable(GL_BLEND);
glDepthMask(GL_TRUE);
glEnable(GL_DEPTH_TEST);
// one sided walls of the cab face the lamp, they are to cast shadows as well
glDisable(GL_CULL_FACE);
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
float const farthest{0.f};
glClearNamedFramebufferfv(m_framebuffer, GL_DEPTH, 0, &farthest);
}
void opengl46_local_shadows::end(std::uint32_t const Drawcalls)
{
glEnable(GL_CULL_FACE);
gl::framebuffer::unbind();
m_timer.end();
m_valid = true;
m_drawcalls = Drawcalls;
++m_updatecount;
auto const elapsed{std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - m_cpustart).count()};
m_cputime = (m_cputime == 0.0 ? elapsed : m_cputime * 0.9 + elapsed * 0.1);
}
glm::vec4 opengl46_local_shadows::weights() const
{
// a face spans twice the distance from the lamp
return glm::vec4{static_cast<float>(CAB_SLOT), SHADOW_NEAR, 2.f / static_cast<float>(std::max(m_size, 1)), setup(m_level).radius};
}
std::string opengl46_local_shadows::info_times()
{
std::string text;
if (auto const time = m_timer.result())
text += " lamp shadows: " + to_string(static_cast<double>(*time) / 1000000.0, 3) + " ms\n";
return text;
}
std::string opengl46_local_shadows::info() const
{
if (m_size == 0)
return "";
return "\nlamp shadows: " + std::to_string(m_size) + " cube, " + (m_valid ? "cab lamp" : "none") + ", " + std::to_string(m_drawcalls) + " drawcalls, cpu " + to_string(m_cputime, 2) +
" ms";
}
void opengl46_local_shadows::draw_debug_ui()
{
char const *levels[]{"no", "low (512, every 4th frame)", "medium (1024, every 2nd frame)", "high (1024, every frame, softer)"};
ImGui::Combo("lamp shadows (gfx.lights.shadows)", &Global.gfx_lights_shadows, levels, IM_ARRAYSIZE(levels));
if (m_size == 0)
return;
ImGui::Text("cube %d x %d, %s, drawn %u times, %u drawcalls, %.2f ms (cpu)", m_size, m_size, m_valid ? "cab lamp" : "no lamp", m_updatecount, m_drawcalls, m_cputime);
}

View File

@@ -0,0 +1,106 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
#pragma once
#include <array>
#include <chrono>
#include <cstdint>
#include <memory>
#include <string>
#include "gl/shader.h"
#include "gl/query.h"
class TDynamicObject;
class TModel3d;
// cube shadow maps of the lamps of the opengl 4.6 renderer (Global.gfx_lights_shadows). for now one map, of the lamp of the
// occupied cab, with the shadows cast by the cab (the model drawn by Render_cab()).
// the map is kept in the space of the cab model, centred on the lamp: it stays right while the vehicle moves and turns, and is
// drawn anew only every few frames (for the moving parts of the cab: levers, doors, wipers) or when the lamp, the cab or the
// level change. the six faces are drawn at once, by a geometry shader (shadowcube.vert/.geom), so the cab costs one draw call
// per submodel, as in the colour pass. the depth is n / d (d the distance along the axis of the face), see local_light_shadow()
// in light_common.glsl, which reads the map for the light with the layer in its headlight_weights.x
class opengl46_local_shadows
{
public:
opengl46_local_shadows() = default;
~opengl46_local_shadows();
opengl46_local_shadows(const opengl46_local_shadows &) = delete;
opengl46_local_shadows &operator=(const opengl46_local_shadows &) = delete;
// creates the programs and the stand-in map. throws gl::shader_exception
void init();
// puts the map (or the stand-in, which shadows nothing) on its texture unit, gl::LOCAL_SHADOW_TEX. once per context and frame
void bind();
// whether the map of the cab lamp is to be drawn in this frame: Vehicle and Cab the occupied vehicle and its cab model,
// Lamp the position of the lamp in the space of the cab model; Vehicle nullptr when the lamp is off. (re)makes the map to
// the size of the level when the level changed
bool update_wanted(TDynamicObject const *Vehicle, TModel3d const *Cab, glm::vec3 const &Lamp, unsigned int const Framestamp);
// sets up the drawing to the map: the framebuffer, viewport, cleared faces, the state of the faces. the geometry is to be
// drawn with program() and a modelview from the space of the model to the space of the map
void begin();
// ends the drawing to the map, leaves the default framebuffer bound and the culling of faces on
void end(std::uint32_t const Drawcalls);
// shadow program of the cube maps, of the alpha tested materials when Alpha
gl::program &program(bool const Alpha)
{
return Alpha ? *m_alpha_program : *m_program;
}
// whether the map holds the shadows of the lamp of Vehicle, the occupied vehicle
bool ready(TDynamicObject const *Vehicle) const
{
return m_valid && (Vehicle != nullptr) && (Vehicle == m_vehicle);
}
// position of the lamp the map was drawn for, in the space of the cab model
glm::vec3 const &lamp() const
{
return m_lamp;
}
// the headlight_weights of the light which reads the map: layer, near plane, size of a texel one metre from the lamp,
// radius of the filter in texels
glm::vec4 weights() const;
// lines for the gpu times and statistics of the debug panel
std::string info_times();
std::string info() const;
// controls for the debug window
void draw_debug_ui();
private:
// size, refresh interval (frames) and filter radius (texels, 0: the 2x2 filter of the hardware) of a level
struct level_setup
{
int size;
unsigned int interval;
float radius;
};
static level_setup setup(int const Level);
void allocate(int const Size);
void release();
std::unique_ptr<gl::program> m_program;
std::unique_ptr<gl::program> m_alpha_program;
GLuint m_texture{0}; // cube map array, one cube
GLuint m_empty{0}; // stand-in: a cube map array of 1x1 faces with the farthest depth
GLuint m_framebuffer{0};
int m_size{0}; // of a face of m_texture, 0 when there is none
int m_level{-1}; // level the map was made for
bool m_valid{false}; // m_texture holds the map of m_vehicle
TDynamicObject const *m_vehicle{nullptr};
TModel3d const *m_cab{nullptr};
glm::vec3 m_lamp{0.f};
unsigned int m_drawnframe{0}; // framestamp the map was last drawn in
gl::gpu_timer m_timer;
// statistics
std::uint32_t m_drawcalls{0}; // of the last drawing of the map
std::uint32_t m_updatecount{0}; // drawings of the map so far
double m_cputime{0.0}; // milliseconds, smoothed
std::chrono::steady_clock::time_point m_cpustart;
};

View File

@@ -191,6 +191,7 @@ bool opengl46_renderer::Init(GLFWwindow *Window)
m_instancecull.init();
m_exposure.init();
m_ao.init();
m_localshadows.init();
}
catch (gl::shader_exception const &e)
{
@@ -723,6 +724,7 @@ void opengl46_renderer::SwapBuffers()
m_debugtimestext += m_exposure.info_times();
m_debugtimestext += m_ao.info_times();
m_debugtimestext += m_instancecull.info_times();
m_debugtimestext += m_localshadows.info_times();
m_debugtimestext += "uilayer: " + to_string( Timer::subsystem.gfx_gui.average(), 2 ) + " ms\n";
if( DebugModeFlag )
m_debugtimestext += m_textures.info();
@@ -765,6 +767,7 @@ void opengl46_renderer::SwapBuffers()
+ " (" + std::to_string( m_lights_dropped ) + " over capacity), "
+ ( Global.gfx_lights_clustered ? "clustered" : "first " + std::to_string( std::max( Global.DynamicLightCount, 0 ) ) )
+ "\n" + m_locallights.info()
+ m_localshadows.info()
+ m_instancecull.info();
if( auto const cullstats = ( m_viewports.empty() || !( Global.gfx_lights_clustered || Global.gfx_lights_clusterheatmap ) ? std::nullopt : m_viewports.front()->clusters->stats() ) ) {
m_debugstatstext +=
@@ -813,6 +816,8 @@ void opengl46_renderer::draw_debug_ui()
}
ImGui::Separator();
m_locallights.draw_debug_ui();
ImGui::Separator();
m_localshadows.draw_debug_ui();
}
ImGui::End();
@@ -931,6 +936,8 @@ void opengl46_renderer::Render_pass(viewport_config &vp, rendermode const Mode)
setup_shadow_empty_map();
setup_env_map(nullptr);
// the cube shadow maps of the lamps, or the stand-in for them
m_localshadows.bind();
if (Global.gfx_shadowmap_enabled && vp.main)
{
@@ -959,6 +966,9 @@ void opengl46_renderer::Render_pass(viewport_config &vp, rendermode const Mode)
setup_env_map(m_env_tex.get());
}
if (vp.main)
Render_local_shadows();
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
setup_drawing(false);
@@ -2478,12 +2488,21 @@ void opengl46_renderer::Bind_Material_Shadow(material_handle const Material)
if (material.textures[0] != null_handle)
{
m_textures.bind(0, material.textures[0]);
m_alpha_shadow_shader->bind();
if (m_cubeshadows)
m_localshadows.program(true).bind();
else
m_alpha_shadow_shader->bind();
}
else if (m_cubeshadows) {
m_localshadows.program(false).bind();
}
else {
m_shadow_shader->bind();
}
}
else if (m_cubeshadows) {
m_localshadows.program(false).bind();
}
else {
m_shadow_shader->bind();
}
@@ -6138,8 +6157,22 @@ void opengl46_renderer::Update_Lights(light_array &Lights, bool const Headlights
m_cablamplit = false;
if( locallights ) {
if( auto const *lamp{ m_locallights.cab_lamp() } ) {
Write_Local_Light( light_data.lights[ light_i++ ], *lamp, camera, mv );
auto &light{ light_data.lights[ light_i++ ] };
Write_Local_Light( light, *lamp, camera, mv );
m_cablamplit = true;
auto const *vehicle{ m_locallights.cab_lamp_vehicle() };
if( m_localshadows.ready( vehicle ) ) {
// view space to the space of the map: the space of the cab model, with the lamp at the origin. the vehicle is
// where it is now, the map stays right as it moves
auto const toworld{ glm::dmat4{ glm::transpose( glm::mat3{ mv } ) } };
auto const tomap{
glm::translate( glm::dmat4{ 1.0 }, -glm::dvec3{ m_localshadows.lamp() } )
* glm::inverse( vehicle->mMatrix )
* glm::translate( glm::dmat4{ 1.0 }, camera - vehicle->GetPosition() )
* toworld };
light.headlight_projection = glm::mat4{ tomap };
light.headlight_weights = m_localshadows.weights();
}
}
}
auto const headlightsstart{light_i};
@@ -6311,7 +6344,32 @@ void opengl46_renderer::Write_Local_Light(gl::light_element &Light, opengl46_loc
Light.linear = 0.0f;
Light.quadratic = 1.0f / sq(std::max(Source.range, 0.01f));
Light.headlight_projection = glm::mat4{1.0f};
Light.headlight_weights = glm::vec4{0.0f};
// no shadow map
Light.headlight_weights = glm::vec4{-1.0f, 0.0f, 0.0f, 0.0f};
}
void opengl46_renderer::Render_local_shadows()
{
// only the light buffers with the lamps read the maps
auto const *vehicle{Global.gfx_lights_clustered ? m_locallights.cab_lamp_vehicle() : nullptr};
if (false == m_localshadows.update_wanted(vehicle, (vehicle != nullptr ? vehicle->mdKabina : nullptr), m_locallights.cab_lamp_local(), m_framestamp))
return;
glDebug("render lamp shadows");
// the cab drawn by the shadow pass, with the programs of the cube map. the camera is put at the vehicle, which Render_cab()
// places the cab relative to, and the modelview takes the cab model to the space of the map
m_renderpass.draw_mode = rendermode::shadows;
m_renderpass.pass_camera.position() = vehicle->GetPosition();
m_renderpass.draw_stats = {};
OpenGLMatrices.mode(GL_MODELVIEW);
OpenGLMatrices.load_matrix(glm::mat4{glm::translate(glm::dmat4{1.0}, -glm::dvec3{m_localshadows.lamp()}) * glm::inverse(vehicle->mMatrix)});
m_localshadows.begin();
m_cubeshadows = true;
Render_cab(vehicle, 0.0f, false);
m_cubeshadows = false;
m_localshadows.end(static_cast<std::uint32_t>(m_renderpass.draw_stats.drawcalls));
m_renderpass = m_colorpass;
glPolygonMode(GL_FRONT_AND_BACK, Global.bWireFrame ? GL_LINE : GL_FILL);
}
void opengl46_renderer::Update_Clusters(viewport_config &vp, glm::ivec2 const &Targetsize)

View File

@@ -36,6 +36,7 @@ http://mozilla.org/MPL/2.0/.
#include "rendering/opengl46/opengl46exposure.h"
#include "rendering/opengl46/opengl46ao.h"
#include "rendering/opengl46/opengl46locallights.h"
#include "rendering/opengl46/opengl46localshadows.h"
#include "rendering/opengl46/opengl46instancecull.h"
// bare-bones render controller, in lack of anything better yet
@@ -300,6 +301,8 @@ class opengl46_renderer : public gfx_renderer {
void Render_pass(viewport_config &vp, rendermode const Mode);
// creates dynamic environment cubemap
bool Render_reflections(viewport_config &vp);
// draws the cube shadow maps of the lamps which need it in this frame (the cab lamp: the cab)
void Render_local_shadows();
bool Render(world_environment *Environment);
void Render(scene::basic_region *Region);
void Render(section_sequence::iterator First, section_sequence::iterator Last);
@@ -507,6 +510,10 @@ class opengl46_renderer : public gfx_renderer {
opengl46_local_lights m_locallights;
// the last Update_Lights() placed the cab lamp in the light buffer: the cab is lit by it, not by an addition to the ambient light
bool m_cablamplit = false;
// cube shadow maps of the lamps
opengl46_local_shadows m_localshadows;
// the shadow pass draws to a cube shadow map, with the programs of m_localshadows
bool m_cubeshadows = false;
double m_precipitationrotation;