17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-11-07 14:41:18 +01: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

@@ -338,6 +338,7 @@ if (WITH_OPENGL_MODERN)
"rendering/opengl46/opengl46exposure.cpp" "rendering/opengl46/opengl46exposure.cpp"
"rendering/opengl46/opengl46ao.cpp" "rendering/opengl46/opengl46ao.cpp"
"rendering/opengl46/opengl46locallights.cpp" "rendering/opengl46/opengl46locallights.cpp"
"rendering/opengl46/opengl46localshadows.cpp"
"rendering/opengl46/opengl46instancecull.cpp" "rendering/opengl46/opengl46instancecull.cpp"
"rendering/opengl46/opengl46precipitation.cpp" "rendering/opengl46/opengl46precipitation.cpp"
"rendering/opengl46/opengl46particles.cpp" "rendering/opengl46/opengl46particles.cpp"

View File

@@ -116,8 +116,22 @@ scales can also be changed in the **Light clusters** debug window.
which bounces around the cab (`gfx.lights.cab.bounce`), and the sun shadows stay. which bounces around the cab (`gfx.lights.cab.bounce`), and the sun shadows stay.
The lamps of models and compartments fade in from the light level at which scenery lamps switch on (`ls_Dark`) to full The lamps of models and compartments fade in from the light level at which scenery lamps switch on (`ls_Dark`) to full
darkness; in daylight they are not gathered at all. None of these lights casts shadows yet, so they also light what is darkness; in daylight they are not gathered at all. Of these lights only the cab lamp casts shadows (see below); the others
behind walls within their reach; that reach is kept short for compartments and the cab. also light what is behind walls within their reach, which is kept short for compartments.
**Shadows of the cab lamp** (`gfx.lights.shadows`): the cab casts shadows in the light of its lamp, the desk, the seats,
the levers and the walls (light leaves the cab only through the windows, glass doesn't stop it). They come from a cube
shadow map around the lamp, drawn with the opaque parts of the cab model only. The map is kept in the space of the cab, so
it stays right as the vehicle moves; it is drawn anew every few frames, for the moving parts of the cab, and at once when
the lamp turns on or the cab changes. All six faces are drawn in one go, so drawing it costs about as much as drawing the
cab once more. Nothing is drawn with the lamp off (in the daylight it is). The level:
| Level | Map | Drawn | Edges |
|---|---|---|---|
| `no` (0) | none | never | lamp without shadows, as before |
| `low` (1) | 512 x 512 per face, 6 MB | every 4th frame | hard (2x2 filter) |
| `medium` (2, default) | 1024 x 1024 per face, 24 MB | every 2nd frame | soft, 4 samples |
| `high` (3) | 1024 x 1024 per face, 24 MB | every frame | softer, 8 samples |
| Entry | Default | Meaning | | Entry | Default | Meaning |
|---|---|---| |---|---|---|
@@ -129,6 +143,7 @@ behind walls within their reach; that reach is kept short for compartments and t
| `gfx.lights.cab.intensity` | `1.5` | brightness of the cab lamp | | `gfx.lights.cab.intensity` | `1.5` | brightness of the cab lamp |
| `gfx.lights.cab.bounce` | `0.3` | 0..1: share of the former ambient light of the cab lamp kept with the lamp on | | `gfx.lights.cab.bounce` | `0.3` | 0..1: share of the former ambient light of the cab lamp kept with the lamp on |
| `gfx.lights.range` | `300` | distance from the camera the lamps of models and vehicles are gathered from, metres | | `gfx.lights.range` | `300` | distance from the camera the lamps of models and vehicles are gathered from, metres |
| `gfx.lights.shadows` | `medium` | `no`/`low`/`medium`/`high` (or 0..3): shadows of the cab lamp, see above |
### Changed option ### Changed option
@@ -165,7 +180,9 @@ editor goes through the first `dynamiclights` headlights, the reflections throug
windows): the switch for clustered lights, the heat map, the grid state and the same counters, and the switches and windows): the switch for clustered lights, the heat map, the grid state and the same counters, and the switches and
scales of the lamps with their counts. Shift+F12 shows the lamps in the `local lights:` row: how many were gathered (from scales of the lamps with their counts. Shift+F12 shows the lamps in the `local lights:` row: how many were gathered (from
scenery models, vehicles and compartments, and the cab lamp), how many were placed in the light buffer of the main view scenery models, vehicles and compartments, and the cab lamp), how many were placed in the light buffer of the main view
(those that may light something in view) and did not fit, and the cpu time of gathering them. (those that may light something in view) and did not fit, and the cpu time of gathering them. The `lamp shadows:` row
gives the size of the cube map, whether it holds the cab lamp, the draw calls and cpu time of its last drawing, and the
timings the GPU time of drawing it (`lamp shadows:`); the level can be changed in the window.
- **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),

