17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-11-04 00:42:05 +01:00

Replace cubemap scenery reflections with SSR, add Vulkan ray traced reflections

This commit is contained in:
2026-10-25 13:54:49 +01:00
parent 89f11895fc
commit f20b1a3071
45 changed files with 2725 additions and 174 deletions

View File

@@ -204,7 +204,6 @@ bool opengl46_renderer::Init(GLFWwindow *Window)
m_instancecull.init();
for (auto &shadowcull : m_shadowcull)
shadowcull.init(&m_instancecull);
m_reflectioncull.init(&m_instancecull);
m_hiz.init();
m_shadowfit.init();
m_exposure.init();
@@ -952,9 +951,6 @@ void opengl46_renderer::SwapBuffers()
text += m_shadowcull[ cascade ].info_times( "instance cull shadows " + std::to_string( cascade + 1 ) );
}
}
if( Global.gfx_gpucull_reflections && m_reflectioncull.ran() ) {
text += m_reflectioncull.info_times( "instance cull reflections, last face" );
}
text += Light_Shadow_Cull_Info( true );
if( Global.gfx_depthprepass_scene || m_contactshadowslast ) {
if( auto const scenedepthtime = m_scenedepthtimer.result() )
@@ -964,6 +960,10 @@ void opengl46_renderer::SwapBuffers()
if( auto const contacttime = m_contactshadowstimer.result() )
text += " contact shadows depth: " + to_string( static_cast<double>( *contacttime ) / 1000000.0, 3 ) + " ms\n";
}
if( Global.gfx_ssr > 0 ) {
if( auto const historytime = m_ssrhistorytimer.result() )
text += " reflections, image of the frame: " + to_string( static_cast<double>( *historytime ) / 1000000.0, 3 ) + " ms\n";
}
if( Global.gfx_gpucull == 1 ) {
auto const twophase { m_instancecull.two_phase() };
text += m_hiz.info_times( twophase );
@@ -1094,10 +1094,6 @@ void opengl46_renderer::SwapBuffers()
culltext += m_shadowcull[ cascade ].info( "gpu cull shadows " + std::to_string( cascade + 1 ), false );
}
}
if( Global.gfx_gpucull_reflections && m_reflectioncull.ran() ) {
m_reflectioncull.collect();
culltext += m_reflectioncull.info( "gpu cull reflections, last face", false );
}
culltext += Light_Shadow_Cull_Info( false );
if( Global.gfx_gpucull == 1 ) {
auto const &parts { m_cullparts };
@@ -1461,6 +1457,8 @@ void opengl46_renderer::draw_debug_ui()
m_exposure.draw_debug_ui();
ImGui::Separator();
m_ao.draw_debug_ui(!m_viewports.empty() && m_viewports.front()->ssao_tex != nullptr);
ImGui::Separator();
m_ssr.draw_debug_ui(nullptr);
}
ImGui::End();
@@ -1482,7 +1480,6 @@ void opengl46_renderer::draw_debug_ui()
if (Global.gfx_gpucull != 0)
{
ImGui::Checkbox("the same for the cascades of the sun shadows (gfx.gpucull.shadows)", &Global.gfx_gpucull_shadows);
ImGui::Checkbox("the same for the environment map, when it's drawn next (gfx.gpucull.reflections)", &Global.gfx_gpucull_reflections);
ImGui::Checkbox("the same for the shadow maps of headlights (gfx.gpucull.shadows.headlights)", &Global.gfx_gpucull_shadows_headlights);
ImGui::Checkbox("the same for the scenery of the shadow maps of lamps, when it's drawn next (gfx.gpucull.shadows.lamps)", &Global.gfx_gpucull_shadows_lamps);
ImGui::Checkbox("the same for the sleepers of the tracks of the view (gfx.gpucull.sleepers)", &Global.gfx_gpucull_sleepers);
@@ -1510,8 +1507,6 @@ void opengl46_renderer::draw_debug_ui()
info += m_shadowcull[cascade].info("gpu cull shadows " + std::to_string(cascade + 1), false);
}
}
if (Global.gfx_gpucull_reflections && m_reflectioncull.ran())
info += m_reflectioncull.info("gpu cull reflections, last face", false);
info += Light_Shadow_Cull_Info(false);
if (!info.empty())
ImGui::TextUnformatted(info.c_str() + 1);
@@ -1573,6 +1568,14 @@ void opengl46_renderer::Render_pass(viewport_config &vp, rendermode const Mode)
m_renderpass.pass_camera.projection(), glm::dmat3{m_renderpass.pass_camera.modelview()}, m_renderpass.pass_camera.position(), cab, glm::ivec2{vp.width, vp.height});
m_cabfuture = m_taa.cab_future();
}
// the screen space reflections: where the points the rays hit were in the image of the history
m_ssrframe = Ssr_View(vp);
if (m_ssrframe)
{
auto const *cab{((false == FreeFlyModeFlag) && Global.render_cab && (simulation::Train != nullptr)) ? simulation::Train->Dynamic() : nullptr};
m_ssr.begin_frame(
m_renderpass.pass_camera.projection(), glm::dmat3{m_renderpass.pass_camera.modelview()}, m_renderpass.pass_camera.position(), cab, glm::ivec2{vp.width, vp.height}, vp.ssr_levels);
}
m_colorpass = m_renderpass; // cache pass data
// parts of the gpu time of the frame; the marks go through the main view only, in the context they were begun in
@@ -1625,6 +1628,10 @@ void opengl46_renderer::Render_pass(viewport_config &vp, rendermode const Mode)
scene_ubs.wiper_timer_return = glm::vec4{-1000.};
}
}
// the reflections: the ground below the horizon of the environment map, and the screen space ones set up for the colour
// pass of the main view only
scene_ubs.env_params = glm::vec4{Global.gfx_ambient_groundalbedo, 0.f, 0.f, 0.f};
gfx::screen_reflections::clear(scene_ubs);
scene_ubs.projection = OpenGLMatrices.data(GL_PROJECTION);
scene_ubs.inv_view = glm::inverse( glm::mat4{ glm::mat3{ m_colorpass.pass_camera.modelview() } } );
scene_ubo->update(scene_ubs);
@@ -1797,16 +1804,22 @@ void opengl46_renderer::Render_pass(viewport_config &vp, rendermode const Mode)
m_indirectcolourpending = false;
m_scenerecorded = false;
auto const contactshadows{Contact_Shadows_Wanted(vp)};
if ((Global.gfx_depthprepass_scene || contactshadows) && vp.main && (false == Global.bWireFrame))
// the screen space reflections trace the depth of the whole scene, not only of what the contact shadows reach
auto const reflections{m_ssrframe && (false == Global.bWireFrame)};
if ((Global.gfx_depthprepass_scene || contactshadows || reflections) && vp.main && (false == Global.bWireFrame))
{
glDebug("render scene depth");
gpupart("scene depth");
Render_Scene_Depth(future, contactshadows && (false == Global.gfx_depthprepass_scene));
Render_Scene_Depth(future, contactshadows && (false == Global.gfx_depthprepass_scene) && (false == reflections));
if (contactshadows)
{
gpupart("contact shadows depth");
Setup_Contact_Shadows(vp);
}
if (reflections)
{
Setup_Screen_Reflections(vp, taa);
}
}
if (vp.main)
m_contactshadowslast = m_contactshadows;
@@ -1910,12 +1923,14 @@ void opengl46_renderer::Render_pass(viewport_config &vp, rendermode const Mode)
}
}
if (m_contactshadows)
if (m_contactshadows || m_ssractive)
{
// the depth they read is of this view only
m_contactshadows = false;
m_ssractive = false;
scene_ubs.contact_shadow_params = glm::vec4{0.f};
scene_ubs.contact_shadow_lamps = glm::vec4{0.f};
gfx::screen_reflections::clear(scene_ubs);
scene_ubo->update(scene_ubs);
}
@@ -1954,6 +1969,14 @@ if (!Global.gfx_skippipeline)
Render_taa(vp);
image = vp.taa_tex[m_taa.target()].get();
}
// the image before the blur is the one the reflections of the next frame take
if (m_ssrframe)
{
if (taa)
Store_Ssr_History(vp, *vp.taa_fb[m_taa.target()], GL_COLOR_ATTACHMENT0);
else
Store_Ssr_History(vp, *vp.main_fb, GL_COLOR_ATTACHMENT0);
}
if (motionblur)
{
if (taa)
@@ -1977,7 +2000,10 @@ if (!Global.gfx_skippipeline)
vp.msaa_fb->blit_to(vp.main_fb.get(), vp.width, vp.height, GL_DEPTH_BUFFER_BIT, GL_DEPTH_ATTACHMENT);
Render_ambient_occlusion(vp, *vp.main2_fb, aomode);
}
if (m_ssrframe)
Store_Ssr_History(vp, *vp.main2_fb, GL_COLOR_ATTACHMENT0);
}
m_ssrframe = false;
// 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
@@ -2170,12 +2196,11 @@ if (!Global.gfx_skippipeline)
setup_matrices();
// the light buffer holds what the last color pass left in it, in the view space of its camera, while the face is drawn
// in its own. the lights go in anew; the map is a cache of the surroundings which lasts for seconds, so the headlights
// of the vehicles around are left out of it and the cost of the pass does not depend on how many of them are on
// in its own: the sun goes in anew, for the fog and the clouds over the sky
Update_Lights(simulation::Lights, false);
// render
// environment...
// the sky and the clouds alone: the scenery is reflected by the screen space reflections (gfx.ssr), the environment map
// gives what they don't find
setup_drawing(true);
setup_shadow_empty_map();
@@ -2184,12 +2209,8 @@ if (!Global.gfx_skippipeline)
scene_ubs.inv_view = glm::inverse(glm::mat4{glm::mat3{m_renderpass.pass_camera.modelview()}});
scene_ubo->update(scene_ubs);
Render(&simulation::Environment);
// opaque parts...
// the passes after this one expect the state the opaque scenery left behind (depth writes on, no blending)
setup_drawing(false);
setup_shadow_bind_map();
scene_ubs.projection = OpenGLMatrices.data(GL_PROJECTION);
scene_ubo->update(scene_ubs);
Render(simulation::Region);
m_env_fb->unbind();
@@ -2276,9 +2297,8 @@ bool opengl46_renderer::Render_reflections(viewport_config &vp)
m_environmentupdatelocation = m_renderpass.pass_camera.position();
m_environmentupdateface = 0;
}
// an update draws the scene six times, which is too much for a single frame. its cost is spread
// by drawing one face of the map per frame, all of them as seen from the place the update began at.
// the first update is done in one go, as there's nothing to show in the meantime
// an update draws the sky six times. its cost is spread by drawing one face of the map per frame, all of them as seen
// from the place the update began at. the first update is done in one go, as there's nothing to show in the meantime
auto const lastface{ m_environmentready ? m_environmentupdateface : 5 };
glViewport(0, 0, gl::ENVMAP_SIZE, gl::ENVMAP_SIZE);
for (; m_environmentupdateface <= lastface; ++m_environmentupdateface)
@@ -4917,11 +4937,6 @@ void opengl46_renderer::walk_region_begin()
passcull = &m_shadowcull[ m_shadowcascade ];
m_shadowcasters.clear();
}
else if( ( m_renderpass.draw_mode == rendermode::reflections )
&& ( Global.gfx_gpucull_reflections )
&& ( Global.reflectiontune.fidelity >= 1 ) ) {
passcull = &m_reflectioncull;
}
}
Begin_Pass_Cull( passcull, ( passcull == &m_instancecull ) );
}
@@ -6463,6 +6478,90 @@ bool opengl46_renderer::Taa_View(viewport_config const &vp) const
return Global.gfx_taa() && vp.main && (vp.taa_tex[0] != nullptr) && (m_pfx_taa != nullptr);
}
// the screen space reflections of the main view trace the depth of the scene drawn before the colour and work out the distance of
// a surface from its depth the way a perspective projection with the far plane at infinity gives it, as the contact shadows do:
// not in the orthographic view of the editor
bool opengl46_renderer::Ssr_View(viewport_config const &vp) const
{
if ((Global.gfx_ssr <= 0) || (false == vp.main) || Global.bWireFrame || Global.gfx_skippipeline || (vp.msaa_fb == nullptr))
return false;
if ((vp.proj_type == viewport_config::vr_left) || (vp.proj_type == viewport_config::vr_right))
return false;
auto const &projection{m_renderpass.pass_camera.projection()};
return (projection[2][3] != 0.f) && (projection[2][2] == 0.f) && (projection[3][2] > 0.f);
}
void opengl46_renderer::Setup_Screen_Reflections(viewport_config &vp, bool const Temporal)
{
if (false == Ssr_History_Target(vp))
return;
if (false == m_contactshadows)
{
// the depth resolved apart from the buffer the colour pass draws to, as for the contact shadows
if (false == Resolved_Depth_Target(vp))
return;
vp.msaa_fb->bind();
auto const scissor{::glIsEnabled(GL_SCISSOR_TEST) == GL_TRUE};
if (scissor)
::glDisable(GL_SCISSOR_TEST);
::glBlitNamedFramebuffer(*vp.msaa_fb, *vp.clusters_depth_fb, 0, 0, vp.width, vp.height, 0, 0, vp.width, vp.height, GL_DEPTH_BUFFER_BIT, GL_NEAREST);
if (scissor)
::glEnable(GL_SCISSOR_TEST);
::glBindTextureUnit(gl::SCENE_DEPTH_TEX, vp.clusters_depth_tex->id);
}
::glBindTextureUnit(gl::SSR_HISTORY_TEX, vp.ssr_tex->id);
m_ssr.setup(scene_ubs, gfx::screen_reflections::steps(Global.gfx_ssr), false, 0.f, Temporal);
scene_ubo->update(scene_ubs);
m_ssractive = true;
}
// the finished image of the frame, before the motion blur, copied to the history the reflections of the next frame take their colour
// from, with its mipmaps for the rough surfaces
void opengl46_renderer::Store_Ssr_History(viewport_config &vp, gl::framebuffer &Source, GLenum const Attachment)
{
if (false == Ssr_History_Target(vp))
return;
m_ssrhistorytimer.begin();
auto const scissor{::glIsEnabled(GL_SCISSOR_TEST) == GL_TRUE};
if (scissor)
::glDisable(GL_SCISSOR_TEST);
Source.blit_to(vp.ssr_fb.get(), vp.width, vp.height, GL_COLOR_BUFFER_BIT, Attachment);
if (scissor)
::glEnable(GL_SCISSOR_TEST);
::glGenerateTextureMipmap(vp.ssr_tex->id);
m_ssrhistorytimer.end();
m_ssr.end_frame();
}
bool opengl46_renderer::Ssr_History_Target(viewport_config &vp)
{
if (vp.ssr_fb)
return true;
// a colour of three floats without a sign in 32 bits: half the memory of rgba16f, and the image of the scene has no alpha
auto texture{std::make_unique<opengl_texture>()};
texture->alloc_rendertarget(GL_R11F_G11F_B10F, GL_RGB, vp.width, vp.height, 1, 1, GL_CLAMP_TO_EDGE);
auto const levels{static_cast<int>(std::floor(std::log2(static_cast<float>(std::max({vp.width, vp.height, 1}))))) + 1};
// the levels below the first are made by glGenerateTextureMipmap() with each copy
::glTextureParameteri(texture->id, GL_TEXTURE_MAX_LEVEL, levels - 1);
::glTextureParameteri(texture->id, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
::glGenerateTextureMipmap(texture->id);
auto framebuffer{std::make_unique<gl::framebuffer>()};
framebuffer->attach(*texture, GL_COLOR_ATTACHMENT0);
if (false == framebuffer->is_complete())
{
ErrorLog("reflection history framebuffer setup failed, no screen space reflections");
Global.gfx_ssr = 0;
return false;
}
gl::framebuffer::unbind();
vp.ssr_tex = std::move(texture);
vp.ssr_fb = std::move(framebuffer);
vp.ssr_levels = levels;
m_ssr.reset();
WriteLog("screen space reflections: history " + std::to_string(vp.width) + " x " + std::to_string(vp.height) + ", " + std::to_string(levels) + " levels");
return true;
}
void opengl46_renderer::Render_ambient_occlusion(viewport_config &vp, gl::framebuffer &Target, int const Mode)
{
glDebug("ambient occlusion");