mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-28 13:27:30 +01:00
Fit the cascades of the sun shadows to the depth of the view
This commit is contained in:
@@ -340,6 +340,7 @@ if (WITH_OPENGL_MODERN)
|
||||
"rendering/opengl46/opengl46localshadows.cpp"
|
||||
"rendering/opengl46/opengl46instancecull.cpp"
|
||||
"rendering/opengl46/opengl46hiz.cpp"
|
||||
"rendering/opengl46/opengl46shadowfit.cpp"
|
||||
"rendering/opengl46/opengl46precipitation.cpp"
|
||||
"rendering/opengl46/opengl46particles.cpp"
|
||||
"rendering/opengl46/opengl46terrain.cpp"
|
||||
|
||||
@@ -27,6 +27,8 @@ void gl::glsl_common_setup()
|
||||
"#define INSTANCE_OFFSETS_GROUP_SIZE " + std::to_string(INSTANCE_OFFSETS_GROUP_SIZE) + "\n" +
|
||||
"#define INSTANCE_COMMAND_GROUP_SIZE " + std::to_string(INSTANCE_COMMAND_GROUP_SIZE) + "\n" +
|
||||
"#define HIZ_GROUP_SIZE " + std::to_string(HIZ_GROUP_SIZE) + "\n" +
|
||||
"#define SHADOW_FIT_GROUP_SIZE " + std::to_string(SHADOW_FIT_GROUP_SIZE) + "\n" +
|
||||
"#define SHADOW_FIT_SSBO_BINDING " + std::to_string(SHADOW_FIT_SSBO_BINDING) + "\n" +
|
||||
"#define INSTANCE_COUNTER_HEADER " + std::to_string(INSTANCE_COUNTER_HEADER) + "\n" +
|
||||
"#define INSTANCE_JOB_SLEEPERS " + std::to_string(INSTANCE_JOB_SLEEPERS) + "U\n" +
|
||||
"#define LIGHT_SSBO_BINDING " + std::to_string(LIGHT_SSBO_BINDING) + "\n" +
|
||||
@@ -264,6 +266,8 @@ void gl::glsl_common_setup()
|
||||
// contact shadows, see gl::scene_ubs
|
||||
vec4 contact_shadow_params;
|
||||
vec4 contact_shadow_lamps;
|
||||
// cascades of the sun shadows fitted to the depth of the view, see gl::scene_ubs
|
||||
vec4 shadow_fit;
|
||||
};
|
||||
|
||||
#ifdef STAGE_VERTEX
|
||||
|
||||
34
gl/ubo.h
34
gl/ubo.h
@@ -101,9 +101,12 @@ namespace gl
|
||||
glm::vec4 contact_shadow_params;
|
||||
// x: how many lamps and headlights a pixel traces the rays to (0: only the sun), yzw unused
|
||||
glm::vec4 contact_shadow_lamps;
|
||||
// x: 1 when the cascades of the sun shadows are fitted to the depth of the view (gfx.shadow.sdsm, opengl46_shadow_fit):
|
||||
// a point outside the map of its cascade goes to the next cascade whose map holds it. yzw unused
|
||||
glm::vec4 shadow_fit;
|
||||
};
|
||||
|
||||
static_assert(sizeof(scene_ubs) == 432 + 80 * MAX_HEADLIGHT_SHADOWS, "bad size of ubs");
|
||||
static_assert(sizeof(scene_ubs) == 448 + 80 * MAX_HEADLIGHT_SHADOWS, "bad size of ubs");
|
||||
|
||||
const size_t MAX_PARAMS = 3;
|
||||
|
||||
@@ -487,6 +490,12 @@ namespace gl
|
||||
// work group of the passes which make the depth pyramid of the occlusion test (hiz_build.comp), 8x8
|
||||
const GLuint HIZ_GROUP_SIZE = 8;
|
||||
|
||||
// the cascades of the sun shadows fitted to the depth of the main view (opengl46_shadow_fit, shadow_fit_*.comp): work group
|
||||
// of its passes, 16x16 pixels, and the binding point of its buffer. the point is one of the instance culling, which binds
|
||||
// its buffers before every use; the fit binds its own before its passes
|
||||
const GLuint SHADOW_FIT_GROUP_SIZE = 16;
|
||||
const GLuint SHADOW_FIT_SSBO_BINDING = 9;
|
||||
|
||||
// layout std430: a bucket of a scene cell queued in the pass (GLSL: instance_cull_job)
|
||||
struct instance_cull_job
|
||||
{
|
||||
@@ -539,5 +548,28 @@ namespace gl
|
||||
|
||||
static_assert(sizeof(instance_draw_command) == 32, "bad size of instance_draw_command");
|
||||
|
||||
// layout std430: what the passes of the cascades fitted to the depth of the view leave for the cpu (GLSL: shadow_fit_ssbo).
|
||||
// the cpu writes it before the passes: the number of the measurement, no pixels, the nearest distance infinite, the bounds
|
||||
// empty. the passes take the extremes with atomic operations on the bits: positive floats order as uints, and the bounds,
|
||||
// which can be negative, are stored in an order preserving form (shadow_fit_order() in shadow_fit_bounds.comp)
|
||||
struct shadow_fit_data
|
||||
{
|
||||
uint32_t measurement; // number given by the cpu, to find the camera and the light it was made with
|
||||
uint32_t samples; // pixels of the view which aren't the sky
|
||||
float depthmin; // nearest and farthest distance of those from the camera, metres
|
||||
float depthmax;
|
||||
float splits[4]; // distances the cascades go from and to (MAX_CASCADES + 1 of them), from the two above
|
||||
uint32_t counts[4]; // pixels taken into the bounds of each cascade (MAX_CASCADES of them)
|
||||
// per cascade the lowest x, y, z, unused, then the highest x, y, z, unused, of the pixels it takes, in metres in the
|
||||
// space of the light (its rotation only), relative to the camera
|
||||
uint32_t bounds[MAX_CASCADES * 8];
|
||||
};
|
||||
|
||||
static_assert(MAX_CASCADES + 1 <= 4, "shadow_fit_data holds the splits of at most 3 cascades");
|
||||
static_assert(offsetof(shadow_fit_data, splits) == 16, "bad layout of shadow_fit_data");
|
||||
static_assert(offsetof(shadow_fit_data, counts) == 32, "bad layout of shadow_fit_data");
|
||||
static_assert(offsetof(shadow_fit_data, bounds) == 48, "bad layout of shadow_fit_data");
|
||||
static_assert(sizeof(shadow_fit_data) == 48 + 32 * MAX_CASCADES, "bad size of shadow_fit_data");
|
||||
|
||||
#pragma pack(pop)
|
||||
}
|
||||
|
||||
@@ -239,6 +239,7 @@ bool opengl46_renderer::Init(GLFWwindow *Window)
|
||||
shadowcull.init(&m_instancecull);
|
||||
m_reflectioncull.init(&m_instancecull);
|
||||
m_hiz.init();
|
||||
m_shadowfit.init();
|
||||
m_exposure.init();
|
||||
m_ao.init();
|
||||
m_localshadows.init();
|
||||
@@ -843,6 +844,8 @@ void opengl46_renderer::SwapBuffers()
|
||||
}
|
||||
}
|
||||
m_debugtimestext += m_localshadows.info_times();
|
||||
if( Global.gfx_shadow_sdsm )
|
||||
m_debugtimestext += m_shadowfit.info_times();
|
||||
m_debugtimestext += "uilayer: " + to_string( Timer::subsystem.gfx_gui.average(), 2 ) + " ms\n";
|
||||
if( DebugModeFlag )
|
||||
m_debugtimestext += m_textures.info();
|
||||
@@ -852,6 +855,36 @@ void opengl46_renderer::SwapBuffers()
|
||||
shadowstats += shadowpass.draw_stats;
|
||||
}
|
||||
|
||||
// the cascades of the sun shadows: where they end and the size of a texel of their maps, fixed or fitted to the depth
|
||||
std::string shadowcascadestext;
|
||||
if( Global.gfx_shadowmap_enabled && ( m_shadowbuffersize > 0 ) ) {
|
||||
auto const &fit { m_shadowfitstate };
|
||||
shadowcascadestext = " sun shadow cascades to";
|
||||
for( std::size_t cascade = 0; cascade < gl::MAX_CASCADES; ++cascade ) {
|
||||
shadowcascadestext += " " + to_string( fit.splits[ cascade + 1 ], 1 );
|
||||
}
|
||||
shadowcascadestext += " m, texels";
|
||||
for( std::size_t cascade = 0; cascade < gl::MAX_CASCADES; ++cascade ) {
|
||||
shadowcascadestext += " " + to_string( 100.f * fit.drawn[ cascade ] / m_shadowbuffersize, 1 );
|
||||
}
|
||||
shadowcascadestext += " cm";
|
||||
if( Global.gfx_shadow_sdsm ) {
|
||||
if( fit.fitted ) {
|
||||
shadowcascadestext += " (whole slices";
|
||||
for( std::size_t cascade = 0; cascade < gl::MAX_CASCADES; ++cascade ) {
|
||||
shadowcascadestext += " " + to_string( 100.f * fit.slice[ cascade ] / m_shadowbuffersize, 1 );
|
||||
}
|
||||
shadowcascadestext +=
|
||||
"), fitted to pixels " + to_string( fit.depthmin, 2 ) + "-" + to_string( fit.depthmax, 1 ) + " m "
|
||||
+ std::to_string( fit.age ) + " frames ago, view turned " + to_string( fit.turn, 1 ) + " deg since";
|
||||
}
|
||||
else {
|
||||
shadowcascadestext += ", fixed: " + std::string{ fit.reason != nullptr ? fit.reason : "" };
|
||||
}
|
||||
}
|
||||
shadowcascadestext += "\n";
|
||||
}
|
||||
|
||||
m_debugstatstext =
|
||||
"triangles: " + to_string( static_cast<int>(m_geometry.primitives_count()), 7 ) + "\n"
|
||||
+ "vehicles: " + to_string( m_colorpass.draw_stats.dynamics, 7 ) + " +" + to_string( shadowstats.dynamics, 7 )
|
||||
@@ -877,6 +910,7 @@ void opengl46_renderer::SwapBuffers()
|
||||
", rays " + to_string( Global.gfx_contactshadows_length, 2 ) + " m within " + to_string( Global.gfx_contactshadows_range, 0 ) + " m\n" :
|
||||
", off in this view\n" ) :
|
||||
std::string{} )
|
||||
+ shadowcascadestext
|
||||
+ " instance buffer: " + std::to_string( m_instancestream->used() / static_cast<GLsizeiptr>( sizeof( glm::mat4 ) ) ) + " matrices, "
|
||||
+ std::to_string( m_instancestream->size() / 1024 ) + " KiB\n"
|
||||
+ " model blocks: " + std::to_string( model_ubo->updates() )
|
||||
@@ -982,6 +1016,27 @@ void opengl46_renderer::draw_debug_ui()
|
||||
}
|
||||
ImGui::End();
|
||||
|
||||
if (ImGui::Begin("Sun shadows"))
|
||||
{
|
||||
ImGui::Checkbox("cascades fitted to the depth of the view (gfx.shadow.sdsm)", &Global.gfx_shadow_sdsm);
|
||||
ImGui::SliderFloat("splits, 0 equal lengths .. 1 equal ratios (gfx.shadow.sdsm.split)", &Global.gfx_shadow_sdsm_split, 0.f, 1.f, "%.2f");
|
||||
auto const &fit{m_shadowfitstate};
|
||||
if (Global.gfx_shadow_sdsm)
|
||||
{
|
||||
if (fit.fitted)
|
||||
ImGui::Text("fitted to pixels %.2f - %.1f m, measured %u frames ago, view turned %.1f deg since", fit.depthmin, fit.depthmax, fit.age, fit.turn);
|
||||
else
|
||||
ImGui::Text("fixed cascades: %s", fit.reason != nullptr ? fit.reason : "");
|
||||
}
|
||||
if (m_shadowbuffersize > 0)
|
||||
{
|
||||
for (std::size_t cascade = 0; cascade < gl::MAX_CASCADES; ++cascade)
|
||||
ImGui::Text("cascade %d: to %.1f m, map %.1f m across (whole slice %.1f m), texel %.1f cm", static_cast<int>(cascade + 1), fit.splits[cascade + 1],
|
||||
fit.drawn[cascade], fit.slice[cascade], 100.f * fit.drawn[cascade] / m_shadowbuffersize);
|
||||
}
|
||||
}
|
||||
ImGui::End();
|
||||
|
||||
if (ImGui::Begin("Weather"))
|
||||
{
|
||||
m_weather.draw_debug_ui();
|
||||
@@ -1453,6 +1508,20 @@ if (!Global.gfx_skippipeline)
|
||||
}
|
||||
}
|
||||
|
||||
// the depth of the finished view, which the cascades of the sun shadows of the next frames are fitted to
|
||||
// (gfx.shadow.sdsm). the main framebuffer holds it resolved for the motion blur or the ambient occlusion
|
||||
if (vp.main && Global.gfx_shadow_sdsm && (Shadow_Fit_Unavailable(vp) == nullptr))
|
||||
{
|
||||
auto const resolved{(vp.main_texd != nullptr) && (Global.gfx_postfx_motionblur_enabled || (aomode != 0))};
|
||||
opengl46_shadow_fit::view const view{
|
||||
m_colorpass.pass_camera.position(),
|
||||
m_colorpass.pass_camera.projection(),
|
||||
glm::mat3{m_colorpass.pass_camera.modelview()},
|
||||
Shadow_Light_Rotation(),
|
||||
Shadow_Fixed_Splits().back()};
|
||||
m_shadowfit.measure(resolved ? vp.main_texd->id : 0, *vp.msaa_fb, vp.width, vp.height, view, m_framestamp);
|
||||
}
|
||||
|
||||
gpupart("exposure, bloom");
|
||||
// the exposure follows the main view; the other viewports use it too
|
||||
if (Global.gfx_postfx_autoexposure && vp.main)
|
||||
@@ -1559,17 +1628,13 @@ if (!Global.gfx_skippipeline)
|
||||
setup_drawing( false );
|
||||
|
||||
glViewport(0, 0, m_shadowbuffersize, m_shadowbuffersize);
|
||||
float csmstageboundaries[] = {0.0f,
|
||||
Global.shadowtune.range / 32,
|
||||
Global.shadowtune.range / 8,
|
||||
Global.shadowtune.range};
|
||||
if( Global.shadowtune.map_size > 2048 ) {
|
||||
// increase coverage if our shadow map is large enough to produce decent results
|
||||
csmstageboundaries[1] *= Global.shadowtune.map_size / 2048;
|
||||
csmstageboundaries[2] *= Global.shadowtune.map_size / 2048;
|
||||
csmstageboundaries[3] *= Global.shadowtune.map_size / 2048;
|
||||
}
|
||||
|
||||
auto csmstageboundaries { Shadow_Fixed_Splits() };
|
||||
// the cascades fitted to the depth of the view (gfx.shadow.sdsm) go between the distances of its pixels, each map
|
||||
// covering the area of the pixels its cascade takes
|
||||
std::array<std::optional<std::array<glm::vec3, 8>>, gl::MAX_CASCADES> fitareas;
|
||||
auto const fitted { Shadow_Fit( vp, csmstageboundaries, fitareas ) };
|
||||
scene_ubs.shadow_fit = glm::vec4{ fitted ? 1.f : 0.f, 0.f, 0.f, 0.f };
|
||||
m_shadowfitstate.splits = csmstageboundaries;
|
||||
|
||||
for( auto idx = 0; idx < m_shadowpass.size(); ++idx ) {
|
||||
|
||||
@@ -1581,8 +1646,12 @@ if (!Global.gfx_skippipeline)
|
||||
m_shadow_fb->attach( *m_shadow_tex, GL_DEPTH_ATTACHMENT, idx );
|
||||
m_shadow_fb->clear( GL_DEPTH_BUFFER_BIT );
|
||||
|
||||
m_shadowfitarea = ( fitareas[ idx ] ? &( *fitareas[ idx ] ) : nullptr );
|
||||
setup_pass( vp, m_renderpass, rendermode::shadows, csmstageboundaries[ idx ], csmstageboundaries[ idx + 1 ] );
|
||||
m_shadowfitarea = nullptr;
|
||||
m_shadowpass[ idx ] = m_renderpass; // store pass config for reference in other stages
|
||||
m_shadowfitstate.slice[ idx ] = m_shadowslicesize;
|
||||
m_shadowfitstate.drawn[ idx ] = m_shadowdrawnsize;
|
||||
|
||||
setup_matrices();
|
||||
scene_ubs.projection = OpenGLMatrices.data( GL_PROJECTION );
|
||||
@@ -2090,12 +2159,53 @@ void opengl46_renderer::setup_pass(viewport_config &Viewport, renderpass_config
|
||||
}
|
||||
pass_camera /= bounding_viewports_count;
|
||||
|
||||
// ...a cascade fitted to the depth of the view (gfx.shadow.sdsm) covers the area of the pixels it takes, within the
|
||||
// slice of the view. the areas are compared across the light, the depth along it stays the one of the slice...
|
||||
auto quantizationstep{0.f};
|
||||
{
|
||||
auto const lightrotation{Shadow_Light_Rotation()};
|
||||
glm::vec3 slicemin{std::numeric_limits<float>::max()}, slicemax{std::numeric_limits<float>::lowest()};
|
||||
for (auto const &point : frustumchunkshapepoints)
|
||||
{
|
||||
auto const lightpoint{lightrotation * glm::vec3{point}};
|
||||
slicemin = glm::min(slicemin, lightpoint);
|
||||
slicemax = glm::max(slicemax, lightpoint);
|
||||
}
|
||||
m_shadowslicesize = std::max(slicemax.x - slicemin.x, slicemax.y - slicemin.y);
|
||||
if (m_shadowfitarea != nullptr)
|
||||
{
|
||||
glm::vec3 areamin{std::numeric_limits<float>::max()}, areamax{std::numeric_limits<float>::lowest()};
|
||||
for (auto const &point : *m_shadowfitarea)
|
||||
{
|
||||
auto const lightpoint{lightrotation * point};
|
||||
areamin = glm::min(areamin, lightpoint);
|
||||
areamax = glm::max(areamax, lightpoint);
|
||||
}
|
||||
auto const fitmin{glm::vec2{glm::max(glm::vec2{slicemin}, glm::vec2{areamin})}};
|
||||
auto const fitmax{glm::vec2{glm::min(glm::vec2{slicemax}, glm::vec2{areamax})}};
|
||||
if ((fitmin.x < fitmax.x) && (fitmin.y < fitmax.y))
|
||||
{
|
||||
// the size changes in steps of a power of two of about 1/16 of it, so that the texels of the map keep
|
||||
// their size until the area grows or shrinks by a step
|
||||
auto const size{std::max(fitmax.x - fitmin.x, fitmax.y - fitmin.y)};
|
||||
quantizationstep = std::exp2(std::ceil(std::log2(std::max(size, 1.f) / 16.f)));
|
||||
frustumchunkshapepoints.clear();
|
||||
auto const inverserotation{glm::transpose(lightrotation)};
|
||||
for (int corner = 0; corner < 8; ++corner)
|
||||
{
|
||||
auto const lightpoint{glm::vec3{(corner & 1) ? fitmax.x : fitmin.x, (corner & 2) ? fitmax.y : fitmin.y, (corner & 4) ? slicemax.z : slicemin.z}};
|
||||
frustumchunkshapepoints.emplace_back(inverserotation * lightpoint, 1.f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ...modelview matrix: determine the centre of frustum chunk in world space...
|
||||
glm::vec3 frustumchunkmin, frustumchunkmax;
|
||||
bounding_box(frustumchunkmin, frustumchunkmax, std::begin(frustumchunkshapepoints), std::end(frustumchunkshapepoints));
|
||||
auto const frustumchunkcentre = (frustumchunkmin + frustumchunkmax) * 0.5f;
|
||||
// ...cap the vertical angle to keep shadows from getting too long...
|
||||
auto const lightvector = glm::normalize(glm::vec3{m_sunlight.direction.x, std::min(m_sunlight.direction.y, Global.gfx_shadow_angle_min), m_sunlight.direction.z});
|
||||
auto const lightvector = Shadow_Light_Vector();
|
||||
// ...place the light source at the calculated centre and setup world space light view matrix...
|
||||
camera.position() = pass_camera + glm::dvec3{frustumchunkcentre};
|
||||
viewmatrix *= glm::lookAt(camera.position(), camera.position() + glm::dvec3{lightvector}, glm::dvec3{0.f, 1.f, 0.f});
|
||||
@@ -2108,9 +2218,11 @@ void opengl46_renderer::setup_pass(viewport_config &Viewport, renderpass_config
|
||||
bounding_box(frustumchunkmin, frustumchunkmax, std::begin(frustumchunkshapepoints), std::end(frustumchunkshapepoints));
|
||||
// quantize the frustum points and add some padding, to reduce shadow shimmer on scale changes
|
||||
auto const frustumchunkdepth { zfar - ( Znear > 1.f ? Znear : 0.f ) };
|
||||
auto const quantizationstep{std::min(frustumchunkdepth, 50.f)};
|
||||
if (quantizationstep <= 0.f)
|
||||
quantizationstep = std::min(frustumchunkdepth, 50.f);
|
||||
frustumchunkmin = quantizationstep * glm::floor(frustumchunkmin * (1.f / quantizationstep));
|
||||
frustumchunkmax = quantizationstep * glm::ceil(frustumchunkmax * (1.f / quantizationstep));
|
||||
m_shadowdrawnsize = std::max(frustumchunkmax.x - frustumchunkmin.x, frustumchunkmax.y - frustumchunkmin.y);
|
||||
// ...use the dimensions to set up light projection boundaries...
|
||||
// NOTE: since we only have one cascade map stage, we extend the chunk forward/back to catch areas normally covered by other stages
|
||||
camera.projection() = ortho_projection(frustumchunkmin.x, frustumchunkmax.x, frustumchunkmin.y, frustumchunkmax.y, frustumchunkmin.z - 500.f, frustumchunkmax.z + 500.f);
|
||||
@@ -7606,6 +7718,7 @@ void opengl46_renderer::Preview_Color(TAnimModel *Instance, float const Squaredi
|
||||
// everything is in the first cascade
|
||||
scene_ubs.cascade_end[idx] = std::numeric_limits<float>::max();
|
||||
}
|
||||
scene_ubs.shadow_fit = glm::vec4{0.f};
|
||||
scene_ubo->update(scene_ubs);
|
||||
scene_ubo->bind_uniform();
|
||||
// the sun is the only light, and there's no fog
|
||||
@@ -8550,6 +8663,158 @@ bool opengl46_renderer::Resolved_Depth_Target(viewport_config &vp)
|
||||
return true;
|
||||
}
|
||||
|
||||
glm::vec3 opengl46_renderer::Shadow_Light_Vector() const
|
||||
{
|
||||
// cap the vertical angle to keep shadows from getting too long
|
||||
return glm::normalize(glm::vec3{m_sunlight.direction.x, std::min(m_sunlight.direction.y, Global.gfx_shadow_angle_min), m_sunlight.direction.z});
|
||||
}
|
||||
|
||||
glm::mat3 opengl46_renderer::Shadow_Light_Rotation() const
|
||||
{
|
||||
// the rotation of the view of the shadow pass (setup_pass())
|
||||
return glm::mat3{glm::lookAt(glm::vec3{0.f}, Shadow_Light_Vector(), glm::vec3{0.f, 1.f, 0.f})};
|
||||
}
|
||||
|
||||
std::array<float, gl::MAX_CASCADES + 1> opengl46_renderer::Shadow_Fixed_Splits() const
|
||||
{
|
||||
static_assert(gl::MAX_CASCADES == 3, "the fixed cascades are made for 3 of them");
|
||||
std::array<float, gl::MAX_CASCADES + 1> splits{0.0f, Global.shadowtune.range / 32, Global.shadowtune.range / 8, Global.shadowtune.range};
|
||||
if (Global.shadowtune.map_size > 2048)
|
||||
{
|
||||
// increase coverage if our shadow map is large enough to produce decent results
|
||||
for (std::size_t idx = 1; idx < splits.size(); ++idx)
|
||||
splits[idx] *= Global.shadowtune.map_size / 2048;
|
||||
}
|
||||
return splits;
|
||||
}
|
||||
|
||||
char const *opengl46_renderer::Shadow_Fit_Unavailable(viewport_config const &Viewport) const
|
||||
{
|
||||
if (false == Global.gfx_shadowmap_enabled)
|
||||
return "no shadow maps";
|
||||
if (Global.bWireFrame)
|
||||
return "wireframe";
|
||||
// the shadows follow the camera, the view shows another one
|
||||
if (DebugCameraFlag)
|
||||
return "debug camera";
|
||||
if (Global.gfx_skippipeline || (Viewport.msaa_fb == nullptr))
|
||||
return "no post processing";
|
||||
if (EditorModeFlag && Global.EditorOrtho)
|
||||
return "orthographic view";
|
||||
auto const shadowviews{std::count_if(m_viewports.begin(), m_viewports.end(), [](auto const &View) { return View->shadow; })};
|
||||
if ((false == Viewport.shadow) || (shadowviews != 1))
|
||||
return "the shadows cover more than one view";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool opengl46_renderer::Shadow_Fit(viewport_config const &Viewport, std::array<float, gl::MAX_CASCADES + 1> &Splits,
|
||||
std::array<std::optional<std::array<glm::vec3, 8>>, gl::MAX_CASCADES> &Areas)
|
||||
{
|
||||
// a measurement older than this many frames, made with the camera this many metres away or the view turned this many
|
||||
// degrees from the one now, isn't used
|
||||
constexpr unsigned int AGE_MAX{6};
|
||||
constexpr float SHIFT_MAX{25.f};
|
||||
constexpr float TURN_MAX{20.f};
|
||||
// metres an area grows by on each side: the vehicles move in the frames since the measurement too
|
||||
constexpr float MARGIN{0.25f};
|
||||
|
||||
auto &state{m_shadowfitstate};
|
||||
state = shadow_fit_state{};
|
||||
if (false == Global.gfx_shadow_sdsm)
|
||||
return false;
|
||||
if ((state.reason = Shadow_Fit_Unavailable(Viewport)) != nullptr)
|
||||
return false;
|
||||
auto const *latest{m_shadowfit.latest()};
|
||||
if (latest == nullptr)
|
||||
{
|
||||
state.reason = "no measurement yet";
|
||||
return false;
|
||||
}
|
||||
auto const &data{latest->data};
|
||||
auto const &source{latest->source};
|
||||
state.age = m_framestamp - latest->frame;
|
||||
state.depthmin = data.depthmin;
|
||||
state.depthmax = data.depthmax;
|
||||
if (state.age > AGE_MAX)
|
||||
{
|
||||
state.reason = "the measurement is too old";
|
||||
return false;
|
||||
}
|
||||
if (data.samples == 0)
|
||||
{
|
||||
state.reason = "only the sky in the view";
|
||||
return false;
|
||||
}
|
||||
if (source.range != Shadow_Fixed_Splits().back())
|
||||
{
|
||||
state.reason = "the range of the shadows changed";
|
||||
return false;
|
||||
}
|
||||
// the shadows of this frame are made for its colour pass
|
||||
auto const &camera{m_colorpass.pass_camera};
|
||||
auto const shift{glm::vec3{source.camera - camera.position()}};
|
||||
if (glm::length(shift) > SHIFT_MAX)
|
||||
{
|
||||
state.reason = "the camera jumped";
|
||||
return false;
|
||||
}
|
||||
auto const turnmatrix{glm::mat3{camera.modelview()} * glm::transpose(source.rotation)};
|
||||
auto const turn{std::acos(std::clamp((turnmatrix[0][0] + turnmatrix[1][1] + turnmatrix[2][2] - 1.f) * 0.5f, -1.f, 1.f))};
|
||||
state.turn = glm::degrees(turn);
|
||||
if (state.turn > TURN_MAX)
|
||||
{
|
||||
state.reason = "the view turned too fast";
|
||||
return false;
|
||||
}
|
||||
auto const &projection{camera.projection()};
|
||||
if ((std::abs(projection[0][0] - source.projection[0][0]) > 0.02f * std::abs(projection[0][0]))
|
||||
|| (std::abs(projection[1][1] - source.projection[1][1]) > 0.02f * std::abs(projection[1][1])))
|
||||
{
|
||||
state.reason = "the field of view changed";
|
||||
return false;
|
||||
}
|
||||
|
||||
// the first cascade from the camera, like the fixed ones. setup_pass() takes distances up to 1 m as shares of the far
|
||||
// end, so the others start past that
|
||||
Splits[0] = 0.f;
|
||||
for (std::size_t split = 1; split < Splits.size(); ++split)
|
||||
Splits[split] = std::max(data.splits[split], Splits[split - 1] + 1.5f);
|
||||
if (false == std::isfinite(Splits.back()))
|
||||
{
|
||||
state.reason = "invalid measurement";
|
||||
return false;
|
||||
}
|
||||
|
||||
auto const inverselight{glm::transpose(source.light)};
|
||||
// the scenery stays where it was while the camera moved, the cab and the train the camera rides with moved along. an area
|
||||
// covers both
|
||||
auto const lightshift{source.light * shift};
|
||||
auto const chord{2.f * std::sin(turn * 0.5f)};
|
||||
for (std::size_t cascade = 0; cascade < gl::MAX_CASCADES; ++cascade)
|
||||
{
|
||||
// with nothing of the view in a cascade, its map covers the whole slice
|
||||
if (data.counts[cascade] == 0)
|
||||
continue;
|
||||
auto const *bounds{&data.bounds[cascade * 8]};
|
||||
glm::vec3 low{opengl46_shadow_fit::unorder(bounds[0]), opengl46_shadow_fit::unorder(bounds[1]), opengl46_shadow_fit::unorder(bounds[2])};
|
||||
glm::vec3 high{opengl46_shadow_fit::unorder(bounds[4]), opengl46_shadow_fit::unorder(bounds[5]), opengl46_shadow_fit::unorder(bounds[6])};
|
||||
if ((false == std::isfinite(low.x + low.y + high.x + high.y)) || (low.x > high.x) || (low.y > high.y))
|
||||
continue;
|
||||
low = glm::min(low, low + lightshift);
|
||||
high = glm::max(high, high + lightshift);
|
||||
// and what came into the view as it turned, out to the reach of the cascade
|
||||
auto const reach{Splits[cascade + 1] + static_cast<float>(gl::MAX_CASCADES - cascade)};
|
||||
auto const margin{reach * chord + MARGIN + 0.01f * std::max(high.x - low.x, high.y - low.y)};
|
||||
low -= glm::vec3{margin};
|
||||
high += glm::vec3{margin};
|
||||
auto &area{Areas[cascade].emplace()};
|
||||
for (int corner = 0; corner < 8; ++corner)
|
||||
area[corner] = inverselight * glm::vec3{(corner & 1) ? high.x : low.x, (corner & 2) ? high.y : low.y, (corner & 4) ? high.z : low.z};
|
||||
}
|
||||
state.fitted = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void opengl46_renderer::Render_cluster_heatmap(viewport_config &vp, gl::framebuffer *Target, glm::ivec2 const &Targetsize)
|
||||
{
|
||||
glDebug("Render_cluster_heatmap");
|
||||
|
||||
@@ -41,6 +41,7 @@ http://mozilla.org/MPL/2.0/.
|
||||
#include "rendering/opengl46/opengl46localshadows.h"
|
||||
#include "rendering/opengl46/opengl46instancecull.h"
|
||||
#include "rendering/opengl46/opengl46hiz.h"
|
||||
#include "rendering/opengl46/opengl46shadowfit.h"
|
||||
|
||||
// bare-bones render controller, in lack of anything better yet
|
||||
class opengl46_renderer : public gfx_renderer {
|
||||
@@ -433,6 +434,19 @@ class opengl46_renderer : public gfx_renderer {
|
||||
void Setup_Contact_Shadows( viewport_config &Viewport );
|
||||
// makes the target of the resolved depth of the viewport if it isn't there yet; false if it can't be made
|
||||
bool Resolved_Depth_Target( viewport_config &Viewport );
|
||||
// the direction the sun shadows are cast in (the sun, its height capped by gfx.shadow.angle.min), and the rotation from
|
||||
// the world to the space of the light of the cascades: x and y across the maps, z along the light
|
||||
glm::vec3 Shadow_Light_Vector() const;
|
||||
glm::mat3 Shadow_Light_Rotation() const;
|
||||
// the distances the fixed cascades of the sun shadows go from and to (shadowtune)
|
||||
std::array<float, gl::MAX_CASCADES + 1> Shadow_Fixed_Splits() const;
|
||||
// why the cascades of the sun shadows can't be fitted to the depth of the viewport (gfx.shadow.sdsm), null if they can
|
||||
char const *Shadow_Fit_Unavailable( viewport_config const &Viewport ) const;
|
||||
// the cascades of the frame fitted to the latest measurement of the depth of the view (gfx.shadow.sdsm): the distances
|
||||
// they go from and to, and for each cascade the corners of the area its map is to cover, camera-relative world space
|
||||
// (none: the whole slice of the view). false: the fixed cascades. fills m_shadowfitstate
|
||||
bool Shadow_Fit( viewport_config const &Viewport, std::array<float, gl::MAX_CASCADES + 1> &Splits,
|
||||
std::array<std::optional<std::array<glm::vec3, 8>>, gl::MAX_CASCADES> &Areas );
|
||||
// whether the depth pass of the whole scene draws the material bound last (Bind_Material()) and the geometry
|
||||
bool Scene_Depth_Draws( gfx::geometry_handle const &Geometry ) const;
|
||||
bool Render(TDynamicObject *Dynamic);
|
||||
@@ -653,6 +667,26 @@ class opengl46_renderer : public gfx_renderer {
|
||||
std::unordered_map<scene::basic_cell::instance_bucket_key, std::vector<std::uint32_t>, scene::basic_cell::instance_bucket_key_hash> m_sleeperbandcounts;
|
||||
// depth pyramid for the occlusion test of the gpu culling
|
||||
opengl46_hiz m_hiz;
|
||||
// the cascades of the sun shadows fitted to the depth of the main view (Global.gfx_shadow_sdsm)
|
||||
opengl46_shadow_fit m_shadowfit;
|
||||
// with a cascade fitted: the corners of the area its map is to cover, for setup_pass()
|
||||
std::array<glm::vec3, 8> const *m_shadowfitarea { nullptr };
|
||||
// what the last shadow pass did, for the debug panel
|
||||
struct shadow_fit_state {
|
||||
bool fitted { false };
|
||||
char const *reason { nullptr }; // why not, with gfx.shadow.sdsm on
|
||||
unsigned int age { 0 }; // frames since the measurement
|
||||
float depthmin { 0.f };
|
||||
float depthmax { 0.f };
|
||||
float turn { 0.f }; // of the view since the measurement, degrees
|
||||
std::array<float, gl::MAX_CASCADES + 1> splits {};
|
||||
// size of the area of the map of each cascade: of the whole slice of the view, and as drawn; metres
|
||||
std::array<float, gl::MAX_CASCADES> slice {};
|
||||
std::array<float, gl::MAX_CASCADES> drawn {};
|
||||
} m_shadowfitstate;
|
||||
// the sizes setup_pass() found for the last shadow pass: of the whole slice of the view and as drawn
|
||||
float m_shadowslicesize { 0.f };
|
||||
float m_shadowdrawnsize { 0.f };
|
||||
// a draw recorded from the walk of the submodel tree of a group in one lod band (Render_Instances_Indirect()), to be
|
||||
// replayed once the instances of its command are known: the state the walk left for it and what it bound
|
||||
struct indirect_draw {
|
||||
|
||||
171
rendering/opengl46/opengl46shadowfit.cpp
Normal file
171
rendering/opengl46/opengl46shadowfit.cpp
Normal file
@@ -0,0 +1,171 @@
|
||||
/*
|
||||
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/opengl46shadowfit.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include "model/Texture.h"
|
||||
#include "utilities/Globals.h"
|
||||
#include "utilities/utilities.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
// the nearest distance the first cascade may start from and the least one the last reaches, metres: the cascades don't
|
||||
// get any closer to the camera than this, however close the pixels are
|
||||
float const SPLIT_NEAR_MIN{0.5f};
|
||||
float const SPLIT_FAR_MIN{16.f};
|
||||
// how far past its distances a cascade takes the pixels, this many metres and this share of the distance: the camera moves
|
||||
// in the frame or two before the cascades are drawn from the measurement, and a pixel which came into another cascade since
|
||||
// has to be within its map
|
||||
float const MARGIN_METRES{1.f};
|
||||
float const MARGIN_SHARE{0.05f};
|
||||
|
||||
} // namespace
|
||||
|
||||
opengl46_shadow_fit::~opengl46_shadow_fit()
|
||||
{
|
||||
release();
|
||||
}
|
||||
|
||||
void opengl46_shadow_fit::init()
|
||||
{
|
||||
auto const make_compute = [](std::string const &Name) {
|
||||
gl::shader shader(Name);
|
||||
return std::make_unique<gl::program>(std::vector<std::reference_wrapper<const gl::shader>>{shader});
|
||||
};
|
||||
m_depthprogram = make_compute("shadow_fit_depth.comp");
|
||||
m_boundsprogram = make_compute("shadow_fit_bounds.comp");
|
||||
m_buffer = std::make_unique<gl::ssbo>(sizeof(gl::shadow_fit_data), gl::SHADOW_FIT_SSBO_BINDING);
|
||||
m_readback = std::make_unique<gl::readback>(sizeof(gl::shadow_fit_data));
|
||||
}
|
||||
|
||||
void opengl46_shadow_fit::release()
|
||||
{
|
||||
if (m_depthfb != 0)
|
||||
glDeleteFramebuffers(1, &m_depthfb);
|
||||
if (m_depth != 0)
|
||||
glDeleteTextures(1, &m_depth);
|
||||
m_depthfb = 0;
|
||||
m_depth = 0;
|
||||
m_depthsize = glm::ivec2{0};
|
||||
}
|
||||
|
||||
void opengl46_shadow_fit::allocate(int const Width, int const Height)
|
||||
{
|
||||
release();
|
||||
m_depthsize = glm::ivec2{Width, Height};
|
||||
// the blit of depth needs the format of the source
|
||||
glCreateTextures(GL_TEXTURE_2D, 1, &m_depth);
|
||||
glTextureStorage2D(m_depth, 1, Global.gfx_format_depth, Width, Height);
|
||||
glTextureParameteri(m_depth, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTextureParameteri(m_depth, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glTextureParameteri(m_depth, GL_TEXTURE_COMPARE_MODE, GL_NONE);
|
||||
glCreateFramebuffers(1, &m_depthfb);
|
||||
glNamedFramebufferTexture(m_depthfb, GL_DEPTH_ATTACHMENT, m_depth, 0);
|
||||
glNamedFramebufferDrawBuffer(m_depthfb, GL_NONE);
|
||||
glNamedFramebufferReadBuffer(m_depthfb, GL_NONE);
|
||||
}
|
||||
|
||||
void opengl46_shadow_fit::measure(GLuint const Depth, GLuint const Source, int const Width, int const Height, view const &View, unsigned int const Frame)
|
||||
{
|
||||
if ((Width <= 0) || (Height <= 0) || (View.range <= 0.f))
|
||||
return;
|
||||
|
||||
m_timer.begin();
|
||||
|
||||
auto depth{Depth};
|
||||
if (depth == 0)
|
||||
{
|
||||
if (m_depthsize != glm::ivec2{Width, Height})
|
||||
allocate(Width, Height);
|
||||
// the scissor test would limit the blit
|
||||
auto const scissor{glIsEnabled(GL_SCISSOR_TEST) == GL_TRUE};
|
||||
if (scissor)
|
||||
glDisable(GL_SCISSOR_TEST);
|
||||
glBlitNamedFramebuffer(Source, m_depthfb, 0, 0, Width, Height, 0, 0, Width, Height, GL_DEPTH_BUFFER_BIT, GL_NEAREST);
|
||||
if (scissor)
|
||||
glEnable(GL_SCISSOR_TEST);
|
||||
depth = m_depth;
|
||||
}
|
||||
|
||||
// numbers start from 1, 0 is no measurement
|
||||
if (++m_measurement == 0)
|
||||
++m_measurement;
|
||||
gl::shadow_fit_data data{};
|
||||
data.measurement = m_measurement;
|
||||
data.depthmin = std::numeric_limits<float>::infinity();
|
||||
for (std::size_t cascade = 0; cascade < gl::MAX_CASCADES; ++cascade)
|
||||
{
|
||||
for (std::size_t axis = 0; axis < 4; ++axis)
|
||||
{
|
||||
data.bounds[cascade * 8 + axis] = 0xFFFFFFFFu;
|
||||
data.bounds[cascade * 8 + 4 + axis] = 0u;
|
||||
}
|
||||
}
|
||||
m_buffer->update(data);
|
||||
m_buffer->bind_base();
|
||||
|
||||
auto const inverseprojection{glm::inverse(View.projection)};
|
||||
auto const viewtolight{glm::mat4{View.light * glm::inverse(View.rotation)}};
|
||||
auto const groupsx{(static_cast<GLuint>(Width) + gl::SHADOW_FIT_GROUP_SIZE - 1) / gl::SHADOW_FIT_GROUP_SIZE};
|
||||
auto const groupsy{(static_cast<GLuint>(Height) + gl::SHADOW_FIT_GROUP_SIZE - 1) / gl::SHADOW_FIT_GROUP_SIZE};
|
||||
glBindTextureUnit(0, depth);
|
||||
|
||||
glProgramUniformMatrix4fv(*m_depthprogram, 0, 1, GL_FALSE, glm::value_ptr(inverseprojection));
|
||||
m_depthprogram->dispatch(groupsx, groupsy);
|
||||
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
|
||||
|
||||
auto const farmin{std::min(SPLIT_FAR_MIN, View.range)};
|
||||
glProgramUniformMatrix4fv(*m_boundsprogram, 0, 1, GL_FALSE, glm::value_ptr(inverseprojection));
|
||||
glProgramUniformMatrix4fv(*m_boundsprogram, 4, 1, GL_FALSE, glm::value_ptr(viewtolight));
|
||||
glProgramUniform4f(*m_boundsprogram, 8, View.range, std::min(SPLIT_NEAR_MIN, farmin * 0.25f), farmin, std::clamp(Global.gfx_shadow_sdsm_split, 0.f, 1.f));
|
||||
glProgramUniform4f(*m_boundsprogram, 9, MARGIN_METRES, MARGIN_SHARE, 0.f, 0.f);
|
||||
m_boundsprogram->dispatch(groupsx, groupsy);
|
||||
// for the copy of the read back
|
||||
glMemoryBarrier(GL_BUFFER_UPDATE_BARRIER_BIT);
|
||||
|
||||
glBindTextureUnit(0, 0);
|
||||
opengl_texture::reset_unit_cache();
|
||||
|
||||
m_timer.end();
|
||||
|
||||
m_readback->push(*m_buffer);
|
||||
m_pending[m_measurement % PENDING] = pending{m_measurement, View, Frame};
|
||||
}
|
||||
|
||||
opengl46_shadow_fit::result const *opengl46_shadow_fit::latest()
|
||||
{
|
||||
gl::shadow_fit_data data;
|
||||
if (m_readback && m_readback->pop(&data))
|
||||
{
|
||||
auto const &source{m_pending[data.measurement % PENDING]};
|
||||
if ((data.measurement != 0) && (source.measurement == data.measurement))
|
||||
m_latest = result{data, source.source, source.frame};
|
||||
}
|
||||
return m_latest ? &(*m_latest) : nullptr;
|
||||
}
|
||||
|
||||
float opengl46_shadow_fit::unorder(std::uint32_t const Value)
|
||||
{
|
||||
auto const bits{((Value & 0x80000000u) != 0u) ? (Value & 0x7FFFFFFFu) : ~Value};
|
||||
float result;
|
||||
std::memcpy(&result, &bits, sizeof(result));
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string opengl46_shadow_fit::info_times()
|
||||
{
|
||||
std::string text;
|
||||
if (auto const time = m_timer.result())
|
||||
text += " sun shadows fitted to the depth: " + to_string(static_cast<double>(*time) / 1000000.0, 3) + " ms\n";
|
||||
return text;
|
||||
}
|
||||
94
rendering/opengl46/opengl46shadowfit.h
Normal file
94
rendering/opengl46/opengl46shadowfit.h
Normal file
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
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 <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
#include "gl/ubo.h"
|
||||
#include "gl/ssbo.h"
|
||||
#include "gl/shader.h"
|
||||
#include "gl/query.h"
|
||||
#include "gl/readback.h"
|
||||
|
||||
// the cascades of the sun shadows fitted to the depth of the main view (sample distribution shadow maps, gfx.shadow.sdsm).
|
||||
// at the end of the colour pass of the main view two compute passes go over its resolved depth: the nearest and the farthest
|
||||
// distance of the pixels which aren't the sky (shadow_fit_depth.comp), then the distances split between the cascades and,
|
||||
// for each cascade, the box of the pixels it takes in the space of the light (shadow_fit_bounds.comp). the result comes back
|
||||
// to the cpu a frame or two later without waiting (gl::readback), with the camera and the light it was measured with, and
|
||||
// the renderer makes the cascades of the frame cover the boxes (opengl46_renderer::Shadow_Fit()) instead of the whole slices
|
||||
// of the view between the fixed distances of shadowtune
|
||||
class opengl46_shadow_fit
|
||||
{
|
||||
public:
|
||||
// the view and the light a measurement was made with
|
||||
struct view
|
||||
{
|
||||
glm::dvec3 camera; // position of the camera
|
||||
glm::mat4 projection; // projection of the view
|
||||
glm::mat3 rotation; // world to view space, rotation only
|
||||
glm::mat3 light; // world to the space of the light, rotation only
|
||||
float range{0.f}; // the farthest distance the cascades may reach
|
||||
};
|
||||
// a measurement which came back
|
||||
struct result
|
||||
{
|
||||
gl::shadow_fit_data data;
|
||||
view source;
|
||||
unsigned int frame{0}; // frame of the renderer it was made in
|
||||
};
|
||||
|
||||
opengl46_shadow_fit() = default;
|
||||
~opengl46_shadow_fit();
|
||||
opengl46_shadow_fit(const opengl46_shadow_fit &) = delete;
|
||||
opengl46_shadow_fit &operator=(const opengl46_shadow_fit &) = delete;
|
||||
|
||||
// creates the programs and the buffer. throws gl::shader_exception
|
||||
void init();
|
||||
// measures the depth of the view: Depth, its resolved depth texture of Width x Height pixels, or 0 to resolve the depth
|
||||
// attachment of Source, the multisampled framebuffer, into a texture of its own. Frame: the frame of the renderer.
|
||||
// texture unit 0 is used (the unit cache is reset), framebuffer bindings are left alone
|
||||
void measure(GLuint const Depth, GLuint const Source, int const Width, int const Height, view const &View, unsigned int const Frame);
|
||||
// takes in the newest measurement the gpu has finished, if any, and returns the newest one taken in so far
|
||||
result const *latest();
|
||||
// the time of the passes, for the debug panel
|
||||
std::string info_times();
|
||||
|
||||
// the ends of the bounds, as the passes store them (shadow_fit_order() in shadow_fit_bounds.comp)
|
||||
static float unorder(std::uint32_t const Value);
|
||||
|
||||
private:
|
||||
// the texture the depth is resolved to when there is no resolved depth to measure
|
||||
void allocate(int const Width, int const Height);
|
||||
void release();
|
||||
|
||||
// a measurement on its way back, kept by its number
|
||||
struct pending
|
||||
{
|
||||
std::uint32_t measurement{0}; // 0: none
|
||||
view source;
|
||||
unsigned int frame{0};
|
||||
};
|
||||
static constexpr std::size_t PENDING = 8; // more than the copies gl::readback keeps in flight
|
||||
|
||||
std::unique_ptr<gl::program> m_depthprogram;
|
||||
std::unique_ptr<gl::program> m_boundsprogram;
|
||||
std::unique_ptr<gl::ssbo> m_buffer;
|
||||
std::unique_ptr<gl::readback> m_readback;
|
||||
std::array<pending, PENDING> m_pending;
|
||||
std::uint32_t m_measurement{0}; // number of the last measurement
|
||||
std::optional<result> m_latest;
|
||||
GLuint m_depth{0};
|
||||
GLuint m_depthfb{0};
|
||||
glm::ivec2 m_depthsize{0};
|
||||
gl::gpu_timer m_timer;
|
||||
};
|
||||
@@ -87,13 +87,22 @@ float sun_shadow(vec3 position)
|
||||
for (cascade = 0U; cascade < MAX_CASCADES; cascade++)
|
||||
if (span_distance <= cascade_end[cascade])
|
||||
break;
|
||||
if (cascade >= MAX_CASCADES)
|
||||
return 0.0;
|
||||
vec4 lightpos = lightview[cascade] * vec4(position, 1.0);
|
||||
vec3 coords = lightpos.xyz / lightpos.w;
|
||||
if (coords.z < 0.0 || any(lessThan(coords.xy, vec2(0.0))) || any(greaterThan(coords.xy, vec2(1.0))))
|
||||
return 0.0;
|
||||
return texture(shadowmap, vec4(coords.xy, float(cascade), coords.z + 0.0001 * float(cascade + 1U)));
|
||||
for (; cascade < MAX_CASCADES; cascade++)
|
||||
{
|
||||
vec4 lightpos = lightview[cascade] * vec4(position, 1.0);
|
||||
vec3 coords = lightpos.xyz / lightpos.w;
|
||||
if (all(greaterThanEqual(coords.xy, vec2(0.0))) && all(lessThanEqual(coords.xy, vec2(1.0))))
|
||||
{
|
||||
if (coords.z < 0.0)
|
||||
return 0.0;
|
||||
return texture(shadowmap, vec4(coords.xy, float(cascade), coords.z + 0.0001 * float(cascade + 1U)));
|
||||
}
|
||||
// outside the map: lit, unless the cascades are fitted to the depth of the view (gfx.shadow.sdsm), whose maps cover
|
||||
// only the area of the surfaces seen; then the first further cascade whose map holds the point
|
||||
if (shadow_fit.x <= 0.0)
|
||||
return 0.0;
|
||||
}
|
||||
return 0.0;
|
||||
#else
|
||||
return 0.0;
|
||||
#endif
|
||||
|
||||
@@ -128,6 +128,21 @@ float calc_shadow_map()
|
||||
for (cascade = 0U; cascade < MAX_CASCADES; cascade++)
|
||||
if (distance <= cascade_end[cascade])
|
||||
break;
|
||||
// cascades fitted to the depth of the view of an earlier frame (gfx.shadow.sdsm) cover only the area of the points seen
|
||||
// then: a point at the edge of the view or one which came closer since may be outside the map of its cascade. it goes to
|
||||
// the first further cascade whose map holds it with the reach of the filter, and is lit past the last one
|
||||
if (shadow_fit.x > 0.0)
|
||||
{
|
||||
float border = 4.0 / float(textureSize(shadowmap, 0).x);
|
||||
for (; cascade < MAX_CASCADES; cascade++)
|
||||
{
|
||||
vec2 fitcoords = (lightview[cascade] * vec4(f_pos.xyz, 1.0)).xy;
|
||||
if (all(greaterThanEqual(fitcoords, vec2(border))) && all(lessThanEqual(fitcoords, vec2(1.0 - border))))
|
||||
break;
|
||||
}
|
||||
if (cascade >= MAX_CASCADES)
|
||||
return 0.0;
|
||||
}
|
||||
// Normal offset: sample the shadow map from a point pushed along the surface normal
|
||||
// by a fraction of the cascade's texel footprint. A constant depth bias can't keep up
|
||||
// with large texels (far cascades, small shadow maps) on surfaces at a grazing angle
|
||||
|
||||
126
shaders/shadow_fit_bounds.comp
Normal file
126
shaders/shadow_fit_bounds.comp
Normal file
@@ -0,0 +1,126 @@
|
||||
// the cascades of the sun shadows fitted to the depth of the main view (opengl46_shadow_fit), second pass: the distances
|
||||
// from the nearest to the farthest pixel which isn't the sky (shadow_fit_depth.comp) split between the cascades, and for each
|
||||
// cascade the box of the pixels it takes, in the space of the light. the cpu makes the maps of the cascades of a frame or
|
||||
// two later cover the boxes: the camera has moved since, so a cascade takes the pixels a little past its distances as well,
|
||||
// and the shading goes to the next cascade with a point outside the map of its own (light_common.glsl)
|
||||
|
||||
#include <common>
|
||||
|
||||
layout(local_size_x = SHADOW_FIT_GROUP_SIZE, local_size_y = SHADOW_FIT_GROUP_SIZE) in;
|
||||
|
||||
layout(binding = 0) uniform sampler2D scene_depth;
|
||||
|
||||
layout(std430, binding = SHADOW_FIT_SSBO_BINDING) buffer shadow_fit_ssbo
|
||||
{
|
||||
uint measurement;
|
||||
uint samples;
|
||||
// positive floats, as bits
|
||||
uint depthmin;
|
||||
uint depthmax;
|
||||
float splits[4];
|
||||
uint counts[4];
|
||||
// per cascade the lowest x, y, z, unused, then the highest x, y, z, unused, as shadow_fit_order() gives them
|
||||
uint bounds[MAX_CASCADES * 8U];
|
||||
};
|
||||
|
||||
// the inverse of the projection of the view
|
||||
layout(location = 0) uniform mat4 inv_projection;
|
||||
// rotation from the view space to the space of the light: x and y across the maps, z along the light
|
||||
layout(location = 4) uniform mat4 view_to_light;
|
||||
// x: the farthest distance the cascades may reach (the one of the fixed cascades), y: the nearest one the first may start
|
||||
// from, z: the least one the last reaches, w: the share of equal ratios in the splits, the rest in equal lengths
|
||||
layout(location = 8) uniform vec4 split_params;
|
||||
// how far past its distances a cascade takes the pixels, x + y * distance metres
|
||||
layout(location = 9) uniform vec4 margin_params;
|
||||
|
||||
shared uint shared_bounds[MAX_CASCADES * 8U];
|
||||
shared uint shared_counts[MAX_CASCADES];
|
||||
|
||||
// a float as a uint which orders the same way, also the negative ones
|
||||
uint shadow_fit_order(float value)
|
||||
{
|
||||
uint bits = floatBitsToUint(value);
|
||||
return ((bits & 0x80000000U) != 0U) ? ~bits : (bits | 0x80000000U);
|
||||
}
|
||||
|
||||
// the distances the cascades go from and to. both ends are moved out to the next step of 1/8 of a power of two, so that
|
||||
// the cascades change only now and then with the pixels coming and going
|
||||
void shadow_fit_splits(out float result[MAX_CASCADES + 1U])
|
||||
{
|
||||
float rangemax = split_params.x;
|
||||
float far = clamp(uintBitsToFloat(depthmax), split_params.z, rangemax);
|
||||
far = min(exp2(ceil(log2(far) * 8.0) / 8.0), rangemax);
|
||||
float near = clamp(uintBitsToFloat(depthmin), split_params.y, far * 0.25);
|
||||
near = exp2(floor(log2(near) * 8.0) / 8.0);
|
||||
result[0] = near;
|
||||
result[MAX_CASCADES] = far;
|
||||
for (uint cascade = 1U; cascade < MAX_CASCADES; ++cascade)
|
||||
{
|
||||
float share = float(cascade) / float(MAX_CASCADES);
|
||||
result[cascade] = mix(near + (far - near) * share, near * pow(far / near, share), split_params.w);
|
||||
}
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
uint index = gl_LocalInvocationIndex;
|
||||
if (index < MAX_CASCADES * 8U)
|
||||
shared_bounds[index] = ((index & 4U) == 0U) ? 0xFFFFFFFFU : 0U;
|
||||
if (index < MAX_CASCADES)
|
||||
shared_counts[index] = 0U;
|
||||
barrier();
|
||||
|
||||
float cascadesplits[MAX_CASCADES + 1U];
|
||||
shadow_fit_splits(cascadesplits);
|
||||
|
||||
ivec2 pixel = ivec2(gl_GlobalInvocationID.xy);
|
||||
ivec2 size = textureSize(scene_depth, 0);
|
||||
if ((samples > 0U) && all(lessThan(pixel, size)))
|
||||
{
|
||||
float depth = texelFetch(scene_depth, pixel, 0).r;
|
||||
if (depth > 0.0)
|
||||
{
|
||||
vec2 ndc = (vec2(pixel) + 0.5) / vec2(size) * 2.0 - 1.0;
|
||||
vec4 position = inv_projection * vec4(ndc, depth, 1.0);
|
||||
vec3 view = position.xyz / position.w;
|
||||
float distance = length(view);
|
||||
vec3 light = mat3(view_to_light) * view;
|
||||
uvec3 ordered = uvec3(shadow_fit_order(light.x), shadow_fit_order(light.y), shadow_fit_order(light.z));
|
||||
for (uint cascade = 0U; cascade < MAX_CASCADES; ++cascade)
|
||||
{
|
||||
// the end of a cascade has the padding of opengl46_renderer::Render_pass(), MAX_CASCADES - cascade metres
|
||||
float low = (cascade == 0U) ? 0.0 : cascadesplits[cascade] - (margin_params.x + margin_params.y * cascadesplits[cascade]);
|
||||
float high = cascadesplits[cascade + 1U] + float(MAX_CASCADES - cascade) + margin_params.x + margin_params.y * cascadesplits[cascade + 1U];
|
||||
if ((distance < low) || (distance > high))
|
||||
continue;
|
||||
uint first = cascade * 8U;
|
||||
atomicAdd(shared_counts[cascade], 1U);
|
||||
atomicMin(shared_bounds[first + 0U], ordered.x);
|
||||
atomicMin(shared_bounds[first + 1U], ordered.y);
|
||||
atomicMin(shared_bounds[first + 2U], ordered.z);
|
||||
atomicMax(shared_bounds[first + 4U], ordered.x);
|
||||
atomicMax(shared_bounds[first + 5U], ordered.y);
|
||||
atomicMax(shared_bounds[first + 6U], ordered.z);
|
||||
}
|
||||
}
|
||||
}
|
||||
barrier();
|
||||
|
||||
if ((index < MAX_CASCADES * 8U) && ((index & 3U) != 3U))
|
||||
{
|
||||
if ((index & 4U) == 0U)
|
||||
{
|
||||
if (shared_bounds[index] != 0xFFFFFFFFU)
|
||||
atomicMin(bounds[index], shared_bounds[index]);
|
||||
}
|
||||
else if (shared_bounds[index] != 0U)
|
||||
atomicMax(bounds[index], shared_bounds[index]);
|
||||
}
|
||||
if ((index < MAX_CASCADES) && (shared_counts[index] > 0U))
|
||||
atomicAdd(counts[index], shared_counts[index]);
|
||||
if (all(equal(gl_GlobalInvocationID.xy, uvec2(0U))))
|
||||
{
|
||||
for (uint split = 0U; split <= MAX_CASCADES; ++split)
|
||||
splits[split] = cascadesplits[split];
|
||||
}
|
||||
}
|
||||
65
shaders/shadow_fit_depth.comp
Normal file
65
shaders/shadow_fit_depth.comp
Normal file
@@ -0,0 +1,65 @@
|
||||
// the cascades of the sun shadows fitted to the depth of the main view (opengl46_shadow_fit), first pass: how many pixels of
|
||||
// the resolved depth of the view aren't the sky, and the nearest and the farthest of their distances from the camera.
|
||||
// the depth is reversed with the far plane at infinity: the sky, which writes no depth, stays at 0.
|
||||
// shadow_fit_bounds.comp splits the distances between the cascades
|
||||
|
||||
#include <common>
|
||||
|
||||
layout(local_size_x = SHADOW_FIT_GROUP_SIZE, local_size_y = SHADOW_FIT_GROUP_SIZE) in;
|
||||
|
||||
layout(binding = 0) uniform sampler2D scene_depth;
|
||||
|
||||
layout(std430, binding = SHADOW_FIT_SSBO_BINDING) buffer shadow_fit_ssbo
|
||||
{
|
||||
uint measurement;
|
||||
uint samples;
|
||||
// positive floats, as bits
|
||||
uint depthmin;
|
||||
uint depthmax;
|
||||
float splits[4];
|
||||
uint counts[4];
|
||||
uint bounds[MAX_CASCADES * 8U];
|
||||
};
|
||||
|
||||
// the inverse of the projection of the view
|
||||
layout(location = 0) uniform mat4 inv_projection;
|
||||
|
||||
shared uint shared_samples;
|
||||
shared uint shared_min;
|
||||
shared uint shared_max;
|
||||
|
||||
void main()
|
||||
{
|
||||
if (gl_LocalInvocationIndex == 0U)
|
||||
{
|
||||
shared_samples = 0U;
|
||||
// infinity
|
||||
shared_min = 0x7F800000U;
|
||||
shared_max = 0U;
|
||||
}
|
||||
barrier();
|
||||
|
||||
ivec2 pixel = ivec2(gl_GlobalInvocationID.xy);
|
||||
ivec2 size = textureSize(scene_depth, 0);
|
||||
if (all(lessThan(pixel, size)))
|
||||
{
|
||||
float depth = texelFetch(scene_depth, pixel, 0).r;
|
||||
if (depth > 0.0)
|
||||
{
|
||||
vec2 ndc = (vec2(pixel) + 0.5) / vec2(size) * 2.0 - 1.0;
|
||||
vec4 position = inv_projection * vec4(ndc, depth, 1.0);
|
||||
uint distance = floatBitsToUint(length(position.xyz / position.w));
|
||||
atomicAdd(shared_samples, 1U);
|
||||
atomicMin(shared_min, distance);
|
||||
atomicMax(shared_max, distance);
|
||||
}
|
||||
}
|
||||
barrier();
|
||||
|
||||
if ((gl_LocalInvocationIndex == 0U) && (shared_samples > 0U))
|
||||
{
|
||||
atomicAdd(samples, shared_samples);
|
||||
atomicMin(depthmin, shared_min);
|
||||
atomicMax(depthmax, shared_max);
|
||||
}
|
||||
}
|
||||
@@ -1607,6 +1607,17 @@ global_settings::ConfigParse_gfx( cParser &Parser, std::string_view const Token
|
||||
Parser >> gfx_shadow_rank_cutoff;
|
||||
gfx_shadow_rank_cutoff = std::clamp(gfx_shadow_rank_cutoff, 1, 3);
|
||||
}
|
||||
else if (Token == "gfx.shadow.sdsm")
|
||||
{
|
||||
Parser.getTokens(1);
|
||||
Parser >> gfx_shadow_sdsm;
|
||||
}
|
||||
else if (Token == "gfx.shadow.sdsm.split")
|
||||
{
|
||||
Parser.getTokens(1);
|
||||
Parser >> gfx_shadow_sdsm_split;
|
||||
gfx_shadow_sdsm_split = std::clamp(gfx_shadow_sdsm_split, 0.f, 1.f);
|
||||
}
|
||||
else if (ConfigParse_gfx_sky(Parser, Token))
|
||||
{
|
||||
// parsed
|
||||
@@ -2006,6 +2017,8 @@ global_settings::export_as_text( std::ostream &Output ) const {
|
||||
export_as_text( Output, "gfx.shadergamma", gfx_shadergamma );
|
||||
export_as_text( Output, "gfx.shadow.angle.min", gfx_shadow_angle_min );
|
||||
export_as_text( Output, "gfx.shadow.rank.cutoff", gfx_shadow_rank_cutoff );
|
||||
export_as_text( Output, "gfx.shadow.sdsm", gfx_shadow_sdsm );
|
||||
export_as_text( Output, "gfx.shadow.sdsm.split", gfx_shadow_sdsm_split );
|
||||
export_as_text( Output, "gfx.ambient.sh", gfx_ambient_sh );
|
||||
export_as_text( Output, "gfx.ambient.sh.strength", gfx_ambient_sh_strength );
|
||||
export_as_text( Output, "gfx.ambient.groundalbedo", gfx_ambient_groundalbedo );
|
||||
|
||||
@@ -334,6 +334,13 @@ struct global_settings {
|
||||
float gfx_distance_factor_max { 3.f };
|
||||
float gfx_shadow_angle_min { -0.2f };
|
||||
int gfx_shadow_rank_cutoff { 3 };
|
||||
// (opengl 4.6) the cascades of the sun shadows are fitted to the depth of the main view of a frame or two before (SDSM):
|
||||
// the distances they cover go from the nearest to the farthest pixel which isn't the sky, and the map of each covers only
|
||||
// the area of the pixels it takes. false: the fixed cascades of shadowtune
|
||||
bool gfx_shadow_sdsm { false };
|
||||
// with gfx_shadow_sdsm: how the distances between the nearest and the farthest pixel are split between the cascades,
|
||||
// 0: in equal lengths, 1: in equal ratios
|
||||
float gfx_shadow_sdsm_split { 0.9f };
|
||||
|
||||
// sky light and exposure of the opengl 4.6 renderer. the switches can also be changed at run time in the debug window
|
||||
// "Sky and exposure"; the scales are multipliers of the calibrated defaults (1 = as calibrated)
|
||||
|
||||
Reference in New Issue
Block a user