From f20b1a3071d6a6751632189a33914e5a1d6a9cae Mon Sep 17 00:00:00 2001 From: Hirek193 Date: Sun, 25 Oct 2026 13:54:49 +0100 Subject: [PATCH] Replace cubemap scenery reflections with SSR, add Vulkan ray traced reflections --- CMakeLists.txt | 2 + application/driveruipanels.cpp | 7 +- ci_shadervalidator/main.cpp | 11 + docs/opengl46-renderer.md | 55 +- gl/glsl_common.cpp | 1 + gl/ubo.h | 22 +- rendering/common/scenewalk.cpp | 3 +- rendering/common/scenewalk.h | 3 +- rendering/common/screenreflections.cpp | 135 ++++ rendering/common/screenreflections.h | 85 +++ rendering/opengl46/opengl46renderer.cpp | 161 ++++- rendering/opengl46/opengl46renderer.h | 26 +- rendering/vulkan14/vulkan14bindings.h | 17 +- rendering/vulkan14/vulkan14capabilities.cpp | 34 + rendering/vulkan14/vulkan14capabilities.h | 6 + rendering/vulkan14/vulkan14commands.cpp | 61 +- rendering/vulkan14/vulkan14commands.h | 2 + rendering/vulkan14/vulkan14context.h | 11 +- rendering/vulkan14/vulkan14debug.cpp | 23 +- rendering/vulkan14/vulkan14device.cpp | 31 + rendering/vulkan14/vulkan14device.h | 7 + rendering/vulkan14/vulkan14frame.cpp | 324 ++++++++- rendering/vulkan14/vulkan14geometry.cpp | 19 +- rendering/vulkan14/vulkan14geometry.h | 8 + rendering/vulkan14/vulkan14indirect.cpp | 7 +- rendering/vulkan14/vulkan14memory.cpp | 22 + rendering/vulkan14/vulkan14memory.h | 3 + rendering/vulkan14/vulkan14raytracing.cpp | 727 ++++++++++++++++++++ rendering/vulkan14/vulkan14raytracing.h | 157 +++++ rendering/vulkan14/vulkan14renderer.cpp | 3 +- rendering/vulkan14/vulkan14renderer.h | 21 + rendering/vulkan14/vulkan14scene.cpp | 67 +- rendering/vulkan14/vulkan14shaders.cpp | 22 +- rendering/vulkan14/vulkan14texture.h | 2 + shaders/ogl46/common.glsl | 8 + shaders/ogl46/envmapping.glsl | 79 +-- shaders/ogl46/light_common.glsl | 68 +- shaders/ogl46/ssr.glsl | 155 +++++ shaders/vk14/common.glsl | 13 +- shaders/vk14/envmapping.glsl | 79 +-- shaders/vk14/light_common.glsl | 74 +- shaders/vk14/rt_reflections.glsl | 147 ++++ shaders/vk14/ssr.glsl | 155 +++++ utilities/Globals.cpp | 19 + utilities/Globals.h | 17 +- 45 files changed, 2725 insertions(+), 174 deletions(-) create mode 100644 rendering/common/screenreflections.cpp create mode 100644 rendering/common/screenreflections.h create mode 100644 rendering/vulkan14/vulkan14raytracing.cpp create mode 100644 rendering/vulkan14/vulkan14raytracing.h create mode 100644 shaders/ogl46/ssr.glsl create mode 100644 shaders/vk14/rt_reflections.glsl create mode 100644 shaders/vk14/ssr.glsl diff --git a/CMakeLists.txt b/CMakeLists.txt index cd1c9684..bf441451 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -322,6 +322,7 @@ set(SOURCES "rendering/common/weatherstate.cpp" "rendering/common/exposure.cpp" "rendering/common/temporalaa.cpp" +"rendering/common/screenreflections.cpp" "gl/buffer.cpp" "gl/fence.cpp" "gl/pbo.cpp" @@ -461,6 +462,7 @@ if (WITH_VULKAN) "rendering/vulkan14/vulkan14particles.cpp" "rendering/vulkan14/vulkan14debug.cpp" "rendering/vulkan14/vulkan14commands.cpp" + "rendering/vulkan14/vulkan14raytracing.cpp" "ref/volk/volk.c") set(DEFINITIONS ${DEFINITIONS} "WITH_VULKAN") diff --git a/application/driveruipanels.cpp b/application/driveruipanels.cpp index a501e250..1b3aaaa8 100644 --- a/application/driveruipanels.cpp +++ b/application/driveruipanels.cpp @@ -1559,8 +1559,11 @@ debug_panel::render_section_settings() const { ImGui::PushStyleColor( ImGuiCol_Text, { Global.UITextColor.r, Global.UITextColor.g, Global.UITextColor.b, Global.UITextColor.a } ); ImGui::TextUnformatted( "Graphics" ); ImGui::PopStyleColor(); - // reflection fidelity - ImGui::SliderInt( ( std::to_string( Global.reflectiontune.fidelity ) + "###reflectionfidelity" ).c_str(), &Global.reflectiontune.fidelity, 0, 2, "Reflection fidelity" ); + // screen space reflections + { + char const *ssrlevels[] { "off", "low", "medium", "high" }; + ImGui::SliderInt( ( std::string( ssrlevels[ std::clamp( Global.gfx_ssr, 0, 3 ) ] ) + "###screenreflections" ).c_str(), &Global.gfx_ssr, 0, 3, "Reflections" ); + } ImGui::SliderInt( ( std::to_string( Global.gfx_shadow_rank_cutoff ) + "###shadowrankcutoff" ).c_str(), &Global.gfx_shadow_rank_cutoff, 1, 3, "Shadow ranks" ); if( ImGui::SliderFloat( ( to_string( std::abs( Global.gfx_shadow_angle_min ), 2 ) + "###shadowanglecutoff" ).c_str(), &Global.gfx_shadow_angle_min, -1.0, -0.2f, "Shadow angle cutoff" ) ) { Global.gfx_shadow_angle_min = quantize( Global.gfx_shadow_angle_min, 0.05f ); diff --git a/ci_shadervalidator/main.cpp b/ci_shadervalidator/main.cpp index 36eca132..171324d5 100644 --- a/ci_shadervalidator/main.cpp +++ b/ci_shadervalidator/main.cpp @@ -125,6 +125,17 @@ bool check_vk14(std::string const &Name) if (!check_text(gfx::preprocess_shader(gfx::shader_api::vulkan14, Name, "#define NO_MOTION_OUTPUT 1\n", {}, false, vk14_dir), "-V --target-env " + vulkan_env)) suc = false; } + // the ray traced reflections (gfx.raytracing): the renderer puts the extensions and REFLECTION_RAYS before the sources + // (vk14::binding_preamble()), the shaders which shade with light_common.glsl trace the rays + if (source.text.find("REFLECTION_RAYS_ACTIVE") != std::string::npos) + { + std::cout << "done, ray traced reflections..." << std::flush; + std::string const raytracing{ + "#extension GL_EXT_ray_query : require\n#extension GL_EXT_buffer_reference2 : require\n#extension GL_EXT_buffer_reference_uvec2 : require\n" + "#extension GL_EXT_nonuniform_qualifier : require\n#define REFLECTION_RAYS 1\n"}; + if (!check_text(gfx::preprocess_shader(gfx::shader_api::vulkan14, Name, raytracing, {}, false, vk14_dir), "-V --target-env " + vulkan_env)) + suc = false; + } std::cout << "done" << std::flush; return suc; } diff --git a/docs/opengl46-renderer.md b/docs/opengl46-renderer.md index cea18c86..15d0814d 100644 --- a/docs/opengl46-renderer.md +++ b/docs/opengl46-renderer.md @@ -229,6 +229,40 @@ order of the light buffer: the cab lamp, the headlights, then the lamps of model All three can be changed while the simulator runs, in the **Light clusters** debug window. +### Reflections + +The reflections of the scenery are found in the image (screen space reflections): the reflected ray of each pixel of a +shiny surface is followed through the depth of the scene drawn before its colour, and where it meets a surface, the colour +is taken from the image of the last frame (moved to where that surface is now; the cab is moved with the vehicle). What the +image doesn't hold (what is out of view, behind something else or behind the camera, the backs of things) reflects the +environment map, which now holds the sky and the clouds only, with the ground below the horizon in the colour of +`gfx.ambient.groundalbedo`. The scenery is no longer drawn into the environment map, so its cost is gone. The image of the +last frame is copied after the anti-aliasing and the ambient occlusion and before the motion blur; the first frame, a jump +of the camera of more than 50 metres and a change of the size of the window go without it (the sky only). Like the contact +shadows they need the depth of the whole scene before its colour, so they turn on the depth pass of `gfx.depthprepass.scene` +(see its cost below), and they are there only in the main view, with the post processing on and not in the orthographic +view of the editor. The rough surfaces read a blurrier copy of the image; those rougher than `gfx.ssr.roughness` reflect +the environment map only. + +| Entry | Default | Meaning | +|---|---|---| +| `gfx.ssr` | `medium` | `no`/`low`/`medium`/`high` (or 0..3): steps of the rays, 16, 32 or 64. `no`: the environment map only (the sky) | +| `gfx.ssr.range` | `250` | 10..2000: the longest ray, metres | +| `gfx.ssr.roughness` | `0.6` | 0..1: the roughest surface (perceptual roughness) that reflects the scene | +| `gfx.raytracing yes/no` | `no` | Vulkan renderer only, on a graphics card with ray queries (`VK_KHR_ray_query`): what the screen space reflections don't find is traced through the scenery around the camera instead of reflecting the sky. Read at the start; with `gfx.ssr no` all the reflections of the scenery are traced | +| `gfx.raytracing.range` | `500` | 10..5000: the scenery within this distance from the camera takes part in the traced reflections, and the rays are this long, metres | + +The traced reflections hold what the environment map held before, as `gfx.reflections.fidelity` says: the tracks and the +terrain (0), the models within `gfx.reflections.range_instances` (1) and the vehicles within +`gfx.reflections.range_vehicles` (2, the default now), except the vehicle of the cab. `gfx.reflections.fidelity` and the +two ranges have no other use now; `gfx.reflections.framerate` still says how often the environment map (the sky) is +drawn. The point hit is lit by the sky, by the sun where nothing stands between them (a ray of its own) and by its own light; the lamps and headlights don't light it. Each part of a model gets its structure +for the tracing the first time it's met (a few a frame, so a new place fills in over some frames), and loses it when it +isn't met for half a minute. + +All the entries can be changed while the simulator runs, in the **Sky and exposure** debug window (the ray tracing can be +turned off and on there once it was started). + ### Weather The clouds, the fog and the stars are drawn from the weather of the simulation (a `weather` section of the scenery, or its @@ -312,8 +346,7 @@ are no longer loaded. Many models of the scenery (trees, bushes, poles, fences: models without lights and animations) are drawn in batches, one batch per model and level of detail. Which of them are drawn, and at which level of detail, can be worked out on the processor (as before) or on the graphics card. On the graphics card the processor no longer goes through the models one by -one, so with dense vegetation it is much less loaded. The main view, the three cascades of the sun shadows and the faces of -the environment map (reflections), the shadow maps of headlights and the scenery of the shadow maps of scenery lamps are drawn +one, so with dense vegetation it is much less loaded. The main view, the three cascades of the sun shadows, the shadow maps of headlights and the scenery of the shadow maps of scenery lamps are drawn this way, and so are the sleepers of the tracks with a `sleepermodel` in the main view; the shadow map of the cab lamp, the cab and other views go as before. @@ -325,7 +358,7 @@ cab and other views go as before. | `gfx.depthprepass yes/no` | `yes` | with `gfx.gpucull yes`: the depth of the models is drawn first, so the colour is worked out only for what stays in view (dense foliage) | | `gfx.gpucull.multidraw yes/no` | `yes` | with `gfx.gpucull yes`: parts of models with the same material (and texture variant) in the same model are drawn with one call (multi draw), see below | | `gfx.gpucull.shadows yes/no` | `yes` | with `gfx.gpucull yes` or `compare`: the models of the cascades of the sun shadows too (each cascade with its own camera and the distances from the real camera, as before; models none of whose materials cast shadows are left out at once) | -| `gfx.gpucull.reflections yes/no` | `yes` | with `gfx.gpucull yes` or `compare`: the models of the faces of the environment map too (its own range of models, as before) | +| `gfx.gpucull.reflections yes/no` | `yes` | no longer used: the environment map holds the sky only (see Reflections) | | `gfx.gpucull.shadows.headlights yes/no` | `yes` | with `gfx.gpucull yes` or `compare`: the models of the shadow maps of headlights too (each map tested against the beam it is drawn with, to its reach, the distances from the real camera, as before) | | `gfx.gpucull.shadows.lamps yes/no` | `yes` | with `gfx.gpucull yes` or `compare`: the models of the scenery part of the shadow maps of scenery lamps too (each map tested against the reach of its lamp, as before); the part with the vehicles has no models of the scenery | | `gfx.gpucull.sleepers yes/no` | `yes` | with `gfx.gpucull yes` or `compare`: the sleepers of the tracks (`sleepermodel`) of the main view too, each one tested against the view and given its level of detail by its distance, as before (the tracks within `sleeperdistance` are chosen as before); they also take the occlusion test, the depth prepass and the two phases of the models | @@ -345,7 +378,7 @@ is not worked out with the depth prepass on). Models whose material is not one o like) are left out of the depth prepass and drawn as before; with the depth prepass on, they don't hide the others either. All of these can be changed while the simulator runs, in the **Instance culling** debug window. -The shadows and the reflections take the same tests as before (which models are in the cascade or the face, their distance +The shadows take the same tests as before (which models are in the cascade or the map, their distance and level of detail), without the occlusion test, the depth prepass and the two phases, which belong to the main view. The copies of the models are sent to the graphics card once and serve all of these views. In the shadows the parts with the same texture for the alpha test (or with none) in the same model go in one call with `gfx.gpucull.multidraw yes`, whatever @@ -409,8 +442,8 @@ read, but it only takes effect with `gfx.lights.clustered no`. - **Outside the beam texture a headlight adds nothing.** Before, the edge of the texture was stretched beyond the beam, which could leave a faint glow around vehicles. This also applies with `gfx.lights.clustered no`. -- **Reflections (environment map)** are lit with the sun as seen from the reflection itself, and vehicle - headlights no longer light the scene in them. +- **Reflections** of the scenery come from the image (screen space reflections, see above) instead of the + environment map, which holds the sky only. - **Motion blur and ambient occlusion** read the real depth of the scene. Before, the depth texture was read in a way the specification leaves undefined, and the result depended on the driver. - **Lines are 1 pixel wide**, as they always were on this kind of context. The widths the code asked for @@ -455,10 +488,9 @@ editor goes through the first `dynamiclights` headlights, the reflections throug made them — fewer than the draws with `gfx.gpucull.multidraw yes` — and the processor time of the whole step) and the timings `instance cull:`, `occlusion depth:` (the depth pyramid of the test) and `depth prepass:`; in two phases each of them adds in brackets the time of the second phase, of the second pyramid and of the depth of the models drawn first. With `compare` it shows how many models both counted and how many levels of detail - differ. With `gfx.gpucull.shadows` and `gfx.gpucull.reflections` the same rows (without the occlusion test and the - prepass) follow for each cascade, `gpu cull shadows 1:` to `3:` (1 the nearest), and for the environment map, - `gpu cull reflections, last face:` (the last face of the map drawn, as the map is drawn now and then), with the timings - `instance cull shadows 1:` … and `instance cull reflections, last face:`. With `gfx.gpucull.shadows.headlights` and + differ. With `gfx.gpucull.shadows` the same rows (without the occlusion test and the + prepass) follow for each cascade, `gpu cull shadows 1:` to `3:` (1 the nearest), with the timings + `instance cull shadows 1:` …. With `gfx.gpucull.shadows.headlights` and `gfx.gpucull.shadows.lamps` they follow for each map of headlights drawn so far, `gpu cull headlight shadows 1:` … (by the slot of the map), and for the scenery of the maps of lamps, `gpu cull lamp shadows, scenery 1:` and `2:` (the first and the second drawn in a frame), each with the numbers of its last drawing and the timings `instance cull headlight shadows 1:` @@ -490,7 +522,8 @@ editor goes through the first `dynamiclights` headlights, the reflections throug (`frame setup`, `sun shadows 1` to `3` — 1 the nearest, with the culling of the cascade —, `reflections`, `lamp and headlight shadows`, `clear, clusters, lights`, `clouds, fog volume`, `sky`, `scene depth` and `contact shadows depth` when drawn, `colour, opaque` — with the culling, the occlusion depth and the depth prepass of the instances —, - `colour, translucent and cab`, `resolve, ambient occlusion, motion blur`, `exposure, bloom`, `tonemapping, output`, `ui`, + `colour, translucent and cab`, `resolve, ambient occlusion, motion blur` — with the copy of the image for the + reflections —, `exposure, bloom`, `tonemapping, output`, `ui`, and `other viewports` when there are more of them; only those in the context of the main window are counted). The timings under `gpu steps:` are measured each from its own pair of marks, and the first mark of a pair can be put while the graphics card still works on what came before, so a short step right after a heavy one may show some of that work; the parts don't. diff --git a/gl/glsl_common.cpp b/gl/glsl_common.cpp index e95a7020..e00810be 100644 --- a/gl/glsl_common.cpp +++ b/gl/glsl_common.cpp @@ -86,6 +86,7 @@ void gl::glsl_common_setup() "#define CLOUD_COVERAGE_TEX " + std::to_string(CLOUD_COVERAGE_TEX) + "\n" + "#define CLOUD_HISTORY_TEX " + std::to_string(CLOUD_HISTORY_TEX) + "\n" + "#define SCENE_DEPTH_TEX " + std::to_string(SCENE_DEPTH_TEX) + "\n" + + "#define SSR_HISTORY_TEX " + std::to_string(SSR_HISTORY_TEX) + "\n" + "#define WEATHER_UBO_BINDING " + std::to_string(WEATHER_UBO_BINDING) + "\n" + "#define WEATHER_SCRATCH_SSBO_BINDING " + std::to_string(WEATHER_SCRATCH_SSBO_BINDING) + "\n" + "#define MAX_CLOUD_LAYERS " + std::to_string(MAX_CLOUD_LAYERS) + "\n" + diff --git a/gl/ubo.h b/gl/ubo.h index df42abca..fa7563cb 100644 --- a/gl/ubo.h +++ b/gl/ubo.h @@ -78,6 +78,9 @@ namespace gl // the depth of the opaque scene of the main view drawn before its colour, resolved, for the contact shadows (light_common.glsl). // bound directly, like the ones above const size_t SCENE_DEPTH_TEX = MAX_TEXTURES + HELPER_TEXTURES + 11; + // the image of the last frame of the main view with its mipmaps, which the screen space reflections take their colour from + // (light_common.glsl, gl::scene_ubs::ssr_params). bound directly, like the ones above + const size_t SSR_HISTORY_TEX = MAX_TEXTURES + HELPER_TEXTURES + 12; struct scene_ubs { @@ -104,9 +107,26 @@ namespace gl // 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; + // the screen space reflections (gfx.ssr, gfx::screen_reflections), set only for the colour pass of the main view, zero + // elsewhere: the view space of the pass to the clip space of the frame whose image the history (SSR_HISTORY_TEX) holds, for + // the world standing still and for the cab with its vehicle, which moves with the camera + glm::mat4 ssr_reprojection; + glm::mat4 ssr_reprojection_cab; + // the view space of the pass to the box around the vehicle of the cab, -1 .. 1 inside: what is in it is taken to the history + // with ssr_reprojection_cab + glm::mat4 ssr_cab_box; + // x: steps of a ray (0: off), y: the longest ray, metres, z: the roughest surface they're traced for, w: the noise of the + // frame, 0 .. 1 + glm::vec4 ssr_params; + // x: the last level of the mipmaps of the history, y: 1 with the box of the cab, z: 1 when the ray tracing fills in what the + // screen doesn't hold (the vulkan renderer, gfx.raytracing), w: the longest traced ray, metres + glm::vec4 ssr_params2; + // the reflection of the surroundings the screen space reflections and the ray tracing don't find, for every pass: x: albedo + // of the ground below the horizon of the environment map (gfx.ambient.groundalbedo), yzw unused + glm::vec4 env_params; }; - static_assert(sizeof(scene_ubs) == 448 + 80 * MAX_HEADLIGHT_SHADOWS, "bad size of ubs"); + static_assert(sizeof(scene_ubs) == 688 + 80 * MAX_HEADLIGHT_SHADOWS, "bad size of ubs"); const size_t MAX_PARAMS = 3; diff --git a/rendering/common/scenewalk.cpp b/rendering/common/scenewalk.cpp index 6948f0b9..cd90d82c 100644 --- a/rendering/common/scenewalk.cpp +++ b/rendering/common/scenewalk.cpp @@ -638,9 +638,10 @@ void scene_walk::Render(cell_sequence::iterator First, cell_sequence::iterator L } } if( Global.reflectiontune.fidelity >= 2 ) { - // opaque parts of vehicles + // opaque parts of vehicles, but the one the backend leaves out (the vehicle of the cab, seen from the inside) for( auto *path : cell->m_paths ) { for( auto *dynamic : path->Dynamics ) { + if( dynamic == m_shadowskipvehicle ) { continue; } Render( dynamic ); } } diff --git a/rendering/common/scenewalk.h b/rendering/common/scenewalk.h index 7d73dbaa..e277133c 100644 --- a/rendering/common/scenewalk.h +++ b/rendering/common/scenewalk.h @@ -515,7 +515,8 @@ class scene_walk // the shadow pass draws the kept scenery part of the map of a lamp: no vehicles, and not the model of the lamp itself bool m_cubescenery{false}; TAnimModel const *m_cubeskipinstance{nullptr}; - // the shadow pass leaves this vehicle out (the one the headlights of the map being drawn belong to) + // the shadow pass leaves this vehicle out (the one the headlights of the map being drawn belong to), and so does the walk of the + // reflection mode (the vehicle of the cab, for the ray traced reflections of the vulkan renderer) TDynamicObject const *m_shadowskipvehicle{nullptr}; // what the picking passes drew, by the index of their colours std::vector m_pickcontrolsitems; diff --git a/rendering/common/screenreflections.cpp b/rendering/common/screenreflections.cpp new file mode 100644 index 00000000..a00ad7f0 --- /dev/null +++ b/rendering/common/screenreflections.cpp @@ -0,0 +1,135 @@ +/* +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/common/screenreflections.h" + +#include "vehicle/DynObj.h" +#include "gl/ubo.h" +#include "utilities/Globals.h" + +namespace +{ + +// the camera moved farther than this since the frame of the history (another camera, another vehicle, a jump): the history is +// no good for the reflections +constexpr double CAMERA_JUMP{50.0}; +// the box around the vehicle of the cab is this much larger than the vehicle on each side, metres +constexpr double CAB_BOX_MARGIN{0.5}; + +} // namespace + +void gfx::screen_reflections::begin_frame(glm::mat4 const &Projection, glm::dmat3 const &View, glm::dvec3 const &Camera, TDynamicObject const *Cab, glm::ivec2 const &Size, int const Levels) +{ + ++m_frame; + m_levels = std::max(Levels, 1); + m_current.projection = glm::dmat4{Projection}; + m_current.view = View; + m_current.camera = Camera; + m_current.vehicle = Cab; + m_current.cab = glm::dmat4{1.0}; + if (Cab != nullptr) + { + // as Render_cab() places it + m_current.cab = glm::translate(glm::dmat4{1.0}, Cab->GetPosition() - Camera) * Cab->mMatrix; + } + m_current.size = Size; + m_current.valid = true; + + m_historyvalid = m_history.valid && (m_history.size == Size) && (glm::length(Camera - m_history.camera) < CAMERA_JUMP); + m_hascab = false; + if (false == m_historyvalid) + { + m_reprojection = glm::mat4{1.f}; + m_reprojectioncab = glm::mat4{1.f}; + m_cabbox = glm::mat4{1.f}; + return; + } + // the view of this frame to the world around the camera, the world around the camera of the history, its view and projection + auto const toworld{glm::dmat4{glm::inverse(View)}}; + m_reprojection = glm::mat4{m_history.projection * glm::dmat4{m_history.view} * glm::translate(glm::dmat4{1.0}, Camera - m_history.camera) * toworld}; + // the cab goes with its vehicle: the view of this frame to the cab, to the world around the camera of the history + if ((Cab != nullptr) && (Cab == m_history.vehicle)) + { + m_reprojectioncab = glm::mat4{m_history.projection * glm::dmat4{m_history.view} * m_history.cab * glm::inverse(m_current.cab) * toworld}; + // the vehicle stands on the origin of its model, along z, up to its height + auto const &dimensions{Cab->MoverParameters->Dim}; + glm::dvec3 const lower{-0.5 * dimensions.W - CAB_BOX_MARGIN, -CAB_BOX_MARGIN, -0.5 * dimensions.L - CAB_BOX_MARGIN}; + glm::dvec3 const upper{0.5 * dimensions.W + CAB_BOX_MARGIN, dimensions.H + CAB_BOX_MARGIN, 0.5 * dimensions.L + CAB_BOX_MARGIN}; + auto const centre{0.5 * (lower + upper)}; + auto const halfsize{glm::max(0.5 * (upper - lower), glm::dvec3{0.1})}; + auto const tobox{glm::scale(glm::dmat4{1.0}, 1.0 / halfsize) * glm::translate(glm::dmat4{1.0}, -centre)}; + m_cabbox = glm::mat4{tobox * glm::inverse(m_current.cab) * toworld}; + m_hascab = true; + } + else + { + m_reprojectioncab = m_reprojection; + m_cabbox = glm::mat4{1.f}; + } +} + +void gfx::screen_reflections::end_frame() +{ + if (m_current.valid) + { + m_history = m_current; + } +} + +void gfx::screen_reflections::setup(gl::scene_ubs &Scene, float const Steps, bool const Raytracing, float const Raytracingrange, bool const Temporal) const +{ + clear(Scene); + auto const screen{m_historyvalid && (Steps > 0.f)}; + if ((false == screen) && (false == Raytracing)) + { + return; + } + Scene.ssr_reprojection = m_reprojection; + Scene.ssr_reprojection_cab = m_reprojectioncab; + Scene.ssr_cab_box = m_cabbox; + Scene.ssr_params = glm::vec4{screen ? Steps : 0.f, Global.gfx_ssr_range, Global.gfx_ssr_roughness, Temporal ? static_cast(m_frame % 16) : 0.f}; + Scene.ssr_params2 = glm::vec4{static_cast(m_levels - 1), m_hascab ? 1.f : 0.f, Raytracing ? 1.f : 0.f, Raytracingrange}; +} + +void gfx::screen_reflections::clear(gl::scene_ubs &Scene) +{ + Scene.ssr_reprojection = glm::mat4{1.f}; + Scene.ssr_reprojection_cab = glm::mat4{1.f}; + Scene.ssr_cab_box = glm::mat4{1.f}; + Scene.ssr_params = glm::vec4{0.f}; + Scene.ssr_params2 = glm::vec4{0.f}; +} + +float gfx::screen_reflections::steps(int const Level) +{ + switch (Level) + { + case 1: return 16.f; + case 2: return 32.f; + case 3: return 64.f; + default: return 0.f; + } +} + +void gfx::screen_reflections::draw_debug_ui(char const *Raytracing) const +{ + char const *levels[]{"no", "low (16 steps)", "medium (32 steps)", "high (64 steps)"}; + ImGui::Combo("screen space reflections (gfx.ssr)", &Global.gfx_ssr, levels, IM_ARRAYSIZE(levels)); + ImGui::SliderFloat("longest ray, m (gfx.ssr.range)", &Global.gfx_ssr_range, 10.f, 2000.f, "%.0f", ImGuiSliderFlags_Logarithmic); + ImGui::SliderFloat("roughest surface reflecting the scene (gfx.ssr.roughness)", &Global.gfx_ssr_roughness, 0.f, 1.f, "%.2f"); + if (Global.gfx_ssr > 0) + { + ImGui::Text("image of the last frame: %s", m_historyvalid ? "in use" : "none (first frame, camera jump or new size)"); + } + if (Raytracing != nullptr) + { + ImGui::TextUnformatted(Raytracing); + } +} diff --git a/rendering/common/screenreflections.h b/rendering/common/screenreflections.h new file mode 100644 index 00000000..6ac81f31 --- /dev/null +++ b/rendering/common/screenreflections.h @@ -0,0 +1,85 @@ +/* +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/. +*/ + +// the screen space reflections of the main view (gfx.ssr), the part shared by the renderers: the matrices which take the points +// the rays hit in a frame to the place they had in the image of the frame kept for the reflections (the history: the finished +// image of the last frame the view was drawn in, with its mipmaps), and the parameters of the shaders (gl::scene_ubs::ssr_*). +// +// the rays go through the depth of the scene drawn before the colour (light_common.glsl, ssr.glsl). the world is taken to stand +// still; what lies in the box around the vehicle of the cab moves with the camera, and is taken to the history with the matrix of +// the cab. the other vehicles moving leave their reflections a frame behind + +#pragma once + +#include + +class TDynamicObject; + +namespace gl +{ +struct scene_ubs; +} + +namespace gfx +{ + +class screen_reflections +{ + public: + // the frame of the colour pass of the main view about to be drawn: Projection as it is drawn (with the offset of the temporal + // anti-aliasing), View the rotation of the camera (the axes of the world to those of the view), Camera its place, Cab the vehicle + // whose cab is drawn (or null), Size of the images of the pass, Levels the mipmaps of the history + void begin_frame(glm::mat4 const &Projection, glm::dmat3 const &View, glm::dvec3 const &Camera, TDynamicObject const *Cab, glm::ivec2 const &Size, int const Levels); + // the image of the frame begun last went to the history + void end_frame(); + // the history is dropped (the renderer made its images anew, the view changed size) + void reset() + { + m_history.valid = false; + } + // the history holds an image the frame begun last can take the reflections from + bool history_valid() const + { + return m_historyvalid; + } + // the parameters of the shaders for the frame begun last: Steps of the rays (0: none), whether the ray tracing fills in what the + // screen doesn't hold (Raytracing, the longest traced ray Raytracingrange) and whether the noise of the steps changes from frame + // to frame (Temporal, with the temporal anti-aliasing). the rays of the screen are off when the history can't be used + void setup(gl::scene_ubs &Scene, float const Steps, bool const Raytracing, float const Raytracingrange, bool const Temporal) const; + // the parameters with the reflections off, for the other passes + static void clear(gl::scene_ubs &Scene); + // the steps of a ray at a level of gfx.ssr (1 low, 2 medium, 3 high), 0: off + static float steps(int const Level); + // the settings of the reflections in a debug window, with a line on the ray tracing (null: the renderer has none) + void draw_debug_ui(char const *Raytracing) const; + + private: + struct frame + { + glm::dmat4 projection{1.0}; // as drawn + glm::dmat3 view{1.0}; + glm::dvec3 camera{0.0}; + glm::dmat4 cab{1.0}; // the cab to the world around the camera + TDynamicObject const *vehicle{nullptr}; + glm::ivec2 size{0}; + bool valid{false}; + }; + + frame m_history; // of the image in the history + frame m_current; // of the frame begun last + glm::mat4 m_reprojection{1.f}; + glm::mat4 m_reprojectioncab{1.f}; + glm::mat4 m_cabbox{1.f}; + bool m_hascab{false}; + int m_levels{1}; + unsigned int m_frame{0}; + bool m_historyvalid{false}; +}; + +} // namespace gfx diff --git a/rendering/opengl46/opengl46renderer.cpp b/rendering/opengl46/opengl46renderer.cpp index ef3102cc..163b83d0 100644 --- a/rendering/opengl46/opengl46renderer.cpp +++ b/rendering/opengl46/opengl46renderer.cpp @@ -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( *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( *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()}; + texture->alloc_rendertarget(GL_R11F_G11F_B10F, GL_RGB, vp.width, vp.height, 1, 1, GL_CLAMP_TO_EDGE); + auto const levels{static_cast(std::floor(std::log2(static_cast(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()}; + 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"); diff --git a/rendering/opengl46/opengl46renderer.h b/rendering/opengl46/opengl46renderer.h index 3fb212d9..ae19cf2f 100644 --- a/rendering/opengl46/opengl46renderer.h +++ b/rendering/opengl46/opengl46renderer.h @@ -47,6 +47,7 @@ http://mozilla.org/MPL/2.0/. #include "rendering/opengl46/opengl46shadowfit.h" #include "rendering/common/scenewalk.h" #include "rendering/common/temporalaa.h" +#include "rendering/common/screenreflections.h" // bare-bones render controller, in lack of anything better yet class opengl46_renderer : public gfx_renderer, public gfx::scene_walk { @@ -272,6 +273,12 @@ class opengl46_renderer : public gfx_renderer, public gfx::scene_walk { std::array, 2> taa_tex; std::array, 2> taa_fb; + // the screen space reflections (main viewport only): the finished image of the last frame with its mipmaps, which they take + // their colour from (Ssr_History_Target()), and the number of its levels. made when it is first needed + std::unique_ptr ssr_tex; + std::unique_ptr ssr_fb; + int ssr_levels { 0 }; + // LDR backbuffer for offscreen rendering std::unique_ptr backbuffer_fb; std::unique_ptr backbuffer_tex; @@ -459,6 +466,14 @@ class opengl46_renderer : public gfx_renderer, public gfx::scene_walk { // the temporal anti-aliasing of the main view (gfx.antialiasing taa), and whether the viewport has it void Render_taa(viewport_config &vp); bool Taa_View(viewport_config const &vp) const; + // the screen space reflections (gfx.ssr): whether the colour pass of the viewport gets them (they need the depth pass of the + // scene), the depth resolved for them with the history bound and the parameters of the pass set, the finished image of the + // frame copied to the history (from the colour attachment Attachment of Source), and the target of the history made when it + // isn't there yet (false if it can't be made) + bool Ssr_View(viewport_config const &vp) const; + void Setup_Screen_Reflections(viewport_config &vp, bool const Temporal); + void Store_Ssr_History(viewport_config &vp, gl::framebuffer &Source, GLenum const Attachment); + bool Ssr_History_Target(viewport_config &vp); bool init_viewport(viewport_config &vp); @@ -556,10 +571,9 @@ class opengl46_renderer : public gfx_renderer, public gfx::scene_walk { // with the one of the cpu (2) opengl46_instance_cull m_instancecull; unsigned int m_instancecullframe { ~0u }; // frame it last ran in - // the cullings of the cascades of the sun shadows and of the faces of the environment map (Global.gfx_gpucull_shadows, - // _reflections), with records and programs shared with m_instancecull + // the cullings of the cascades of the sun shadows (Global.gfx_gpucull_shadows), with records and programs shared with + // m_instancecull std::array m_shadowcull; - opengl46_instance_cull m_reflectioncull; // the cullings of the maps of headlights, by the slot of the map, and of the scenery parts of the maps of lamps, by their order // among those drawn in a frame: the passes of a frame don't share the buffers of a culling. made when first needed std::vector> m_headlightcull; @@ -695,6 +709,12 @@ class opengl46_renderer : public gfx_renderer, public gfx::scene_walk { // a vehicle drawn in the colour pass of the last frame moved, and one drawn in this frame did (motion blur, Render_pass()) bool m_vehiclesmoved { true }; gl::gpu_timer m_contactshadowstimer; // resolve of the depth + // the screen space reflections of the main view: the matrices and the parameters, set up for the colour pass under way, begun + // for the frame (the history written at its end), and the copy of the image to the history with its mipmaps + gfx::screen_reflections m_ssr; + bool m_ssractive { false }; + bool m_ssrframe { false }; + gl::gpu_timer m_ssrhistorytimer; renderpass_config m_colorpass; // parametrs of most recent color pass std::array m_shadowpass; // parametrs of most recent shadowmap pass for each of csm stages TSubModel const *m_pickcontrolitem{nullptr}; diff --git a/rendering/vulkan14/vulkan14bindings.h b/rendering/vulkan14/vulkan14bindings.h index fc1ccd40..94ea4caa 100644 --- a/rendering/vulkan14/vulkan14bindings.h +++ b/rendering/vulkan14/vulkan14bindings.h @@ -69,7 +69,9 @@ constexpr std::uint32_t LOCAL_SHADOW_LAMPS_TEX = 22; constexpr std::uint32_t HEADLIGHT_SHADOW_TEX = 23; // the depth of the opaque scene drawn before its colour (the stage P3: the contact shadows; the soft particles of P6) constexpr std::uint32_t SCENE_DEPTH_TEX = 24; -constexpr std::uint32_t PASS_BINDING_COUNT = 25; +// the image of the last frame of the main view with its mipmaps, which the screen space reflections take their colour from +constexpr std::uint32_t SSR_HISTORY_TEX = 25; +constexpr std::uint32_t PASS_BINDING_COUNT = 26; // the offsets of the bindings above with dynamic offsets, in the order of the bindings constexpr std::uint32_t PASS_DYNAMIC_OFFSETS = 6; // the textures of a material take the bindings of their units (#pragma texture(name, unit, ...)), 0 .. MAX_TEXTURES - 1 @@ -94,6 +96,14 @@ constexpr std::uint32_t POST_EXPOSURE_SSBO = 4; // those the shader declares from 0 on constexpr std::uint32_t COMPUTE_SET = 1; +// the ray traced reflections (gfx.raytracing, vk14::ray_tracing): set 2 of the pipelines of the scene, one for each frame in flight, +// written with the frame: the acceleration structure of the scenery around the camera, the records of its instances, and the +// textures they read (an array of as many as the device allows, partly bound, the last binding) +constexpr std::uint32_t RT_SET = 2; +constexpr std::uint32_t RT_SCENE = 0; +constexpr std::uint32_t RT_RECORDS = 1; +constexpr std::uint32_t RT_TEXTURES = 2; + // the push constants of the graphics pipelines of the scene and of the post-processing (vk14::draw_constants): the draw, and // the parameters of the special shaders (the celestial bodies, the post-processing) constexpr std::uint32_t PUSH_CONSTANTS_SIZE = 112; @@ -115,7 +125,8 @@ namespace vk14 { // the bindings above as #define lines, with the constants of the game the shaders share with the opengl renderer (the sizes of -// the blocks, the cluster grid), and MASZYNA_CONSTANTS, which tells shaders/vk14/common.glsl not to write its own -std::string binding_preamble(); +// the blocks, the cluster grid), and MASZYNA_CONSTANTS, which tells shaders/vk14/common.glsl not to write its own. Raytracing: +// the shaders of the scene trace the reflections the screen doesn't hold (REFLECTION_RAYS, with the extensions it needs) +std::string binding_preamble(bool const Raytracing = false); } // namespace vk14 diff --git a/rendering/vulkan14/vulkan14capabilities.cpp b/rendering/vulkan14/vulkan14capabilities.cpp index b9b31ea4..94dc7898 100644 --- a/rendering/vulkan14/vulkan14capabilities.cpp +++ b/rendering/vulkan14/vulkan14capabilities.cpp @@ -10,6 +10,7 @@ http://mozilla.org/MPL/2.0/. #include "stdafx.h" #include "rendering/vulkan14/vulkan14capabilities.h" +#include #include namespace vk14 @@ -239,6 +240,38 @@ void query_capabilities(VkPhysicalDevice Physical, feature_chain &Queried, capab Caps.sampler_filter_minmax = (Queried.v12.samplerFilterMinmax == VK_TRUE); Caps.shader_output_layer = (Queried.v12.shaderOutputLayer == VK_TRUE); + // the ray queries: the structures of the extensions are asked about only where the device has the extensions + if (hasextension(VK_KHR_ACCELERATION_STRUCTURE_EXTENSION_NAME) && hasextension(VK_KHR_RAY_QUERY_EXTENSION_NAME) && hasextension(VK_KHR_DEFERRED_HOST_OPERATIONS_EXTENSION_NAME)) + { + VkPhysicalDeviceAccelerationStructureFeaturesKHR acceleration{}; + acceleration.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ACCELERATION_STRUCTURE_FEATURES_KHR; + VkPhysicalDeviceRayQueryFeaturesKHR rayquery{}; + rayquery.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_QUERY_FEATURES_KHR; + acceleration.pNext = &rayquery; + VkPhysicalDeviceFeatures2 features{}; + features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; + features.pNext = &acceleration; + vkGetPhysicalDeviceFeatures2(Physical, &features); + VkPhysicalDeviceAccelerationStructurePropertiesKHR accelerationproperties{}; + accelerationproperties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ACCELERATION_STRUCTURE_PROPERTIES_KHR; + VkPhysicalDeviceVulkan12Properties indexing{}; + indexing.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_PROPERTIES; + indexing.pNext = &accelerationproperties; + VkPhysicalDeviceProperties2 properties2{}; + properties2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2; + properties2.pNext = &indexing; + vkGetPhysicalDeviceProperties2(Physical, &properties2); + Caps.scratch_alignment = std::max(accelerationproperties.minAccelerationStructureScratchOffsetAlignment, 1); + // the textures of the scenery the reflections are traced through, beside the other bindings of the stage + // (combined image samplers: they count as samplers too) + auto const textures{std::min({Caps.limits.maxPerStageDescriptorSampledImages, Caps.limits.maxDescriptorSetSampledImages, Caps.limits.maxPerStageDescriptorSamplers, + Caps.limits.maxDescriptorSetSamplers, Caps.limits.maxPerStageResources})}; + Caps.max_indexed_textures = std::min(textures > 64 ? textures - 64 : 0, 65536); + Caps.ray_query = (acceleration.accelerationStructure == VK_TRUE) && (rayquery.rayQuery == VK_TRUE) && (Queried.v12.bufferDeviceAddress == VK_TRUE) && + (Queried.v12.runtimeDescriptorArray == VK_TRUE) && (Queried.v12.descriptorBindingPartiallyBound == VK_TRUE) && (Queried.v12.descriptorBindingVariableDescriptorCount == VK_TRUE) && + (Queried.v12.shaderSampledImageArrayNonUniformIndexing == VK_TRUE) && (Caps.max_indexed_textures >= 256); + } + // can the renderer run on it if (!Caps.ext_swapchain) { @@ -294,6 +327,7 @@ std::string capability_report(capabilities const &Caps) line(std::string("shader output layer ") + flag(Caps.shader_output_layer)); line(std::string("fill mode non solid ") + flag(Caps.fill_mode_non_solid)); line(std::string("pipeline statistics ") + flag(Caps.pipeline_statistics_query)); + line(std::string("ray queries ") + flag(Caps.ray_query)); return report; } diff --git a/rendering/vulkan14/vulkan14capabilities.h b/rendering/vulkan14/vulkan14capabilities.h index bc688282..dc347828 100644 --- a/rendering/vulkan14/vulkan14capabilities.h +++ b/rendering/vulkan14/vulkan14capabilities.h @@ -93,6 +93,12 @@ struct capabilities bool pipeline_statistics_query{false}; bool sampler_filter_minmax{false}; bool shader_output_layer{false}; + // ray queries in the shaders with what they need (VK_KHR_acceleration_structure, VK_KHR_ray_query, VK_KHR_deferred_host_operations, + // the device addresses of buffers and arrays of textures indexed in the shaders), for the ray traced reflections (gfx.raytracing). + // the alignment of the scratch memory of the builds of acceleration structures, and the most textures the shading may index + bool ray_query{false}; + std::uint32_t scratch_alignment{256}; + std::uint32_t max_indexed_textures{0}; // empty when the renderer can run on the device, otherwise one line per missing thing std::vector missing; diff --git a/rendering/vulkan14/vulkan14commands.cpp b/rendering/vulkan14/vulkan14commands.cpp index 5038a6d7..ef23aac9 100644 --- a/rendering/vulkan14/vulkan14commands.cpp +++ b/rendering/vulkan14/vulkan14commands.cpp @@ -63,7 +63,8 @@ enum call_type : std::uint32_t call_begin_query, call_end_query, call_begin_label, - call_end_label + call_end_label, + call_build_acceleration_structures }; struct call_header @@ -165,6 +166,14 @@ struct update_call VkDeviceSize offset, size; std::size_t data; }; +// the builds of acceleration structures: the infos, the geometries of all of them one after another and their ranges +struct build_call +{ + std::uint32_t count; + std::size_t infos; + std::size_t geometries; + std::size_t ranges; +}; // the copies and the blit: the info with its regions template struct copy_call { @@ -689,6 +698,36 @@ void command_stream::end_debug_utils_label() add(call_end_label, 0); } +void command_stream::build_acceleration_structures(std::uint32_t const Count, VkAccelerationStructureBuildGeometryInfoKHR const *Infos, VkAccelerationStructureBuildRangeInfoKHR const *const *Ranges) +{ + if (Count == 0) + { + return; + } + std::vector infos(Infos, Infos + Count); + std::vector geometries; + std::vector ranges; + for (std::uint32_t i = 0; i < Count; ++i) + { + auto const &info{Infos[i]}; + assert(info.pNext == nullptr); + for (std::uint32_t geometry = 0; geometry < info.geometryCount; ++geometry) + { + geometries.push_back(info.pGeometries != nullptr ? info.pGeometries[geometry] : *info.ppGeometries[geometry]); + ranges.push_back(Ranges[i][geometry]); + } + infos[i].pGeometries = nullptr; + infos[i].ppGeometries = nullptr; + } + auto const call{add(call_build_acceleration_structures, sizeof(build_call))}; + auto const infodata{append(infos.data(), sizeof(VkAccelerationStructureBuildGeometryInfoKHR) * infos.size())}; + auto const geometrydata{append(geometries.data(), sizeof(VkAccelerationStructureGeometryKHR) * geometries.size())}; + auto const rangedata{append(ranges.data(), sizeof(VkAccelerationStructureBuildRangeInfoKHR) * ranges.size())}; + payload(call) = build_call{Count, infodata, geometrydata, rangedata}; + close(call); + work(); +} + // the replay void command_stream::execute(std::size_t const Call, VkCommandBuffer const Buffer, VkRenderingFlags const Flags) const @@ -956,6 +995,26 @@ void command_stream::execute(std::size_t const Call, VkCommandBuffer const Buffe vkCmdEndDebugUtilsLabelEXT(Buffer); } break; + case call_build_acceleration_structures: + { + auto const &data{payload(Call)}; + std::vector infos(at(data.infos), at(data.infos) + data.count); + std::vector ranges(data.count); + auto const *geometries{data.geometries != no_data ? at(data.geometries) : nullptr}; + auto const *range{data.ranges != no_data ? at(data.ranges) : nullptr}; + for (std::uint32_t i = 0; i < data.count; ++i) + { + infos[i].pGeometries = geometries; + ranges[i] = range; + if (geometries != nullptr) + { + geometries += infos[i].geometryCount; + range += infos[i].geometryCount; + } + } + vkCmdBuildAccelerationStructuresKHR(Buffer, data.count, infos.data(), ranges.data()); + break; + } default: break; } diff --git a/rendering/vulkan14/vulkan14commands.h b/rendering/vulkan14/vulkan14commands.h index e9147b12..39250a53 100644 --- a/rendering/vulkan14/vulkan14commands.h +++ b/rendering/vulkan14/vulkan14commands.h @@ -78,6 +78,8 @@ class command_stream void end_query(VkQueryPool Pool, std::uint32_t Query); void begin_debug_utils_label(VkDebugUtilsLabelEXT const *Label); void end_debug_utils_label(); + // the builds of acceleration structures (VK_KHR_acceleration_structure, gfx.raytracing), outside of a rendering + void build_acceleration_structures(std::uint32_t Count, VkAccelerationStructureBuildGeometryInfoKHR const *Infos, VkAccelerationStructureBuildRangeInfoKHR const *const *Ranges); // the stream is made anew void clear(); diff --git a/rendering/vulkan14/vulkan14context.h b/rendering/vulkan14/vulkan14context.h index 47e570eb..ca7bfb56 100644 --- a/rendering/vulkan14/vulkan14context.h +++ b/rendering/vulkan14/vulkan14context.h @@ -32,6 +32,7 @@ http://mozilla.org/MPL/2.0/. #include "rendering/vulkan14/vulkan14grass.h" #include "rendering/vulkan14/vulkan14particles.h" #include "rendering/vulkan14/vulkan14commands.h" +#include "rendering/vulkan14/vulkan14raytracing.h" #include "utilities/workerpool.h" #include "model/material.h" #include "gl/ubo.h" @@ -123,6 +124,11 @@ struct viewport_targets // the temporal anti-aliasing (gfx.antialiasing taa, the main viewport): the two images of the history, written in turn (hdr, the // lock of the thin things in alpha) std::array, 2> taa_history; + // the screen space reflections (gfx.ssr, the main viewport): the finished image of the last frame with its mipmaps, which they take + // their colour from, and whether it was cleared once it was made (it's read before the first frame is copied to it) + std::shared_ptr ssr_history; + bool ssr_cleared{false}; + bool reflections{false}; // the history was asked for (it can be missing when it couldn't be made) // the tonemapped image, before the chromatic aberration, and the one which goes to the window std::shared_ptr tonemapped; std::shared_ptr output; @@ -275,7 +281,6 @@ struct cull_resources { instance_cull main; std::array cascades; - instance_cull reflections; // the maps of headlights, by the slot of the map, and the scenery parts of the maps of lamps, by their order among those drawn // in a frame: the passes of a frame don't share the buffers of a culling. made when first needed std::vector> headlights; @@ -361,6 +366,8 @@ struct scene_state bool grass_tried{false}; // the smoke and the rain or snow of the gpu gpu_particles particles; + // the ray traced reflections (gfx.raytracing), on a device with ray queries; its set is set RT_SET of the pipelines of the scene + std::unique_ptr raytracing; // the heatmap of the light clusters (gfx.lights.clusterheatmap), made when it's first drawn std::shared_ptr cluster_heatmap; bool cluster_heatmap_tried{false}; @@ -369,6 +376,8 @@ struct scene_state // the format of the levels of the bloom chains: no alpha, no negative values, half the memory of rgba16f, where the device // draws to it VkFormat bloom_format{VK_FORMAT_B10G11R11_UFLOAT_PACK32}; + // the same for the history of the screen space reflections, where the device blits and filters it + VkFormat history_format{VK_FORMAT_B10G11R11_UFLOAT_PACK32}; // the formats of the passes which don't depend on the viewport target_formats shadow_formats; target_formats reflection_formats; diff --git a/rendering/vulkan14/vulkan14debug.cpp b/rendering/vulkan14/vulkan14debug.cpp index 33dbec80..3f806e8a 100644 --- a/rendering/vulkan14/vulkan14debug.cpp +++ b/rendering/vulkan14/vulkan14debug.cpp @@ -72,11 +72,6 @@ std::string vulkan14_renderer::cull_info() text += cull.cascades[cascade].info("gpu cull shadows " + std::to_string(cascade + 1), false); } } - if (Global.gfx_gpucull_reflections && cull.reflections.ran()) - { - cull.reflections.collect(); - text += cull.reflections.info("gpu cull reflections, last face", false); - } if (Global.gfx_gpucull != 0) { // the maps are drawn now and then, the numbers are of the last drawing of each @@ -177,6 +172,10 @@ std::string vulkan14_renderer::scene_info() { text += "cluster lists: " + std::to_string(stats.index_count) + ", overflow " + std::to_string(stats.overflow_count) + " (peak " + std::to_string(stats.overflow_peak) + ")\n"; } + if (scene.raytracing && m_raytracingon) + { + text += scene.raytracing->info() + "\n"; + } return text; } @@ -318,6 +317,19 @@ void vulkan14_renderer::draw_debug_ui() ImGui::SliderInt("ao steps per side", &Global.gfx_postfx_ao_steps, 1, 16); } } + ImGui::Separator(); + std::string raytracing; + if (m_context->scene.raytracing) + { + ImGui::Checkbox("ray traced reflections where the screen has none (gfx.raytracing)", &m_raytracingon); + ImGui::SliderFloat("range of their rays and of the scenery, m (gfx.raytracing.range)", &Global.gfx_raytracing_range, 10.f, 5000.f, "%.0f", ImGuiSliderFlags_Logarithmic); + raytracing = m_context->scene.raytracing->info(); + } + else + { + raytracing = (Global.gfx_raytracing ? "ray traced reflections: the gpu has no ray queries" : "ray traced reflections: off (gfx.raytracing yes in eu07.ini, taken at the start)"); + } + m_ssr.draw_debug_ui(raytracing.c_str()); } ImGui::End(); @@ -338,7 +350,6 @@ void vulkan14_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); diff --git a/rendering/vulkan14/vulkan14device.cpp b/rendering/vulkan14/vulkan14device.cpp index a6eed96f..5c96ac13 100644 --- a/rendering/vulkan14/vulkan14device.cpp +++ b/rendering/vulkan14/vulkan14device.cpp @@ -10,6 +10,7 @@ http://mozilla.org/MPL/2.0/. #include "stdafx.h" #include "rendering/vulkan14/vulkan14device.h" #include "rendering/vulkan14/vulkan14instance.h" +#include "utilities/Globals.h" #include @@ -289,6 +290,32 @@ bool device::create(instance const &Instance, std::string &Reason) } } + // the ray traced reflections, asked for in eu07.ini (gfx.raytracing), where the device has what they need + VkPhysicalDeviceAccelerationStructureFeaturesKHR acceleration{}; + acceleration.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ACCELERATION_STRUCTURE_FEATURES_KHR; + VkPhysicalDeviceRayQueryFeaturesKHR rayquery{}; + rayquery.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_QUERY_FEATURES_KHR; + m_raytracing = Global.gfx_raytracing && m_capabilities.ray_query; + if (m_raytracing) + { + extensions.push_back(VK_KHR_ACCELERATION_STRUCTURE_EXTENSION_NAME); + extensions.push_back(VK_KHR_RAY_QUERY_EXTENSION_NAME); + extensions.push_back(VK_KHR_DEFERRED_HOST_OPERATIONS_EXTENSION_NAME); + enabled.v12.bufferDeviceAddress = VK_TRUE; + enabled.v12.runtimeDescriptorArray = VK_TRUE; + enabled.v12.descriptorBindingPartiallyBound = VK_TRUE; + enabled.v12.descriptorBindingVariableDescriptorCount = VK_TRUE; + enabled.v12.shaderSampledImageArrayNonUniformIndexing = VK_TRUE; + acceleration.accelerationStructure = VK_TRUE; + rayquery.rayQuery = VK_TRUE; + acceleration.pNext = &rayquery; + enabled.v14.pNext = &acceleration; + } + else if (Global.gfx_raytracing) + { + log_info("gfx.raytracing: " + m_capabilities.device_name + " has no ray queries (or not what they need), the reflections keep to the screen and the environment map"); + } + VkDeviceCreateInfo create{}; create.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; create.pNext = enabled.head(); @@ -316,6 +343,10 @@ bool device::create(instance const &Instance, std::string &Reason) log_info("using " + m_capabilities.device_name + ", queue families: graphics " + std::to_string(m_families.graphics) + ", compute " + std::to_string(m_families.compute) + (m_families.dedicated_compute ? " (own)" : " (shared)") + ", transfer " + std::to_string(m_families.transfer) + (m_families.dedicated_transfer ? " (own)" : " (shared)")); + if (m_raytracing) + { + log_info("ray queries on, for the ray traced reflections (gfx.raytracing)"); + } return true; } diff --git a/rendering/vulkan14/vulkan14device.h b/rendering/vulkan14/vulkan14device.h index 7aaae3d0..9f5090d2 100644 --- a/rendering/vulkan14/vulkan14device.h +++ b/rendering/vulkan14/vulkan14device.h @@ -76,6 +76,12 @@ class device { return m_memory_budget; } + // the ray queries are on with what they need (gfx.raytracing on a device which has them): the device addresses of buffers, the + // acceleration structures, the arrays of textures indexed in the shaders + bool raytracing_enabled() const + { + return m_raytracing; + } private: static queue_families find_queue_families(VkInstance Instance, VkPhysicalDevice Physical, VkSurfaceKHR Surface, std::uint32_t &TimestampBits); @@ -88,6 +94,7 @@ class device feature_chain m_queried; bool m_portability_subset{false}; bool m_memory_budget{false}; + bool m_raytracing{false}; VkQueue m_graphics_queue{VK_NULL_HANDLE}; VkQueue m_compute_queue{VK_NULL_HANDLE}; diff --git a/rendering/vulkan14/vulkan14frame.cpp b/rendering/vulkan14/vulkan14frame.cpp index e0766657..07e5f8fd 100644 --- a/rendering/vulkan14/vulkan14frame.cpp +++ b/rendering/vulkan14/vulkan14frame.cpp @@ -307,8 +307,8 @@ void vulkan14_renderer::init_frame_data() namespace { -bool make_targets(vk14::allocator &Memory, vk14::deletion_queue &Deletions, std::uint64_t const Value, std::uint32_t &Formatscount, VkSampleCountFlags const Samplecounts, VkFormat const Bloomformat, vk14::viewport_targets &Targets, - int const Width, int const Height, bool const Srgb, bool const Taa) +bool make_targets(vk14::allocator &Memory, vk14::deletion_queue &Deletions, std::uint64_t const Value, std::uint32_t &Formatscount, VkSampleCountFlags const Samplecounts, VkFormat const Bloomformat, VkFormat const Historyformat, + vk14::viewport_targets &Targets, int const Width, int const Height, bool const Srgb, bool const Taa, bool const Reflections) { // as many samples as asked for, as far as the device has them for the colour and the depth auto samples{static_cast(std::clamp(1 << Global.iMultisampling, 1, 8))}; @@ -320,7 +320,7 @@ bool make_targets(vk14::allocator &Memory, vk14::deletion_queue &Deletions, std: auto const aberration{Global.gfx_postfx_chromaticaberration_enabled}; VkExtent2D const extent{static_cast(std::max(Width, 1)), static_cast(std::max(Height, 1))}; if ((Targets.extent.width == extent.width) && (Targets.extent.height == extent.height) && (Targets.samples == samples) && (Targets.motion == motion) && (Targets.taa == Taa) && (Targets.aberration == aberration) && - (Targets.srgb == Srgb) && Targets.output) + (Targets.srgb == Srgb) && (Targets.reflections == Reflections) && Targets.output) { return true; } @@ -336,6 +336,7 @@ bool make_targets(vk14::allocator &Memory, vk14::deletion_queue &Deletions, std: Targets.taa = Taa; Targets.aberration = aberration; Targets.srgb = Srgb; + Targets.reflections = Reflections; auto make = [&](std::shared_ptr &Image, VkFormat Format, VkImageUsageFlags Usage, VkSampleCountFlagBits Samples, char const *Name) { vk14::image_desc desc; @@ -364,7 +365,9 @@ bool make_targets(vk14::allocator &Memory, vk14::deletion_queue &Deletions, std: ok = ok && make(Targets.msaa_motion, velocity, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, samples, "msaa velocity"); } } - ok = ok && make(Targets.colour, hdr, sampledcolour, VK_SAMPLE_COUNT_1_BIT, "scene colour") && + // the images of the scene the history of the screen space reflections is copied from (blits) are a source of transfers as well + auto const historysource{Reflections ? VK_IMAGE_USAGE_TRANSFER_SRC_BIT : 0u}; + ok = ok && make(Targets.colour, hdr, sampledcolour | historysource, VK_SAMPLE_COUNT_1_BIT, "scene colour") && make(Targets.depth, depth, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT, VK_SAMPLE_COUNT_1_BIT, "scene depth"); if (motion) { @@ -376,7 +379,23 @@ bool make_targets(vk14::allocator &Memory, vk14::deletion_queue &Deletions, std: } if (Taa) { - ok = ok && make(Targets.taa_history[0], hdr, sampledcolour, VK_SAMPLE_COUNT_1_BIT, "taa history") && make(Targets.taa_history[1], hdr, sampledcolour, VK_SAMPLE_COUNT_1_BIT, "taa history"); + ok = ok && make(Targets.taa_history[0], hdr, sampledcolour | historysource, VK_SAMPLE_COUNT_1_BIT, "taa history") && + make(Targets.taa_history[1], hdr, sampledcolour | historysource, VK_SAMPLE_COUNT_1_BIT, "taa history"); + } + if (ok && Reflections) + { + // the history of the screen space reflections, with its mipmaps made by blits; without it they're off + vk14::image_desc desc; + desc.format = Historyformat; + desc.extent = {extent.width, extent.height, 1}; + desc.mips = static_cast(std::floor(std::log2(static_cast(std::max(extent.width, extent.height))))) + 1; + desc.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; + Targets.ssr_history = std::make_shared(); + if (false == Targets.ssr_history->create(Memory, desc, "reflection history")) + { + ErrorLog("reflection history setup failed, no screen space reflections"); + Targets.ssr_history.reset(); + } } auto const output{Srgb ? VK_FORMAT_R8G8B8A8_SRGB : VK_FORMAT_R8G8B8A8_UNORM}; if (aberration) @@ -653,6 +672,11 @@ bool vulkan14_renderer::Render() clear_sky_images(); ctx.passes.end_pass(); } + // the set of the ray tracing of the frame has a structure to read from its first frame on + if (scene.raytracing) + { + scene.raytracing->begin_frame(ctx.commands, slot, value); + } // the block of the weather and the scene for the compute passes before the first pass of the scene m_draw->scene_offset = 0; m_draw->light_offset = 0; @@ -693,6 +717,11 @@ bool vulkan14_renderer::Render() // and so may the tracks the sleeper records were made for, and the models drawn on their own Clear_Indirect_Records(); scene.glare_visible.clear(); + // and the geometry the structures of the ray tracing were built from + if (scene.raytracing) + { + scene.raytracing->clear(ctx.uploads.current_value()); + } } if ((EditorModeFlag) || (m_nodestateversion != scene::node_state_version)) { @@ -883,8 +912,10 @@ void vulkan14_renderer::render_scene() auto const &viewport{m_viewports[i]}; auto const srgb{false == Global.gfx_shadergamma}; auto const *history{scene.targets[i].taa_history[0].get()}; + auto const *reflections{scene.targets[i].ssr_history.get()}; if (false == make_targets(ctx.memory, ctx.deletions, ctx.uploads.current_value(), scene.formats_count, ctx.device.caps().limits.framebufferColorSampleCounts & ctx.device.caps().limits.framebufferDepthSampleCounts, scene.bloom_format, - scene.targets[i], viewport.width, viewport.height, srgb, viewport.main && Global.gfx_taa())) + scene.history_format, scene.targets[i], viewport.width, viewport.height, srgb, viewport.main && Global.gfx_taa(), + viewport.main && (false == Global.gfx_skippipeline) && ((Global.gfx_ssr > 0) || (m_raytracingon && scene.raytracing)))) { continue; } @@ -893,6 +924,23 @@ void vulkan14_renderer::render_scene() // new images of the history, nothing in them yet m_taa.reset(); } + auto &targets{scene.targets[i]}; + if (targets.ssr_history.get() != reflections) + { + m_ssr.reset(); + } + if (targets.ssr_history && (false == targets.ssr_cleared)) + { + // the pass sets hold the history from now on, it's read (by none of the pixels) before the first frame goes to it + targets.ssr_cleared = true; + auto &image{*targets.ssr_history}; + ctx.passes.begin_pass("reflection history", {vk14::use_of(image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_PIPELINE_STAGE_2_CLEAR_BIT, VK_ACCESS_2_TRANSFER_WRITE_BIT)}); + VkClearColorValue const black{{0.f, 0.f, 0.f, 0.f}}; + VkImageSubresourceRange const range{VK_IMAGE_ASPECT_COLOR_BIT, 0, image.desc().mips, 0, 1}; + ctx.commands->clear_color_image(image.handle(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &black, 1, &range); + vk14::barriers(ctx.commands, {vk14::use_of(image, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, sampled_stage, sampled_access)}); + ctx.passes.end_pass(); + } render_viewport(i); } m_current_viewport = (m_viewports.empty() ? nullptr : &m_viewports.front()); @@ -1190,6 +1238,17 @@ void vulkan14_renderer::setup_pass(viewport_config &Viewport, renderpass_config } case rendermode::reflections: { + if (m_rtgather) + { + // the scenery for the ray traced reflections: around the camera of the colour pass in the axes of the world, the draws + // going to the world around the camera; a box of the range of the rays stands for the frustum + auto const range{Global.gfx_raytracing_range}; + camera.position() = m_colorpass.pass_camera.position(); + camera.projection() = ortho_projection(-range, range, -range, range, -range, range); + frustumtest_proj = ortho_frustumtest_projection(-range, range, -range, range, -range, range); + Config.draw_range = range; + break; + } // the faces of the map are drawn over a few frames. each is drawn from the place the camera was at // when the update began, so they fit together along the edges when the camera moves in the meantime camera.position() = m_environmentupdatelocation; @@ -1232,6 +1291,15 @@ void vulkan14_renderer::render_viewport(std::size_t const Index) 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. the ray traced ones fill in what + // they don't find, or go on their own without them + m_ssrframe = (ssr_wanted(vp) || raytracing_wanted(vp)) && targets.ssr_history; + 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}, + static_cast(targets.ssr_history->desc().mips)); + } m_colorpass = m_renderpass; scene_ubs.time = Timer::GetTime(); @@ -1279,6 +1347,10 @@ void vulkan14_renderer::render_viewport(std::size_t const Index) scene_ubs.contact_shadow_params = glm::vec4{0.f}; scene_ubs.contact_shadow_lamps = glm::vec4{0.f}; scene_ubs.shadow_fit = glm::vec4{0.f}; + // 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); if (vp.main) { @@ -1318,6 +1390,12 @@ void vulkan14_renderer::render_viewport(std::size_t const Index) render_headlight_shadows(); ready_lamp_maps(); } + // the scenery around the camera the reflections are traced through, before the passes which trace them + auto const raytracing{raytracing_wanted(vp)}; + if (raytracing) + { + render_ray_scene(vp); + } // the motion blur only when something in the view moves (see opengl46_renderer::Render_pass()) auto const motionblur{Global.gfx_postfx_motionblur_enabled && targets.motion && @@ -1393,13 +1471,18 @@ void vulkan14_renderer::render_viewport(std::size_t const Index) // the depth of the whole opaque scene before its colour, which the contact shadows need m_indirectcolourpending = false; auto const contactshadows{contact_shadows_wanted(vp)}; - if ((Global.gfx_depthprepass_scene || contactshadows) && vp.main && (false == Global.bWireFrame)) + auto const reflections{m_ssrframe && (false == Global.bWireFrame)}; + if ((Global.gfx_depthprepass_scene || contactshadows || reflections) && vp.main && (false == Global.bWireFrame)) { render_scene_depth(Index, future); if (contactshadows) { setup_contact_shadows(Index); } + if (reflections) + { + setup_screen_reflections(Index, taa); + } } // opaque parts... @@ -1474,12 +1557,14 @@ void vulkan14_renderer::render_viewport(std::size_t const Index) } commands->end_rendering(); end_draws(); - 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); } // the images resolved and drawn are read by the post-processing m_colorpass.draw_stats = m_renderpass.draw_stats; @@ -1502,7 +1587,8 @@ void vulkan14_renderer::render_scene_depth(std::size_t const Index, glm::mat4 co auto &draw{*m_draw}; auto const commands{ctx.commands}; auto const msaa{targets.samples != VK_SAMPLE_COUNT_1_BIT}; - auto const contact{contact_shadows_wanted(m_viewports[Index])}; + // the depth copied apart from the one the colour pass goes on with, for the contact shadows and the screen space reflections + auto const contact{contact_shadows_wanted(m_viewports[Index]) || m_ssrframe}; commands->end_rendering(); // what the draws go on with auto const set{draw.set}; @@ -1643,6 +1729,211 @@ void vulkan14_renderer::setup_contact_shadows(std::size_t const Index) m_contactshadows = true; } +// 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 as the contact shadows do: not in the orthographic view of the editor (opengl46_renderer::Ssr_View()) +bool vulkan14_renderer::ssr_wanted(viewport_config const &Viewport) const +{ + if ((Global.gfx_ssr <= 0) || (false == Viewport.main) || Global.bWireFrame || Global.gfx_skippipeline) + { + 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 vulkan14_renderer::setup_screen_reflections(std::size_t const Index, bool const Temporal) +{ + auto &targets{m_context->scene.targets[Index]}; + if ((false == bool(targets.scene_depth)) || (false == bool(targets.ssr_history))) + { + return; + } + m_ssr.setup(scene_ubs, gfx::screen_reflections::steps(Global.gfx_ssr), raytracing_wanted(m_viewports[Index]), Global.gfx_raytracing_range, Temporal); + upload_scene_block(); + m_ssractive = true; +} + +// the ray traced reflections, of the main view only (as the screen space ones, which they fill in) +bool vulkan14_renderer::raytracing_wanted(viewport_config const &Viewport) const +{ + if ((false == m_raytracingon) || (false == bool(m_context->scene.raytracing)) || (false == Viewport.main) || Global.bWireFrame || Global.gfx_skippipeline) + { + 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); +} + +// the scenery around the camera for the ray traced reflections: the walk of the scene in its reflection mode (the sections, the models +// and the vehicles in their ranges, gfx.reflections.*) around the camera of the colour pass, noting the draws instead of making them, +// built into the acceleration structures of the frame. the vehicle of the cab is left out: from the inside of the cab it would be its +// own shell that the rays hit +void vulkan14_renderer::render_ray_scene(viewport_config &Viewport) +{ + auto &ctx{*m_context}; + auto &scene{ctx.scene}; + m_rtdraws.clear(); + m_rtinstances.clear(); + auto const skipvehicle{m_shadowskipvehicle}; + if ((false == FreeFlyModeFlag) && Global.render_cab && (simulation::Train != nullptr)) + { + m_shadowskipvehicle = simulation::Train->Dynamic(); + } + auto const colourpass{m_renderpass}; + m_rtgather = true; + setup_pass(Viewport, m_renderpass, rendermode::reflections); + setup_matrices(); + // the walk binds materials and notes the draws; nothing is recorded + auto &draw{*m_draw}; + auto const rendering{draw.rendering}; + draw.rendering = false; + draw.blending = false; + Render(simulation::Region); + draw.rendering = rendering; + m_rtgather = false; + m_shadowskipvehicle = skipvehicle; + m_renderpass = colourpass; + setup_matrices(); + + ctx.passes.begin_pass("ray tracing scene"); + scene.raytracing->build(ctx.commands, ctx.slot, ctx.uploads.current_value(), m_framestamp, m_rtdraws, ctx.images); + ctx.passes.end_pass(); +} + +// a draw of the walk noted for the ray tracing: the chunk with its data on the gpu, the transform, the texture and the colour of the +// material, for each instance of an instanced draw +void vulkan14_renderer::gather_ray_draw(gfx::geometry_handle const &Geometry) +{ + auto &draw{*m_draw}; + if ((Geometry == null_handle) || (draw.program == nullptr) || (draw.program->vertices != vk14::vertex_kind::scene) || draw.blending || draw.glare) + { + return; + } + if ((Geometry.bank == 0) || (Geometry.bank > m_banks.size()) || (m_banks[Geometry.bank - 1] == nullptr)) + { + return; + } + gfx::indirect_source indirect; + if (false == m_geometry.indirect_source(Geometry, indirect)) + { + return; + } + auto const &chunk{static_cast(m_banks[Geometry.bank - 1])->last()}; + if ((chunk.topology != VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST) || (chunk.vertex_address == 0)) + { + return; + } + vk14::ray_draw ray; + ray.key = static_cast(Geometry); + ray.source = chunk; + // the diffuse colour of the material as material_diffuse() of the shaders makes it + glm::vec3 const colour{glm::pow(glm::max(glm::vec3{model_ubs.param[0]}, glm::vec3{0.f}), glm::vec3{2.2f})}; + auto const value{std::max({model_ubs.param[0].r, model_ubs.param[0].g, model_ubs.param[0].b})}; + ray.colour = (value > 0.f ? colour * (model_ubs.param[1].x / value) : glm::vec3{0.f}); + ray.glow = model_ubs.emission; + // the diffuse texture of the material, and its alpha test when it has an alpha channel + if (draw.textures[0] != null_handle) + { + auto &texture{m_context->textures->mark_as_used(draw.textures[0])}; + if (texture.create() && (m_context->images.find(texture.get_id()) != nullptr)) + { + ray.texture = texture.get_id(); + ray.cutoff = (texture.get_has_alpha() ? std::max(model_ubs.opacity, 0.f) : 0.f); + } + } + glm::mat4 const modelview{model_ubs.modelview}; + if (m_current_instance_count > 0) + { + for (std::size_t instance = 0; instance < m_current_instance_count; ++instance) + { + auto const index{static_cast(m_current_instance_base) + instance}; + if (index >= m_rtinstances.size()) + { + break; + } + ray.transform = m_rtinstances[index] * modelview; + m_rtdraws.push_back(ray); + } + } + else + { + ray.transform = modelview; + m_rtdraws.push_back(ray); + } +} + +// 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, each level blitted from the one before it +void vulkan14_renderer::store_ssr_history(std::size_t const Index, vk14::image &Source) +{ + auto &ctx{*m_context}; + auto &targets{ctx.scene.targets[Index]}; + if (false == bool(targets.ssr_history)) + { + return; + } + auto const commands{ctx.commands}; + auto &image{*targets.ssr_history}; + auto const mips{image.desc().mips}; + auto const width{static_cast(image.desc().extent.width)}; + auto const height{static_cast(image.desc().extent.height)}; + ctx.passes.begin_pass("reflections, image of the frame", {vk14::use_of(Source, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_PIPELINE_STAGE_2_BLIT_BIT, VK_ACCESS_2_TRANSFER_READ_BIT), + vk14::use_of(image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_PIPELINE_STAGE_2_BLIT_BIT, VK_ACCESS_2_TRANSFER_WRITE_BIT)}); + auto blit = [&](VkImage From, std::uint32_t FromLevel, VkImageLayout FromLayout, std::uint32_t ToLevel, glm::ivec2 const &FromSize, glm::ivec2 const &ToSize) { + VkImageBlit2 region{}; + region.sType = VK_STRUCTURE_TYPE_IMAGE_BLIT_2; + region.srcSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, FromLevel, 0, 1}; + region.srcOffsets[1] = {FromSize.x, FromSize.y, 1}; + region.dstSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, ToLevel, 0, 1}; + region.dstOffsets[1] = {ToSize.x, ToSize.y, 1}; + VkBlitImageInfo2 info{}; + info.sType = VK_STRUCTURE_TYPE_BLIT_IMAGE_INFO_2; + info.srcImage = From; + info.srcImageLayout = FromLayout; + info.dstImage = image.handle(); + info.dstImageLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + info.regionCount = 1; + info.pRegions = ®ion; + info.filter = VK_FILTER_LINEAR; + commands->blit_image2(&info); + }; + auto barrier = [&](std::uint32_t Level, VkImageLayout From, VkImageLayout To, VkPipelineStageFlags2 SrcStage, VkAccessFlags2 SrcAccess, VkPipelineStageFlags2 DstStage, VkAccessFlags2 DstAccess) { + VkImageMemoryBarrier2 entry{}; + entry.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; + entry.srcStageMask = SrcStage; + entry.srcAccessMask = SrcAccess; + entry.dstStageMask = DstStage; + entry.dstAccessMask = DstAccess; + entry.oldLayout = From; + entry.newLayout = To; + entry.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + entry.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + entry.image = image.handle(); + entry.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, Level, 1, 0, 1}; + VkDependencyInfo dependency{}; + dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + dependency.imageMemoryBarrierCount = 1; + dependency.pImageMemoryBarriers = &entry; + commands->pipeline_barrier2(&dependency); + }; + glm::ivec2 const sourcesize{static_cast(Source.desc().extent.width), static_cast(Source.desc().extent.height)}; + blit(Source.handle(), 0, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, 0, sourcesize, {width, height}); + for (std::uint32_t level = 1; level < mips; ++level) + { + barrier(level - 1, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_PIPELINE_STAGE_2_BLIT_BIT, VK_ACCESS_2_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_2_BLIT_BIT, VK_ACCESS_2_TRANSFER_READ_BIT); + blit(image.handle(), level - 1, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, level, {std::max(width >> (level - 1), 1), std::max(height >> (level - 1), 1)}, {std::max(width >> level, 1), std::max(height >> level, 1)}); + } + // the levels read from are in the layout of the sources, the last one in that of the destination: all of them read by the shaders + for (std::uint32_t level = 0; level < mips; ++level) + { + auto const from{(level + 1 < mips) ? VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL : VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL}; + barrier(level, from, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_PIPELINE_STAGE_2_BLIT_BIT, VK_ACCESS_2_TRANSFER_WRITE_BIT, sampled_stage, sampled_access); + } + image.state = vk14::image_state{VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, sampled_stage, sampled_access}; + ctx.passes.end_pass(); + m_ssr.end_frame(); +} + // the rendering of the colour of a viewport: begun with the images cleared, or again after the depth under the cursor was read void vulkan14_renderer::begin_colour_rendering(std::size_t const Index, bool const Clear) { @@ -1815,7 +2106,7 @@ void vulkan14_renderer::render_shadows(viewport_config &Viewport) vk14::barriers(commands, {vk14::use_of(*scene.shadow_map, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, sampled_stage, sampled_access)}); } -// the environment map, a face of it per frame (opengl46_renderer::Render_reflections()) +// the environment map (the sky and the clouds around the camera), a face of it per frame (opengl46_renderer::Render_reflections()) bool vulkan14_renderer::render_reflections(viewport_config &Viewport) { auto &ctx{*m_context}; @@ -1957,12 +2248,9 @@ void vulkan14_renderer::render_reflection_face(viewport_config &Viewport) scene_ubs.projection = OpenGLMatrices.data(GL_PROJECTION); scene_ubs.inv_view = glm::inverse(glm::mat4{glm::mat3{m_renderpass.pass_camera.modelview()}}); upload_scene_block(); + // the sky and the clouds alone: the scenery is reflected by the screen space reflections (gfx.ssr) and the ray tracing, the + // environment map gives what they don't find render_environment(&simulation::Environment); - // opaque parts... - walk_blending(false); - setup_shadow_matrices(); - upload_scene_block(); - Render(simulation::Region); commands->end_rendering(); end_draws(); @@ -2720,6 +3008,12 @@ void vulkan14_renderer::render_post(std::size_t const Index, bool const Motionbl post_pass("postfx_taa.frag", *targets.taa_history[target], {targets.colour.get(), targets.motion_image.get(), targets.depth.get(), targets.taa_history[1 - target].get()}, constants, "temporal anti-aliasing"); hdr = targets.taa_history[target].get(); } + // the image before the blur is the one the reflections of the next frame take + if (m_ssrframe && m_viewports[Index].main) + { + store_ssr_history(Index, *hdr); + } + m_ssrframe = false; if (Motionblur && targets.blurred) { vk14::draw_constants constants; diff --git a/rendering/vulkan14/vulkan14geometry.cpp b/rendering/vulkan14/vulkan14geometry.cpp index 4659840d..8bd1527e 100644 --- a/rendering/vulkan14/vulkan14geometry.cpp +++ b/rendering/vulkan14/vulkan14geometry.cpp @@ -11,6 +11,8 @@ http://mozilla.org/MPL/2.0/. #include "rendering/vulkan14/vulkan14geometry.h" #include "rendering/vulkan14/vulkan14texture.h" +#include + namespace vk14 { @@ -20,6 +22,9 @@ namespace // what opengl reads for the user data of a vertex without any (the attribute isn't given) gfx::vertex_userdata const no_userdata{glm::vec4{0.f, 0.f, 0.f, 1.f}}; +// the numbers the data of the banks get, unique across the banks (a bank made where another one was gets a new one) +std::atomic g_generation{0}; + } // namespace VkPrimitiveTopology topology_of(unsigned int const Type) @@ -140,6 +145,7 @@ bool vulkan14_geometrybank::build() } place.indexcount = static_cast(indices.size()) - place.indexfirst; place.dirty = false; + place.generation = ++g_generation; } m_layoutdirty = false; m_chunksdirty = false; @@ -150,9 +156,11 @@ bool vulkan14_geometrybank::build() auto &memory{*m_resources.memory}; auto &uploads{*m_resources.uploads}; + // with the ray tracing the vertices and the indices are built into acceleration structures, and the shading reads them + auto const raytracing{m_resources.raytracing ? (VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR) : 0}; auto const make = [&](std::shared_ptr &Buffer, void const *Data, VkDeviceSize const Size, VkBufferUsageFlags const Usage, char const *Name) { Buffer = std::make_shared(); - if ((false == Buffer->create(memory, Size, Usage | VK_BUFFER_USAGE_TRANSFER_DST_BIT, memory_kind::device, Name)) || (false == uploads.upload_buffer(Buffer, Data, Size))) + if ((false == Buffer->create(memory, Size, Usage | VK_BUFFER_USAGE_TRANSFER_DST_BIT | raytracing, memory_kind::device, Name)) || (false == uploads.upload_buffer(Buffer, Data, Size))) { Buffer.reset(); return false; @@ -196,6 +204,7 @@ void vulkan14_geometrybank::update() continue; } place.dirty = false; + place.generation = ++g_generation; auto const &chunk{m_chunks[i]}; gpu_vertices(chunk, vertices, userdata); vertexparts.emplace_back(place.vertexfirst * sizeof(gfx::basic_vertex), place.vertexcount * sizeof(gfx::basic_vertex)); @@ -245,6 +254,14 @@ bool vulkan14_geometrybank::source(gfx::geometry_handle const &Geometry, chunk_s Source.vertices = m_vertices->handle(); Source.userdata = m_userdata->handle(); Source.topology = topology_of(m_chunks[index].type); + Source.vertexfirst = place.vertexfirst; + Source.vertexcount = place.vertexcount; + Source.generation = place.generation; + if (m_resources.raytracing) + { + Source.vertex_address = m_vertices->address(); + Source.index_address = (m_indices ? m_indices->address() : 0); + } if (place.indexcount > 0) { Source.indices = m_indices->handle(); diff --git a/rendering/vulkan14/vulkan14geometry.h b/rendering/vulkan14/vulkan14geometry.h index 9c1cb55f..804641bc 100644 --- a/rendering/vulkan14/vulkan14geometry.h +++ b/rendering/vulkan14/vulkan14geometry.h @@ -36,6 +36,13 @@ struct chunk_source std::uint32_t first{0}; // first index, or vertex std::int32_t basevertex{0}; VkPrimitiveTopology topology{VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST}; + // for the ray tracing: the vertices of the chunk in the buffer of the bank, the device addresses of the buffers (0 without the ray + // tracing), and the number the data of the chunk on the gpu has: another one when the buffers were made anew or the chunk changed + std::uint32_t vertexfirst{0}; + std::uint32_t vertexcount{0}; + VkDeviceAddress vertex_address{0}; + VkDeviceAddress index_address{0}; + std::uint64_t generation{0}; }; // the vulkan primitive of a primitive of opengl (GL_TRIANGLES ...) @@ -68,6 +75,7 @@ class vulkan14_geometrybank : public gfx::geometry_bank std::uint32_t indexfirst{0}; std::uint32_t indexcount{0}; bool dirty{true}; + std::uint64_t generation{0}; // of its data on the gpu, for the ray tracing (chunk_source::generation) }; void create_(gfx::geometry_handle const &Geometry) override; diff --git a/rendering/vulkan14/vulkan14indirect.cpp b/rendering/vulkan14/vulkan14indirect.cpp index 61af5d60..7d5df18b 100644 --- a/rendering/vulkan14/vulkan14indirect.cpp +++ b/rendering/vulkan14/vulkan14indirect.cpp @@ -57,7 +57,7 @@ bool vulkan14_renderer::init_cull() return false; } } - if ((false == cull.reflections.init(environment, device, ctx.pipelines, ctx.shaders, scene.pass_layout, &cull.main)) || (false == cull.hiz.init(device, ctx.pipelines, ctx.shaders, scene.pass_layout))) + if (false == cull.hiz.init(device, ctx.pipelines, ctx.shaders, scene.pass_layout)) { return false; } @@ -90,7 +90,6 @@ void vulkan14_renderer::destroy_cull() } culls->clear(); } - cull.reflections.destroy(device); cull.main.destroy(device); cull.hiz.destroy(device); cull.shadowfit.destroy(device); @@ -140,10 +139,6 @@ void vulkan14_renderer::walk_region_begin() passcull = &cull.cascades[m_shadowcascade]; m_shadowcasters.clear(); } - else if ((m_renderpass.draw_mode == rendermode::reflections) && Global.gfx_gpucull_reflections && (Global.reflectiontune.fidelity >= 1)) - { - passcull = &cull.reflections; - } } } Begin_Pass_Cull(passcull, (passcull == &cull.main)); diff --git a/rendering/vulkan14/vulkan14memory.cpp b/rendering/vulkan14/vulkan14memory.cpp index 5c0a7315..682137d8 100644 --- a/rendering/vulkan14/vulkan14memory.cpp +++ b/rendering/vulkan14/vulkan14memory.cpp @@ -35,6 +35,11 @@ bool allocator::create(instance const &Instance, device const &Device, std::stri { info.flags |= VMA_ALLOCATOR_CREATE_EXT_MEMORY_BUDGET_BIT; } + if (Device.raytracing_enabled()) + { + // the acceleration structures are built from the device addresses of the buffers + info.flags |= VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT; + } // the functions as volk loaded them VmaVulkanFunctions functions{}; if (!VK14_VERIFY(vmaImportVulkanFunctionsFromVolk(&info, &functions))) @@ -109,6 +114,7 @@ buffer &buffer::operator=(buffer &&Other) noexcept std::swap(m_allocation, Other.m_allocation); std::swap(m_size, Other.m_size); std::swap(m_mapped, Other.m_mapped); + std::swap(m_address, Other.m_address); } return *this; } @@ -167,6 +173,22 @@ void buffer::destroy() m_allocation = VK_NULL_HANDLE; m_size = 0; m_mapped = nullptr; + m_address = 0; +} + +VkDeviceAddress buffer::address() const +{ + if ((m_buffer == VK_NULL_HANDLE) || (m_address != 0)) + { + return m_address; + } + VmaAllocatorInfo allocator{}; + vmaGetAllocatorInfo(m_allocator, &allocator); + VkBufferDeviceAddressInfo info{}; + info.sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO; + info.buffer = m_buffer; + m_address = vkGetBufferDeviceAddress(allocator.device, &info); + return m_address; } void buffer::flush(VkDeviceSize Offset, VkDeviceSize Size) const diff --git a/rendering/vulkan14/vulkan14memory.h b/rendering/vulkan14/vulkan14memory.h index 607ed917..28cfcc2e 100644 --- a/rendering/vulkan14/vulkan14memory.h +++ b/rendering/vulkan14/vulkan14memory.h @@ -89,6 +89,8 @@ class buffer { return m_mapped; } + // the device address of the buffer, made with VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT (the ray tracing) + VkDeviceAddress address() const; // makes the writes of the cpu seen by the gpu where the memory isn't coherent void flush(VkDeviceSize Offset, VkDeviceSize Size) const; // makes the writes of the gpu seen by the cpu where the memory isn't coherent (readback memory may be cached) @@ -104,6 +106,7 @@ class buffer VmaAllocation m_allocation{VK_NULL_HANDLE}; VkDeviceSize m_size{0}; std::uint8_t *m_mapped{nullptr}; + mutable VkDeviceAddress m_address{0}; // asked for the first time it's needed }; // the state of an image the barriers start from: its layout and the last stage and access which used it diff --git a/rendering/vulkan14/vulkan14raytracing.cpp b/rendering/vulkan14/vulkan14raytracing.cpp new file mode 100644 index 00000000..e1afc0dc --- /dev/null +++ b/rendering/vulkan14/vulkan14raytracing.cpp @@ -0,0 +1,727 @@ +/* +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/vulkan14/vulkan14raytracing.h" +#include "rendering/vulkan14/vulkan14device.h" +#include "rendering/vulkan14/vulkan14commands.h" +#include "rendering/vulkan14/vulkan14texture.h" +#include "rendering/vulkan14/vulkan14bindings.h" + +#include +#include + +namespace vk14 +{ + +namespace +{ + +// the record of an instance the shading reads (shaders/vk14/rt_reflections.glsl, rt_record) +struct record +{ + VkDeviceAddress vertices; + VkDeviceAddress indices; + std::uint32_t texture; + std::uint32_t flags; + float cutoff; + float glow; + glm::vec4 colour; +}; +static_assert(sizeof(record) == 48, "the record of an instance of the ray tracing doesn't match the shaders"); + +std::uint32_t constexpr record_alpha{1}; +// the bottom level structures built in a frame at most, and their triangles: the rest waits for the next frames +std::uint32_t constexpr builds_per_frame{96}; +std::uint64_t constexpr triangles_per_frame{2000000}; +// the instances of the top level structure at most (the records take 24 bits of the custom index) +std::uint32_t constexpr max_instances{1u << 20}; +// a bottom level structure not met in this many frames goes, and a texture keeps its index in the array this long +std::uint64_t constexpr bottom_lifetime{1800}; +std::uint64_t constexpr texture_lifetime{600}; +// the size of the vertices of the banks (gfx::basic_vertex) +VkDeviceSize constexpr vertex_stride{48}; + +VkDeviceSize grow(VkDeviceSize const Needed, VkDeviceSize const Current) +{ + return std::max(Needed, Current + Current / 2); +} + +void destroy_structure(VkDevice const Device, VkAccelerationStructureKHR const Structure) +{ + if (Structure != VK_NULL_HANDLE) + { + vkDestroyAccelerationStructureKHR(Device, Structure, nullptr); + } +} + +// waits for the writes of the builds before the structures are read: by the builds which follow and by the shaders +void build_barrier(command_stream *Commands, VkPipelineStageFlags2 const Source, VkAccessFlags2 const SourceAccess, VkPipelineStageFlags2 const Destination, VkAccessFlags2 const DestinationAccess) +{ + VkMemoryBarrier2 barrier{}; + barrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER_2; + barrier.srcStageMask = Source; + barrier.srcAccessMask = SourceAccess; + barrier.dstStageMask = Destination; + barrier.dstAccessMask = DestinationAccess; + VkDependencyInfo dependency{}; + dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + dependency.memoryBarrierCount = 1; + dependency.pMemoryBarriers = &barrier; + Commands->pipeline_barrier2(&dependency); +} + +} // namespace + +ray_tracing::~ray_tracing() +{ + destroy(); +} + +bool ray_tracing::init(device const &Device, allocator &Memory, deletion_queue &Deletions, std::uint32_t const Frames, std::string &Reason) +{ + using namespace vk14::bindings; + m_device = &Device; + m_memory = &Memory; + m_deletions = &Deletions; + m_scratchalignment = Device.caps().scratch_alignment; + m_texturecount = std::min(Device.caps().max_indexed_textures, 16384); + auto const handle{Device.handle()}; + + // the structure, the records and the textures, the last binding as many as the set is made with and partly bound + std::array bindings{{{RT_SCENE, VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, 1, VK_SHADER_STAGE_FRAGMENT_BIT, nullptr}, + {RT_RECORDS, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_FRAGMENT_BIT, nullptr}, + {RT_TEXTURES, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, m_texturecount, VK_SHADER_STAGE_FRAGMENT_BIT, nullptr}}}; + std::array const flags{0, 0, VK_DESCRIPTOR_BINDING_PARTIALLY_BOUND_BIT | VK_DESCRIPTOR_BINDING_VARIABLE_DESCRIPTOR_COUNT_BIT}; + VkDescriptorSetLayoutBindingFlagsCreateInfo bindingflags{}; + bindingflags.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_BINDING_FLAGS_CREATE_INFO; + bindingflags.bindingCount = static_cast(flags.size()); + bindingflags.pBindingFlags = flags.data(); + VkDescriptorSetLayoutCreateInfo layout{}; + layout.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; + layout.pNext = &bindingflags; + layout.bindingCount = static_cast(bindings.size()); + layout.pBindings = bindings.data(); + if (!VK14_VERIFY(vkCreateDescriptorSetLayout(handle, &layout, nullptr, &m_layout))) + { + m_layout = VK_NULL_HANDLE; + Reason = "no layout of the set of the ray tracing"; + return false; + } + name_object(handle, VK_OBJECT_TYPE_DESCRIPTOR_SET_LAYOUT, handle_value(m_layout), "ray tracing set"); + + std::array const sizes{{{VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, Frames}, {VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, Frames}, {VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, m_texturecount * Frames}}}; + VkDescriptorPoolCreateInfo pool{}; + pool.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; + pool.maxSets = Frames; + pool.poolSizeCount = static_cast(sizes.size()); + pool.pPoolSizes = sizes.data(); + if (!VK14_VERIFY(vkCreateDescriptorPool(handle, &pool, nullptr, &m_pool))) + { + m_pool = VK_NULL_HANDLE; + Reason = "no pool of the sets of the ray tracing"; + return false; + } + m_frames.resize(Frames); + std::vector const layouts(Frames, m_layout); + std::vector const counts(Frames, m_texturecount); + std::vector sets(Frames, VK_NULL_HANDLE); + VkDescriptorSetVariableDescriptorCountAllocateInfo variable{}; + variable.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_VARIABLE_DESCRIPTOR_COUNT_ALLOCATE_INFO; + variable.descriptorSetCount = Frames; + variable.pDescriptorCounts = counts.data(); + VkDescriptorSetAllocateInfo allocation{}; + allocation.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + allocation.pNext = &variable; + allocation.descriptorPool = m_pool; + allocation.descriptorSetCount = Frames; + allocation.pSetLayouts = layouts.data(); + if (!VK14_VERIFY(vkAllocateDescriptorSets(handle, &allocation, sets.data()))) + { + Reason = "no sets of the ray tracing"; + return false; + } + for (std::uint32_t frame = 0; frame < Frames; ++frame) + { + m_frames[frame].set = sets[frame]; + m_frames[frame].written_textures.assign(m_texturecount, {}); + } + // the index 0 is the white texture of the records without one + m_textureowners.assign(m_texturecount, 0); + m_textureused.assign(m_texturecount, 0); + m_freetextures.clear(); + for (std::uint32_t index = m_texturecount; index > 1; --index) + { + m_freetextures.push_back(index - 1); + } + log_info("ray traced reflections: up to " + std::to_string(m_texturecount) + " textures of the scenery, " + std::to_string(Frames) + " sets"); + return true; +} + +void ray_tracing::destroy() +{ + if (m_device == nullptr) + { + return; + } + auto const handle{m_device->handle()}; + for (auto &bottom : m_bottoms) + { + destroy_structure(handle, bottom.second.handle); + } + m_bottoms.clear(); + m_bottombytes = 0; + for (auto &frame : m_frames) + { + destroy_structure(handle, frame.top); + } + m_frames.clear(); + if (m_pool != VK_NULL_HANDLE) + { + vkDestroyDescriptorPool(handle, m_pool, nullptr); + m_pool = VK_NULL_HANDLE; + } + if (m_layout != VK_NULL_HANDLE) + { + vkDestroyDescriptorSetLayout(handle, m_layout, nullptr); + m_layout = VK_NULL_HANDLE; + } + m_textureindices.clear(); + m_device = nullptr; +} + +void ray_tracing::clear(std::uint64_t const Value) +{ + if (m_device == nullptr) + { + return; + } + auto const handle{m_device->handle()}; + for (auto &entry : m_bottoms) + { + m_deletions->push(Value, [handle, structure = entry.second.handle, storage = entry.second.storage]() { destroy_structure(handle, structure); }); + } + m_bottoms.clear(); + m_bottombytes = 0; +} + +bool ray_tracing::ensure(std::shared_ptr &Buffer, VkDeviceSize const Size, VkBufferUsageFlags const Usage, memory_kind const Kind, char const *Name, std::uint64_t const Value) +{ + if (Buffer && (Buffer->size() >= Size)) + { + return true; + } + auto const current{Buffer ? Buffer->size() : 0}; + auto made{std::make_shared()}; + if (false == made->create(*m_memory, grow(Size, current), Usage, Kind, Name)) + { + return false; + } + if (Buffer) + { + m_deletions->push(Value, [old = std::move(Buffer)]() {}); + } + Buffer = std::move(made); + return true; +} + +VkDeviceAddress ray_tracing::aligned_scratch(frame_data const &Frame) const +{ + auto const address{Frame.scratch->address()}; + auto const alignment{static_cast(m_scratchalignment)}; + return (address + alignment - 1) / alignment * alignment; +} + +void ray_tracing::begin_frame(command_stream *Commands, std::uint32_t const Slot, std::uint64_t const Value) +{ + if ((m_device == nullptr) || (Slot >= m_frames.size()) || (m_frames[Slot].top != VK_NULL_HANDLE) || (m_white == VK_NULL_HANDLE)) + { + return; + } + // the shaders statically use the set: it gets an empty structure, a record and the white texture before anything else + auto &frame{m_frames[Slot]}; + if (false == ensure(frame.records, sizeof(record), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, memory_kind::upload, "ray tracing records", Value)) + { + return; + } + std::memset(frame.records->mapped(), 0, sizeof(record)); + frame.records->flush(0, sizeof(record)); + if (false == build_top(Commands, frame, 0, Value)) + { + return; + } + // the build of the frame later on reuses the structure and the scratch + build_barrier(Commands, VK_PIPELINE_STAGE_2_ACCELERATION_STRUCTURE_BUILD_BIT_KHR, VK_ACCESS_2_ACCELERATION_STRUCTURE_WRITE_BIT_KHR, + VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_2_ACCELERATION_STRUCTURE_BUILD_BIT_KHR, VK_ACCESS_2_ACCELERATION_STRUCTURE_READ_BIT_KHR | VK_ACCESS_2_ACCELERATION_STRUCTURE_WRITE_BIT_KHR); + VkWriteDescriptorSetAccelerationStructureKHR structure{}; + structure.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET_ACCELERATION_STRUCTURE_KHR; + structure.accelerationStructureCount = 1; + structure.pAccelerationStructures = &frame.top; + VkDescriptorBufferInfo const records{frame.records->handle(), 0, VK_WHOLE_SIZE}; + VkDescriptorImageInfo const white{m_whitesampler, m_white, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL}; + std::array writes{}; + for (auto &write : writes) + { + write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + write.dstSet = frame.set; + write.descriptorCount = 1; + } + writes[0].pNext = &structure; + writes[0].dstBinding = vk14::bindings::RT_SCENE; + writes[0].descriptorType = VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR; + writes[1].dstBinding = vk14::bindings::RT_RECORDS; + writes[1].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + writes[1].pBufferInfo = &records; + writes[2].dstBinding = vk14::bindings::RT_TEXTURES; + writes[2].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + writes[2].pImageInfo = &white; + vkUpdateDescriptorSets(m_device->handle(), static_cast(writes.size()), writes.data(), 0, nullptr); + frame.written_top = frame.top; + frame.written_records = frame.records->handle(); + frame.written_textures[0] = {m_white, m_whitesampler, {}}; +} + +bool ray_tracing::build_top(command_stream *Commands, frame_data &Frame, std::uint32_t const Count, std::uint64_t const Value) +{ + auto const handle{m_device->handle()}; + // the instances: room for the count, kept from frame to frame + auto const capacity{std::max(Count, 1)}; + if (false == ensure(Frame.instances, sizeof(VkAccelerationStructureInstanceKHR) * capacity, VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR, + memory_kind::upload, "ray tracing instances", Value)) + { + return false; + } + VkAccelerationStructureGeometryKHR geometry{}; + geometry.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_KHR; + geometry.geometryType = VK_GEOMETRY_TYPE_INSTANCES_KHR; + geometry.geometry.instances.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_INSTANCES_DATA_KHR; + geometry.geometry.instances.arrayOfPointers = VK_FALSE; + geometry.geometry.instances.data.deviceAddress = Frame.instances->address(); + VkAccelerationStructureBuildGeometryInfoKHR info{}; + info.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_GEOMETRY_INFO_KHR; + info.type = VK_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL_KHR; + info.flags = VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR; + info.mode = VK_BUILD_ACCELERATION_STRUCTURE_MODE_BUILD_KHR; + info.geometryCount = 1; + info.pGeometries = &geometry; + // the structure is made for more instances than the frame has, so it lasts a while as the count changes + auto count{Count}; + if ((Frame.top == VK_NULL_HANDLE) || (Count > Frame.top_capacity)) + { + auto const room{std::max(static_cast(grow(Count, Frame.top_capacity)), 64)}; + VkAccelerationStructureBuildSizesInfoKHR sizes{}; + sizes.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_SIZES_INFO_KHR; + vkGetAccelerationStructureBuildSizesKHR(handle, VK_ACCELERATION_STRUCTURE_BUILD_TYPE_DEVICE_KHR, &info, &room, &sizes); + auto storage{std::make_shared()}; + VkAccelerationStructureKHR made{VK_NULL_HANDLE}; + auto ok{storage->create(*m_memory, sizes.accelerationStructureSize, VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_STORAGE_BIT_KHR | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT, memory_kind::device, + "ray tracing top level")}; + if (ok) + { + VkAccelerationStructureCreateInfoKHR create{}; + create.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_CREATE_INFO_KHR; + create.buffer = storage->handle(); + create.size = sizes.accelerationStructureSize; + create.type = VK_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL_KHR; + ok = VK14_VERIFY(vkCreateAccelerationStructureKHR(handle, &create, nullptr, &made)); + } + if (ok) + { + name_object(handle, VK_OBJECT_TYPE_ACCELERATION_STRUCTURE_KHR, handle_value(made), "ray tracing top level"); + if (Frame.top != VK_NULL_HANDLE) + { + m_deletions->push(Value, [handle, structure = Frame.top, oldstorage = Frame.storage]() { destroy_structure(handle, structure); }); + } + Frame.top = made; + Frame.storage = std::move(storage); + Frame.top_capacity = room; + Frame.top_scratch = sizes.buildScratchSize + m_scratchalignment; + } + else + { + // no memory for a bigger one: the old one (the set holds it) with as many instances as it has room for + if (Frame.top == VK_NULL_HANDLE) + { + return false; + } + count = Frame.top_capacity; + } + } + if (false == ensure(Frame.scratch, Frame.top_scratch, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT, memory_kind::device, "ray tracing scratch", Value)) + { + return false; + } + info.dstAccelerationStructure = Frame.top; + info.scratchData.deviceAddress = aligned_scratch(Frame); + VkAccelerationStructureBuildRangeInfoKHR range{}; + range.primitiveCount = count; + VkAccelerationStructureBuildRangeInfoKHR const *ranges{&range}; + Commands->build_acceleration_structures(1, &info, &ranges); + return true; +} + +std::uint32_t ray_tracing::texture_index(std::uint64_t const Id, std::uint64_t const Frame) +{ + if (Id == 0) + { + return 0; + } + auto const lookup{m_textureindices.find(Id)}; + if (lookup != m_textureindices.end()) + { + m_textureused[lookup->second] = Frame; + return lookup->second; + } + if (m_freetextures.empty()) + { + // the indices not used for a while are taken back (the sets write them anew before they're read again) + for (std::uint32_t index = 1; index < m_texturecount; ++index) + { + if ((m_textureowners[index] != 0) && (m_textureused[index] + texture_lifetime < Frame)) + { + m_textureindices.erase(m_textureowners[index]); + m_textureowners[index] = 0; + m_freetextures.push_back(index); + } + } + if (m_freetextures.empty()) + { + return 0; + } + } + auto const index{m_freetextures.back()}; + m_freetextures.pop_back(); + m_textureindices.emplace(Id, index); + m_textureowners[index] = Id; + m_textureused[index] = Frame; + return index; +} + +void ray_tracing::build(command_stream *Commands, std::uint32_t const Slot, std::uint64_t const Value, std::uint64_t const Frame, std::vector const &Draws, image_table const &Images) +{ + if ((m_device == nullptr) || (Slot >= m_frames.size())) + { + return; + } + auto const handle{m_device->handle()}; + auto &frame{m_frames[Slot]}; + + // the bottom level structures the draws need: the ones not there yet or out of date are built, as many as a frame lets + struct pending_build + { + VkAccelerationStructureGeometryKHR geometry; + VkAccelerationStructureBuildRangeInfoKHR range; + VkAccelerationStructureBuildGeometryInfoKHR info; + VkDeviceSize scratch; + }; + std::vector builds; + std::uint64_t triangles{0}; + std::uint32_t waiting{0}; + VkDeviceSize scratchsize{0}; + auto const scratchalignment{static_cast(m_scratchalignment)}; + for (auto const &draw : Draws) + { + auto lookup{m_bottoms.find(draw.key)}; + if ((lookup != m_bottoms.end()) && (lookup->second.generation == draw.source.generation)) + { + lookup->second.used = Frame; + continue; + } + if ((builds.size() >= builds_per_frame) || (triangles >= triangles_per_frame)) + { + ++waiting; + continue; + } + auto const &source{draw.source}; + auto const indexed{source.indices != VK_NULL_HANDLE}; + auto const primitives{source.count / 3}; + if ((primitives == 0) || (source.vertex_address == 0) || (indexed && (source.index_address == 0)) || (source.vertexcount == 0)) + { + continue; + } + pending_build build{}; + build.geometry.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_KHR; + build.geometry.geometryType = VK_GEOMETRY_TYPE_TRIANGLES_KHR; + auto &triangles_data{build.geometry.geometry.triangles}; + triangles_data.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_TRIANGLES_DATA_KHR; + triangles_data.vertexFormat = VK_FORMAT_R32G32B32_SFLOAT; + triangles_data.vertexData.deviceAddress = source.vertex_address + vertex_stride * source.vertexfirst; + triangles_data.vertexStride = vertex_stride; + triangles_data.maxVertex = source.vertexcount - 1; + triangles_data.indexType = (indexed ? VK_INDEX_TYPE_UINT32 : VK_INDEX_TYPE_NONE_KHR); + triangles_data.indexData.deviceAddress = (indexed ? source.index_address + sizeof(std::uint32_t) * source.first : 0); + build.range.primitiveCount = primitives; + build.info.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_GEOMETRY_INFO_KHR; + build.info.type = VK_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL_KHR; + build.info.flags = VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR; + build.info.mode = VK_BUILD_ACCELERATION_STRUCTURE_MODE_BUILD_KHR; + build.info.geometryCount = 1; + VkAccelerationStructureBuildSizesInfoKHR sizes{}; + sizes.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_SIZES_INFO_KHR; + build.info.pGeometries = &build.geometry; + vkGetAccelerationStructureBuildSizesKHR(handle, VK_ACCELERATION_STRUCTURE_BUILD_TYPE_DEVICE_KHR, &build.info, &primitives, &sizes); + build.info.pGeometries = nullptr; + auto storage{std::make_shared()}; + if (false == storage->create(*m_memory, sizes.accelerationStructureSize, VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_STORAGE_BIT_KHR | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT, memory_kind::device, "ray tracing bottom level")) + { + continue; + } + VkAccelerationStructureCreateInfoKHR create{}; + create.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_CREATE_INFO_KHR; + create.buffer = storage->handle(); + create.size = sizes.accelerationStructureSize; + create.type = VK_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL_KHR; + VkAccelerationStructureKHR structure{VK_NULL_HANDLE}; + if (!VK14_VERIFY(vkCreateAccelerationStructureKHR(handle, &create, nullptr, &structure))) + { + continue; + } + if (lookup != m_bottoms.end()) + { + // the data of the chunk changed: the old structure goes once the frames which read it are done + m_bottombytes -= lookup->second.bytes; + m_deletions->push(Value, [handle, old = lookup->second.handle, oldstorage = lookup->second.storage]() { destroy_structure(handle, old); }); + } + VkAccelerationStructureDeviceAddressInfoKHR addressinfo{}; + addressinfo.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_DEVICE_ADDRESS_INFO_KHR; + addressinfo.accelerationStructure = structure; + auto &entry{m_bottoms[draw.key]}; + entry.storage = std::move(storage); + entry.handle = structure; + entry.address = vkGetAccelerationStructureDeviceAddressKHR(handle, &addressinfo); + entry.generation = source.generation; + entry.used = Frame; + entry.bytes = sizes.accelerationStructureSize; + m_bottombytes += entry.bytes; + build.info.dstAccelerationStructure = structure; + build.scratch = scratchsize; + scratchsize += (sizes.buildScratchSize + scratchalignment - 1) / scratchalignment * scratchalignment; + builds.push_back(build); + triangles += primitives; + } + + // the vertices and the indices copied to the banks in this frame (the uploads go to the queue before the frame) are read by the builds + if (false == builds.empty()) + { + build_barrier(Commands, VK_PIPELINE_STAGE_2_ALL_TRANSFER_BIT, VK_ACCESS_2_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_2_ACCELERATION_STRUCTURE_BUILD_BIT_KHR, VK_ACCESS_2_SHADER_READ_BIT); + if (false == ensure(frame.scratch, scratchsize + scratchalignment, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT, memory_kind::device, "ray tracing scratch", Value)) + { + // no memory for the builds: the structures made for them go, they're tried again + for (auto const &build : builds) + { + for (auto it = m_bottoms.begin(); it != m_bottoms.end(); ++it) + { + if (it->second.handle == build.info.dstAccelerationStructure) + { + m_bottombytes -= it->second.bytes; + m_deletions->push(Value, [handle, old = it->second.handle, oldstorage = it->second.storage]() { destroy_structure(handle, old); }); + m_bottoms.erase(it); + break; + } + } + } + builds.clear(); + } + else + { + auto const scratchbase{aligned_scratch(frame)}; + std::vector infos; + std::vector ranges; + infos.reserve(builds.size()); + ranges.reserve(builds.size()); + for (auto &build : builds) + { + build.info.pGeometries = &build.geometry; + build.info.scratchData.deviceAddress = scratchbase + build.scratch; + infos.push_back(build.info); + ranges.push_back(&build.range); + } + Commands->build_acceleration_structures(static_cast(infos.size()), infos.data(), ranges.data()); + // the top level structure reads them, and its build uses the scratch memory again + build_barrier(Commands, VK_PIPELINE_STAGE_2_ACCELERATION_STRUCTURE_BUILD_BIT_KHR, VK_ACCESS_2_ACCELERATION_STRUCTURE_WRITE_BIT_KHR, VK_PIPELINE_STAGE_2_ACCELERATION_STRUCTURE_BUILD_BIT_KHR, + VK_ACCESS_2_ACCELERATION_STRUCTURE_READ_BIT_KHR | VK_ACCESS_2_ACCELERATION_STRUCTURE_WRITE_BIT_KHR); + } + } + + // the records and the instances of the draws with their structures + std::size_t instancecount{0}; + for (auto const &draw : Draws) + { + auto const lookup{m_bottoms.find(draw.key)}; + if ((lookup != m_bottoms.end()) && (lookup->second.generation == draw.source.generation)) + { + ++instancecount; + } + } + instancecount = std::min(instancecount, max_instances); + if ((false == ensure(frame.records, sizeof(record) * std::max(instancecount, 1), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, memory_kind::upload, "ray tracing records", Value)) || + (false == ensure(frame.instances, sizeof(VkAccelerationStructureInstanceKHR) * std::max(instancecount, 1), + VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR, memory_kind::upload, "ray tracing instances", Value))) + { + instancecount = 0; + } + auto *records{reinterpret_cast(frame.records ? frame.records->mapped() : nullptr)}; + auto *instances{reinterpret_cast(frame.instances ? frame.instances->mapped() : nullptr)}; + std::vector usedtextures; + std::uint32_t written{0}; + for (auto const &draw : Draws) + { + if ((written >= instancecount) || (records == nullptr) || (instances == nullptr)) + { + break; + } + auto const lookup{m_bottoms.find(draw.key)}; + if ((lookup == m_bottoms.end()) || (lookup->second.generation != draw.source.generation)) + { + continue; + } + auto const &source{draw.source}; + auto const texture{texture_index(draw.texture, Frame)}; + if (texture != 0) + { + usedtextures.push_back(texture); + } + auto &entry{records[written]}; + entry.vertices = source.vertex_address + vertex_stride * source.vertexfirst; + entry.indices = (source.indices != VK_NULL_HANDLE ? source.index_address + sizeof(std::uint32_t) * source.first : 0); + entry.texture = texture; + auto const alpha{(texture != 0) && (draw.cutoff > 0.f)}; + entry.flags = (alpha ? record_alpha : 0); + entry.cutoff = draw.cutoff; + entry.glow = draw.glow; + entry.colour = glm::vec4{draw.colour, 1.f}; + auto &instance{instances[written]}; + // the rows of the transform, which glm keeps by the columns + for (int row = 0; row < 3; ++row) + { + for (int column = 0; column < 4; ++column) + { + instance.transform.matrix[row][column] = draw.transform[column][row]; + } + } + instance.instanceCustomIndex = written; + instance.mask = 0xFF; + instance.instanceShaderBindingTableRecordOffset = 0; + // the models have faces of one side, seen from both in the reflections; an alpha tested texture leaves holes in its faces + instance.flags = VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR | (alpha ? VK_GEOMETRY_INSTANCE_FORCE_NO_OPAQUE_BIT_KHR : VK_GEOMETRY_INSTANCE_FORCE_OPAQUE_BIT_KHR); + instance.accelerationStructureReference = lookup->second.address; + ++written; + } + if (written > 0) + { + frame.records->flush(0, sizeof(record) * written); + frame.instances->flush(0, sizeof(VkAccelerationStructureInstanceKHR) * written); + } + if (build_top(Commands, frame, written, Value)) + { + build_barrier(Commands, VK_PIPELINE_STAGE_2_ACCELERATION_STRUCTURE_BUILD_BIT_KHR, VK_ACCESS_2_ACCELERATION_STRUCTURE_WRITE_BIT_KHR, VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT, + VK_ACCESS_2_ACCELERATION_STRUCTURE_READ_BIT_KHR); + } + + // the set of the frame: what changed in it + std::vector writes; + std::vector images; + images.reserve(usedtextures.size()); + VkWriteDescriptorSetAccelerationStructureKHR structure{}; + structure.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET_ACCELERATION_STRUCTURE_KHR; + structure.accelerationStructureCount = 1; + structure.pAccelerationStructures = &frame.top; + VkDescriptorBufferInfo const recordsinfo{frame.records ? frame.records->handle() : VK_NULL_HANDLE, 0, VK_WHOLE_SIZE}; + if ((frame.top != VK_NULL_HANDLE) && (frame.top != frame.written_top)) + { + VkWriteDescriptorSet write{}; + write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + write.pNext = &structure; + write.dstSet = frame.set; + write.dstBinding = vk14::bindings::RT_SCENE; + write.descriptorCount = 1; + write.descriptorType = VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR; + writes.push_back(write); + frame.written_top = frame.top; + } + if (frame.records && (frame.records->handle() != frame.written_records)) + { + VkWriteDescriptorSet write{}; + write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + write.dstSet = frame.set; + write.dstBinding = vk14::bindings::RT_RECORDS; + write.descriptorCount = 1; + write.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + write.pBufferInfo = &recordsinfo; + writes.push_back(write); + frame.written_records = frame.records->handle(); + } + std::sort(usedtextures.begin(), usedtextures.end()); + usedtextures.erase(std::unique(usedtextures.begin(), usedtextures.end()), usedtextures.end()); + for (auto const index : usedtextures) + { + // the texture as its material samples it; a texture which isn't a plain 2d image (or is gone) reads as white + auto view{m_white}; + auto sampler{m_whitesampler}; + std::shared_ptr source; + if (auto const *entry{Images.find(m_textureowners[index])}) + { + if (entry->target && (entry->target->desc().view_type == VK_IMAGE_VIEW_TYPE_2D) && (entry->sampler != VK_NULL_HANDLE)) + { + view = entry->target->view(); + sampler = entry->sampler; + source = entry->target; + } + } + auto &cached{frame.written_textures[index]}; + if ((cached.view == view) && (cached.sampler == sampler) && (cached.source.lock() == source)) + { + continue; + } + cached = {view, sampler, source}; + images.push_back({sampler, view, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL}); + VkWriteDescriptorSet write{}; + write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + write.dstSet = frame.set; + write.dstBinding = vk14::bindings::RT_TEXTURES; + write.dstArrayElement = index; + write.descriptorCount = 1; + write.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + write.pImageInfo = &images.back(); + writes.push_back(write); + } + if (false == writes.empty()) + { + vkUpdateDescriptorSets(handle, static_cast(writes.size()), writes.data(), 0, nullptr); + } + + // the structures not met for a while go + for (auto it = m_bottoms.begin(); it != m_bottoms.end();) + { + if (it->second.used + bottom_lifetime < Frame) + { + m_bottombytes -= it->second.bytes; + m_deletions->push(Value, [handle, old = it->second.handle, oldstorage = it->second.storage]() { destroy_structure(handle, old); }); + it = m_bottoms.erase(it); + } + else + { + ++it; + } + } + + m_lastinstances = written; + m_lastrecords = written; + m_lastbuilt = static_cast(builds.size()); + m_lastwaiting = waiting; + m_lasttextures = static_cast(usedtextures.size()); +} + +std::string ray_tracing::info() const +{ + return "ray traced reflections: " + std::to_string(m_lastinstances) + " instances, " + std::to_string(m_bottoms.size()) + " chunks (" + memory_size_string(m_bottombytes) + "), built " + std::to_string(m_lastbuilt) + + ", waiting " + std::to_string(m_lastwaiting) + ", textures " + std::to_string(m_lasttextures); +} + +} // namespace vk14 diff --git a/rendering/vulkan14/vulkan14raytracing.h b/rendering/vulkan14/vulkan14raytracing.h new file mode 100644 index 00000000..3f8534f8 --- /dev/null +++ b/rendering/vulkan14/vulkan14raytracing.h @@ -0,0 +1,157 @@ +/* +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/. +*/ + +// the ray traced reflections of the vulkan renderer (gfx.raytracing, on a gpu with ray queries): what the screen space reflections +// don't find is traced through the scenery around the camera (shaders/vk14/rt_reflections.glsl). +// +// the renderer walks the scene for it once a frame (the walk in its reflection mode, gathering the draws instead of making them) and +// hands the draws over: each chunk of geometry gets a bottom level acceleration structure of its own the first time it's met (a few +// of them a frame, the rest wait for the next ones; made anew when its data changes, let go when it isn't met for a while), each draw +// an instance of the top level structure of the frame with a record the shading reads (where its vertices are, the texture and the +// colour of its material). each frame in flight has its own top level structure, buffers and set of the pipelines of the scene (set +// RT_SET), written while the frame is made: the gpu is done with the frame which used them before + +#pragma once + +#include "rendering/vulkan14/vulkan14memory.h" +#include "rendering/vulkan14/vulkan14geometry.h" + +#include +#include +#include + +namespace vk14 +{ + +class device; +class command_stream; +class image_table; + +// a draw of the walk for the ray tracing: what an instance of the top level structure is made of +struct ray_draw +{ + glm::mat4 transform{1.f}; // the vertices of the chunk to the world around the camera (the axes of the world, the camera at the origin) + std::uint64_t key{0}; // the chunk, gfx::geometry_handle + chunk_source source; // where its data is + std::uint64_t texture{0}; // the id of the diffuse texture in the image table, 0: none + glm::vec3 colour{1.f}; // the diffuse colour of the material, linear + float cutoff{0.f}; // the alpha test of the texture, 0: none + float glow{0.f}; // the light of its own +}; + +class ray_tracing +{ + public: + ray_tracing() = default; + ray_tracing(ray_tracing const &) = delete; + ray_tracing &operator=(ray_tracing const &) = delete; + ~ray_tracing(); + + // Frames: the frames in flight + bool init(device const &Device, allocator &Memory, deletion_queue &Deletions, std::uint32_t Frames, std::string &Reason); + // the texture of the records without one (index 0 of the array), before the first frame + void set_white(VkImageView View, VkSampler Sampler) + { + m_white = View; + m_whitesampler = Sampler; + } + void destroy(); + bool ready() const + { + return m_layout != VK_NULL_HANDLE; + } + // the layout of the set of the pipelines of the scene + VkDescriptorSetLayout layout() const + { + return m_layout; + } + // the frame Slot begins (its frame before is done on the gpu): the set of the slot gets an empty structure the first time, so the + // shaders always have one to read. Value: of the timeline, when the gpu is done with the frame + void begin_frame(command_stream *Commands, std::uint32_t Slot, std::uint64_t Value); + // the scenery of the frame: the bottom level structures of the draws which aren't there yet (as many as a frame lets), the top + // level structure with the instances of the draws, their records and textures written to the set of the frame. outside of a + // rendering, before the passes which trace the rays + void build(command_stream *Commands, std::uint32_t Slot, std::uint64_t Value, std::uint64_t Frame, std::vector const &Draws, image_table const &Images); + // the set of the frame + VkDescriptorSet set(std::uint32_t Slot) const + { + return (Slot < m_frames.size() ? m_frames[Slot].set : VK_NULL_HANDLE); + } + // a scenery is being loaded, or the geometry is gone: the bottom level structures go + void clear(std::uint64_t Value); + // for the debug panel: the structures, their memory, the instances of the last frame + std::string info() const; + + private: + struct bottom + { + std::shared_ptr storage; + VkAccelerationStructureKHR handle{VK_NULL_HANDLE}; + VkDeviceAddress address{0}; + std::uint64_t generation{0}; // of the data of the chunk it was built from + std::uint64_t used{0}; // the frame it was last met in + VkDeviceSize bytes{0}; + }; + struct frame_data + { + VkDescriptorSet set{VK_NULL_HANDLE}; + // the top level structure, its memory and the instances it's built from, the records of the shading, the scratch of the builds + std::shared_ptr storage; + VkAccelerationStructureKHR top{VK_NULL_HANDLE}; + std::uint32_t top_capacity{0}; // instances + VkDeviceSize top_scratch{0}; // what a build of the structure needs of the scratch, with the alignment + std::shared_ptr instances; + std::shared_ptr records; + std::shared_ptr scratch; + // what the set holds: the structure, the records, and the textures by their index + VkAccelerationStructureKHR written_top{VK_NULL_HANDLE}; + VkBuffer written_records{VK_NULL_HANDLE}; + struct written_texture + { + VkImageView view{VK_NULL_HANDLE}; + VkSampler sampler{VK_NULL_HANDLE}; + std::weak_ptr source; // a view made after this one is gone can get its handle + }; + std::vector written_textures; + }; + + // a buffer of the frame at least Size bytes, grown when it isn't (the old one stays when the new one can't be made) + bool ensure(std::shared_ptr &Buffer, VkDeviceSize Size, VkBufferUsageFlags Usage, memory_kind Kind, char const *Name, std::uint64_t Value); + // the top level structure of the frame for Count instances, recorded to Commands; false: no memory + bool build_top(command_stream *Commands, frame_data &Frame, std::uint32_t Count, std::uint64_t Value); + // the index of a texture in the array of the set, 0: none (white, or no room) + std::uint32_t texture_index(std::uint64_t Id, std::uint64_t Frame); + VkDeviceAddress aligned_scratch(frame_data const &Frame) const; + + device const *m_device{nullptr}; + allocator *m_memory{nullptr}; + deletion_queue *m_deletions{nullptr}; + VkDescriptorSetLayout m_layout{VK_NULL_HANDLE}; + VkDescriptorPool m_pool{VK_NULL_HANDLE}; + VkImageView m_white{VK_NULL_HANDLE}; + VkSampler m_whitesampler{VK_NULL_HANDLE}; + std::uint32_t m_texturecount{0}; + std::uint32_t m_scratchalignment{256}; + std::vector m_frames; + std::unordered_map m_bottoms; // by the chunk + VkDeviceSize m_bottombytes{0}; + // the indices of the textures in the array: by the id of the image, the image of each index and the frame it was last used in + std::unordered_map m_textureindices; + std::vector m_textureowners; + std::vector m_textureused; + std::vector m_freetextures; + // what the last frame had, for the panel + std::uint32_t m_lastinstances{0}; + std::uint32_t m_lastrecords{0}; + std::uint32_t m_lastbuilt{0}; + std::uint32_t m_lastwaiting{0}; + std::uint32_t m_lasttextures{0}; +}; + +} // namespace vk14 diff --git a/rendering/vulkan14/vulkan14renderer.cpp b/rendering/vulkan14/vulkan14renderer.cpp index 65566e5f..d7624c8e 100644 --- a/rendering/vulkan14/vulkan14renderer.cpp +++ b/rendering/vulkan14/vulkan14renderer.cpp @@ -343,7 +343,7 @@ bool vulkan14_renderer::context::create_core(std::uint32_t const Frames, std::st Reason = "no ring for the data of the frames"; return false; } - if ((false == shaders.init(vk14::binding_preamble(), Reason)) || (false == pipelines.init(device, Reason)) || (false == passes.create(device, Frames, Reason))) + if ((false == shaders.init(vk14::binding_preamble(device.raytracing_enabled()), Reason)) || (false == pipelines.init(device, Reason)) || (false == passes.create(device, Frames, Reason))) { return false; } @@ -355,6 +355,7 @@ bool vulkan14_renderer::context::create_core(std::uint32_t const Frames, std::st resources.images = &images; resources.thread = std::this_thread::get_id(); resources.pending = &pending; + resources.raytracing = device.raytracing_enabled(); textures = std::make_unique([this]() -> texture_source * { return new vk14::vulkan14_texture(resources); }); return imgui.create(resources, shaders, pipelines, ring, Reason); } diff --git a/rendering/vulkan14/vulkan14renderer.h b/rendering/vulkan14/vulkan14renderer.h index 9e14d95f..18c60c61 100644 --- a/rendering/vulkan14/vulkan14renderer.h +++ b/rendering/vulkan14/vulkan14renderer.h @@ -29,6 +29,7 @@ http://mozilla.org/MPL/2.0/. #include "rendering/common/shadowfit.h" #include "rendering/common/gpuparticles.h" #include "rendering/common/temporalaa.h" +#include "rendering/common/screenreflections.h" #include "rendering/particles.h" #include "model/material.h" #include "vehicle/Camera.h" @@ -46,6 +47,7 @@ struct draw_constants; enum class blend_mode; class instance_cull; struct image_use; +struct ray_draw; } // namespace vk14 class vulkan14_renderer : public gfx_renderer, public gfx::scene_walk @@ -271,6 +273,16 @@ class vulkan14_renderer : public gfx_renderer, public gfx::scene_walk void render_shadows(viewport_config &Viewport); bool render_reflections(viewport_config &Viewport); void render_reflection_face(viewport_config &Viewport); + // the ray traced reflections (gfx.raytracing, vulkan14raytracing.h): whether the colour pass of the viewport gets them, and the + // scenery around the camera gathered by the walk in its reflection mode (the draws noted instead of made) and built into the + // acceleration structures of the frame; a draw noted, with the state of the walk + bool raytracing_wanted(viewport_config const &Viewport) const; + void render_ray_scene(viewport_config &Viewport); + void gather_ray_draw(gfx::geometry_handle const &Geometry); + bool m_rtgather{false}; + bool m_raytracingon{true}; // switched off in the debug window + std::vector m_rtdraws; + std::vector m_rtinstances; // the modelviews of the instanced draws noted (walk_instances()) void render_picking(rendermode const Mode); // the sky dome and the sun and the moon void render_environment(world_environment *Environment); @@ -302,6 +314,15 @@ class vulkan14_renderer : public gfx_renderer, public gfx::scene_walk bool contact_shadows_wanted(viewport_config const &Viewport) const; void setup_contact_shadows(std::size_t const Viewport); bool m_contactshadows{false}; + // the screen space reflections (gfx.ssr) of the main viewport: whether its colour pass gets them (they need the depth of the + // scene drawn before the colour), the parameters of the pass set for them, the finished image of the frame (before the motion + // blur) copied to the history with its mipmaps; the matrices and the state, begun for the frame and set up for its colour pass + bool ssr_wanted(viewport_config const &Viewport) const; + void setup_screen_reflections(std::size_t const Viewport, bool const Temporal); + void store_ssr_history(std::size_t const Viewport, vk14::image &Source); + gfx::screen_reflections m_ssr; + bool m_ssrframe{false}; + bool m_ssractive{false}; // the materials material_handle invalid_material() const { diff --git a/rendering/vulkan14/vulkan14scene.cpp b/rendering/vulkan14/vulkan14scene.cpp index 8af897c5..64217ef0 100644 --- a/rendering/vulkan14/vulkan14scene.cpp +++ b/rendering/vulkan14/vulkan14scene.cpp @@ -190,6 +190,8 @@ std::array, 3> pass_ views[HEADLIGHT_SHADOW_TEX] = (headlights != nullptr ? headlights->view() : scene.headlight_shadow_empty->view()); // the depth of the scene of the main view drawn before its colour, which the contact shadows read views[SCENE_DEPTH_TEX] = (colour && (false == scene.targets.empty()) && scene.targets.front().scene_depth ? scene.targets.front().scene_depth->view() : scene.depth_empty->view()); + // the image of the last frame of the main view, which the screen space reflections take their colour from + views[SSR_HISTORY_TEX] = (colour && (false == scene.targets.empty()) && scene.targets.front().ssr_history ? scene.targets.front().ssr_history->view() : scene.white->view()); } return result; } @@ -303,7 +305,20 @@ bool vulkan14_renderer::init_scene(std::string &Reason) } } VkPushConstantRange const graphicsconstants{VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, PUSH_CONSTANTS_SIZE}; - scene.scene_layout = vk14::make_pipeline_layout(device, {scene.pass_layout, scene.material_set.layout}, {graphicsconstants}, "scene"); + // the ray traced reflections: the scenery around the camera, set RT_SET of the scene, which the shaders of the scene read when they + // were made with REFLECTION_RAYS (vk14::binding_preamble()) + std::vector scenesets{scene.pass_layout, scene.material_set.layout}; + if (ctx.device.raytracing_enabled()) + { + scene.raytracing = std::make_unique(); + static_assert(RT_SET == 2, "the set of the ray tracing follows the pass set and the material set"); + if (false == scene.raytracing->init(ctx.device, ctx.memory, ctx.deletions, frames, Reason)) + { + return false; + } + scenesets.push_back(scene.raytracing->layout()); + } + scene.scene_layout = vk14::make_pipeline_layout(device, scenesets, {graphicsconstants}, "scene"); if ((scene.scene_layout == VK_NULL_HANDLE) || (false == scene.material_set.create_template(device, scene.scene_layout, "material textures"))) { Reason = "no layout of the pipelines of the scene"; @@ -386,6 +401,10 @@ bool vulkan14_renderer::init_scene(std::string &Reason) } std::uint32_t const white{0xffffffff}; ctx.uploads.upload(scene.white, &white, sizeof(white), {{0, {1, 1, 1}}}, false); + if (scene.raytracing) + { + scene.raytracing->set_white(scene.white->view(), ctx.sampling.repeat_aniso); + } } { vk14::image_desc desc; @@ -518,6 +537,13 @@ bool vulkan14_renderer::init_scene(std::string &Reason) { scene.bloom_format = VK_FORMAT_R16G16B16A16_SFLOAT; } + // the history of the screen space reflections is copied and filtered by blits + vkGetPhysicalDeviceFormatProperties(ctx.device.physical(), scene.history_format, &properties); + auto const blits{VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT}; + if ((properties.optimalTilingFeatures & blits) != blits) + { + scene.history_format = VK_FORMAT_R16G16B16A16_SFLOAT; + } } // the formats of the passes which don't depend on the viewport scene.shadow_formats.depth = VK_FORMAT_D32_SFLOAT; @@ -737,6 +763,11 @@ void vulkan14_renderer::destroy_scene() return; } destroy_cull(); + if (scene.raytracing) + { + scene.raytracing->destroy(); + scene.raytracing.reset(); + } scene.lamps.destroy(); scene.terrain.reset(); scene.grass.destroy(); @@ -939,6 +970,7 @@ void vulkan14_renderer::write_pass_sets(vk14::instance_cull *Cull) write(handle, LOCAL_SHADOW_LAMPS_TEX, nullptr, image(ctx.sampling.shadow, views[LOCAL_SHADOW_LAMPS_TEX])); write(handle, HEADLIGHT_SHADOW_TEX, nullptr, image(ctx.sampling.shadow, views[HEADLIGHT_SHADOW_TEX])); write(handle, SCENE_DEPTH_TEX, nullptr, image(ctx.sampling.clamp_nearest, views[SCENE_DEPTH_TEX])); + write(handle, SSR_HISTORY_TEX, nullptr, image(ctx.sampling.clamp_linear, views[SSR_HISTORY_TEX])); } vkUpdateDescriptorSets(device, static_cast(writes.size()), writes.data(), 0, nullptr); } @@ -1003,6 +1035,19 @@ void vulkan14_renderer::bind_pass_set() info.pDynamicOffsets = offsets.data(); draw.commands->bind_descriptor_sets2(&info); draw.set_dirty = false; + // the set of the ray tracing of the frame, which the shaders of the scene made with REFLECTION_RAYS read + if (scene.raytracing) + { + VkDescriptorSet const rtset{scene.raytracing->set(m_context->slot)}; + VkBindDescriptorSetsInfo rt{}; + rt.sType = VK_STRUCTURE_TYPE_BIND_DESCRIPTOR_SETS_INFO; + rt.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT; + rt.layout = scene.scene_layout; + rt.firstSet = vk14::bindings::RT_SET; + rt.descriptorSetCount = 1; + rt.pDescriptorSets = &rtset; + draw.commands->bind_descriptor_sets2(&rt); + } } // the pass set of the colour of the main view bound for the compute kernels (their layouts share set 0 and the push constants), @@ -1575,6 +1620,13 @@ std::uint32_t vulkan14_renderer::push_draw_block() std::uint32_t vulkan14_renderer::walk_instances(glm::mat4 const *Modelviews, std::size_t const Count) { + if (m_rtgather) + { + // noted for the ray tracing: the base is the index of the first of them among those noted + auto const base{static_cast(m_rtinstances.size())}; + m_rtinstances.insert(m_rtinstances.end(), Modelviews, Modelviews + Count); + return base; + } auto &scene{m_context->scene}; auto &instances{scene.instances}; auto const capacity{static_cast(instances.window / instances.stride) - 1}; @@ -1618,6 +1670,11 @@ void vulkan14_renderer::open_instance_window() void vulkan14_renderer::walk_draw(gfx::geometry_handle const &Geometry) { model_ubs.set_modelview(OpenGLMatrices.data(GL_MODELVIEW)); + if (m_rtgather) + { + gather_ray_draw(Geometry); + return; + } if (m_indirectrecording) { // the draw is made later, from the gpu culling, with this state @@ -1630,6 +1687,14 @@ void vulkan14_renderer::walk_draw(gfx::geometry_handle const &Geometry) void vulkan14_renderer::walk_draw(std::vector::iterator First, std::vector::iterator Last) { model_ubs.set_modelview(OpenGLMatrices.data(GL_MODELVIEW)); + if (m_rtgather) + { + for (; First != Last; ++First) + { + gather_ray_draw(*First); + } + return; + } // the chunks drawn together share the block of the draw auto newblock{true}; for (; First != Last; ++First) diff --git a/rendering/vulkan14/vulkan14shaders.cpp b/rendering/vulkan14/vulkan14shaders.cpp index 3196f453..3005e9f6 100644 --- a/rendering/vulkan14/vulkan14shaders.cpp +++ b/rendering/vulkan14/vulkan14shaders.cpp @@ -181,6 +181,12 @@ bool shader_library::init(std::string const &Preamble, std::string &Reason) m_cachedir = user_cache_dir("shaders"); m_binaries = Global.gfx_shader_binaries; + if (m_binaries && (Preamble.find("#define REFLECTION_RAYS") != std::string::npos)) + { + // the binaries are made without the ray traced reflections + log_info("shaders: the ray traced reflections are on (gfx.raytracing), the SPIR-V binaries aren't used"); + m_binaries = false; + } if (m_binaries) { // the binaries are compiled with the constants written in the files, the sources get those of the renderer @@ -752,9 +758,18 @@ void set_draw_state(command_stream *Commands, VkExtent2D Extent) namespace vk14 { -std::string binding_preamble() +std::string binding_preamble(bool const Raytracing) { std::string preamble{"#define MASZYNA_CONSTANTS 1\n"}; + if (Raytracing) + { + // the extensions go before anything else of the source; shaders/vk14/rt_reflections.glsl says what they're for + preamble += "#extension GL_EXT_ray_query : require\n" + "#extension GL_EXT_buffer_reference2 : require\n" + "#extension GL_EXT_buffer_reference_uvec2 : require\n" + "#extension GL_EXT_nonuniform_qualifier : require\n" + "#define REFLECTION_RAYS 1\n"; + } auto const define = [&preamble](char const *Name, auto const Value, char const *Suffix = "") { preamble += std::string("#define ") + Name + " " + std::to_string(Value) + Suffix + "\n"; }; using namespace vk14::bindings; define("UI_SET", UI_SET); @@ -786,6 +801,7 @@ std::string binding_preamble() define("LOCAL_SHADOW_LAMPS_TEX", LOCAL_SHADOW_LAMPS_TEX); define("HEADLIGHT_SHADOW_TEX", HEADLIGHT_SHADOW_TEX); define("SCENE_DEPTH_TEX", SCENE_DEPTH_TEX); + define("SSR_HISTORY_TEX", SSR_HISTORY_TEX); define("PASS_BINDING_COUNT", PASS_BINDING_COUNT); define("MATERIAL_TEXTURE_FIRST", MATERIAL_TEXTURE_FIRST); define("CL_CLUSTER_UBO", CL_CLUSTER_UBO); @@ -799,6 +815,10 @@ std::string binding_preamble() define("POST_TEXTURES", POST_TEXTURES); define("POST_EXPOSURE_SSBO", POST_EXPOSURE_SSBO); define("COMPUTE_SET", COMPUTE_SET); + define("RT_SET", RT_SET); + define("RT_SCENE", RT_SCENE); + define("RT_RECORDS", RT_RECORDS); + define("RT_TEXTURES", RT_TEXTURES); define("PUSH_CONSTANTS_SIZE", PUSH_CONSTANTS_SIZE); define("SPEC_OUTPUT_GAMMA", SPEC_OUTPUT_GAMMA); define("SPEC_SHADOWMAP_ENABLED", SPEC_SHADOWMAP_ENABLED); diff --git a/rendering/vulkan14/vulkan14texture.h b/rendering/vulkan14/vulkan14texture.h index 95ae4410..2dc97283 100644 --- a/rendering/vulkan14/vulkan14texture.h +++ b/rendering/vulkan14/vulkan14texture.h @@ -139,6 +139,8 @@ struct resources // the thread of the renderer: the content given by other threads waits for it (update_from_memory()) std::thread::id thread; pending_textures *pending{nullptr}; + // the ray tracing is on (gfx.raytracing): the geometry is built into acceleration structures and read by its device address + bool raytracing{false}; }; // the vulkan format of the data of a texture and how the data has to be changed for it diff --git a/shaders/ogl46/common.glsl b/shaders/ogl46/common.glsl index 3234ccdb..3dad7f79 100644 --- a/shaders/ogl46/common.glsl +++ b/shaders/ogl46/common.glsl @@ -95,6 +95,7 @@ #define CLOUD_COVERAGE_TEX 21 #define CLOUD_HISTORY_TEX 22 #define SCENE_DEPTH_TEX 23 +#define SSR_HISTORY_TEX 24 #define WEATHER_UBO_BINDING 5 #define WEATHER_SCRATCH_SSBO_BINDING 9 #define MAX_CLOUD_LAYERS 4 @@ -289,6 +290,13 @@ layout(std140, binding = SCENE_UBO_BINDING) uniform scene_ubo vec4 contact_shadow_lamps; // cascades of the sun shadows fitted to the depth of the view, see gl::scene_ubs vec4 shadow_fit; + // screen space reflections and the reflection of the surroundings, see gl::scene_ubs + mat4 ssr_reprojection; + mat4 ssr_reprojection_cab; + mat4 ssr_cab_box; + vec4 ssr_params; + vec4 ssr_params2; + vec4 env_params; }; #ifdef STAGE_VERTEX diff --git a/shaders/ogl46/envmapping.glsl b/shaders/ogl46/envmapping.glsl index ef9f9535..ea798957 100644 --- a/shaders/ogl46/envmapping.glsl +++ b/shaders/ogl46/envmapping.glsl @@ -1,66 +1,49 @@ +// the environment map: the sky and the clouds around the camera (the reflection pass draws nothing else into it since the +// screen space reflections took over the scenery), with its mipmaps and the levels prefiltered with the GGX lobe layout(binding = ENV_TEX) uniform samplerCube envmap; // the same map prefiltered with the GGX lobe (envmap_prefilter.comp), used when env_ggx is 1 layout(binding = ENV_GGX_TEX) uniform samplerCube envmap_ggx; -vec3 envmap_color( vec3 normal ) +// the world space direction of the reflection of the view off a surface of the normal (view space) +vec3 envmap_direction(vec3 normal) +{ + vec3 refvec = reflect(f_pos.xyz, normal); // view space + return vec3(inv_view * vec4(refvec, 0.0)); // world space +} + +// the map in a world space direction, at a level of its mipmaps +vec3 envmap_sample_lod(vec3 direction, float lod) { - vec3 envcolor; if (ENVMAP_ENABLED) { - vec3 refvec = reflect(f_pos.xyz, normal); // view space - refvec = vec3(inv_view * vec4(refvec, 0.0)); // world space - envcolor = texture(envmap, refvec).rgb; + vec3 envcolor = textureLod(envmap, direction, lod).rgb; // Sanitize. The env cubemap can briefly contain NaN/Inf after a // reflection-pass regeneration (m_empty_cubemap was bound during the - // face render and on NVIDIA can sample as undefined). Without this - // guard, a single bad texel propagates through env_reflection in - // apply_lights() and produces one-frame black flashes on glossy - // (esp. specgloss) surfaces. + // face render and on NVIDIA can sample as undefined), and the mipmap + // generation propagates a single NaN texel across the whole mip chain. + // Without this guard a single bad texel produces one-frame black + // flashes on glossy (esp. specgloss) surfaces. if (any(isnan(envcolor)) || any(isinf(envcolor))) envcolor = vec3(0.0); - envcolor = max(envcolor, vec3(0.0)); + return max(envcolor, vec3(0.0)); } else { - envcolor = vec3(0.5); + return vec3(0.5); } - return envcolor; } -// Roughness-aware env map lookup — uses mip levels for blurry reflections. -// lod 0.0 = mirror sharp, lod ~8.0 = fully diffuse blur. -vec3 envmap_color_lod(vec3 fragnormal, float lod) -{ - if (ENVMAP_ENABLED) - { - vec3 refvec = reflect(f_pos.xyz, fragnormal); // view space — matches envmap_color exactly - refvec = vec3(inv_view * vec4(refvec, 0.0)); // world space — was missing - vec3 envcolor = textureLod(envmap, refvec, lod).rgb; - // See envmap_color() above — same NaN/Inf sanitize, also needed here - // because mipmap generation propagates a single NaN texel across the - // whole mip chain. - if (any(isnan(envcolor)) || any(isinf(envcolor))) envcolor = vec3(0.0); - return max(envcolor, vec3(0.0)); - } - else - { - return vec3(0.5); // was vec3(0.0), match the non-LOD fallback - } -} - -// reflection of the surroundings in a surface of the given perceptual roughness (0 mirror, 1 matte) -vec3 envmap_color_rough(vec3 fragnormal, float roughness) +// the map in a world space direction for a surface of the given perceptual roughness (0 mirror, 1 matte) +vec3 envmap_sample_rough(vec3 direction, float roughness) { // the plain map, its mipmaps (box filtered) standing for the roughness const float MAX_REFLECTION_LOD = 8.0; - vec3 boxcolor = envmap_color_lod(fragnormal, roughness * MAX_REFLECTION_LOD); + vec3 boxcolor = envmap_sample_lod(direction, roughness * MAX_REFLECTION_LOD); if (ENVMAP_ENABLED) { if (env_ggx == 0U || roughness <= ENVMAP_GGX_ROUGHNESS_MIN) return boxcolor; - vec3 refvec = reflect(f_pos.xyz, fragnormal); - refvec = vec3(inv_view * vec4(refvec, 0.0)); float level = (roughness - ENVMAP_GGX_ROUGHNESS_MIN) / (1.0 - ENVMAP_GGX_ROUGHNESS_MIN) * float(ENVMAP_GGX_LEVELS - 1U); - vec3 ggxcolor = textureLod(envmap_ggx, refvec, level).rgb; + vec3 ggxcolor = textureLod(envmap_ggx, direction, level).rgb; if (any(isnan(ggxcolor)) || any(isinf(ggxcolor))) ggxcolor = vec3(0.0); ggxcolor = max(ggxcolor, vec3(0.0)); // the prefiltered map takes over over the first tenth of the roughness above its lowest level @@ -71,3 +54,21 @@ vec3 envmap_color_rough(vec3 fragnormal, float roughness) return boxcolor; } } + +vec3 envmap_color( vec3 normal ) +{ + return envmap_sample_lod(envmap_direction(normal), 0.0); +} + +// Roughness-aware env map lookup — uses mip levels for blurry reflections. +// lod 0.0 = mirror sharp, lod ~8.0 = fully diffuse blur. +vec3 envmap_color_lod(vec3 fragnormal, float lod) +{ + return envmap_sample_lod(envmap_direction(fragnormal), lod); +} + +// reflection of the surroundings in a surface of the given perceptual roughness (0 mirror, 1 matte) +vec3 envmap_color_rough(vec3 fragnormal, float roughness) +{ + return envmap_sample_rough(envmap_direction(fragnormal), roughness); +} diff --git a/shaders/ogl46/light_common.glsl b/shaders/ogl46/light_common.glsl index d4bc4c52..cbae4a0f 100644 --- a/shaders/ogl46/light_common.glsl +++ b/shaders/ogl46/light_common.glsl @@ -10,6 +10,7 @@ layout(binding = HEADLIGHT_SHADOW_TEX) uniform sampler2DArrayShadow headlight_sh layout(binding = SCENE_DEPTH_TEX) uniform sampler2D scene_depth; #include "envmapping.glsl" +#include "ssr.glsl" #include "conversion.glsl" #include "weather_common.glsl" @@ -657,6 +658,55 @@ vec3 add_sky_ambient(vec3 basecolor, vec3 fragnormal) return max(basecolor + sky_ambient_directional(worldnormal) * (sky_ambient_factor * share), vec3(0.0)); } +// the light of the ground below the horizon of the environment map, which holds the sky alone: the ground of the albedo +// env_params.x lit by the sky and the sun, as a surface facing up +vec3 reflection_ground() +{ + vec3 light = ambient; + if (sky_ambient_factor != 0.0) + light = max(light + sky_ambient_directional(vec3(0.0, 1.0, 0.0)) * sky_ambient_factor, vec3(0.0)); + light *= AMBIENT_SCALE; + if (lights_count > 0U) + { + vec3 sun = -normalize(mat3(inv_view) * lights[0].dir); + light += lights[0].color * (SUN_DIFFUSE_SCALE * lights[0].intensity * max(sun.y, 0.0)); + } + return light * env_params.x; +} + +// the reflection of the surroundings the scene doesn't give (the screen space reflections, the ray tracing): the environment +// map above the horizon (the sky and the clouds), the ground below it +vec3 reflection_surroundings(vec3 fragnormal, float roughness) +{ + vec3 direction = normalize(envmap_direction(fragnormal)); + vec3 sky = envmap_sample_rough(normalize(vec3(direction.x, max(direction.y, 0.0), direction.z)), roughness); + float below = smoothstep(0.0, 0.25, -direction.y); + if ((below <= 0.0) || (false == ENVMAP_ENABLED)) + return sky; + return mix(sky, reflection_ground(), below); +} + +// the least weight of the reflection of the surroundings (its specular colour times its share) a surface traces the scene for +const float REFLECTION_SCENE_WEIGHT = 0.004; + +// the reflection of the scene in a surface of the given perceptual roughness: the colour times how much of the reflection the +// scene gives (rgb), and that share (a), the rest is left to reflection_surroundings() +vec4 reflection_scene(vec3 fragnormal, float roughness) +{ + vec3 direction = normalize(reflect(f_pos.xyz, fragnormal)); + vec4 result = ssr_trace(f_pos.xyz, normalize(f_normal), direction, roughness); +#ifdef REFLECTION_RAYS_ACTIVE + // what the screen doesn't hold is traced through the scenery (shaders/vk14/rt_reflections.glsl) + if ((result.a < 0.99) && (ssr_params2.z > 0.0) && (roughness <= ssr_params.z)) + { + vec4 traced = rt_trace(f_pos.xyz, normalize(f_normal), direction, roughness); + result.rgb += traced.rgb * (1.0 - result.a); + result.a += traced.a * (1.0 - result.a); + } +#endif + return result; +} + // lights the surface: the ambient light, the sun (the moon), the headlights and lamps and the reflection of the surroundings. // the units are the ones of the ambient light, the irradiance over pi: a white lambertian surface lit by 1 of it shows 1. // Texturecolor is the albedo (the base colour of a metal), Specularity the specular level of a dielectric (0.5 is the @@ -734,13 +784,22 @@ vec3 shade_surface(vec3 fragcolor, vec3 fragnormal, vec3 texturecolor, float ref ambient_share = 1.0; diffuse_albedo *= 1.0 - cubemap_spec * min(cubemap_share, 1.0); } - vec3 envcolor = (matte ? vec3(0.0) : envmap_color_rough(fragnormal, env_roughness)); + // the reflection of the surroundings: the scene where the screen (or the ray tracing) gives it, the sky and the ground of the + // environment map elsewhere + vec3 envcolor = vec3(0.0); + vec4 scenecolor = vec4(0.0); + if (!matte) + { + envcolor = reflection_surroundings(fragnormal, env_roughness); + if (((ssr_params.x > 0.0) || (ssr_params2.z > 0.0)) && (cubemap_share * dot(cubemap_spec, vec3(1.0 / 3.0)) > REFLECTION_SCENE_WEIGHT)) + scenecolor = reflection_scene(fragnormal, env_roughness); + } if (lights_count == 0U) return fragcolor * diffuse_albedo + emissioncolor * texturecolor + fragcolor * env_spec * ambient_share - + envcolor * cubemap_spec * cubemap_share; + + (scenecolor.rgb + envcolor * (1.0 - scenecolor.a)) * cubemap_spec * cubemap_share; vec3 light_dir = normalize(-lights[0].dir); float NdotL = max(dot(fragnormal, light_dir), 0.0); @@ -757,7 +816,8 @@ vec3 shade_surface(vec3 fragcolor, vec3 fragnormal, vec3 texturecolor, float ref if (shadowtone < 1.0) shadow = clamp(calc_shadow(), 0.0, 1.0); - // the cubemap knows nothing of what shades the fragment: it is dimmed a little in the shadow of the sun + // the environment map knows nothing of what shades the fragment: it is dimmed a little in the shadow of the sun (the scene the + // reflection finds is lit as it is) float cubemap_shadow = 1.0; if (sunless) { @@ -771,7 +831,7 @@ vec3 shade_surface(vec3 fragcolor, vec3 fragnormal, vec3 texturecolor, float ref // the reflection of the surroundings, before the light of the sun and the lamps joins the ambient light vec3 env_reflection = fragcolor * env_spec * ambient_share - + envcolor * cubemap_spec * (cubemap_share * cubemap_shadow); + + (scenecolor.rgb + envcolor * ((1.0 - scenecolor.a) * cubemap_shadow)) * cubemap_spec * cubemap_share; // the shadow leaves the share of the sun Shadowtone tells (the old shadow colour of the scenery) to the diffuse light, // nothing to the highlight diff --git a/shaders/ogl46/ssr.glsl b/shaders/ogl46/ssr.glsl new file mode 100644 index 00000000..02c88792 --- /dev/null +++ b/shaders/ogl46/ssr.glsl @@ -0,0 +1,155 @@ +// screen space reflections (gfx.ssr) of the colour pass of the main view, included by light_common.glsl. the reflection of a +// surface is traced as a ray through the depth of the opaque scene drawn before the colour (scene_depth: the reverse z of a +// projection with the far plane at infinity, a depth d is a surface n / d away along the axis of the view), and its colour is +// taken from the image of the last frame (ssr_history) at the place the point hit had in that frame. what the screen doesn't +// hold (off the image, behind something nearer, too far, too rough) is left to the caller: the alpha of the result says how much +// of the reflection the screen gave. scene_ubs.ssr_params is set only for the main view, zero elsewhere +layout(binding = SSR_HISTORY_TEX) uniform sampler2D ssr_history; + +// the steps of a ray that found something are refined this many times, halving the step +const int SSR_REFINE_STEPS = 5; + +// a value 0 .. 1 different for the neighbouring pixels (interleaved gradient noise), and from frame to frame with the temporal +// anti-aliasing, which blends the steps of the rays into a smooth image +float ssr_noise() +{ + vec2 pixel = gl_FragCoord.xy + 5.588238 * ssr_params.w; + return fract(52.9829189 * fract(dot(pixel, vec2(0.06711056, 0.00583715)))); +} + +// the distance along the axis of the view of the surface in the depth at a place of the image (0 .. 1), 0: nothing there (the sky) +float ssr_surface(vec2 coords, vec2 size, float near) +{ + float depth = texelFetch(scene_depth, clamp(ivec2(coords * size), ivec2(0), ivec2(size) - 1), 0).r; + return (depth > 0.0 ? near / depth : 0.0); +} + +// the reflection the screen holds, of a surface at Position (view space) with the geometric normal Normal (view space, either +// side), for a ray along Direction (view space, normalized) and the perceptual roughness Roughness: the colour (rgb) times how +// much of it the screen holds (a) +vec4 ssr_trace(vec3 position, vec3 normal, vec3 direction, float roughness) +{ + float steps = ssr_params.x; + if ((steps < 1.0) || (roughness > ssr_params.z)) + return vec4(0.0); + // the side of the surface the camera sees; a ray going into it (a normal map turned far from the face) finds nothing + if (dot(normal, position) > 0.0) + normal = -normal; + if (dot(normal, direction) <= 0.0) + return vec4(0.0); + vec2 size = vec2(textureSize(scene_depth, 0)); + float near = projection[3][2]; + float pixelscale = 2.0 / (abs(projection[1][1]) * size.y); + // the ray starts two pixels off the surface, so the depth of the surface itself doesn't stop it + vec4 start = projection * vec4(position, 1.0); + vec3 origin = position + normal * (start.w * pixelscale * 2.0); + vec4 c0 = projection * vec4(origin, 1.0); + // the distance along the axis of the view changes with the ray: it ends in front of the near plane + float dw = (projection * vec4(direction, 0.0)).w; + float raylength = ssr_params.y; + if (dw < 0.0) + raylength = min(raylength, (c0.w - near * 2.0) / -dw); + if (raylength <= 0.0) + return vec4(0.0); + vec3 end = origin + direction * raylength; + vec4 c1 = projection * vec4(end, 1.0); + // the points of the ray are interpolated across the image (u) with 1 / w, which is linear there + float k0 = 1.0 / c0.w; + float k1 = 1.0 / c1.w; + vec2 s0 = c0.xy * k0; + vec2 s1 = c1.xy * k1; + vec2 delta = s1 - s0; + // the part of the ray over the image + float umax = 1.0; + if (abs(delta.x) > 1e-6) + umax = min(umax, ((delta.x > 0.0 ? 1.0 : -1.0) - s0.x) / delta.x); + if (abs(delta.y) > 1e-6) + umax = min(umax, ((delta.y > 0.0 ? 1.0 : -1.0) - s0.y) / delta.y); + if (umax <= 0.0) + return vec4(0.0); + // not more steps than the pixels the ray crosses + float pixels = length(delta * umax * size * 0.5); + float count = clamp(floor(pixels), 1.0, steps); + float jitter = ssr_noise(); + + float uprev = 0.0; + float wprev = c0.w; + float uhit = -1.0; + for (float i = 0.0; i < count; i += 1.0) + { + float u = umax * min((i + jitter) / count, 1.0); + float w = 1.0 / mix(k0, k1, u); + vec2 coords = mix(s0, s1, u) * 0.5 + 0.5; + float surface = ssr_surface(coords, size, near); + float bias = w * pixelscale * 0.5; + // the things in the depth are taken to be this thick behind their faces, with the length of the step on top + float thickness = max(0.25, surface * 0.02) + abs(w - wprev); + if ((surface > 0.0) && (w - surface > bias) && (w - surface < thickness)) + { + // refined between the last step in front of the surface and this one behind it + float lo = uprev; + float hi = u; + for (int r = 0; r < SSR_REFINE_STEPS; ++r) + { + float mid = 0.5 * (lo + hi); + float wmid = 1.0 / mix(k0, k1, mid); + float smid = ssr_surface(mix(s0, s1, mid) * 0.5 + 0.5, size, near); + if ((smid > 0.0) && (wmid - smid > 0.0)) + hi = mid; + else + lo = mid; + } + // what the ray went behind has to be close to it there: otherwise the ray went behind something thin, and goes on + float whit = 1.0 / mix(k0, k1, hi); + float shit = ssr_surface(mix(s0, s1, hi) * 0.5 + 0.5, size, near); + if ((shit > 0.0) && (abs(whit - shit) < max(0.25, shit * 0.02) + whit * pixelscale * 2.0)) + { + uhit = hi; + break; + } + } + uprev = u; + wprev = w; + } + if (uhit < 0.0) + return vec4(0.0); + + // the point hit, view space + vec3 hit = mix(origin * k0, end * k1, uhit) / mix(k0, k1, uhit); + vec2 hitcoords = mix(s0, s1, uhit) * 0.5 + 0.5; + // where it was in the last frame: the cab moves with the camera, the world stands still + mat4 reprojection = ssr_reprojection; + if (ssr_params2.y > 0.0) + { + vec3 box = (ssr_cab_box * vec4(hit, 1.0)).xyz; + if (all(lessThanEqual(abs(box), vec3(1.0)))) + reprojection = ssr_reprojection_cab; + } + vec4 previous = reprojection * vec4(hit, 1.0); + if (previous.w <= 0.0) + return vec4(0.0); + vec2 coords = (previous.xy / previous.w) * 0.5 + 0.5; + + float share = 1.0; + // faded towards the edges of the image, where it was hit and where it was in the last frame + vec2 edges = min(min(hitcoords, 1.0 - hitcoords), min(coords, 1.0 - coords)); + share *= smoothstep(0.0, 0.04, min(edges.x, edges.y)); + // and towards the end of the ray + float travelled = length(hit - origin); + share *= 1.0 - smoothstep(0.75, 1.0, travelled / ssr_params.y); + // the rougher surfaces hand over to the environment map + share *= 1.0 - smoothstep(ssr_params.z * 0.7, ssr_params.z, roughness); + // a ray towards the camera meets the backs of what the image shows + share *= 1.0 - smoothstep(0.4, 0.9, direction.z); + if (share <= 0.0) + return vec4(0.0); + + // the lobe of a rough surface covers more of the image the farther the point hit is: a blurrier level of the history + float alpha = roughness * roughness; + float hitpixel = max((projection * vec4(hit, 1.0)).w, near) * pixelscale; + float lod = clamp(log2(max(2.0 * travelled * alpha / hitpixel, 1.0)), 0.0, ssr_params2.x); + vec3 color = textureLod(ssr_history, coords, lod).rgb; + if (any(isnan(color)) || any(isinf(color))) + return vec4(0.0); + return vec4(max(color, vec3(0.0)) * share, share); +} diff --git a/shaders/vk14/common.glsl b/shaders/vk14/common.glsl index 458eb33b..9424c05b 100644 --- a/shaders/vk14/common.glsl +++ b/shaders/vk14/common.glsl @@ -59,7 +59,8 @@ #define LOCAL_SHADOW_LAMPS_TEX 22 #define HEADLIGHT_SHADOW_TEX 23 #define SCENE_DEPTH_TEX 24 -#define PASS_BINDING_COUNT 25 +#define SSR_HISTORY_TEX 25 +#define PASS_BINDING_COUNT 26 #define MATERIAL_TEXTURE_FIRST 0 #define CL_CLUSTER_UBO 0 #define CL_AABB_SSBO 1 @@ -72,6 +73,10 @@ #define POST_TEXTURES 4 #define POST_EXPOSURE_SSBO 4 #define COMPUTE_SET 1 +#define RT_SET 2 +#define RT_SCENE 0 +#define RT_RECORDS 1 +#define RT_TEXTURES 2 #define PUSH_CONSTANTS_SIZE 112 #define SPEC_OUTPUT_GAMMA 0 #define SPEC_SHADOWMAP_ENABLED 1 @@ -259,6 +264,12 @@ layout(std140, set = PASS_SET, binding = SCENE_UBO) uniform scene_ubo vec4 contact_shadow_params; vec4 contact_shadow_lamps; vec4 shadow_fit; + mat4 ssr_reprojection; + mat4 ssr_reprojection_cab; + mat4 ssr_cab_box; + vec4 ssr_params; + vec4 ssr_params2; + vec4 env_params; }; #ifndef LAYOUT_COMPUTE diff --git a/shaders/vk14/envmapping.glsl b/shaders/vk14/envmapping.glsl index 1239845a..3c9506fb 100644 --- a/shaders/vk14/envmapping.glsl +++ b/shaders/vk14/envmapping.glsl @@ -1,66 +1,49 @@ +// the environment map: the sky and the clouds around the camera (the reflection pass draws nothing else into it since the +// screen space reflections took over the scenery), with its mipmaps and the levels prefiltered with the GGX lobe layout(set = PASS_SET, binding = ENV_TEX) uniform samplerCube envmap; // the same map prefiltered with the GGX lobe (envmap_prefilter.comp), used when env_ggx is 1 layout(set = PASS_SET, binding = ENV_GGX_TEX) uniform samplerCube envmap_ggx; -vec3 envmap_color( vec3 normal ) +// the world space direction of the reflection of the view off a surface of the normal (view space) +vec3 envmap_direction(vec3 normal) +{ + vec3 refvec = reflect(f_pos.xyz, normal); // view space + return vec3(inv_view * vec4(refvec, 0.0)); // world space +} + +// the map in a world space direction, at a level of its mipmaps +vec3 envmap_sample_lod(vec3 direction, float lod) { - vec3 envcolor; if (ENVMAP_ENABLED) { - vec3 refvec = reflect(f_pos.xyz, normal); // view space - refvec = vec3(inv_view * vec4(refvec, 0.0)); // world space - envcolor = texture(envmap, refvec).rgb; + vec3 envcolor = textureLod(envmap, direction, lod).rgb; // Sanitize. The env cubemap can briefly contain NaN/Inf after a // reflection-pass regeneration (m_empty_cubemap was bound during the - // face render and on NVIDIA can sample as undefined). Without this - // guard, a single bad texel propagates through env_reflection in - // apply_lights() and produces one-frame black flashes on glossy - // (esp. specgloss) surfaces. + // face render and on NVIDIA can sample as undefined), and the mipmap + // generation propagates a single NaN texel across the whole mip chain. + // Without this guard a single bad texel produces one-frame black + // flashes on glossy (esp. specgloss) surfaces. if (any(isnan(envcolor)) || any(isinf(envcolor))) envcolor = vec3(0.0); - envcolor = max(envcolor, vec3(0.0)); + return max(envcolor, vec3(0.0)); } else { - envcolor = vec3(0.5); + return vec3(0.5); } - return envcolor; } -// Roughness-aware env map lookup — uses mip levels for blurry reflections. -// lod 0.0 = mirror sharp, lod ~8.0 = fully diffuse blur. -vec3 envmap_color_lod(vec3 fragnormal, float lod) -{ - if (ENVMAP_ENABLED) - { - vec3 refvec = reflect(f_pos.xyz, fragnormal); // view space — matches envmap_color exactly - refvec = vec3(inv_view * vec4(refvec, 0.0)); // world space — was missing - vec3 envcolor = textureLod(envmap, refvec, lod).rgb; - // See envmap_color() above — same NaN/Inf sanitize, also needed here - // because mipmap generation propagates a single NaN texel across the - // whole mip chain. - if (any(isnan(envcolor)) || any(isinf(envcolor))) envcolor = vec3(0.0); - return max(envcolor, vec3(0.0)); - } - else - { - return vec3(0.5); // was vec3(0.0), match the non-LOD fallback - } -} - -// reflection of the surroundings in a surface of the given perceptual roughness (0 mirror, 1 matte) -vec3 envmap_color_rough(vec3 fragnormal, float roughness) +// the map in a world space direction for a surface of the given perceptual roughness (0 mirror, 1 matte) +vec3 envmap_sample_rough(vec3 direction, float roughness) { // the plain map, its mipmaps (box filtered) standing for the roughness const float MAX_REFLECTION_LOD = 8.0; - vec3 boxcolor = envmap_color_lod(fragnormal, roughness * MAX_REFLECTION_LOD); + vec3 boxcolor = envmap_sample_lod(direction, roughness * MAX_REFLECTION_LOD); if (ENVMAP_ENABLED) { if (env_ggx == 0U || roughness <= ENVMAP_GGX_ROUGHNESS_MIN) return boxcolor; - vec3 refvec = reflect(f_pos.xyz, fragnormal); - refvec = vec3(inv_view * vec4(refvec, 0.0)); float level = (roughness - ENVMAP_GGX_ROUGHNESS_MIN) / (1.0 - ENVMAP_GGX_ROUGHNESS_MIN) * float(ENVMAP_GGX_LEVELS - 1U); - vec3 ggxcolor = textureLod(envmap_ggx, refvec, level).rgb; + vec3 ggxcolor = textureLod(envmap_ggx, direction, level).rgb; if (any(isnan(ggxcolor)) || any(isinf(ggxcolor))) ggxcolor = vec3(0.0); ggxcolor = max(ggxcolor, vec3(0.0)); // the prefiltered map takes over over the first tenth of the roughness above its lowest level @@ -71,3 +54,21 @@ vec3 envmap_color_rough(vec3 fragnormal, float roughness) return boxcolor; } } + +vec3 envmap_color( vec3 normal ) +{ + return envmap_sample_lod(envmap_direction(normal), 0.0); +} + +// Roughness-aware env map lookup — uses mip levels for blurry reflections. +// lod 0.0 = mirror sharp, lod ~8.0 = fully diffuse blur. +vec3 envmap_color_lod(vec3 fragnormal, float lod) +{ + return envmap_sample_lod(envmap_direction(fragnormal), lod); +} + +// reflection of the surroundings in a surface of the given perceptual roughness (0 mirror, 1 matte) +vec3 envmap_color_rough(vec3 fragnormal, float roughness) +{ + return envmap_sample_rough(envmap_direction(fragnormal), roughness); +} diff --git a/shaders/vk14/light_common.glsl b/shaders/vk14/light_common.glsl index 2ac0e89a..88ae7795 100644 --- a/shaders/vk14/light_common.glsl +++ b/shaders/vk14/light_common.glsl @@ -14,6 +14,7 @@ layout(set = PASS_SET, binding = HEADLIGHT_SHADOW_TEX) uniform sampler2DArraySha layout(set = PASS_SET, binding = SCENE_DEPTH_TEX) uniform sampler2D scene_depth; #include "envmapping.glsl" +#include "ssr.glsl" #include "conversion.glsl" #include "weather_common.glsl" @@ -661,6 +662,61 @@ vec3 add_sky_ambient(vec3 basecolor, vec3 fragnormal) return max(basecolor + sky_ambient_directional(worldnormal) * (sky_ambient_factor * share), vec3(0.0)); } +// the light of the ground below the horizon of the environment map, which holds the sky alone: the ground of the albedo +// env_params.x lit by the sky and the sun, as a surface facing up +vec3 reflection_ground() +{ + vec3 light = ambient; + if (sky_ambient_factor != 0.0) + light = max(light + sky_ambient_directional(vec3(0.0, 1.0, 0.0)) * sky_ambient_factor, vec3(0.0)); + light *= AMBIENT_SCALE; + if (lights_count > 0U) + { + vec3 sun = -normalize(mat3(inv_view) * lights[0].dir); + light += lights[0].color * (SUN_DIFFUSE_SCALE * lights[0].intensity * max(sun.y, 0.0)); + } + return light * env_params.x; +} + +// the reflection of the surroundings the scene doesn't give (the screen space reflections, the ray tracing): the environment +// map above the horizon (the sky and the clouds), the ground below it +vec3 reflection_surroundings(vec3 fragnormal, float roughness) +{ + vec3 direction = normalize(envmap_direction(fragnormal)); + vec3 sky = envmap_sample_rough(normalize(vec3(direction.x, max(direction.y, 0.0), direction.z)), roughness); + float below = smoothstep(0.0, 0.25, -direction.y); + if ((below <= 0.0) || (false == ENVMAP_ENABLED)) + return sky; + return mix(sky, reflection_ground(), below); +} + +// the ray traced reflections, in the fragment shaders of the scene (the layout with the set of the ray tracing) +#if defined(REFLECTION_RAYS) && defined(STAGE_FRAGMENT) && !defined(LAYOUT_COMPUTE) && !defined(LAYOUT_POST) && !defined(LAYOUT_CLUSTER) && !defined(LAYOUT_UI) +#define REFLECTION_RAYS_ACTIVE 1 +#include "rt_reflections.glsl" +#endif + +// the least weight of the reflection of the surroundings (its specular colour times its share) a surface traces the scene for +const float REFLECTION_SCENE_WEIGHT = 0.004; + +// the reflection of the scene in a surface of the given perceptual roughness: the colour times how much of the reflection the +// scene gives (rgb), and that share (a), the rest is left to reflection_surroundings() +vec4 reflection_scene(vec3 fragnormal, float roughness) +{ + vec3 direction = normalize(reflect(f_pos.xyz, fragnormal)); + vec4 result = ssr_trace(f_pos.xyz, normalize(f_normal), direction, roughness); +#ifdef REFLECTION_RAYS_ACTIVE + // what the screen doesn't hold is traced through the scenery (shaders/vk14/rt_reflections.glsl) + if ((result.a < 0.99) && (ssr_params2.z > 0.0) && (roughness <= ssr_params.z)) + { + vec4 traced = rt_trace(f_pos.xyz, normalize(f_normal), direction, roughness); + result.rgb += traced.rgb * (1.0 - result.a); + result.a += traced.a * (1.0 - result.a); + } +#endif + return result; +} + // lights the surface: the ambient light, the sun (the moon), the headlights and lamps and the reflection of the surroundings. // the units are the ones of the ambient light, the irradiance over pi: a white lambertian surface lit by 1 of it shows 1. // Texturecolor is the albedo (the base colour of a metal), Specularity the specular level of a dielectric (0.5 is the @@ -738,13 +794,22 @@ vec3 shade_surface(vec3 fragcolor, vec3 fragnormal, vec3 texturecolor, float ref ambient_share = 1.0; diffuse_albedo *= 1.0 - cubemap_spec * min(cubemap_share, 1.0); } - vec3 envcolor = (matte ? vec3(0.0) : envmap_color_rough(fragnormal, env_roughness)); + // the reflection of the surroundings: the scene where the screen (or the ray tracing) gives it, the sky and the ground of the + // environment map elsewhere + vec3 envcolor = vec3(0.0); + vec4 scenecolor = vec4(0.0); + if (!matte) + { + envcolor = reflection_surroundings(fragnormal, env_roughness); + if (((ssr_params.x > 0.0) || (ssr_params2.z > 0.0)) && (cubemap_share * dot(cubemap_spec, vec3(1.0 / 3.0)) > REFLECTION_SCENE_WEIGHT)) + scenecolor = reflection_scene(fragnormal, env_roughness); + } if (lights_count == 0U) return fragcolor * diffuse_albedo + emissioncolor * texturecolor + fragcolor * env_spec * ambient_share - + envcolor * cubemap_spec * cubemap_share; + + (scenecolor.rgb + envcolor * (1.0 - scenecolor.a)) * cubemap_spec * cubemap_share; vec3 light_dir = normalize(-lights[0].dir); float NdotL = max(dot(fragnormal, light_dir), 0.0); @@ -761,7 +826,8 @@ vec3 shade_surface(vec3 fragcolor, vec3 fragnormal, vec3 texturecolor, float ref if (shadowtone < 1.0) shadow = clamp(calc_shadow(), 0.0, 1.0); - // the cubemap knows nothing of what shades the fragment: it is dimmed a little in the shadow of the sun + // the environment map knows nothing of what shades the fragment: it is dimmed a little in the shadow of the sun (the scene the + // reflection finds is lit as it is) float cubemap_shadow = 1.0; if (sunless) { @@ -775,7 +841,7 @@ vec3 shade_surface(vec3 fragcolor, vec3 fragnormal, vec3 texturecolor, float ref // the reflection of the surroundings, before the light of the sun and the lamps joins the ambient light vec3 env_reflection = fragcolor * env_spec * ambient_share - + envcolor * cubemap_spec * (cubemap_share * cubemap_shadow); + + (scenecolor.rgb + envcolor * ((1.0 - scenecolor.a) * cubemap_shadow)) * cubemap_spec * cubemap_share; // the shadow leaves the share of the sun Shadowtone tells (the old shadow colour of the scenery) to the diffuse light, // nothing to the highlight diff --git a/shaders/vk14/rt_reflections.glsl b/shaders/vk14/rt_reflections.glsl new file mode 100644 index 00000000..dc20a753 --- /dev/null +++ b/shaders/vk14/rt_reflections.glsl @@ -0,0 +1,147 @@ +// the ray traced reflections of the vulkan renderer (gfx.raytracing, vk14::ray_tracing), included by light_common.glsl in the +// fragment shaders of the scene when the renderer puts REFLECTION_RAYS before the sources (with the extensions: GL_EXT_ray_query for +// the rays, GL_EXT_buffer_reference2 and _uvec2 for the vertices read by their device addresses, GL_EXT_nonuniform_qualifier for the +// textures picked by the instance hit). what the screen space reflections don't find is traced through the scenery around the camera +// the renderer gathered for the frame (the walk of the scene in its reflection mode: the sections, the models and the vehicles within +// gfx.raytracing.range, as gfx.reflections.fidelity says), and the point hit is lit by the sky, the sun with a ray of its own towards +// it and its own emission. the space of the rays is the world around the camera: the axes of the world, the camera at the origin + +layout(set = RT_SET, binding = RT_SCENE) uniform accelerationStructureEXT rt_scene; + +// what the renderer wrote for an instance (vk14::ray_tracing::record), picked by its custom index +struct rt_record +{ + uvec2 vertices; // the device address of the first vertex of the chunk (gfx::basic_vertex, 12 floats) + uvec2 indices; // of its first index, zero: the vertices go one after another + uint texindex; // in rt_textures, 0: none (white) + uint flags; // 1: the texture is alpha tested with cutoff + float cutoff; // the alpha test, as opacity of the model block + float glow; // the light of its own, as emission of the model block + vec4 colour; // the diffuse colour of the material, linear, as material_diffuse() makes it +}; + +layout(std430, set = RT_SET, binding = RT_RECORDS) readonly buffer rt_record_ssbo +{ + rt_record rt_records[]; +}; + +layout(set = RT_SET, binding = RT_TEXTURES) uniform sampler2D rt_textures[]; + +layout(buffer_reference, std430, buffer_reference_align = 4) readonly buffer rt_vertex_data +{ + float rt_vertex[]; +}; + +layout(buffer_reference, std430, buffer_reference_align = 4) readonly buffer rt_index_data +{ + uint rt_index[]; +}; + +const uint RT_FLAG_ALPHA = 1U; +const uint RT_VERTEX_FLOATS = 12U; + +// the vertices of a triangle of the record +uvec3 rt_triangle(rt_record record, uint primitive) +{ + if ((record.indices.x == 0U) && (record.indices.y == 0U)) + return uvec3(primitive * 3U) + uvec3(0U, 1U, 2U); + rt_index_data indices = rt_index_data(record.indices); + return uvec3(indices.rt_index[primitive * 3U], indices.rt_index[primitive * 3U + 1U], indices.rt_index[primitive * 3U + 2U]); +} + +// the texture coordinates of a point of a triangle, its barycentric coordinates Weights +vec2 rt_coords(rt_record record, uvec3 triangle, vec2 weights) +{ + rt_vertex_data vertices = rt_vertex_data(record.vertices); + vec2 a = vec2(vertices.rt_vertex[triangle.x * RT_VERTEX_FLOATS + 6U], vertices.rt_vertex[triangle.x * RT_VERTEX_FLOATS + 7U]); + vec2 b = vec2(vertices.rt_vertex[triangle.y * RT_VERTEX_FLOATS + 6U], vertices.rt_vertex[triangle.y * RT_VERTEX_FLOATS + 7U]); + vec2 c = vec2(vertices.rt_vertex[triangle.z * RT_VERTEX_FLOATS + 6U], vertices.rt_vertex[triangle.z * RT_VERTEX_FLOATS + 7U]); + return a * (1.0 - weights.x - weights.y) + b * weights.x + c * weights.y; +} + +vec3 rt_normal(rt_record record, uvec3 triangle, vec2 weights) +{ + rt_vertex_data vertices = rt_vertex_data(record.vertices); + vec3 a = vec3(vertices.rt_vertex[triangle.x * RT_VERTEX_FLOATS + 3U], vertices.rt_vertex[triangle.x * RT_VERTEX_FLOATS + 4U], vertices.rt_vertex[triangle.x * RT_VERTEX_FLOATS + 5U]); + vec3 b = vec3(vertices.rt_vertex[triangle.y * RT_VERTEX_FLOATS + 3U], vertices.rt_vertex[triangle.y * RT_VERTEX_FLOATS + 4U], vertices.rt_vertex[triangle.y * RT_VERTEX_FLOATS + 5U]); + vec3 c = vec3(vertices.rt_vertex[triangle.z * RT_VERTEX_FLOATS + 3U], vertices.rt_vertex[triangle.z * RT_VERTEX_FLOATS + 4U], vertices.rt_vertex[triangle.z * RT_VERTEX_FLOATS + 5U]); + return a * (1.0 - weights.x - weights.y) + b * weights.x + c * weights.y; +} + +// the reflection the scenery gives, of a surface at Position (view space) with the geometric normal Normal (view space, either side), +// for a ray along Direction (view space, normalized) and the perceptual roughness Roughness: the colour (rgb, times a) and whether +// the ray hit something (a) +vec4 rt_trace(vec3 position, vec3 normal, vec3 direction, float roughness) +{ + mat3 toworld = mat3(inv_view); + vec3 point = toworld * position; + vec3 face = normalize(toworld * normal); + // the side of the surface the camera sees, the ray starting a little off it + if (dot(face, point) > 0.0) + face = -face; + vec3 ray = normalize(toworld * direction); + vec3 origin = point + face * (0.02 + length(point) * 0.0005); + + rayQueryEXT query; + rayQueryInitializeEXT(query, rt_scene, gl_RayFlagsNoneEXT, 0xFF, origin, 0.0, ray, ssr_params2.w); + while (rayQueryProceedEXT(query)) + { + // the instances with alpha tested textures aren't opaque: the texture tells whether the triangle is hit there + if (rayQueryGetIntersectionTypeEXT(query, false) == gl_RayQueryCandidateIntersectionTriangleEXT) + { + rt_record record = rt_records[rayQueryGetIntersectionInstanceCustomIndexEXT(query, false)]; + if ((record.flags & RT_FLAG_ALPHA) == 0U) + { + rayQueryConfirmIntersectionEXT(query); + continue; + } + uvec3 triangle = rt_triangle(record, uint(rayQueryGetIntersectionPrimitiveIndexEXT(query, false))); + vec2 coords = rt_coords(record, triangle, rayQueryGetIntersectionBarycentricsEXT(query, false)); + float alpha = textureLod(rt_textures[nonuniformEXT(record.texindex)], coords, 0.0).a; + if (alpha >= record.cutoff) + rayQueryConfirmIntersectionEXT(query); + } + } + if (rayQueryGetIntersectionTypeEXT(query, true) != gl_RayQueryCommittedIntersectionTriangleEXT) + return vec4(0.0); + + float distance = rayQueryGetIntersectionTEXT(query, true); + rt_record record = rt_records[rayQueryGetIntersectionInstanceCustomIndexEXT(query, true)]; + uvec3 triangle = rt_triangle(record, uint(rayQueryGetIntersectionPrimitiveIndexEXT(query, true))); + vec2 weights = rayQueryGetIntersectionBarycentricsEXT(query, true); + mat4x3 objecttoworld = rayQueryGetIntersectionObjectToWorldEXT(query, true); + vec3 hitnormal = normalize(transpose(inverse(mat3(objecttoworld))) * rt_normal(record, triangle, weights)); + if (dot(hitnormal, ray) > 0.0) + hitnormal = -hitnormal; + vec3 hit = origin + ray * distance; + + // the colour of the surface: the farther and the rougher, the blurrier the level of the texture + vec3 albedo = record.colour.rgb; + if (record.texindex != 0U) + { + vec2 coords = rt_coords(record, triangle, weights); + float lod = clamp(log2(1.0 + distance * 0.05) * 2.0 + roughness * 6.0, 0.0, 12.0); + albedo *= textureLod(rt_textures[nonuniformEXT(record.texindex)], coords, lod).rgb; + } + // the light of the sky, the sun where nothing stands between the point and it, and the light of its own + vec3 light = max(ambient + sky_ambient_directional(hitnormal) * sky_ambient_factor, vec3(0.0)) * AMBIENT_SCALE; + if (lights_count > 0U) + { + vec3 sun = -normalize(toworld * lights[0].dir); + float facing = dot(hitnormal, sun); + if (facing > 0.0) + { + rayQueryEXT shadow; + rayQueryInitializeEXT(shadow, rt_scene, gl_RayFlagsTerminateOnFirstHitEXT | gl_RayFlagsOpaqueEXT, 0xFF, hit + hitnormal * (0.02 + distance * 0.0005), 0.0, sun, ssr_params2.w); + while (rayQueryProceedEXT(shadow)) + { + } + if (rayQueryGetIntersectionTypeEXT(shadow, true) == gl_RayQueryCommittedIntersectionNoneEXT) + light += lights[0].color * (SUN_DIFFUSE_SCALE * lights[0].intensity * cloud_shadow(hit) * pow(facing, SUN_NDOTL_SHARPNESS)); + } + } + vec3 color = albedo * (light + vec3(record.glow)); + if (any(isnan(color)) || any(isinf(color))) + return vec4(0.0); + return vec4(max(color, vec3(0.0)), 1.0); +} diff --git a/shaders/vk14/ssr.glsl b/shaders/vk14/ssr.glsl new file mode 100644 index 00000000..74456752 --- /dev/null +++ b/shaders/vk14/ssr.glsl @@ -0,0 +1,155 @@ +// screen space reflections (gfx.ssr) of the colour pass of the main view, included by light_common.glsl. the reflection of a +// surface is traced as a ray through the depth of the opaque scene drawn before the colour (scene_depth: the reverse z of a +// projection with the far plane at infinity, a depth d is a surface n / d away along the axis of the view), and its colour is +// taken from the image of the last frame (ssr_history) at the place the point hit had in that frame. what the screen doesn't +// hold (off the image, behind something nearer, too far, too rough) is left to the caller: the alpha of the result says how much +// of the reflection the screen gave. scene_ubs.ssr_params is set only for the main view, zero elsewhere +layout(set = PASS_SET, binding = SSR_HISTORY_TEX) uniform sampler2D ssr_history; + +// the steps of a ray that found something are refined this many times, halving the step +const int SSR_REFINE_STEPS = 5; + +// a value 0 .. 1 different for the neighbouring pixels (interleaved gradient noise), and from frame to frame with the temporal +// anti-aliasing, which blends the steps of the rays into a smooth image +float ssr_noise() +{ + vec2 pixel = gl_FragCoord.xy + 5.588238 * ssr_params.w; + return fract(52.9829189 * fract(dot(pixel, vec2(0.06711056, 0.00583715)))); +} + +// the distance along the axis of the view of the surface in the depth at a place of the image (0 .. 1), 0: nothing there (the sky) +float ssr_surface(vec2 coords, vec2 size, float near) +{ + float depth = texelFetch(scene_depth, clamp(ivec2(coords * size), ivec2(0), ivec2(size) - 1), 0).r; + return (depth > 0.0 ? near / depth : 0.0); +} + +// the reflection the screen holds, of a surface at Position (view space) with the geometric normal Normal (view space, either +// side), for a ray along Direction (view space, normalized) and the perceptual roughness Roughness: the colour (rgb) times how +// much of it the screen holds (a) +vec4 ssr_trace(vec3 position, vec3 normal, vec3 direction, float roughness) +{ + float steps = ssr_params.x; + if ((steps < 1.0) || (roughness > ssr_params.z)) + return vec4(0.0); + // the side of the surface the camera sees; a ray going into it (a normal map turned far from the face) finds nothing + if (dot(normal, position) > 0.0) + normal = -normal; + if (dot(normal, direction) <= 0.0) + return vec4(0.0); + vec2 size = vec2(textureSize(scene_depth, 0)); + float near = projection[3][2]; + float pixelscale = 2.0 / (abs(projection[1][1]) * size.y); + // the ray starts two pixels off the surface, so the depth of the surface itself doesn't stop it + vec4 start = projection * vec4(position, 1.0); + vec3 origin = position + normal * (start.w * pixelscale * 2.0); + vec4 c0 = projection * vec4(origin, 1.0); + // the distance along the axis of the view changes with the ray: it ends in front of the near plane + float dw = (projection * vec4(direction, 0.0)).w; + float raylength = ssr_params.y; + if (dw < 0.0) + raylength = min(raylength, (c0.w - near * 2.0) / -dw); + if (raylength <= 0.0) + return vec4(0.0); + vec3 end = origin + direction * raylength; + vec4 c1 = projection * vec4(end, 1.0); + // the points of the ray are interpolated across the image (u) with 1 / w, which is linear there + float k0 = 1.0 / c0.w; + float k1 = 1.0 / c1.w; + vec2 s0 = c0.xy * k0; + vec2 s1 = c1.xy * k1; + vec2 delta = s1 - s0; + // the part of the ray over the image + float umax = 1.0; + if (abs(delta.x) > 1e-6) + umax = min(umax, ((delta.x > 0.0 ? 1.0 : -1.0) - s0.x) / delta.x); + if (abs(delta.y) > 1e-6) + umax = min(umax, ((delta.y > 0.0 ? 1.0 : -1.0) - s0.y) / delta.y); + if (umax <= 0.0) + return vec4(0.0); + // not more steps than the pixels the ray crosses + float pixels = length(delta * umax * size * 0.5); + float count = clamp(floor(pixels), 1.0, steps); + float jitter = ssr_noise(); + + float uprev = 0.0; + float wprev = c0.w; + float uhit = -1.0; + for (float i = 0.0; i < count; i += 1.0) + { + float u = umax * min((i + jitter) / count, 1.0); + float w = 1.0 / mix(k0, k1, u); + vec2 coords = mix(s0, s1, u) * 0.5 + 0.5; + float surface = ssr_surface(coords, size, near); + float bias = w * pixelscale * 0.5; + // the things in the depth are taken to be this thick behind their faces, with the length of the step on top + float thickness = max(0.25, surface * 0.02) + abs(w - wprev); + if ((surface > 0.0) && (w - surface > bias) && (w - surface < thickness)) + { + // refined between the last step in front of the surface and this one behind it + float lo = uprev; + float hi = u; + for (int r = 0; r < SSR_REFINE_STEPS; ++r) + { + float mid = 0.5 * (lo + hi); + float wmid = 1.0 / mix(k0, k1, mid); + float smid = ssr_surface(mix(s0, s1, mid) * 0.5 + 0.5, size, near); + if ((smid > 0.0) && (wmid - smid > 0.0)) + hi = mid; + else + lo = mid; + } + // what the ray went behind has to be close to it there: otherwise the ray went behind something thin, and goes on + float whit = 1.0 / mix(k0, k1, hi); + float shit = ssr_surface(mix(s0, s1, hi) * 0.5 + 0.5, size, near); + if ((shit > 0.0) && (abs(whit - shit) < max(0.25, shit * 0.02) + whit * pixelscale * 2.0)) + { + uhit = hi; + break; + } + } + uprev = u; + wprev = w; + } + if (uhit < 0.0) + return vec4(0.0); + + // the point hit, view space + vec3 hit = mix(origin * k0, end * k1, uhit) / mix(k0, k1, uhit); + vec2 hitcoords = mix(s0, s1, uhit) * 0.5 + 0.5; + // where it was in the last frame: the cab moves with the camera, the world stands still + mat4 reprojection = ssr_reprojection; + if (ssr_params2.y > 0.0) + { + vec3 box = (ssr_cab_box * vec4(hit, 1.0)).xyz; + if (all(lessThanEqual(abs(box), vec3(1.0)))) + reprojection = ssr_reprojection_cab; + } + vec4 previous = reprojection * vec4(hit, 1.0); + if (previous.w <= 0.0) + return vec4(0.0); + vec2 coords = (previous.xy / previous.w) * 0.5 + 0.5; + + float share = 1.0; + // faded towards the edges of the image, where it was hit and where it was in the last frame + vec2 edges = min(min(hitcoords, 1.0 - hitcoords), min(coords, 1.0 - coords)); + share *= smoothstep(0.0, 0.04, min(edges.x, edges.y)); + // and towards the end of the ray + float travelled = length(hit - origin); + share *= 1.0 - smoothstep(0.75, 1.0, travelled / ssr_params.y); + // the rougher surfaces hand over to the environment map + share *= 1.0 - smoothstep(ssr_params.z * 0.7, ssr_params.z, roughness); + // a ray towards the camera meets the backs of what the image shows + share *= 1.0 - smoothstep(0.4, 0.9, direction.z); + if (share <= 0.0) + return vec4(0.0); + + // the lobe of a rough surface covers more of the image the farther the point hit is: a blurrier level of the history + float alpha = roughness * roughness; + float hitpixel = max((projection * vec4(hit, 1.0)).w, near) * pixelscale; + float lod = clamp(log2(max(2.0 * travelled * alpha / hitpixel, 1.0)), 0.0, ssr_params2.x); + vec3 color = textureLod(ssr_history, coords, lod).rgb; + if (any(isnan(color)) || any(isinf(color))) + return vec4(0.0); + return vec4(max(color, vec3(0.0)) * share, share); +} diff --git a/utilities/Globals.cpp b/utilities/Globals.cpp index 06832b6a..2534070e 100644 --- a/utilities/Globals.cpp +++ b/utilities/Globals.cpp @@ -1888,6 +1888,20 @@ global_settings::ConfigParse_gfx_sky( cParser &Parser, std::string_view const To 0; } else if( Token == "gfx.contactshadows.length" ) { readvalue( gfx_contactshadows_length, 0.05f, 5.f ); } else if( Token == "gfx.contactshadows.range" ) { readvalue( gfx_contactshadows_range, 1.f, 1000.f ); } + else if( Token == "gfx.ssr" ) { + // no/low/medium/high, or the number of the level + Parser.getTokens( 1 ); + std::string level; + Parser >> level; + gfx_ssr = + ( level == "low" || level == "1" ) ? 1 : + ( level == "medium" || level == "yes" || level == "true" || level == "2" ) ? 2 : + ( level == "high" || level == "3" ) ? 3 : + 0; } + else if( Token == "gfx.ssr.range" ) { readvalue( gfx_ssr_range, 10.f, 2000.f ); } + else if( Token == "gfx.ssr.roughness" ) { readvalue( gfx_ssr_roughness, 0.f, 1.f ); } + else if( Token == "gfx.raytracing" ) { readswitch( gfx_raytracing ); } + else if( Token == "gfx.raytracing.range" ) { readvalue( gfx_raytracing_range, 10.f, 5000.f ); } else if( Token == "gfx.fog" ) { // volumetric (yes) or analytic (no, old): the same weather either way Parser.getTokens( 1 ); @@ -2272,6 +2286,11 @@ global_settings::export_as_text( std::ostream &Output ) const { export_as_text( Output, "gfx.contactshadows", std::string{ gfx_contactshadows == 3 ? "high" : gfx_contactshadows == 2 ? "medium" : gfx_contactshadows == 1 ? "low" : "no" } ); export_as_text( Output, "gfx.contactshadows.length", gfx_contactshadows_length ); export_as_text( Output, "gfx.contactshadows.range", gfx_contactshadows_range ); + export_as_text( Output, "gfx.ssr", std::string{ gfx_ssr == 3 ? "high" : gfx_ssr == 2 ? "medium" : gfx_ssr == 1 ? "low" : "no" } ); + export_as_text( Output, "gfx.ssr.range", gfx_ssr_range ); + export_as_text( Output, "gfx.ssr.roughness", gfx_ssr_roughness ); + export_as_text( Output, "gfx.raytracing", gfx_raytracing ); + export_as_text( Output, "gfx.raytracing.range", gfx_raytracing_range ); export_as_text( Output, "gfx.fog", std::string{ gfx_fog == 1 ? "volumetric" : "analytic" } ); export_as_text( Output, "gfx.fog.quality", std::string{ gfx_fog_quality == 1 ? "low" : gfx_fog_quality == 3 ? "high" : gfx_fog_quality == 4 ? "ultra" : "medium" } ); export_as_text( Output, "gfx.fog.range", gfx_fog_range ); diff --git a/utilities/Globals.h b/utilities/Globals.h index 1f73ff32..2e8af782 100644 --- a/utilities/Globals.h +++ b/utilities/Globals.h @@ -166,8 +166,11 @@ struct global_settings { float range{ 250.f }; } shadowtune; struct reflectiontune_t { + // how often the environment map (the sky and the clouds around the camera, the reflection of what the screen space + // reflections and the ray tracing don't find) is drawn anew double update_interval{ 300.0 }; - int fidelity{ 0 }; // 0: sections, 1: +static models, 2: +vehicles + // what the ray traced reflections of the vulkan renderer hold (gfx.raytracing): 0: sections, 1: +static models, 2: +vehicles + int fidelity{ 2 }; float range_instances{ 750.f }; float range_vehicles{ 250.f }; } reflectiontune; @@ -466,6 +469,18 @@ struct global_settings { int gfx_contactshadows { 0 }; float gfx_contactshadows_length { 0.5f }; float gfx_contactshadows_range { 50.f }; + // screen space reflections of the opengl 4.6 and vulkan renderers, in place of the scenery in the environment map: the + // reflection of a surface traced through the depth of the scene drawn before its colour (which they turn on, see + // gfx_depthprepass_scene) and taken from the image of the last frame; where the screen doesn't hold it, the environment map + // (the sky) or the ray tracing. 0 none, 1 low, 2 medium, 3 high (steps of the rays); the longest ray, metres, and the + // roughest surface they're traced for (perceptual roughness, rougher ones reflect the environment map) + int gfx_ssr { 2 }; + float gfx_ssr_range { 250.f }; + float gfx_ssr_roughness { 0.6f }; + // (vulkan) ray traced reflections where the screen space ones find nothing, on a gpu with ray queries: the scenery within + // the range, metres, as gfx.reflections.fidelity says. taken at the start + bool gfx_raytracing { false }; + float gfx_raytracing_range { 500.f }; // weather of the opengl 4.6 renderer (debug window "Weather"); the weather itself comes from the scenery, see // environment/weather.h. fog: 1 volumetric (a volume over the view with the shafts of the sun, the shadows of the clouds and // the light of lamps and headlights, worked out along the ray past it), 0 analytic (the same fog along the ray only, the way