View File

@@ -56,6 +56,7 @@ void gl::glsl_common_setup()
"#define ATMOSPHERE_MULTISCATTERING_UNIT " + std::to_string(ATMOSPHERE_MULTISCATTERING_UNIT) + "\n" + "#define ATMOSPHERE_MULTISCATTERING_UNIT " + std::to_string(ATMOSPHERE_MULTISCATTERING_UNIT) + "\n" +
"#define ATMOSPHERE_SKYVIEW_UNIT " + std::to_string(ATMOSPHERE_SKYVIEW_UNIT) + "\n" + "#define ATMOSPHERE_SKYVIEW_UNIT " + std::to_string(ATMOSPHERE_SKYVIEW_UNIT) + "\n" +
"#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" +
R"STRING( R"STRING(
const uint LIGHT_SPOT = 0U; const uint LIGHT_SPOT = 0U;
const uint LIGHT_POINT = 1U; const uint LIGHT_POINT = 1U;

View File

@@ -58,6 +58,8 @@ namespace gl
const size_t ENVMAP_GGX_GROUP_SIZE = 8; const size_t ENVMAP_GGX_GROUP_SIZE = 8;
// sky-view table of the atmosphere (sky_atmosphere.frag). past the units the texture cache keeps track of, bound directly // sky-view table of the atmosphere (sky_atmosphere.frag). past the units the texture cache keeps track of, bound directly
const size_t SKY_VIEW_TEX = MAX_TEXTURES + HELPER_TEXTURES + 0; const size_t SKY_VIEW_TEX = MAX_TEXTURES + HELPER_TEXTURES + 0;
// cube shadow maps of the lamps (cube map array, depth compared; opengl46_local_shadows). bound directly, like the one above
const size_t LOCAL_SHADOW_TEX = MAX_TEXTURES + HELPER_TEXTURES + 1;
struct scene_ubs struct scene_ubs
{ {
@@ -136,6 +138,10 @@ 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.
// SPOT and POINT (lamps): view space to the space of the cube shadow map of the lamp (origin at the lamp), and
// x: layer of the cube map array the map is in, below 0 when the lamp has no shadows; y: near plane of the map;
// z: size of a texel of the map one metre from the lamp; w: radius of the filter, in texels
glm::mat4 headlight_projection; glm::mat4 headlight_projection;
glm::vec4 headlight_weights; glm::vec4 headlight_weights;
}; };

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}}; 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 = light{};
m_cablamp.position = vehicle->GetWorldPosition(glm::dvec3{lamp}); 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); 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_cablamp.range = std::clamp(glm::length(bounds.extent) * 1.25f, 2.5f, 8.f);
m_cablampon = (max_component(m_cablamp.color) > 0.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; 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 // 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) 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; std::vector<light> m_lights;
light m_cablamp; light m_cablamp;
bool m_cablampon{false}; 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<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<scene::basic_cell const *, cell_data> m_cells;
std::unordered_map<TSubModel const *, section_data> m_sections; // compartments, by their submodel 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_instancecull.init();
m_exposure.init(); m_exposure.init();
m_ao.init(); m_ao.init();
m_localshadows.init();
} }
catch (gl::shader_exception const &e) catch (gl::shader_exception const &e)
{ {
@@ -723,6 +724,7 @@ void opengl46_renderer::SwapBuffers()
m_debugtimestext += m_exposure.info_times(); m_debugtimestext += m_exposure.info_times();
m_debugtimestext += m_ao.info_times(); m_debugtimestext += m_ao.info_times();
m_debugtimestext += m_instancecull.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"; m_debugtimestext += "uilayer: " + to_string( Timer::subsystem.gfx_gui.average(), 2 ) + " ms\n";
if( DebugModeFlag ) if( DebugModeFlag )
m_debugtimestext += m_textures.info(); m_debugtimestext += m_textures.info();
@@ -765,6 +767,7 @@ void opengl46_renderer::SwapBuffers()
+ " (" + std::to_string( m_lights_dropped ) + " over capacity), " + " (" + std::to_string( m_lights_dropped ) + " over capacity), "
+ ( Global.gfx_lights_clustered ? "clustered" : "first " + std::to_string( std::max( Global.DynamicLightCount, 0 ) ) ) + ( Global.gfx_lights_clustered ? "clustered" : "first " + std::to_string( std::max( Global.DynamicLightCount, 0 ) ) )
+ "\n" + m_locallights.info() + "\n" + m_locallights.info()
+ m_localshadows.info()
+ m_instancecull.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() ) ) { if( auto const cullstats = ( m_viewports.empty() || !( Global.gfx_lights_clustered || Global.gfx_lights_clusterheatmap ) ? std::nullopt : m_viewports.front()->clusters->stats() ) ) {
m_debugstatstext += m_debugstatstext +=
@@ -813,6 +816,8 @@ void opengl46_renderer::draw_debug_ui()
} }
ImGui::Separator(); ImGui::Separator();
m_locallights.draw_debug_ui(); m_locallights.draw_debug_ui();
ImGui::Separator();
m_localshadows.draw_debug_ui();
} }
ImGui::End(); ImGui::End();
@@ -931,6 +936,8 @@ void opengl46_renderer::Render_pass(viewport_config &vp, rendermode const Mode)
setup_shadow_empty_map(); setup_shadow_empty_map();
setup_env_map(nullptr); 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) 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()); setup_env_map(m_env_tex.get());
} }
if (vp.main)
Render_local_shadows();
glClearColor(0.0f, 0.0f, 0.0f, 0.0f); glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
setup_drawing(false); setup_drawing(false);
@@ -2478,12 +2488,21 @@ void opengl46_renderer::Bind_Material_Shadow(material_handle const Material)
if (material.textures[0] != null_handle) if (material.textures[0] != null_handle)
{ {
m_textures.bind(0, material.textures[0]); m_textures.bind(0, material.textures[0]);
if (m_cubeshadows)
m_localshadows.program(true).bind();
else
m_alpha_shadow_shader->bind(); m_alpha_shadow_shader->bind();
} }
else if (m_cubeshadows) {
m_localshadows.program(false).bind();
}
else { else {
m_shadow_shader->bind(); m_shadow_shader->bind();
} }
} }
else if (m_cubeshadows) {
m_localshadows.program(false).bind();
}
else { else {
m_shadow_shader->bind(); m_shadow_shader->bind();
} }
@@ -6138,8 +6157,22 @@ void opengl46_renderer::Update_Lights(light_array &Lights, bool const Headlights
m_cablamplit = false; m_cablamplit = false;
if( locallights ) { if( locallights ) {
if( auto const *lamp{ m_locallights.cab_lamp() } ) { 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; 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}; 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.linear = 0.0f;
Light.quadratic = 1.0f / sq(std::max(Source.range, 0.01f)); Light.quadratic = 1.0f / sq(std::max(Source.range, 0.01f));
Light.headlight_projection = glm::mat4{1.0f}; 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) 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/opengl46exposure.h"
#include "rendering/opengl46/opengl46ao.h" #include "rendering/opengl46/opengl46ao.h"
#include "rendering/opengl46/opengl46locallights.h" #include "rendering/opengl46/opengl46locallights.h"
#include "rendering/opengl46/opengl46localshadows.h"
#include "rendering/opengl46/opengl46instancecull.h" #include "rendering/opengl46/opengl46instancecull.h"
// bare-bones render controller, in lack of anything better yet // 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); void Render_pass(viewport_config &vp, rendermode const Mode);
// creates dynamic environment cubemap // creates dynamic environment cubemap
bool Render_reflections(viewport_config &vp); 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); 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);
@@ -507,6 +510,10 @@ class opengl46_renderer : public gfx_renderer {
opengl46_local_lights m_locallights; 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 // 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; 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; double m_precipitationrotation;

View File

@@ -2,6 +2,8 @@
uniform sampler2DArrayShadow shadowmap; uniform sampler2DArrayShadow shadowmap;
#endif #endif
uniform sampler2D headlightmap; uniform sampler2D headlightmap;
// cube shadow maps of the lamps, see gl::LOCAL_SHADOW_TEX and opengl46_local_shadows
layout(binding = LOCAL_SHADOW_TEX) uniform samplerCubeArrayShadow local_shadowmap;
#include <envmapping.glsl> #include <envmapping.glsl>
#include <conversion.glsl> #include <conversion.glsl>
@@ -232,10 +234,58 @@ vec2 calc_headlights(light_s light, vec3 fragnormal, vec3 dpdx, vec3 dpdy)
// closest distance (squared) the light of a lamp is computed for: nearer the inverse square would grow without a limit // closest distance (squared) the light of a lamp is computed for: nearer the inverse square would grow without a limit
const float LOCAL_LIGHT_MIN_DISTANCE2 = 0.01; const float LOCAL_LIGHT_MIN_DISTANCE2 = 0.01;
// one comparison with the cube shadow map at the point of the space of the map, moved towards the lamp by Bias (metres)
float local_light_shadow_tap(vec3 position, float layer, float near, float bias)
{
vec3 axes = abs(position);
float major = max(axes.x, max(axes.y, axes.z));
return texture(local_shadowmap, vec4(position, layer), near / max(major - bias, near));
}
// share of the light of a lamp with a cube shadow map (headlight_weights.x is its layer) which reaches the fragment, 0..1.
// the map holds n / d of the closest surface, d the distance along the axis of the face, n the near plane: the farther, the
// smaller (as the depth of the scene, reverse z). the comparison gives 1 where the map holds a value at least as large as
// the reference, i.e. a surface at most as far: in shadow
float local_light_shadow(light_s light, vec3 fragnormal)
{
// the space of the map: origin at the lamp, axes of the model the shadows are cast by
vec3 position = (light.headlight_projection * vec4(f_pos.xyz, 1.0)).xyz;
vec3 normal = normalize(mat3(light.headlight_projection) * fragnormal);
float distance = length(position);
// a texel of the map is as large as this here. the point is moved along the normal by about a texel, which keeps a surface
// from shadowing itself at grazing angles, and the reference is moved towards the lamp by a part of a texel
float texel = light.headlight_weights.z * distance;
vec3 tolamp = -position / max(distance, 1e-4);
float NdotL = clamp(dot(normal, tolamp), 0.0, 1.0);
position += normal * (texel * (0.5 + 1.5 * (1.0 - NdotL)));
float bias = texel * 1.5;
float layer = light.headlight_weights.x;
float radius = light.headlight_weights.w;
if (radius <= 0.0)
return local_light_shadow_tap(position, layer, light.headlight_weights.y, bias);
// a disc of samples in the plane of the surface, each compared with its own distance: points of the surface itself, which
// samples spread across the direction to the lamp would not be at grazing angles. rotated per pixel to turn the banding into noise
vec3 side = normalize(cross(normal, (abs(normal.y) < 0.99) ? vec3(0.0, 1.0, 0.0) : vec3(1.0, 0.0, 0.0)));
vec3 up = cross(normal, side);
float angle = 6.2831853 * fract(52.9829189 * fract(dot(gl_FragCoord.xy, vec2(0.06711056, 0.00583715))));
vec2 rotation = vec2(cos(angle), sin(angle));
float spread = texel * radius;
const vec2 disc[8] = vec2[](vec2(-0.326, -0.406), vec2(-0.840, -0.074), vec2(-0.696, 0.457), vec2(-0.203, 0.621),
vec2(0.962, -0.195), vec2(0.473, -0.480), vec2(0.519, 0.767), vec2(0.185, -0.893));
int count = (radius > 1.5) ? 8 : 4;
float shadow = 0.0;
for (int i = 0; i < count; i++)
{
vec2 offset = vec2(disc[i].x * rotation.x - disc[i].y * rotation.y, disc[i].x * rotation.y + disc[i].y * rotation.x) * spread;
shadow += local_light_shadow_tap(position + side * offset.x + up * offset.y, layer, light.headlight_weights.y, bias);
}
return shadow / float(count);
}
// light of a lamp (point or spot light: the lamps of models and vehicles, the cab lamp) reaching the fragment, x the diffuse // light of a lamp (point or spot light: the lamps of models and vehicles, the cab lamp) reaching the fragment, x the diffuse
// and y the specular part. it falls off with the square of the distance, and smoothly to nothing at the range of the light, // and y the specular part. it falls off with the square of the distance, and smoothly to nothing at the range of the light,
// 1 / sqrt(quadratic), past which the culling of the clusters does not take it. a spot light fades from its inner cone to the // 1 / sqrt(quadratic), past which the culling of the clusters does not take it. a spot light fades from its inner cone to the
// outer one // outer one. a lamp with a shadow map is held back by what is between it and the fragment
vec2 calc_local_light(light_s light, vec3 fragnormal) vec2 calc_local_light(light_s light, vec3 fragnormal)
{ {
vec3 tolight = light.pos - f_pos.xyz; vec3 tolight = light.pos - f_pos.xyz;
@@ -245,6 +295,8 @@ vec2 calc_local_light(light_s light, vec3 fragnormal)
float atten = edge * edge / max(distance2, LOCAL_LIGHT_MIN_DISTANCE2); float atten = edge * edge / max(distance2, LOCAL_LIGHT_MIN_DISTANCE2);
if (light.type == LIGHT_SPOT) if (light.type == LIGHT_SPOT)
atten *= smoothstep(light.out_cutoff, light.in_cutoff, dot(-light_dir, normalize(light.dir))); atten *= smoothstep(light.out_cutoff, light.in_cutoff, dot(-light_dir, normalize(light.dir)));
if ((atten > 0.0) && (light.headlight_weights.x >= 0.0) && (dot(fragnormal, light_dir) > 0.0))
atten *= 1.0 - local_light_shadow(light, fragnormal);
if (atten <= 0.0) if (atten <= 0.0)
return vec2(0.0); return vec2(0.0);
vec2 part = calc_light(light_dir, fragnormal); vec2 part = calc_light(light_dir, fragnormal);

47
shaders/shadowcube.geom Normal file
View File

@@ -0,0 +1,47 @@
// draws the geometry into the six faces of a cube shadow map at once, one invocation per face. the input is in the space of the
// map, with the lamp at the origin. the faces go in the order of the faces of a cube map (+x, -x, +y, -y, +z, -z), each looks the
// way glm::lookAt() with the usual up vectors of cube maps sets it, with a 90 degree projection of infinite reach and reversed
// depth like the one of the scene: the depth is near / d, d the distance along the axis of the face
layout(triangles, invocations = 6) in;
layout(triangle_strip, max_vertices = 3) out;
in vec3 g_pos[];
in vec2 g_coord[];
out vec2 f_coord;
// near plane of the map, metres: closer surfaces (the lamp itself) cast no shadows
layout(location = 0) uniform float cube_near;
// layer of the cube map array the first face of the map is in
layout(location = 1) uniform int cube_layer;
// right, up and forward axes of the faces
const vec3 face_right[6] = vec3[](vec3(0.0, 0.0, -1.0), vec3(0.0, 0.0, 1.0), vec3(1.0, 0.0, 0.0), vec3(1.0, 0.0, 0.0), vec3(1.0, 0.0, 0.0), vec3(-1.0, 0.0, 0.0));
const vec3 face_up[6] = vec3[](vec3(0.0, -1.0, 0.0), vec3(0.0, -1.0, 0.0), vec3(0.0, 0.0, 1.0), vec3(0.0, 0.0, -1.0), vec3(0.0, -1.0, 0.0), vec3(0.0, -1.0, 0.0));
const vec3 face_forward[6] = vec3[](vec3(1.0, 0.0, 0.0), vec3(-1.0, 0.0, 0.0), vec3(0.0, 1.0, 0.0), vec3(0.0, -1.0, 0.0), vec3(0.0, 0.0, 1.0), vec3(0.0, 0.0, -1.0));
void main()
{
int face = gl_InvocationID;
vec4 clip[3];
for (int i = 0; i < 3; i++)
clip[i] = vec4(dot(g_pos[i], face_right[face]), dot(g_pos[i], face_up[face]), cube_near, dot(g_pos[i], face_forward[face]));
// triangles wholly outside of the face are not sent further
if ((clip[0].w < cube_near) && (clip[1].w < cube_near) && (clip[2].w < cube_near))
return;
if ((clip[0].x > clip[0].w) && (clip[1].x > clip[1].w) && (clip[2].x > clip[2].w))
return;
if ((clip[0].x < -clip[0].w) && (clip[1].x < -clip[1].w) && (clip[2].x < -clip[2].w))
return;
if ((clip[0].y > clip[0].w) && (clip[1].y > clip[1].w) && (clip[2].y > clip[2].w))
return;
if ((clip[0].y < -clip[0].w) && (clip[1].y < -clip[1].w) && (clip[2].y < -clip[2].w))
return;
for (int i = 0; i < 3; i++)
{
gl_Layer = cube_layer + face;
gl_Position = clip[i];
f_coord = g_coord[i];
EmitVertex();
}
EndPrimitive();
}

15
shaders/shadowcube.vert Normal file
View File

@@ -0,0 +1,15 @@
layout(location = 0) in vec3 v_vert;
layout(location = 2) in vec2 v_coord;
// position in the space of the cube shadow map being drawn (origin at the lamp), see shadowcube.geom
out vec3 g_pos;
out vec2 g_coord;
#include <common>
void main()
{
g_pos = (effective_modelview() * vec4(v_vert, 1.0f)).xyz;
g_coord = v_coord;
gl_Position = vec4(g_pos, 1.0f);
}

View File

@@ -1606,6 +1606,16 @@ global_settings::ConfigParse_gfx_sky( cParser &Parser, std::string_view const To
else if( Token == "gfx.lights.cab.intensity" ) { readvalue( gfx_lights_cab_intensity, 0.f, 100.f ); } else if( Token == "gfx.lights.cab.intensity" ) { readvalue( gfx_lights_cab_intensity, 0.f, 100.f ); }
else if( Token == "gfx.lights.cab.bounce" ) { readvalue( gfx_lights_cab_bounce, 0.f, 1.f ); } else if( Token == "gfx.lights.cab.bounce" ) { readvalue( gfx_lights_cab_bounce, 0.f, 1.f ); }
else if( Token == "gfx.lights.range" ) { readvalue( gfx_lights_range, 0.f, 2000.f ); } else if( Token == "gfx.lights.range" ) { readvalue( gfx_lights_range, 0.f, 2000.f ); }
else if( Token == "gfx.lights.shadows" ) {
// no/low/medium/high, or the number of the level
Parser.getTokens( 1 );
std::string level;
Parser >> level;
gfx_lights_shadows =
( level == "low" || level == "1" ) ? 1 :
( level == "medium" || level == "yes" || level == "true" || level == "2" ) ? 2 :
( level == "high" || level == "3" ) ? 3 :
0; }
else { return false; } else { return false; }
return true; return true;
@@ -1881,6 +1891,7 @@ global_settings::export_as_text( std::ostream &Output ) const {
export_as_text( Output, "gfx.lights.cab.intensity", gfx_lights_cab_intensity ); export_as_text( Output, "gfx.lights.cab.intensity", gfx_lights_cab_intensity );
export_as_text( Output, "gfx.lights.cab.bounce", gfx_lights_cab_bounce ); export_as_text( Output, "gfx.lights.cab.bounce", gfx_lights_cab_bounce );
export_as_text( Output, "gfx.lights.range", gfx_lights_range ); export_as_text( Output, "gfx.lights.range", gfx_lights_range );
export_as_text( Output, "gfx.lights.shadows", std::string{ gfx_lights_shadows == 3 ? "high" : gfx_lights_shadows == 2 ? "medium" : gfx_lights_shadows == 1 ? "low" : "no" } );
export_as_text( Output, "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 );

View File

@@ -343,6 +343,9 @@ struct global_settings {
float gfx_lights_cab_intensity { 1.5f }; // brightness of the cab lamp float gfx_lights_cab_intensity { 1.5f }; // brightness of the cab lamp
float gfx_lights_cab_bounce { 0.3f }; // share of the former ambient light of the cab lamp kept, for the light which bounces around the cab float gfx_lights_cab_bounce { 0.3f }; // share of the former ambient light of the cab lamp kept, for the light which bounces around the cab
float gfx_lights_range { 300.f }; // distance from the camera the lights of models and vehicles are taken from, metres float gfx_lights_range { 300.f }; // distance from the camera the lights of models and vehicles are taken from, metres
// shadows of the lamps (cube shadow maps), 0 none, 1 low, 2 medium, 3 high; see opengl46_local_shadows. for now the lamp of the
// occupied cab, its shadows cast by the cab
int gfx_lights_shadows { 2 };
float ui_fontsize = 13.0f; float ui_fontsize = 13.0f;
float ui_scale = 1.0f; float ui_scale = 1.0f;