diff --git a/CMakeLists.txt b/CMakeLists.txt index 76515b36..7eccd8b9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -328,6 +328,7 @@ set(SOURCES "rendering/common/instancecull.cpp" "rendering/common/shadowfit.cpp" "rendering/common/localshadows.cpp" +"rendering/common/cabprobe.cpp" "rendering/common/terrainmaterials.cpp" "rendering/common/grassfield.cpp" "rendering/common/gpuparticles.cpp" @@ -424,6 +425,7 @@ if (WITH_OPENGL_MODERN) "rendering/opengl46/opengl46exposure.cpp" "rendering/opengl46/opengl46ao.cpp" "rendering/opengl46/opengl46localshadows.cpp" + "rendering/opengl46/opengl46cabprobe.cpp" "rendering/opengl46/opengl46instancecull.cpp" "rendering/opengl46/opengl46hiz.cpp" "rendering/opengl46/opengl46shadowfit.cpp" @@ -473,6 +475,7 @@ if (WITH_VULKAN) "rendering/vulkan14/vulkan14indirect.cpp" "rendering/vulkan14/vulkan14shadows.cpp" "rendering/vulkan14/vulkan14lamps.cpp" + "rendering/vulkan14/vulkan14cabprobe.cpp" "rendering/vulkan14/vulkan14terrain.cpp" "rendering/vulkan14/vulkan14grass.cpp" "rendering/vulkan14/vulkan14particles.cpp" diff --git a/docs/opengl46-renderer.md b/docs/opengl46-renderer.md index 12c7a329..e1e2782a 100644 --- a/docs/opengl46-renderer.md +++ b/docs/opengl46-renderer.md @@ -128,17 +128,28 @@ should its test of the matrices show the camera motion the wrong way. ### Ambient occlusion -Darkening of creases, corners and the ground under objects, worked out from the depth of the image (screen space) -and multiplied into the image before the motion blur and the tonemapping. It works with and without motion blur. -It darkens all light alike, the sun and the headlights included; limiting it to the ambient light needs the depth -before the shading (a depth prepass), which is planned later. +Darkening of creases, corners and the ground under objects, worked out from the depth of the image (screen space). +With `gfx.postfx.ao.lighting yes` (the default, OpenGL and Vulkan) the ground truth occlusion is worked out from the depth +of the whole scene drawn before its colour (it turns that depth pass on, like the contact shadows and the screen space +reflections) and the shading takes it into the ambient light only: the diffuse ambient light, with the light coming back +from more bounces off surfaces of their own albedo, and the reflection of the surroundings (specular occlusion, +Lagarde 2014). The sun, the headlights, the lamps and the reflection of the scene the screen space reflections find +aren't darkened. Surfaces the depth pass doesn't hold (translucent ones, the grass, the cab drawn after rain or snow, +the edges of objects) get no occlusion. With `gfx.postfx.ao.lighting no`, or with `ssao`, the occlusion is multiplied +into the image before the motion blur and the tonemapping, darkening all light alike, as before. + +Cost: with `lighting` the occlusion itself costs the same; the depth pass of the scene costs CPU in OpenGL when nothing +else turned it on (`gfx.ssr`, `gfx.contactshadows`, `gfx.depthprepass.scene`; with `gfx.depthprepass.replay yes` it is +the walk of the colour pass recorded, not a second walk), and every shaded pixel reads the occlusion. `gfx.postfx.ao.lighting no` +is the cheaper setting. | Entry | Default | Meaning | |---|---|---| | `gfx.postfx.ao gtao/ssao/no` | `gtao` | `gtao` (or `yes`): ground truth ambient occlusion (Jimenez et al. 2016), at half the resolution with a depth aware smoothing and upsampling. `ssao`: the older, experimental ssao. `no`: none, and no targets are made for it; the mode can then not be turned on in the debug window | | `gfx.postfx.ao.radius` | `1.5` | 0.1..10: reach of the occluders, metres | | `gfx.postfx.ao.strength` | `1.0` | 0..4: power the occlusion is raised to; 1 = as computed, 0 = none | -| `gfx.postfx.ao.bounce` | `0.3` | 0..1: albedo of the surfaces assumed for the light that comes back after more bounces (brightens the occlusion a little) | +| `gfx.postfx.ao.bounce` | `0.3` | 0..1: albedo of the surfaces assumed for the light that comes back after more bounces (brightens the occlusion a little); over the image only, in the light of the surfaces their own albedo is taken | +| `gfx.postfx.ao.lighting yes/no` | `yes` | with `gtao`: the occlusion darkens the ambient light and the reflection of the surroundings in the shading (needs the depth of the scene before its colour); `no`: it darkens the finished image | | `gfx.postfx.ao.quality slices steps` | `3 4` | directions searched around each pixel (1..8) and samples on each side of a direction (1..16) | ### Lamps @@ -158,7 +169,8 @@ scales can also be changed in the **Light clusters** debug window. section holds light points of its own (these light it then, through its self-illumination). - **The cab lamp** of the occupied cab is a point light under the top of the cab model. Before, it added to the ambient light of the whole cab and weakened the shadows of the sun in it; now a share of that ambient light is kept for the light - which bounces around the cab (`gfx.lights.cab.bounce`), and the sun shadows stay. + which bounces around the cab (`gfx.lights.cab.bounce`), and the sun shadows stay. With the probe of the cab (below) the + light which bounces comes from the probe instead and nothing of the lamp is kept in the ambient light. The lamps of models and compartments fade in from the light level at which scenery lamps switch on (`ls_Dark`) to full darkness; in daylight they are not gathered at all. The cab lamp and the lamps of scenery models nearest to the camera cast @@ -232,6 +244,31 @@ shadows of the sun, see `headlight shadows:` in Shift+F12); with the vehicle on | `gfx.lights.shadows.headlights` | `2` | 0..4: how many sets of headlights (vehicle ends) cast shadows; 0: none | | `gfx.lights.shadows.headlights.range` | `250` | 10..1000: distance ahead of the vehicle within which things cast the shadows of its headlights, metres | +**The probe of the cab** (`gfx.cabprobe`, OpenGL and Vulkan): two cube maps drawn from the middle of the box of the cab model, +in the space of the cab (they stay right while the vehicle moves): the light of the cab lamp the cab reflects (with the +shadows of the lamp) and the light of what glows in it (the lights of the desk), and the albedo of the cab. The glasses +aren't drawn, so through them the probe shows nothing. They are drawn every `gfx.cabprobe.interval` frames (for the moving +parts and the lights of the desk) and at once when the cab or the lamp change, the six faces in one pass with the opaque +parts of the cab (as the shadow map of the lamp). The parts of the cab read them: +- the light of the lamp the cab reflects lights the cab again (`gfx.cabprobe.bounce`, from spherical harmonics of the + probe) in place of `gfx.lights.cab.bounce`, so the light bounces where the lamp actually lights the cab; +- the cab reflects itself (`gfx.cabprobe.reflections`) where the screen space reflections find nothing: the glasses show the + lit cab at night, the glossy parts reflect the cab instead of the sky. The reflected ray is taken to the box of the cab + (the walls), the cab shows the light of the lamp it reflects and its albedo lit by the ambient light, through the windows + the sky is seen as before. + +The ambient light and the sun are left out of the probe, the cab gets them as before. `gfx.cabprobe no` is the old way (the +share of the lamp in the ambient light, the sky in the reflections). Cost: one draw of the opaque cab (about the draw calls of +the cab in the colour pass) every `interval` frames, 2 cube maps of the size of the level with their mipmaps and the depth (64: 0.5 MB, 128: 2 MB, 256: 8 MB), two texture reads in +the reflections of the cab. + +| Entry | Default | Meaning | +|---|---|---| +| `gfx.cabprobe` | `medium` | `no`/`low`/`medium`/`high` (or 0..3): the probe of the cab, faces of 64/128/256 pixels; `no`: none | +| `gfx.cabprobe.interval` | `4` | 1..120: frames between the updates of the probe | +| `gfx.cabprobe.bounce` | `1.0` | 0..4: share of the light of the lamp the cab reflects which lights it again | +| `gfx.cabprobe.reflections yes/no` | `yes` | the cab reflects itself where the screen space reflections find nothing; `no`: the sky, as before | + ### Contact shadows Short shadows where things touch or nearly touch: the rails on the sleepers, bolts and edges of panels, a wheel on the rail, diff --git a/gl/glsl_common.cpp b/gl/glsl_common.cpp index a938ce8c..a6b6ef33 100644 --- a/gl/glsl_common.cpp +++ b/gl/glsl_common.cpp @@ -97,6 +97,12 @@ void gl::glsl_common_setup() "#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 SCENE_AO_TEX " + std::to_string(SCENE_AO_TEX) + "\n" + + "#define CAB_PROBE_TEX " + std::to_string(CAB_PROBE_TEX) + "\n" + + "#define CAB_PROBE_ALBEDO_TEX " + std::to_string(CAB_PROBE_ALBEDO_TEX) + "\n" + + "#define CAB_PROBE_SSBO_BINDING " + std::to_string(CAB_PROBE_SSBO_BINDING) + "\n" + + "#define CAB_PROBE_SH_SIZE " + std::to_string(CAB_PROBE_SH_SIZE) + "\n" + + "#define CAB_PROBE_SH_GROUP_SIZE " + std::to_string(CAB_PROBE_SH_GROUP_SIZE) + "\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 929e615e..8fcc5e1b 100644 --- a/gl/ubo.h +++ b/gl/ubo.h @@ -83,6 +83,14 @@ namespace gl // 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; + // (MAX_TEXTURES + HELPER_TEXTURES + 13 is WATER_MASK_TEX, below) + // the ambient occlusion of the main view worked out from the depth in SCENE_DEPTH_TEX before the colour, half its size, which + // the shading takes into the ambient light (gfx.postfx.ao.lighting, gl::scene_ubs::ao_params). bound directly, like the ones above + const size_t SCENE_AO_TEX = MAX_TEXTURES + HELPER_TEXTURES + 14; + // the probe of the occupied cab (gfx::cab_probe, gl::scene_ubs::cab_probe_box): cube maps of the light of its lamps reflected by + // the cab and of its albedo. bound directly, like the ones above + const size_t CAB_PROBE_TEX = MAX_TEXTURES + HELPER_TEXTURES + 15; + const size_t CAB_PROBE_ALBEDO_TEX = MAX_TEXTURES + HELPER_TEXTURES + 16; struct scene_ubs { @@ -129,9 +137,26 @@ namespace gl // the water on the windscreens (mat_rain_windscreen): xyz the velocity of the air against the vehicle of the camera (the // share of the wind the particles of the rain drift with, less the velocity of the vehicle), world space, m/s; w unused glm::vec4 rain_wind; + // the ambient occlusion in the light of the surfaces (gfx.postfx.ao.lighting), set only for the colour pass of the main view, + // zero elsewhere: x 1 when SCENE_AO_TEX holds the occlusion of the depth in SCENE_DEPTH_TEX, y the power it's raised to + // (gfx.postfx.ao.strength), zw unused + glm::vec4 ao_params; + // the probe of the occupied cab (gfx::cab_probe), set only for the colour pass of the main view, zero elsewhere: the view + // space of the pass to the space of the probe (the axes of the cab model, metres, the origin in the middle of its box) + glm::mat4 cab_probe_view; + // xyz half the size of the box of the cab, w 1 when the probe is there + glm::vec4 cab_probe_box; + // x the share of the light of the lamps the cab reflects which lights it again (gfx.cabprobe.bounce), y 1 when the cab reflects + // itself (gfx.cabprobe.reflections), z the last level of the mipmaps of the probe, w unused + glm::vec4 cab_probe_params; + // the drawing of the probe (cabprobe.glsl), set for it only: xyz the cab lamp in the space of the probe, w 1 / its range + // squared (0: no lamp); rgb its light, a 1 with its shadow map (LOCAL_SHADOW_TEX); the headlight_weights of that map + glm::vec4 cab_probe_lamp; + glm::vec4 cab_probe_lamp_color; + glm::vec4 cab_probe_lamp_shadow; }; - static_assert(sizeof(scene_ubs) == 704 + 80 * MAX_HEADLIGHT_SHADOWS, "bad size of ubs"); + static_assert(sizeof(scene_ubs) == 864 + 80 * MAX_HEADLIGHT_SHADOWS, "bad size of ubs"); const size_t MAX_PARAMS = 3; @@ -147,7 +172,9 @@ namespace gl float fog_density; float alpha_mult; float shadow_tone; - UBS_PAD(12); + // 1 for the parts of the occupied cab, which the probe of the cab lights and reflects (gl::scene_ubs::cab_probe_box) + float cab_interior{0.f}; + UBS_PAD(8); void set_modelview(const glm::mat4 &mv) { @@ -368,6 +395,19 @@ namespace gl glm::vec4 ambient_sh[SKY_SH_COEFFICIENTS]; }; + // layout std430: the light of the lamps the occupied cab reflects, which lights the cab again (GLSL: cab_probe_ssbo, written by + // cabprobe_sh.comp from the probe of the cab): the coefficients of the real spherical harmonics of the light a surface facing a + // direction of the space of the probe gets, bands 0..2, in units of light_data.ambient; rgb used, w is padding + struct cab_probe_data + { + glm::vec4 bounce_sh[SKY_SH_COEFFICIENTS]; + }; + // binding point of the opengl renderer, bound with the colour passes + const GLuint CAB_PROBE_SSBO_BINDING = 17; + // the face of the level of the probe the spherical harmonics are taken from, at most, and the work group of cabprobe_sh.comp + const size_t CAB_PROBE_SH_SIZE = 16; + const size_t CAB_PROBE_SH_GROUP_SIZE = 64; + static_assert(sizeof(sky_data) == 144, "bad size of sky_data"); // layout std430: luminance histogram of the image and the exposure worked out from it (GLSL: exposure_ssbo) diff --git a/rendering/common/cabprobe.cpp b/rendering/common/cabprobe.cpp new file mode 100644 index 00000000..5c3dd749 --- /dev/null +++ b/rendering/common/cabprobe.cpp @@ -0,0 +1,205 @@ +/* +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/cabprobe.h" + +#include "utilities/Globals.h" +#include "utilities/Logs.h" +#include "utilities/utilities.h" +#include "vehicle/DynObj.h" + +namespace gfx +{ + +namespace +{ + +// a change of the light of the cab lamp larger than this share of it (the lamp switched, dimmed, the daylight it fades in) has the +// probe drawn anew at once; smaller ones wait for the next update +float const LAMP_CHANGED{0.05f}; + +float max_component(glm::vec3 const &Color) +{ + return std::max(Color.r, std::max(Color.g, Color.b)); +} + +} // namespace + +int cab_probe::size_of(int const Level) +{ + switch (Level) + { + case 1: + return 64; + case 2: + return 128; + case 3: + return 256; + default: + return 0; + } +} + +void cab_probe::allocate(int const Size) +{ + release(); + if (Size <= 0) + return; + if (false == gpu_allocate(Size)) + { + ErrorLog("cab probe framebuffer setup failed, no cab probe"); + release(); + Global.gfx_cabprobe = 0; + m_level = 0; + return; + } + m_size = Size; + WriteLog("cab probe: " + std::to_string(Size) + " x " + std::to_string(Size) + " cube"); +} + +void cab_probe::release() +{ + gpu_release(); + m_size = 0; + m_valid = false; +} + +bool cab_probe::update_wanted(TDynamicObject const *Vehicle, TModel3d const *Cab, glm::vec3 const &Center, glm::vec3 const &Extent, gfx::local_lights::light const *Lamp, unsigned int const Framestamp) +{ + auto const level{std::clamp(Global.gfx_cabprobe, 0, 3)}; + if (level != m_level) + { + m_level = level; + auto const size{size_of(level)}; + if (size != m_size) + allocate(size); + m_valid = false; + } + if ((m_size == 0) || (Vehicle == nullptr) || (Cab == nullptr) || (Extent == glm::vec3{0.f})) + { + // with no cab drawn nothing reads the probe; once there is one again it may be another + m_valid = false; + return false; + } + auto const lamp{Lamp != nullptr ? Lamp->color : glm::vec3{0.f}}; + auto const lampchange{glm::length(lamp - m_lamp)}; + auto const lampchanged{(lampchange > 0.f) && ((max_component(m_lamp) <= 0.f) || (max_component(lamp) <= 0.f) || (lampchange > LAMP_CHANGED * glm::length(m_lamp)))}; + if ((Vehicle != m_vehicle) || (Cab != m_cab) || (Center != m_center) || (Extent != m_extent) || lampchanged) + { + m_valid = false; + } + // the moving parts of the cab and the lights of the desk are followed every few frames + if (m_valid && (Framestamp - m_drawnframe < static_cast(std::max(Global.gfx_cabprobe_interval, 1)))) + { + return false; + } + m_vehicle = Vehicle; + m_cab = Cab; + m_center = Center; + m_extent = Extent; + m_lamp = lamp; + m_drawnframe = Framestamp; + return true; +} + +void cab_probe::setup_drawing(gl::scene_ubs &Scene, gfx::local_lights::light const *Lamp, glm::vec3 const &Lamplocal, bool const Shadows, glm::vec4 const &Shadowweights) const +{ + if (Lamp == nullptr) + { + Scene.cab_probe_lamp = glm::vec4{0.f}; + Scene.cab_probe_lamp_color = glm::vec4{0.f}; + Scene.cab_probe_lamp_shadow = glm::vec4{0.f}; + return; + } + // as the light buffer has it (Write_Local_Light()): the shading takes the light to end at 1 / sqrt(quadratic) + Scene.cab_probe_lamp = glm::vec4{Lamplocal - m_center, 1.f / sq(std::max(Lamp->range, 0.01f))}; + Scene.cab_probe_lamp_color = glm::vec4{Lamp->color, Shadows ? 1.f : 0.f}; + Scene.cab_probe_lamp_shadow = Shadowweights; +} + +void cab_probe::begin() +{ + m_cpustart = std::chrono::steady_clock::now(); + gpu_begin(); +} + +void cab_probe::end(std::uint32_t const Drawcalls) +{ + gpu_end(); + m_valid = true; + m_drawcalls = Drawcalls; + ++m_updatecount; + auto const elapsed{std::chrono::duration(std::chrono::steady_clock::now() - m_cpustart).count()}; + m_cputime = (m_cputime == 0.0 ? elapsed : m_cputime * 0.9 + elapsed * 0.1); +} + +glm::dmat4 cab_probe::model_to_probe() const +{ + return glm::translate(glm::dmat4{1.0}, -glm::dvec3{m_center}); +} + +void cab_probe::setup_scene(gl::scene_ubs &Scene, TDynamicObject const *Vehicle, glm::dvec3 const &Camera, glm::mat3 const &View) const +{ + if (false == ready(Vehicle)) + { + clear_scene(Scene); + return; + } + // view space to the space of the probe: the space of the cab model, with the origin in the middle of its box. the vehicle is where + // it is now, the probe stays right as it moves + auto const toworld{glm::dmat4{glm::transpose(glm::dmat3{View})}}; + auto const toprobe{model_to_probe() * glm::inverse(Vehicle->mMatrix) * glm::translate(glm::dmat4{1.0}, Camera - Vehicle->GetPosition()) * toworld}; + Scene.cab_probe_view = glm::mat4{toprobe}; + Scene.cab_probe_box = glm::vec4{m_extent, 1.f}; + auto const levels{static_cast(std::floor(std::log2(static_cast(std::max(m_size, 1)))))}; + Scene.cab_probe_params = glm::vec4{Global.gfx_cabprobe_bounce, Global.gfx_cabprobe_reflections ? 1.f : 0.f, levels, 0.f}; +} + +void cab_probe::clear_scene(gl::scene_ubs &Scene) +{ + Scene.cab_probe_view = glm::mat4{1.f}; + Scene.cab_probe_box = glm::vec4{0.f}; + Scene.cab_probe_params = glm::vec4{0.f}; +} + +int cab_probe::sh_level() const +{ + auto level{0}; + while ((m_size >> level) > static_cast(gl::CAB_PROBE_SH_SIZE)) + ++level; + return level; +} + +int cab_probe::sh_size() const +{ + return std::max(m_size >> sh_level(), 1); +} + +std::string cab_probe::info() const +{ + if (m_size == 0) + return "cab probe: off"; + return "cab probe: " + std::to_string(m_size) + " px, " + (m_valid ? "ready" : "none") + ", draw calls " + std::to_string(m_drawcalls) + ", cpu " + to_string(m_cputime, 2) + " ms, updates " + + std::to_string(m_updatecount); +} + +void cab_probe::draw_debug_ui() +{ + char const *levels[]{"off", "low (64)", "medium (128)", "high (256)"}; + ImGui::Combo("cab probe (gfx.cabprobe)", &Global.gfx_cabprobe, levels, IM_ARRAYSIZE(levels)); + if (Global.gfx_cabprobe <= 0) + return; + ImGui::SliderInt("frames between its updates (gfx.cabprobe.interval)", &Global.gfx_cabprobe_interval, 1, 60); + ImGui::SliderFloat("light bounced around the cab (gfx.cabprobe.bounce)", &Global.gfx_cabprobe_bounce, 0.f, 4.f); + ImGui::Checkbox("the cab reflects itself (gfx.cabprobe.reflections)", &Global.gfx_cabprobe_reflections); + ImGui::TextUnformatted(info().c_str()); +} + +} // namespace gfx diff --git a/rendering/common/cabprobe.h b/rendering/common/cabprobe.h new file mode 100644 index 00000000..40bc5548 --- /dev/null +++ b/rendering/common/cabprobe.h @@ -0,0 +1,116 @@ +/* +This Source Code Form is subject to the +terms of the Mozilla Public License, v. +2.0. If a copy of the MPL was not +distributed with this file, You can +obtain one at +http://mozilla.org/MPL/2.0/. +*/ + +#pragma once + +#include +#include +#include + +#include "gl/ubo.h" +#include "rendering/common/locallights.h" + +class TDynamicObject; +class TModel3d; + +namespace gfx +{ + +// the probe of the occupied cab (Global.gfx_cabprobe, the stage A4 of claude/renderers-plan.md), the part shared by the renderers; +// the backend (opengl46_cab_probe, vk14::cab_probe_maps) keeps the maps and draws to them through the gpu_* methods. +// - two cube maps drawn from the middle of the box of the cab model (mdKabina), in the space of the model, so they stay right while +// the vehicle moves and turns: the light of the cab lamp the cab reflects (with the shadow map of the lamp) and the light of what +// glows in it, and the albedo of the cab. both are cleared to nothing and premultiplied by the share of the direction the cab +// fills, which is in their alpha (the glasses aren't drawn: through them the cab shows nothing), and have mipmaps; +// - the six faces are drawn at once (a geometry shader with opengl, multiview with vulkan) by the programs of the probe +// (cabprobe.vert, cabprobe.frag), every few frames (Global.gfx_cabprobe_interval: the moving parts of the cab, the lights of the +// desk) and at once when the cab, its box, the level or the cab lamp change; the light reflected is then made into spherical +// harmonics (cabprobe_sh.comp, gl::cab_probe_data); +// - the shading of the parts of the cab (gl::model_ubs::cab_interior) reads them through gl::scene_ubs::cab_probe_*: the reflected +// light lights the cab again, in place of the share of the light of the lamp the ambient light kept (Global.gfx_lights_cab_bounce), +// and the cab reflects itself where the screen space reflections don't find it, the box standing for its walls; what the cab +// shows there is the light of the lamp it reflects and its albedo lit by the ambient light of the reflecting surface. +// the ambient light and the sun are left out of the probe: the cab gets them as before +class cab_probe +{ + public: + cab_probe() = default; + virtual ~cab_probe() = default; + cab_probe(const cab_probe &) = delete; + cab_probe &operator=(const cab_probe &) = delete; + + // size of a face of the maps of the level (Global.gfx_cabprobe), 0 for none + static int size_of(int const Level); + // whether the probe is to be drawn in this frame. Vehicle: the occupied vehicle whose cab is drawn (nullptr: none), Cab its cab + // model, Center and Extent the box of the model (model space); Lamp the cab lamp when it's a light of the frame (nullptr: none). + // (re)makes the maps when the level changed + bool update_wanted(TDynamicObject const *Vehicle, TModel3d const *Cab, glm::vec3 const &Center, glm::vec3 const &Extent, gfx::local_lights::light const *Lamp, unsigned int const Framestamp); + // the fields of the block of the scene the drawing of the probe reads: the cab lamp (Lamp, at Lamplocal in the space of the cab + // model, nullptr: none) with its shadow map when Shadows (Shadowweights: the headlight_weights of the map) + void setup_drawing(gl::scene_ubs &Scene, gfx::local_lights::light const *Lamp, glm::vec3 const &Lamplocal, bool const Shadows, glm::vec4 const &Shadowweights) const; + // the drawing to the probe: the target, the viewport, the cleared faces, the state of the faces. the cab is to be drawn with the + // programs of the probe and a modelview from the space of the cab model to the space of the probe (model_to_probe()) + void begin(); + // ends the drawing: the mipmaps and the spherical harmonics of the light + void end(std::uint32_t const Drawcalls); + // the space of the cab model to the space of the probe: the axes of the model, the origin in the middle of the box + glm::dmat4 model_to_probe() const; + // whether the probe holds the cab of Vehicle + bool ready(TDynamicObject const *Vehicle) const + { + return m_valid && (Vehicle != nullptr) && (Vehicle == m_vehicle); + } + // the fields of the block of the scene the colour pass of the main view reads the probe with: the cab of Vehicle drawn, seen from + // Camera (world space) with the rotation of the view View. cleared when the probe doesn't hold the cab + void setup_scene(gl::scene_ubs &Scene, TDynamicObject const *Vehicle, glm::dvec3 const &Camera, glm::mat3 const &View) const; + // the fields cleared, for the passes other than the colour pass of the main view + static void clear_scene(gl::scene_ubs &Scene); + // size of a face of the maps, 0 when there are none + int size() const + { + return m_size; + } + // the level of the mipmaps the spherical harmonics are taken from, and its size + int sh_level() const; + int sh_size() const; + // line for the statistics of the debug panel + std::string info() const; + // controls and counters for the debug window + void draw_debug_ui(); + + protected: + // the maps of Size x Size faces with their mipmaps; false when they can't be made + virtual bool gpu_allocate(int const Size) = 0; + virtual void gpu_release() = 0; + virtual void gpu_begin() = 0; + virtual void gpu_end() = 0; + + int m_size{0}; + + private: + void allocate(int const Size); + void release(); + + int m_level{-1}; + bool m_valid{false}; + TDynamicObject const *m_vehicle{nullptr}; + TModel3d const *m_cab{nullptr}; + glm::vec3 m_center{0.f}; + glm::vec3 m_extent{0.f}; + // the light of the cab lamp the probe was drawn with, 0 without the lamp + glm::vec3 m_lamp{0.f}; + unsigned int m_drawnframe{0}; + // statistics + std::uint32_t m_drawcalls{0}; + std::uint32_t m_updatecount{0}; + double m_cputime{0.0}; + std::chrono::steady_clock::time_point m_cpustart; +}; + +} // namespace gfx diff --git a/rendering/common/locallights.cpp b/rendering/common/locallights.cpp index beace1f1..e6f2a759 100644 --- a/rendering/common/locallights.cpp +++ b/rendering/common/locallights.cpp @@ -502,6 +502,18 @@ gfx::local_lights::section_data const &gfx::local_lights::model_bounds(TModel3d return m_models.emplace(Model, data).first->second; } +bool gfx::local_lights::cab_bounds(TModel3d *Cab, gfx::geometrybank_manager const &Geometry, glm::vec3 &Center, glm::vec3 &Extent) +{ + if (Cab == nullptr) + { + return false; + } + auto const &bounds{model_bounds(Cab, Geometry)}; + Center = bounds.center; + Extent = bounds.extent; + return (bounds.extent != glm::vec3{0.f}); +} + std::string gfx::local_lights::info() const { return "local lights: " + std::to_string(m_lights.size()) + " (scenery " + std::to_string(m_scenerycount) + ", vehicles " + std::to_string(m_vehiclecount) + ", compartments " + diff --git a/rendering/common/locallights.h b/rendering/common/locallights.h index 23ef958a..3f3c7125 100644 --- a/rendering/common/locallights.h +++ b/rendering/common/locallights.h @@ -85,6 +85,8 @@ class local_lights { return m_cablamplocal; } + // the box of the cab model Cab (model space): its middle and half its size; false when the model has no geometry + bool cab_bounds(TModel3d *Cab, gfx::geometrybank_manager const &Geometry, glm::vec3 &Center, glm::vec3 &Extent); // counts of the last pass which wrote the lights to the light buffer: placed, and left out for the lack of room void placed(std::uint32_t const Placed, std::uint32_t const Dropped) { diff --git a/rendering/common/scenewalk.cpp b/rendering/common/scenewalk.cpp index 722d7c56..c49b803e 100644 --- a/rendering/common/scenewalk.cpp +++ b/rendering/common/scenewalk.cpp @@ -1792,10 +1792,14 @@ bool scene_walk::Render_cab(TDynamicObject const *Dynamic, float const Lightleve auto const luminance { Global.fLuminance * ( Dynamic->fShade > 0.0f ? Dynamic->fShade : 1.0f ) }; if( Lightlevel > 0.f ) { // crude way to light the cabin, until we have something more complete in place. with the lamp of the cab among the - // lights a share of it is kept for the light which bounces around the cab - auto const share { m_cablamplit ? Global.gfx_lights_cab_bounce : 1.f }; - walk_ambient_add( ( Dynamic->InteriorLight * Lightlevel ) * std::clamp( 1.25f - (float)luminance, 0.f, 1.f ) * share ); + // lights a share of it is kept for the light which bounces around the cab, unless the probe of the cab gives that light + auto const share { m_cablamplit ? ( m_cabprobelit ? 0.f : Global.gfx_lights_cab_bounce ) : 1.f }; + if( share > 0.f ) { + walk_ambient_add( ( Dynamic->InteriorLight * Lightlevel ) * std::clamp( 1.25f - (float)luminance, 0.f, 1.f ) * share ); + } } + // the parts of the cab read its probe + model_ubs.cab_interior = 1.f; // render if (true == Alpha) @@ -1809,6 +1813,7 @@ bool scene_walk::Render_cab(TDynamicObject const *Dynamic, float const Lightleve Render(Dynamic->mdKabina, Dynamic->Material(), 0.0); } // post-render restore + model_ubs.cab_interior = 0.f; walk_ambient_restore(); walk_sunlight(); diff --git a/rendering/common/scenewalk.h b/rendering/common/scenewalk.h index e277133c..35dfd15a 100644 --- a/rendering/common/scenewalk.h +++ b/rendering/common/scenewalk.h @@ -512,6 +512,9 @@ class scene_walk bool m_previewing{false}; // the last lighting of the pass placed the cab lamp among the lights: the cab is lit by it, not by an addition to the ambient light bool m_cablamplit{false}; + // the colour pass reads the probe of the cab (gfx::cab_probe): the light of the cab lamp the cab reflects lights it again from the + // probe, so the ambient light keeps no share of the lamp + bool m_cabprobelit{false}; // 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}; diff --git a/rendering/opengl46/opengl46ao.cpp b/rendering/opengl46/opengl46ao.cpp index b9e83317..a5e82d1b 100644 --- a/rendering/opengl46/opengl46ao.cpp +++ b/rendering/opengl46/opengl46ao.cpp @@ -103,6 +103,15 @@ void opengl46_ao::apply(int const Mode, opengl_texture &Depth, opengl_texture &O } } +void opengl46_ao::end_timing() +{ + if (m_timing) + { + m_timer.end(); + m_timing = false; + } +} + std::string opengl46_ao::info_times() { std::string text; @@ -127,4 +136,5 @@ void opengl46_ao::draw_debug_ui(bool const Available) ImGui::SliderFloat("ao bounce albedo", &Global.gfx_postfx_ao_bounce, 0.f, 1.f); ImGui::SliderInt("ao slices", &Global.gfx_postfx_ao_slices, 1, 8); ImGui::SliderInt("ao steps per side", &Global.gfx_postfx_ao_steps, 1, 16); + ImGui::Checkbox("ao in the ambient light, not over the image (gfx.postfx.ao.lighting)", &Global.gfx_postfx_ao_lighting); } diff --git a/rendering/opengl46/opengl46ao.h b/rendering/opengl46/opengl46ao.h index d74ff27c..dcc84874 100644 --- a/rendering/opengl46/opengl46ao.h +++ b/rendering/opengl46/opengl46ao.h @@ -24,7 +24,9 @@ class opengl_texture; // (gtao_main.comp) and a depth aware smoothing (gtao_denoise.comp) // - its application to the image: a multiplier drawn over it (postfx_ssao_apply.frag), which also applies the older // ssao (postfx_ssao.frag, run by the renderer) when that one is chosen -// switched by Global.gfx_postfx_ao (0 off, 1 older ssao, 2 gtao); the targets are made with the viewport when it is not 0 +// switched by Global.gfx_postfx_ao (0 off, 1 older ssao, 2 gtao); the targets are made with the viewport when it is not 0. with +// Global.gfx_postfx_ao_lighting the ground truth one is computed from the depth of the scene drawn before the colour and read by the +// shading of the colour pass (light_common.glsl, ambient_occlusion()) instead of being applied to the image class opengl46_ao { public: @@ -40,6 +42,8 @@ class opengl46_ao // multiplies the image of the framebuffer by the occlusion. Mode: Global.gfx_postfx_ao. the viewport has to be set // to the size of Depth. leaves the framebuffer bound void apply(int const Mode, opengl_texture &Depth, opengl_texture &Occlusion, gl::framebuffer &Target, glm::mat4 const &Projection); + // ends the gpu time of the occlusion computed for the light of the surfaces (gfx.postfx.ao.lighting), which isn't applied + void end_timing(); // lines for the gpu times of the debug panel std::string info_times(); // controls of the debug window. Available: the targets were made, so the mode can be changed diff --git a/rendering/opengl46/opengl46cabprobe.cpp b/rendering/opengl46/opengl46cabprobe.cpp new file mode 100644 index 00000000..93332899 --- /dev/null +++ b/rendering/opengl46/opengl46cabprobe.cpp @@ -0,0 +1,162 @@ +/* +This Source Code Form is subject to the +terms of the Mozilla Public License, v. +2.0. If a copy of the MPL was not +distributed with this file, You can +obtain one at +http://mozilla.org/MPL/2.0/. +*/ + +#include "stdafx.h" +#include "rendering/opengl46/opengl46cabprobe.h" + +#include "gl/framebuffer.h" +#include "gl/ubo.h" +#include "model/Texture.h" +#include "utilities/Globals.h" +#include "utilities/Logs.h" +#include "utilities/utilities.h" + +namespace +{ + +// uniform location of cabprobe_sh.comp: the level of the probe read and its size +GLint const SH_LEVEL_LOCATION{0}; + +// cube map of Size x Size faces with its mipmaps +GLuint make_cube(GLenum const Format, int const Size, bool const Mipmaps) +{ + GLuint texture{0}; + glCreateTextures(GL_TEXTURE_CUBE_MAP, 1, &texture); + auto const levels{Mipmaps ? static_cast(std::floor(std::log2(static_cast(std::max(Size, 1))))) + 1 : 1}; + glTextureStorage2D(texture, levels, Format, Size, Size); + glTextureParameteri(texture, GL_TEXTURE_MIN_FILTER, Mipmaps ? GL_LINEAR_MIPMAP_LINEAR : GL_NEAREST); + glTextureParameteri(texture, GL_TEXTURE_MAG_FILTER, Mipmaps ? GL_LINEAR : GL_NEAREST); + glTextureParameteri(texture, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTextureParameteri(texture, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTextureParameteri(texture, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); + return texture; +} + +} // namespace + +opengl46_cab_probe::~opengl46_cab_probe() +{ + gpu_release(); + if (m_empty != 0) + glDeleteTextures(1, &m_empty); + if (m_white != 0) + glDeleteTextures(1, &m_white); +} + +void opengl46_cab_probe::init() +{ + gl::shader vertex("cabprobe.vert"); + gl::shader geometry("cabprobe.geom"); + { + gl::shader fragment("cabprobe.frag"); + m_program = std::make_unique(std::vector>{vertex, geometry, fragment}); + } + { + gl::shader fragment("cabprobe_alpha.frag"); + m_alpha_program = std::make_unique(std::vector>{vertex, geometry, fragment}); + } + { + gl::shader compute("cabprobe_sh.comp"); + m_sh_program = std::make_unique(std::vector>{compute}); + } + m_sh = std::make_unique(static_cast(sizeof(gl::cab_probe_data)), gl::CAB_PROBE_SSBO_BINDING); + m_sh->update(gl::cab_probe_data{}); + + // a sampler with no texture to read from is undefined, also where the shading doesn't read it + m_empty = make_cube(GL_RGBA8, 1, false); + std::uint8_t const nothing[4]{0, 0, 0, 0}; + glClearTexImage(m_empty, 0, GL_RGBA, GL_UNSIGNED_BYTE, nothing); + glCreateTextures(GL_TEXTURE_2D, 1, &m_white); + glTextureStorage2D(m_white, 1, GL_RGBA8, 1, 1); + std::uint8_t const white[4]{255, 255, 255, 255}; + glClearTexImage(m_white, 0, GL_RGBA, GL_UNSIGNED_BYTE, white); +} + +void opengl46_cab_probe::bind() +{ + glBindTextureUnit(gl::CAB_PROBE_TEX, (m_light != 0) ? m_light : m_empty); + glBindTextureUnit(gl::CAB_PROBE_ALBEDO_TEX, (m_albedo != 0) ? m_albedo : m_empty); + if (m_sh) + m_sh->bind_base(); +} + +bool opengl46_cab_probe::gpu_allocate(int const Size) +{ + m_light = make_cube(GL_RGBA16F, Size, true); + m_albedo = make_cube(GL_RGBA8, Size, true); + m_depth = make_cube(GL_DEPTH_COMPONENT32F, Size, false); + // all the faces at once, the geometry shader picks the face + glCreateFramebuffers(1, &m_framebuffer); + glNamedFramebufferTexture(m_framebuffer, GL_COLOR_ATTACHMENT0, m_light, 0); + glNamedFramebufferTexture(m_framebuffer, GL_COLOR_ATTACHMENT1, m_albedo, 0); + glNamedFramebufferTexture(m_framebuffer, GL_DEPTH_ATTACHMENT, m_depth, 0); + GLenum const buffers[2]{GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1}; + glNamedFramebufferDrawBuffers(m_framebuffer, 2, buffers); + glNamedFramebufferReadBuffer(m_framebuffer, GL_NONE); + return (glCheckNamedFramebufferStatus(m_framebuffer, GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE); +} + +void opengl46_cab_probe::gpu_release() +{ + if (m_framebuffer != 0) + glDeleteFramebuffers(1, &m_framebuffer); + for (auto *texture : {&m_light, &m_albedo, &m_depth}) + { + if (*texture != 0) + glDeleteTextures(1, texture); + *texture = 0; + } + m_framebuffer = 0; + if (m_sh) + m_sh->update(gl::cab_probe_data{}); +} + +void opengl46_cab_probe::gpu_begin() +{ + m_timer.begin(); + // the framebuffer is bound past the cache of gl::framebuffer, which the end of the drawing resets + gl::framebuffer::unbind(); + glBindFramebuffer(GL_FRAMEBUFFER, m_framebuffer); + glViewport(0, 0, m_size, m_size); + float const nothing[4]{0.f, 0.f, 0.f, 0.f}; + glClearNamedFramebufferfv(m_framebuffer, GL_COLOR, 0, nothing); + glClearNamedFramebufferfv(m_framebuffer, GL_COLOR, 1, nothing); + float const farthest{0.f}; + glClearNamedFramebufferfv(m_framebuffer, GL_DEPTH, 0, &farthest); + glDisable(GL_BLEND); + glDepthMask(GL_TRUE); + glEnable(GL_DEPTH_TEST); + // one sided walls face the probe from either side + glDisable(GL_CULL_FACE); + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); +} + +void opengl46_cab_probe::gpu_end() +{ + glEnable(GL_CULL_FACE); + gl::framebuffer::unbind(); + glGenerateTextureMipmap(m_light); + glGenerateTextureMipmap(m_albedo); + // the light the cab lights itself with again, from a small level of the light reflected + glBindTextureUnit(0, m_light); + glProgramUniform2i(*m_sh_program, SH_LEVEL_LOCATION, sh_level(), sh_size()); + m_sh->bind_base(); + m_sh_program->dispatch(1); + glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT); + opengl_texture::reset_unit_cache(); + m_timer.end(); +} + +std::string opengl46_cab_probe::info_times() +{ + std::string text; + if (auto const time = m_timer.result()) + text += " cab probe: " + to_string(static_cast(*time) / 1000000.0, 3) + " ms\n"; + return text; +} diff --git a/rendering/opengl46/opengl46cabprobe.h b/rendering/opengl46/opengl46cabprobe.h new file mode 100644 index 00000000..194de4ca --- /dev/null +++ b/rendering/opengl46/opengl46cabprobe.h @@ -0,0 +1,67 @@ +/* +This Source Code Form is subject to the +terms of the Mozilla Public License, v. +2.0. If a copy of the MPL was not +distributed with this file, You can +obtain one at +http://mozilla.org/MPL/2.0/. +*/ + +#pragma once + +#include +#include + +#include "gl/shader.h" +#include "gl/query.h" +#include "gl/ssbo.h" +#include "rendering/common/cabprobe.h" + +// the probe of the occupied cab of the opengl 4.6 renderer (Global.gfx_cabprobe): the maps and the drawing to them of gfx::cab_probe, +// which tells when they're drawn and what the shading reads. the six faces of the maps are drawn at once, by a geometry shader +// (cabprobe.vert/.geom/.frag), the spherical harmonics of the light worked out by cabprobe_sh.comp +class opengl46_cab_probe : public gfx::cab_probe +{ + public: + opengl46_cab_probe() = default; + ~opengl46_cab_probe() override; + + // creates the programs, the stand-ins and the storage of the spherical harmonics. throws gl::shader_exception + void init(); + // puts the maps (or the stand-ins) on their texture units, gl::CAB_PROBE_TEX and gl::CAB_PROBE_ALBEDO_TEX, and the spherical + // harmonics on gl::CAB_PROBE_SSBO_BINDING. once per context and frame + void bind(); + // program of the probe, of the alpha tested materials when Alpha + gl::program &program(bool const Alpha) + { + return Alpha ? *m_alpha_program : *m_program; + } + // a white texture, for the materials without a diffuse texture + GLuint white() const + { + return m_white; + } + // lines for the gpu times of the debug panel + std::string info_times(); + + protected: + bool gpu_allocate(int const Size) override; + void gpu_release() override; + void gpu_begin() override; + void gpu_end() override; + + private: + std::unique_ptr m_program; + std::unique_ptr m_alpha_program; + std::unique_ptr m_sh_program; + std::unique_ptr m_sh; + // the light reflected and the albedo (cube maps with mipmaps), the depth of the faces, and the framebuffer drawing to all faces + GLuint m_light{0}; + GLuint m_albedo{0}; + GLuint m_depth{0}; + GLuint m_framebuffer{0}; + // stand-ins: a cube map of 1x1 faces of nothing, a 1x1 white texture + GLuint m_empty{0}; + GLuint m_white{0}; + gl::gpu_timer m_timer; +}; diff --git a/rendering/opengl46/opengl46renderer.cpp b/rendering/opengl46/opengl46renderer.cpp index 27f846e6..07ea963a 100644 --- a/rendering/opengl46/opengl46renderer.cpp +++ b/rendering/opengl46/opengl46renderer.cpp @@ -217,6 +217,7 @@ bool opengl46_renderer::Init(GLFWwindow *Window) m_exposure.init(); m_ao.init(); m_localshadows.init(); + m_cabprobe.init(); } catch (gl::shader_exception const &e) { @@ -952,6 +953,7 @@ void opengl46_renderer::SwapBuffers() text += m_weather.info_times(); text += m_exposure.info_times(); text += m_ao.info_times(); + text += m_cabprobe.info_times(); text += m_instancecull.info_times(); // the cullings of the other passes; the environment map is drawn now and then, its numbers are of its last update if( Global.gfx_gpucull_shadows ) { @@ -1052,6 +1054,7 @@ void opengl46_renderer::SwapBuffers() ", rays " + to_string( Global.gfx_contactshadows_length, 2 ) + " m within " + to_string( Global.gfx_contactshadows_range, 0 ) + " m\n" : ", off in this view\n" ) : std::string{} ) + + ( Global.gfx_cabprobe > 0 ? " " + m_cabprobe.info() + "\n" : std::string{} ) + shadowcascadestext + " instance buffer: " + std::to_string( m_instancestream->used() / static_cast( sizeof( glm::mat4 ) ) ) + " matrices, " + std::to_string( m_instancestream->size() / 1024 ) + " KiB\n" @@ -1466,6 +1469,7 @@ 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); + m_cabprobe.draw_debug_ui(); ImGui::Separator(); m_ssr.draw_debug_ui(nullptr); } @@ -1669,6 +1673,8 @@ void opengl46_renderer::Render_pass(viewport_config &vp, rendermode const Mode) setup_env_map(nullptr); // the cube shadow maps of the lamps, or the stand-in for them m_localshadows.bind(); + // the probe of the cab, or the stand-ins + m_cabprobe.bind(); if (Global.gfx_shadowmap_enabled && vp.main) { @@ -1704,6 +1710,9 @@ void opengl46_renderer::Render_pass(viewport_config &vp, rendermode const Mode) Render_local_shadows(); Render_lamp_shadows(); Render_headlight_shadows(); + // after the map of the cab lamp, which it reads + gpupart("cab probe"); + Render_cab_probe(); } gpupart("clear, clusters, lights"); @@ -1822,18 +1831,31 @@ void opengl46_renderer::Render_pass(viewport_config &vp, rendermode const Mode) if( Global.gfx_shadowmap_enabled ) setup_shadow_bind_map(); + // the probe of the cab, which the parts of the cab drawn in the colour pass of the main view read + if (vp.main) + { + auto const *cab{Cab_Probe_Vehicle()}; + m_cabprobe.setup_scene(scene_ubs, cab, m_colorpass.pass_camera.position(), glm::mat3{m_colorpass.pass_camera.modelview()}); + m_cabprobelit = (scene_ubs.cab_probe_box.w > 0.f); + if (m_cabprobelit) + scene_ubo->update(scene_ubs); + } + // the depth of the whole opaque scene before its colour, which the contact shadows need m_scenedepthdrawn = false; m_indirectcolourpending = false; m_scenerecorded = false; + m_aolighting = false; auto const contactshadows{Contact_Shadows_Wanted(vp)}; // 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)) + // and so does the ambient occlusion in the light of the surfaces + auto const aolighting{Ao_Lighting_Wanted(vp)}; + if ((Global.gfx_depthprepass_scene || contactshadows || reflections || aolighting) && vp.main && (false == Global.bWireFrame)) { glDebug("render scene depth"); gpupart("scene depth"); - Render_Scene_Depth(future, contactshadows && (false == Global.gfx_depthprepass_scene) && (false == reflections)); + Render_Scene_Depth(future, contactshadows && (false == Global.gfx_depthprepass_scene) && (false == reflections) && (false == aolighting)); if (contactshadows) { gpupart("contact shadows depth"); @@ -1843,6 +1865,11 @@ void opengl46_renderer::Render_pass(viewport_config &vp, rendermode const Mode) { Setup_Screen_Reflections(vp, taa); } + if (aolighting) + { + gpupart("ambient occlusion"); + Setup_Ao_Lighting(vp); + } } if (vp.main) m_contactshadowslast = m_contactshadows; @@ -1946,14 +1973,17 @@ void opengl46_renderer::Render_pass(viewport_config &vp, rendermode const Mode) } } - if (m_contactshadows || m_ssractive) + if (m_contactshadows || m_ssractive || m_aolighting || m_cabprobelit) { - // the depth they read is of this view only + // the depth they read is of this view only, and so is the probe of the cab m_contactshadows = false; m_ssractive = false; + m_cabprobelit = false; scene_ubs.contact_shadow_params = glm::vec4{0.f}; scene_ubs.contact_shadow_lamps = glm::vec4{0.f}; + scene_ubs.ao_params = glm::vec4{0.f}; gfx::screen_reflections::clear(scene_ubs); + gfx::cab_probe::clear_scene(scene_ubs); scene_ubo->update(scene_ubs); } @@ -1970,8 +2000,10 @@ void opengl46_renderer::Render_pass(viewport_config &vp, rendermode const Mode) if (!Global.gfx_skippipeline) { gpupart("resolve, ambient occlusion, motion blur"); - // the targets of the ambient occlusion are there only when it was on at the start - auto const aomode{(vp.ssao_tex != nullptr) ? Global.gfx_postfx_ao : 0}; + // the targets of the ambient occlusion are there only when it was on at the start. in the light of the surfaces it's + // in the image already + auto const aomode{((vp.ssao_tex != nullptr) && (false == m_aolighting)) ? Global.gfx_postfx_ao : 0}; + m_aolighting = false; if (motionblur || taa) { vp.main_fb->clear(GL_COLOR_BUFFER_BIT); @@ -3297,7 +3329,7 @@ void opengl46_renderer::Bind_Material( material_handle const Material, TSubModel material.textures : material.GetTextures(TSubModel::VariantId(sm))}; - if (m_scenedepthpass) + if (m_scenedepthpass || m_cabprobepass) { // the depth of the whole scene: the programs of the depth prepass, the same choice as for the instances in // Render_Instances_Indirect(). what they can't draw (blended, materials of other programs) is left to the colour pass @@ -3307,6 +3339,24 @@ void opengl46_renderer::Bind_Material( material_handle const Material, TSubModel auto const prepassclass{Prepass_Class(Material)}; if (prepassclass == 0) return; + if (m_cabprobepass) + { + // the probe of the cab: the programs of the probe, of the same materials, the albedo from the diffuse texture (a white + // one without it), alpha tested as the depth prepass tests it + auto texturebound{false}; + if (materialtextures[0] != null_handle) + texturebound = m_textures.mark_as_used(materialtextures[0]).bind(0); + if (false == texturebound) + { + if (prepassclass == 2) + return; + ::glBindTextureUnit(0, m_cabprobe.white()); + opengl_texture::units[0] = m_cabprobe.white(); + } + m_cabprobe.program(prepassclass == 2).bind(); + m_scenedepthskip = false; + return; + } if (prepassclass == 2) { // a texture which isn't there (yet) would leave the one bound before to the alpha test @@ -3488,7 +3538,7 @@ void opengl46_renderer::draw(const gfx::geometry_handle &handle) Record_Scene_Draw( &handle, 1, false ); return; } - if( m_scenedepthpass && ( false == Scene_Depth_Draws( handle ) ) ) { + if( ( m_scenedepthpass || m_cabprobepass ) && ( false == Scene_Depth_Draws( handle ) ) ) { return; } model_ubo->update(model_ubs); @@ -3512,7 +3562,7 @@ void opengl46_renderer::draw(std::vector::iterator it, } return; } - if( m_scenedepthpass && ( ( it == end ) || ( false == Scene_Depth_Draws( *it ) ) ) ) { + if( ( m_scenedepthpass || m_cabprobepass ) && ( ( it == end ) || ( false == Scene_Depth_Draws( *it ) ) ) ) { return; } model_ubo->update(model_ubs); @@ -4496,6 +4546,46 @@ void opengl46_renderer::Setup_Contact_Shadows( viewport_config &Viewport ) m_contactshadows = true; } +// the ambient occlusion goes into the light of the surfaces of the colour pass of the main view (gfx.postfx.ao.lighting): the ground +// truth one, made from the depth of the whole scene drawn before the colour, which needs the targets of the occlusion and a +// perspective projection with the far plane at infinity (the distance of a surface worked out from its depth), as the contact shadows +bool opengl46_renderer::Ao_Lighting_Wanted( viewport_config const &Viewport ) const +{ + if( ( Global.gfx_postfx_ao != 2 ) || ( false == Global.gfx_postfx_ao_lighting ) || ( false == Viewport.main ) || Global.bWireFrame || Global.gfx_skippipeline + || ( Viewport.msaa_fb == nullptr ) || ( Viewport.ssao_tex == nullptr ) || ( Viewport.ssao_blur_tex == nullptr ) ) { + return false; + } + auto const &projection { scene_ubs.projection }; + return ( projection[ 2 ][ 3 ] != 0.f ) && ( projection[ 2 ][ 2 ] == 0.f ) && ( projection[ 3 ][ 2 ] > 0.f ); +} + +void opengl46_renderer::Setup_Ao_Lighting( viewport_config &Viewport ) +{ + if( ( false == m_contactshadows ) && ( false == m_ssractive ) ) { + // the depth resolved apart from the buffer the colour pass draws to, as for the contact shadows + if( false == Resolved_Depth_Target( Viewport ) ) { return; } + Viewport.msaa_fb->bind(); + auto const scissor { ::glIsEnabled( GL_SCISSOR_TEST ) == GL_TRUE }; + if( scissor ) { ::glDisable( GL_SCISSOR_TEST ); } + ::glBlitNamedFramebuffer( + *Viewport.msaa_fb, *Viewport.clusters_depth_fb, + 0, 0, Viewport.width, Viewport.height, 0, 0, Viewport.width, Viewport.height, + GL_DEPTH_BUFFER_BIT, GL_NEAREST ); + if( scissor ) { ::glEnable( GL_SCISSOR_TEST ); } + ::glBindTextureUnit( gl::SCENE_DEPTH_TEX, Viewport.clusters_depth_tex->id ); + } + glDebug( "ambient occlusion" ); + // the projection the depth was drawn with + m_ao.compute( *Viewport.clusters_depth_tex, *Viewport.ssao_tex, *Viewport.ssao_blur_tex, m_colorpass.pass_camera.projection() ); + m_ao.end_timing(); + // the compute passes leave the units of the textures to be bound anew + ::glBindTextureUnit( gl::SCENE_DEPTH_TEX, Viewport.clusters_depth_tex->id ); + ::glBindTextureUnit( gl::SCENE_AO_TEX, Viewport.ssao_blur_tex->id ); + scene_ubs.ao_params = glm::vec4{ 1.f, Global.gfx_postfx_ao_strength, 0.f, 0.f }; + scene_ubo->update( scene_ubs ); + m_aolighting = true; +} + // Renders the per-track sleeper instances (TTrack::m_sleeper_local_transforms) using the // existing GPU-instanced submodel pipeline. The track owns a vector of pre-baked // local-space matrices; we compose each with `view * translate(track_origin - camera)` @@ -6194,6 +6284,54 @@ void opengl46_renderer::Render_local_shadows() glPolygonMode(GL_FRONT_AND_BACK, Global.bWireFrame ? GL_LINE : GL_FILL); } +TDynamicObject *opengl46_renderer::Cab_Probe_Vehicle() const +{ + if (FreeFlyModeFlag || (false == Global.render_cab) || (simulation::Train == nullptr)) + return nullptr; + auto *vehicle{simulation::Train->Dynamic()}; + if ((vehicle == nullptr) || (false == vehicle->bDisplayCab) || (vehicle->mdKabina == nullptr) || (vehicle->mdKabina == vehicle->mdModel)) + return nullptr; + return vehicle; +} + +void opengl46_renderer::Render_cab_probe() +{ + auto *vehicle{Cab_Probe_Vehicle()}; + glm::vec3 center{0.f}; + glm::vec3 extent{0.f}; + if ((vehicle != nullptr) && (false == m_locallights.cab_bounds(vehicle->mdKabina, m_geometry, center, extent))) + vehicle = nullptr; + // the cab lamp lights the probe when it's a light of the frame + auto const *lamp{(vehicle != nullptr) && (m_locallights.cab_lamp_vehicle() == vehicle) ? m_locallights.cab_lamp() : nullptr}; + if (false == m_cabprobe.update_wanted(vehicle, (vehicle != nullptr ? vehicle->mdKabina : nullptr), center, extent, lamp, m_framestamp)) + return; + + glDebug("render cab probe"); + // the lamp with its shadow map, when the map holds the shadows of the lamp where it is + auto const shadows{(lamp != nullptr) && m_localshadows.ready(vehicle) && (m_localshadows.lamp() == m_locallights.cab_lamp_local())}; + m_cabprobe.setup_drawing(scene_ubs, lamp, m_locallights.cab_lamp_local(), shadows, m_localshadows.weights()); + scene_ubo->update(scene_ubs); + // the cab drawn by the colour mode with the programs of the probe. the camera is put at the vehicle, which Render_cab() places + // the cab relative to, and the modelview takes the cab model to the space of the probe + m_renderpass.draw_mode = rendermode::color; + m_renderpass.pass_camera.position() = vehicle->GetPosition(); + m_renderpass.draw_stats = {}; + OpenGLMatrices.mode(GL_MODELVIEW); + OpenGLMatrices.load_matrix(glm::mat4{m_cabprobe.model_to_probe() * glm::inverse(vehicle->mMatrix)}); + setup_drawing(false); + m_cabprobe.begin(); + m_cabprobepass = true; + m_scenedepthskip = true; + Render_cab(vehicle, 0.0f, false); + m_cabprobepass = false; + m_scenedepthskip = true; + m_cabprobe.end(static_cast(m_renderpass.draw_stats.drawcalls)); + m_renderpass = m_colorpass; + glPolygonMode(GL_FRONT_AND_BACK, Global.bWireFrame ? GL_LINE : GL_FILL); + // the maps may have been made anew + m_cabprobe.bind(); +} + void opengl46_renderer::Render_lamp_shadows() { m_lampscenerydrawn = 0; diff --git a/rendering/opengl46/opengl46renderer.h b/rendering/opengl46/opengl46renderer.h index 47ddd242..e2450366 100644 --- a/rendering/opengl46/opengl46renderer.h +++ b/rendering/opengl46/opengl46renderer.h @@ -43,6 +43,7 @@ http://mozilla.org/MPL/2.0/. #include "rendering/opengl46/opengl46ao.h" #include "rendering/common/locallights.h" #include "rendering/opengl46/opengl46localshadows.h" +#include "rendering/opengl46/opengl46cabprobe.h" #include "rendering/opengl46/opengl46instancecull.h" #include "rendering/opengl46/opengl46hiz.h" #include "rendering/opengl46/opengl46shadowfit.h" @@ -319,6 +320,10 @@ class opengl46_renderer : public gfx_renderer, public gfx::scene_walk { bool Render_reflections(viewport_config &vp); // draws the cube shadow maps of the lamps which need it in this frame (the cab lamp: the cab) void Render_local_shadows(); + // the occupied vehicle whose cab is drawn in the colour pass, nullptr when there's none (the conditions of Render_cab()) + TDynamicObject *Cab_Probe_Vehicle() const; + // draws the probe of the occupied cab when it needs it in this frame + void Render_cab_probe(); // draws the parts of the cube shadow maps of the scenery lamps which need it in this frame void Render_lamp_shadows(); // one part of the map of a scenery lamp: the scenery, or the vehicles by it (and Cab, the occupied cab) over a copy of it @@ -415,6 +420,11 @@ class opengl46_renderer : public gfx_renderer, public gfx::scene_walk { void Setup_Contact_Shadows( viewport_config &Viewport ); // makes the target of the resolved depth of the viewport if it isn't there yet; false if it can't be made bool Resolved_Depth_Target( viewport_config &Viewport ); + // whether the colour pass of the viewport takes the ambient occlusion into the light of its surfaces (gfx.postfx.ao.lighting), + // which needs the depth pass of the scene + bool Ao_Lighting_Wanted( viewport_config const &Viewport ) const; + // works out the ambient occlusion of the depth drawn by Render_Scene_Depth() and sets it up for the colour pass + void Setup_Ao_Lighting( viewport_config &Viewport ); // resolves the depth drawn so far in the colour pass of the viewport for the soft particles (gfx.particles.soft) and binds it // to gl::SCENE_DEPTH_TEX; false when the viewport has no such depth bool Setup_Particles_Depth( viewport_config &Viewport ); @@ -708,6 +718,9 @@ class opengl46_renderer : public gfx_renderer, public gfx::scene_walk { // contact shadows: set up for the colour pass under way, and whether the last main view had them bool m_contactshadows { false }; bool m_contactshadowslast { false }; + // the ambient occlusion of the colour pass of the main view under way is in the light of its surfaces (gfx.postfx.ao.lighting), + // not applied to its image + bool m_aolighting { false }; // 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 @@ -767,6 +780,9 @@ class opengl46_renderer : public gfx_renderer, public gfx::scene_walk { opengl46_local_shadows m_localshadows; // the shadow pass draws to a cube shadow map, with the programs of m_localshadows bool m_cubeshadows = false; + // the probe of the occupied cab, and the colour mode draws the cab into it with its programs (Render_cab_probe()) + opengl46_cab_probe m_cabprobe; + bool m_cabprobepass = false; double m_precipitationrotation; diff --git a/rendering/vulkan14/vulkan14ao.cpp b/rendering/vulkan14/vulkan14ao.cpp index eb8b8819..0f374bd9 100644 --- a/rendering/vulkan14/vulkan14ao.cpp +++ b/rendering/vulkan14/vulkan14ao.cpp @@ -116,6 +116,70 @@ void vulkan14_renderer::destroy_ao() scene.ssao_noise.reset(); } +// the ambient occlusion goes into the light of the surfaces of the colour pass of the main view (gfx.postfx.ao.lighting, +// opengl46_renderer::Ao_Lighting_Wanted()): the ground truth one, made from the depth of the whole scene drawn before the colour, +// which needs a perspective projection with the far plane at infinity (the distance of a surface worked out from its depth) +bool vulkan14_renderer::ao_lighting_wanted(viewport_config const &Viewport) const +{ + if ((Global.gfx_postfx_ao != 2) || (false == Global.gfx_postfx_ao_lighting) || (false == Viewport.main) || Global.bWireFrame || Global.gfx_skippipeline) + { + return false; + } + auto const &targets{m_context->scene.targets}; + if (targets.empty() || (false == bool(targets.front().ssao)) || (false == bool(targets.front().ssao_blur))) + { + return false; + } + auto const &projection{scene_ubs.projection}; + return (projection[2][3] != 0.f) && (projection[2][2] == 0.f) && (projection[3][2] > 0.f); +} + +// the ground truth ambient occlusion of the depth of the scene drawn before the colour (render_scene_depth()), for the shading of the +// colour pass, left in the layout it reads it in. outside a rendering +bool vulkan14_renderer::compute_ao_lighting(std::size_t const Index) +{ + auto &ctx{*m_context}; + auto &scene{ctx.scene}; + auto &targets{scene.targets[Index]}; + auto const commands{ctx.commands}; + if ((false == bool(targets.ssao)) || (false == bool(targets.ssao_blur)) || (false == bool(targets.scene_depth))) + { + return false; + } + ctx.passes.begin_pass("ambient occlusion"); + // the projection the depth was drawn with + auto const &projection{m_colorpass.pass_camera.projection()}; + auto const inverseprojection{glm::inverse(projection)}; + auto const width{targets.ssao->desc().extent.width}; + auto const height{targets.ssao->desc().extent.height}; + auto const groups_x{(width + GROUP_SIZE - 1) / GROUP_SIZE}; + auto const groups_y{(height + GROUP_SIZE - 1) / GROUP_SIZE}; + // horizon search + vk14::barriers(commands, {vk14::use_of(*targets.scene_depth, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, sampled_stage, sampled_access), + vk14::use_of(*targets.ssao, VK_IMAGE_LAYOUT_GENERAL, compute_stage, VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT)}); + gtao_constants constants{}; + constants.inv_projection = inverseprojection; + constants.params = glm::vec2{Global.gfx_postfx_ao_radius, projection[0][0] * 0.5f * static_cast(width)}; + constants.clipw_row = glm::vec4{projection[0][3], projection[1][3], projection[2][3], projection[3][3]}; + constants.quality = glm::ivec2{Global.gfx_postfx_ao_slices, Global.gfx_postfx_ao_steps}; + auto const depthimage{vk14::image_descriptor(ctx.sampling.clamp_nearest, targets.scene_depth->view(), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)}; + scene.gtao_main.dispatch(commands, {depthimage, vk14::storage_image(targets.ssao->view())}, constants, groups_x, groups_y); + // smoothing + vk14::barriers(commands, {vk14::use_of(*targets.ssao, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, sampled_stage, sampled_access), + vk14::use_of(*targets.ssao_blur, VK_IMAGE_LAYOUT_GENERAL, compute_stage, VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT)}); + scene.gtao_denoise.dispatch(commands, {depthimage, vk14::image_descriptor(ctx.sampling.clamp_nearest, targets.ssao->view(), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL), vk14::storage_image(targets.ssao_blur->view())}, + inverseprojection, groups_x, groups_y); + vk14::barriers(commands, {vk14::use_of(*targets.ssao_blur, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, sampled_stage, sampled_access)}); + return true; +} + +// the block of the scene of the colour pass set up to read the occlusion (light_common.glsl, ambient_occlusion()) +void vulkan14_renderer::setup_ao_lighting() +{ + scene_ubs.ao_params = glm::vec4{1.f, Global.gfx_postfx_ao_strength, 0.f, 0.f}; + upload_scene_block(); +} + // the ambient occlusion of the viewport from the depth of its scene (resolved), multiplied into its colour // (opengl46_renderer::Render_ambient_occlusion()). Mode: Global.gfx_postfx_ao. outside a rendering void vulkan14_renderer::render_ao(std::size_t const Index, int const Mode) diff --git a/rendering/vulkan14/vulkan14bindings.h b/rendering/vulkan14/vulkan14bindings.h index b32c178e..f092d980 100644 --- a/rendering/vulkan14/vulkan14bindings.h +++ b/rendering/vulkan14/vulkan14bindings.h @@ -73,7 +73,14 @@ constexpr std::uint32_t SCENE_DEPTH_TEX = 24; constexpr std::uint32_t SSR_HISTORY_TEX = 25; // the water on the windscreens (gfx::windscreen_water): the drops, their grids and the wetted paths the glasses read constexpr std::uint32_t WATER_SSBO = 26; -constexpr std::uint32_t PASS_BINDING_COUNT = 27; +// the ambient occlusion of the main view in the light of the surfaces (gfx.postfx.ao.lighting, the stage A3), half its size +constexpr std::uint32_t SCENE_AO_TEX = 27; +// the probe of the occupied cab (gfx::cab_probe, the stage A4): the cube maps of the light of its lamps the cab reflects and of its +// albedo, and the light it lights the cab with again (gl::cab_probe_data) +constexpr std::uint32_t CAB_PROBE_TEX = 28; +constexpr std::uint32_t CAB_PROBE_ALBEDO_TEX = 29; +constexpr std::uint32_t CAB_PROBE_SSBO = 30; +constexpr std::uint32_t PASS_BINDING_COUNT = 31; // 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 diff --git a/rendering/vulkan14/vulkan14cabprobe.cpp b/rendering/vulkan14/vulkan14cabprobe.cpp new file mode 100644 index 00000000..7ad1992d --- /dev/null +++ b/rendering/vulkan14/vulkan14cabprobe.cpp @@ -0,0 +1,405 @@ +/* +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 probe of the occupied cab in the vulkan renderer (the stage A4 of the plan; opengl46_cab_probe and +// opengl46_renderer::Render_cab_probe()): the maps of vk14::cab_probe_maps, the cab drawn to them by the colour mode of the shared walk +// of the scene with the programs of the probe (the six faces at once by multiview), their mipmaps and the spherical harmonics of +// the light the cab reflects (cabprobe_sh.comp) + +#include "stdafx.h" +#include "rendering/vulkan14/vulkan14context.h" +#include "rendering/vulkan14/vulkan14commands.h" +#include "simulation/simulation.h" +#include "utilities/Globals.h" +#include "vehicle/DynObj.h" +#include "vehicle/Train.h" + +namespace +{ + +VkPipelineStageFlags2 const colour_stage{VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT}; +VkAccessFlags2 const colour_access{VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_2_COLOR_ATTACHMENT_READ_BIT}; +VkPipelineStageFlags2 const depth_stage{VK_PIPELINE_STAGE_2_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_2_LATE_FRAGMENT_TESTS_BIT}; +VkAccessFlags2 const depth_access{VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_READ_BIT}; +VkPipelineStageFlags2 const sampled_stage{VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_2_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT}; +VkAccessFlags2 const sampled_access{VK_ACCESS_2_SHADER_SAMPLED_READ_BIT}; + +// the formats of the maps: the light reflected (hdr) and the albedo +VkFormat const LIGHT_FORMAT{VK_FORMAT_R16G16B16A16_SFLOAT}; +VkFormat const ALBEDO_FORMAT{VK_FORMAT_R8G8B8A8_UNORM}; + +// the mipmaps of a cube map whose first level was drawn: each level from the one before it, all of them left in the layout the +// shading reads them in (the environment map makes its own the same way, render_reflections()) +void make_cube_mipmaps(vk14::command_stream *Commands, vk14::image &Image) +{ + auto const mips{Image.desc().mips}; + auto const size{static_cast(Image.desc().extent.width)}; + auto barrier = [&](std::uint32_t Level, std::uint32_t Count, 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, Count, 0, 6}; + VkDependencyInfo dependency{}; + dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + dependency.imageMemoryBarrierCount = 1; + dependency.pImageMemoryBarriers = &entry; + Commands->pipeline_barrier2(&dependency); + }; + // the first level was drawn, the others are undefined + barrier(0, 1, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, colour_stage, VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT, VK_PIPELINE_STAGE_2_BLIT_BIT, VK_ACCESS_2_TRANSFER_READ_BIT); + if (mips > 1) + { + barrier(1, mips - 1, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_PIPELINE_STAGE_2_NONE, VK_ACCESS_2_NONE, VK_PIPELINE_STAGE_2_BLIT_BIT, VK_ACCESS_2_TRANSFER_WRITE_BIT); + } + for (std::uint32_t level = 1; level < mips; ++level) + { + VkImageBlit2 blit{}; + blit.sType = VK_STRUCTURE_TYPE_IMAGE_BLIT_2; + blit.srcSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, level - 1, 0, 6}; + blit.srcOffsets[1] = {std::max(size >> (level - 1), 1), std::max(size >> (level - 1), 1), 1}; + blit.dstSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, level, 0, 6}; + blit.dstOffsets[1] = {std::max(size >> level, 1), std::max(size >> level, 1), 1}; + VkBlitImageInfo2 info{}; + info.sType = VK_STRUCTURE_TYPE_BLIT_IMAGE_INFO_2; + info.srcImage = Image.handle(); + info.srcImageLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; + info.dstImage = Image.handle(); + info.dstImageLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + info.regionCount = 1; + info.pRegions = &blit; + info.filter = VK_FILTER_LINEAR; + Commands->blit_image2(&info); + 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); + } + barrier(0, mips, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_PIPELINE_STAGE_2_BLIT_BIT, VK_ACCESS_2_TRANSFER_READ_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}; +} + +VkRenderingAttachmentInfo cleared_attachment(VkImageView const View, VkImageLayout const Layout, VkClearValue const &Value) +{ + VkRenderingAttachmentInfo info{}; + info.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO; + info.imageView = View; + info.imageLayout = Layout; + info.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + info.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + info.clearValue = Value; + return info; +} + +} // namespace + +namespace vk14 +{ + +cab_probe_maps::~cab_probe_maps() +{ + destroy(); +} + +void cab_probe_maps::init(VkDevice const Device, allocator &Memory, deletion_queue &Deletions) +{ + m_device = Device; + m_memory = &Memory; + m_deletions = &Deletions; +} + +void cab_probe_maps::destroy() +{ + // the device is idle: the maps go at once + m_deletions = nullptr; + let_go(); +} + +bool cab_probe_maps::gpu_allocate(int const Size) +{ + if ((m_memory == nullptr) || (Size <= 0)) + { + return false; + } + auto const size{static_cast(Size)}; + // the maps: cube maps with their mipmaps, read by the shading, drawn to through a view of the six layers of the first level + auto make_map = [&](std::shared_ptr &Map, VkImageView &View, VkFormat const Format, char const *Name) { + image_desc desc; + desc.format = Format; + desc.extent = {size, size, 1}; + desc.layers = 6; + desc.mips = static_cast(std::floor(std::log2(static_cast(size)))) + 1; + desc.view_type = VK_IMAGE_VIEW_TYPE_CUBE; + desc.flags = VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT; + desc.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; + Map = std::make_shared(); + if (false == Map->create(*m_memory, desc, Name)) + { + Map.reset(); + return false; + } + VkImageViewCreateInfo info{}; + info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + info.image = Map->handle(); + info.viewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY; + info.format = Format; + info.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 6}; + if (vkCreateImageView(m_device, &info, nullptr, &View) != VK_SUCCESS) + { + View = VK_NULL_HANDLE; + return false; + } + return true; + }; + if ((false == make_map(m_light, m_lightview, LIGHT_FORMAT, "cab probe light")) || (false == make_map(m_albedo, m_albedoview, ALBEDO_FORMAT, "cab probe albedo"))) + { + let_go(); + return false; + } + image_desc depth; + depth.format = VK_FORMAT_D32_SFLOAT; + depth.extent = {size, size, 1}; + depth.layers = 6; + depth.view_type = VK_IMAGE_VIEW_TYPE_2D_ARRAY; + depth.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; + m_depth = std::make_shared(); + if (false == m_depth->create(*m_memory, depth, "cab probe depth")) + { + let_go(); + return false; + } + m_target = draw_target{m_light.get(), m_albedo.get(), m_depth.get(), m_lightview, m_albedoview, size}; + m_drawn = false; + return true; +} + +void cab_probe_maps::gpu_release() +{ + let_go(); +} + +void cab_probe_maps::let_go() +{ + m_target = draw_target{}; + m_drawn = false; + std::vector views; + for (auto *view : {&m_lightview, &m_albedoview}) + { + if (*view != VK_NULL_HANDLE) + { + views.push_back(*view); + } + *view = VK_NULL_HANDLE; + } + if (m_deletions != nullptr) + { + // the frames in flight may still read them + m_deletions->push(m_value, [device = m_device, views = std::move(views), light = std::move(m_light), albedo = std::move(m_albedo), depth = std::move(m_depth)]() { + for (auto *view : views) + { + vkDestroyImageView(device, view, nullptr); + } + }); + } + else + { + for (auto *view : views) + { + vkDestroyImageView(m_device, view, nullptr); + } + } + m_light.reset(); + m_albedo.reset(); + m_depth.reset(); +} + +} // namespace vk14 + +bool vulkan14_renderer::init_cab_probe(std::string &Reason) +{ + auto &ctx{*m_context}; + auto &scene{ctx.scene}; + auto const device{ctx.device.handle()}; + try + { + auto program = [&](char const *Fragment) { + auto result{std::make_shared()}; + result->vertex = ctx.shaders.get("cabprobe.vert"); + result->fragment = ctx.shaders.get(Fragment); + result->fragment_name = Fragment; + result->vertex_name = "cabprobe.vert"; + result->vertices = vk14::vertex_kind::scene; + result->name = std::string("cabprobe.vert + ") + Fragment; + return result; + }; + scene.cabprobe_program = program("cabprobe.frag"); + scene.cabprobe_alpha_program = program("cabprobe_alpha.frag"); + if (false == scene.cabprobe_sh.create(device, ctx.pipelines, ctx.shaders.get("cabprobe_sh.comp"), scene.pass_layout, {}, "cab probe spherical harmonics")) + { + Reason = "no compute pipeline of the cab probe"; + return false; + } + } + catch (gfx::shader_exception const &e) + { + Reason = std::string("invalid shader: ") + e.what(); + return false; + } + // the light reflected and the albedo, the six faces drawn at once + scene.cabprobe_formats.colour = {LIGHT_FORMAT, ALBEDO_FORMAT}; + scene.cabprobe_formats.colours = 2; + scene.cabprobe_formats.depth = VK_FORMAT_D32_SFLOAT; + scene.cabprobe_formats.view_mask = 0x3f; + scene.cabprobe_formats.id = ++scene.formats_count; + // the light the cab lights itself with again, cleared with the stand-ins in the first frame + if (false == scene.cabprobe_bounce.create(ctx.memory, sizeof(gl::cab_probe_data), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, vk14::memory_kind::device, "cab probe bounce")) + { + Reason = "no buffer of the cab probe"; + return false; + } + // the stand-in of the maps: 1x1 faces of nothing, cleared with the other stand-ins in the first frame + vk14::image_desc desc; + desc.format = VK_FORMAT_R8G8B8A8_UNORM; + desc.view_type = VK_IMAGE_VIEW_TYPE_CUBE; + desc.flags = VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT; + desc.layers = 6; + desc.extent = {1, 1, 1}; + desc.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; + scene.cabprobe_empty = std::make_shared(); + if (false == scene.cabprobe_empty->create(ctx.memory, desc, "empty cab probe")) + { + scene.cabprobe_empty.reset(); + Reason = "no stand-in of the cab probe"; + return false; + } + scene.cabprobe.init(device, ctx.memory, ctx.deletions); + return true; +} + +void vulkan14_renderer::destroy_cab_probe() +{ + auto &ctx{*m_context}; + auto &scene{ctx.scene}; + scene.cabprobe.destroy(); + scene.cabprobe_sh.destroy(ctx.device.handle()); + scene.cabprobe_program.reset(); + scene.cabprobe_alpha_program.reset(); + scene.cabprobe_bounce.destroy(); + scene.cabprobe_empty.reset(); +} + +TDynamicObject *vulkan14_renderer::cab_probe_vehicle() const +{ + // the conditions of Render_cab() + if (FreeFlyModeFlag || (false == Global.render_cab) || (simulation::Train == nullptr)) + { + return nullptr; + } + auto *vehicle{simulation::Train->Dynamic()}; + if ((vehicle == nullptr) || (false == vehicle->bDisplayCab) || (vehicle->mdKabina == nullptr) || (vehicle->mdKabina == vehicle->mdModel)) + { + return nullptr; + } + return vehicle; +} + +void vulkan14_renderer::render_cab_probe() +{ + auto &ctx{*m_context}; + auto &scene{ctx.scene}; + auto &probe{scene.cabprobe}; + auto const commands{ctx.commands}; + auto *vehicle{cab_probe_vehicle()}; + glm::vec3 center{0.f}; + glm::vec3 extent{0.f}; + if ((vehicle != nullptr) && (false == m_locallights.cab_bounds(vehicle->mdKabina, m_geometry, center, extent))) + { + vehicle = nullptr; + } + // the cab lamp lights the probe when it's a light of the frame + auto const *lamp{(vehicle != nullptr) && (m_locallights.cab_lamp_vehicle() == vehicle) ? m_locallights.cab_lamp() : nullptr}; + if (false == probe.update_wanted(vehicle, (vehicle != nullptr ? vehicle->mdKabina : nullptr), center, extent, lamp, m_framestamp)) + { + return; + } + auto const &target{probe.target()}; + if ((target.light == nullptr) || (target.albedo == nullptr) || (target.depth == nullptr) || (false == bool(scene.cabprobe_program))) + { + return; + } + ctx.passes.begin_pass("cab probe"); + // the lamp with its shadow map, when the map holds the shadows of the lamp where it is + auto const shadows{(lamp != nullptr) && scene.lamps.ready(vehicle) && (scene.lamps.lamp() == m_locallights.cab_lamp_local())}; + probe.setup_drawing(scene_ubs, lamp, m_locallights.cab_lamp_local(), shadows, scene.lamps.weights()); + // the cab drawn by the colour mode with the programs of the probe. the camera is put at the vehicle, which Render_cab() places the cab + // relative to, and the modelview takes the cab model to the space of the probe + m_renderpass.draw_mode = rendermode::color; + m_renderpass.pass_camera.position() = vehicle->GetPosition(); + m_renderpass.draw_stats = {}; + OpenGLMatrices.mode(GL_MODELVIEW); + OpenGLMatrices.load_matrix(glm::mat4{probe.model_to_probe() * glm::inverse(vehicle->mMatrix)}); + probe.begin(); + std::vector const uses{vk14::use_of(*target.light, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, colour_stage, colour_access), + vk14::use_of(*target.albedo, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, colour_stage, colour_access), + vk14::use_of(*target.depth, VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL, depth_stage, depth_access)}; + vk14::barriers(commands, uses); + VkClearValue nothing{}; + nothing.color = {{0.f, 0.f, 0.f, 0.f}}; + VkClearValue farthest{}; + farthest.depthStencil = {0.f, 0}; + std::array colours{cleared_attachment(target.light_view, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, nothing), + cleared_attachment(target.albedo_view, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, nothing)}; + auto depth{cleared_attachment(target.depth->view(), VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL, farthest)}; + VkRenderingInfo rendering{}; + rendering.sType = VK_STRUCTURE_TYPE_RENDERING_INFO; + rendering.renderArea = {{0, 0}, {target.size, target.size}}; + rendering.layerCount = 1; + rendering.viewMask = scene.cabprobe_formats.view_mask; + rendering.colorAttachmentCount = static_cast(colours.size()); + rendering.pColorAttachments = colours.data(); + rendering.pDepthAttachment = &depth; + commands->begin_rendering(&rendering); + remember_rendering(rendering, uses); + // one sided walls face the probe from either side + start_draws(scene.cabprobe_formats, static_cast(target.size), static_cast(target.size), vk14::scene_state::set_plain, false); + m_draw->cullface = false; + walk_blending(false); + upload_scene_block(); + upload_light_header(); + m_draw->probepass = true; + m_draw->depthskip = true; + Render_cab(vehicle, 0.0f, false); + m_draw->probepass = false; + m_draw->depthskip = true; + if (m_draw->rendering) + { + commands->end_rendering(); + } + end_draws(); + // the mipmaps, and the light the cab lights itself with again from a small level of the light reflected + make_cube_mipmaps(commands, *target.light); + make_cube_mipmaps(commands, *target.albedo); + struct + { + glm::ivec2 level; + } constants{glm::ivec2{probe.sh_level(), probe.sh_size()}}; + scene.cabprobe_sh.dispatch(commands, + {vk14::image_descriptor(ctx.sampling.clamp_linear, target.light->view(), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL), + vk14::buffer_descriptor(scene.cabprobe_bounce.handle(), 0, sizeof(gl::cab_probe_data))}, + constants, 1); + vk14::memory_barrier(commands, VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT, VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT, VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT, VK_ACCESS_2_SHADER_STORAGE_READ_BIT); + probe.drawn(); + probe.end(static_cast(m_renderpass.draw_stats.drawcalls)); + m_renderpass = m_colorpass; + ctx.passes.end_pass(); +} diff --git a/rendering/vulkan14/vulkan14cabprobe.h b/rendering/vulkan14/vulkan14cabprobe.h new file mode 100644 index 00000000..672f355c --- /dev/null +++ b/rendering/vulkan14/vulkan14cabprobe.h @@ -0,0 +1,93 @@ +/* +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 probe of the occupied cab in the vulkan renderer (the stage A4 of the plan, opengl46_cab_probe): the maps of gfx::cab_probe, +// which tells when they're drawn and what the shading reads. the six faces are drawn at once by multiview (cabprobe.vert reads the +// face from gl_ViewIndex) instead of the geometry shader of the opengl renderer; the renderer draws to the target the class keeps +// (target()), makes the mipmaps and the spherical harmonics (vulkan14cabprobe.cpp) + +#pragma once + +#include "rendering/common/cabprobe.h" +#include "rendering/vulkan14/vulkan14memory.h" + +#include + +namespace vk14 +{ + +class cab_probe_maps : public gfx::cab_probe +{ + public: + // what the drawing draws to: the views of the six layers of the first level of the maps and of the depth + struct draw_target + { + image *light{nullptr}; + image *albedo{nullptr}; + image *depth{nullptr}; + VkImageView light_view{VK_NULL_HANDLE}; + VkImageView albedo_view{VK_NULL_HANDLE}; + std::uint32_t size{0}; + }; + + cab_probe_maps() = default; + ~cab_probe_maps() override; + + // Device, Memory, Deletions: what the maps are made and let go with; called once + void init(VkDevice Device, allocator &Memory, deletion_queue &Deletions); + // the maps go (the device is about to) + void destroy(); + // the value of the timeline the gpu reaches when it's done with the frame being recorded, before the drawing + void begin_frame(std::uint64_t const Value) + { + m_value = Value; + } + draw_target const &target() const + { + return m_target; + } + // the maps, once drawn (in the layout the shading reads them in), for the pass sets; null: the stand-in is read + image *light_map() const + { + return m_drawn ? m_light.get() : nullptr; + } + image *albedo_map() const + { + return m_drawn ? m_albedo.get() : nullptr; + } + // the maps were drawn and are in the layout the shading reads them in + void drawn() + { + m_drawn = true; + } + + protected: + bool gpu_allocate(int const Size) override; + void gpu_release() override; + // the drawing is recorded by the renderer + void gpu_begin() override {} + void gpu_end() override {} + + private: + void let_go(); + + VkDevice m_device{VK_NULL_HANDLE}; + allocator *m_memory{nullptr}; + deletion_queue *m_deletions{nullptr}; + std::uint64_t m_value{0}; + std::shared_ptr m_light; + std::shared_ptr m_albedo; + std::shared_ptr m_depth; + VkImageView m_lightview{VK_NULL_HANDLE}; + VkImageView m_albedoview{VK_NULL_HANDLE}; + draw_target m_target; + bool m_drawn{false}; +}; + +} // namespace vk14 diff --git a/rendering/vulkan14/vulkan14context.h b/rendering/vulkan14/vulkan14context.h index 79603dda..29c7d6d0 100644 --- a/rendering/vulkan14/vulkan14context.h +++ b/rendering/vulkan14/vulkan14context.h @@ -28,6 +28,7 @@ http://mozilla.org/MPL/2.0/. #include "rendering/vulkan14/vulkan14compute.h" #include "rendering/vulkan14/vulkan14cull.h" #include "rendering/vulkan14/vulkan14shadows.h" +#include "rendering/vulkan14/vulkan14cabprobe.h" #include "rendering/vulkan14/vulkan14terrain.h" #include "rendering/vulkan14/vulkan14grass.h" #include "rendering/vulkan14/vulkan14particles.h" @@ -390,6 +391,14 @@ struct scene_state local_shadow_maps lamps; std::shared_ptr cube_shadow, cube_alpha_shadow; target_formats cube_formats; + // the probe of the occupied cab (the stage A4): its maps, the programs and the targets drawing it, the kernel of the spherical + // harmonics of the light it reflects and their storage (CAB_PROBE_SSBO), and the stand-in of its maps + cab_probe_maps cabprobe; + std::shared_ptr cabprobe_program, cabprobe_alpha_program; + target_formats cabprobe_formats; + compute_kernel cabprobe_sh; + buffer cabprobe_bounce; + std::shared_ptr cabprobe_empty; // the materials of the heightmap terrain (the stage P6), made with the bank of the textures std::unique_ptr terrain; // the grass of the heightmap terrain, and its program, made when it's first drawn @@ -643,6 +652,8 @@ struct vulkan14_renderer::draw_state // don't draw is left out (depthskip, set by walk_material()) bool depthpass{false}; bool depthskip{true}; + // the probe of the cab (the stage A4): the walk draws with the programs of the probe, what they don't draw is left out alike + bool probepass{false}; // the pass sets of a culling, the instance buffer its output, for the indirect draws of its instances; null: the pass sets VkDescriptorSet const *cull_sets{nullptr}; // the rendering under way, begun again where it was after it was ended for the compute passes of the culling diff --git a/rendering/vulkan14/vulkan14debug.cpp b/rendering/vulkan14/vulkan14debug.cpp index 4936fc77..8332a60a 100644 --- a/rendering/vulkan14/vulkan14debug.cpp +++ b/rendering/vulkan14/vulkan14debug.cpp @@ -164,6 +164,10 @@ std::string vulkan14_renderer::scene_info() text += " contact shadows: " + std::string{Global.gfx_contactshadows == 3 ? "high" : Global.gfx_contactshadows == 2 ? "medium" : "low"} + ", rays " + to_string(Global.gfx_contactshadows_length, 2) + " m within " + to_string(Global.gfx_contactshadows_range, 0) + " m\n"; } + if (Global.gfx_cabprobe > 0) + { + text += " " + scene.cabprobe.info() + "\n"; + } text += cascades + line(" submodels:", colour.submodels, shadowstats.submodels) + line(" paths: ", colour.paths, shadowstats.paths) + line(" shapes: ", colour.shapes, shadowstats.shapes) + " traction: " + to_string(colour.traction, 7) + "\n" + " lines: " + to_string(colour.lines, 7) + "\n" + "particles: " + to_string(colour.particles, 7) + "\nlights: " + to_string(static_cast(m_lights_lit), 7) + " (" + std::to_string(m_lights_dropped) + " over capacity), " + @@ -223,6 +227,8 @@ void vulkan14_renderer::draw_debug_ui() ImGui::Separator(); scene.lamps.draw_debug_ui(); ImGui::Separator(); + scene.cabprobe.draw_debug_ui(); + ImGui::Separator(); char const *contactlevels[]{"no", "low (the sun)", "medium (the sun and 2 lamps)", "high (the sun and 4 lamps)"}; ImGui::Combo("contact shadows (gfx.contactshadows)", &Global.gfx_contactshadows, contactlevels, IM_ARRAYSIZE(contactlevels)); ImGui::SliderFloat("length of their rays, m (gfx.contactshadows.length)", &Global.gfx_contactshadows_length, 0.05f, 5.f, "%.2f", ImGuiSliderFlags_Logarithmic); @@ -323,6 +329,7 @@ void vulkan14_renderer::draw_debug_ui() ImGui::SliderFloat("ao bounce albedo", &Global.gfx_postfx_ao_bounce, 0.f, 1.f); ImGui::SliderInt("ao slices", &Global.gfx_postfx_ao_slices, 1, 8); ImGui::SliderInt("ao steps per side", &Global.gfx_postfx_ao_steps, 1, 16); + ImGui::Checkbox("ao in the ambient light, not over the image (gfx.postfx.ao.lighting)", &Global.gfx_postfx_ao_lighting); } } ImGui::Separator(); diff --git a/rendering/vulkan14/vulkan14frame.cpp b/rendering/vulkan14/vulkan14frame.cpp index c09acdf0..d5a8f6ff 100644 --- a/rendering/vulkan14/vulkan14frame.cpp +++ b/rendering/vulkan14/vulkan14frame.cpp @@ -609,6 +609,7 @@ bool vulkan14_renderer::Render() scene.cull.main.begin_frame(slot, value, ctx.completed()); } scene.lamps.begin_frame(ctx.commands, value); + scene.cabprobe.begin_frame(value); if (scene.terrain) { // textures of new heightmap terrain layers go to the arrays the terrain shader reads them from @@ -679,7 +680,8 @@ bool vulkan14_renderer::Render() if (false == scene.images_ready) { scene.images_ready = true; - std::vector const standins{scene.shadow_empty.get(), scene.env_empty.get(), scene.volume_empty.get(), scene.cube_shadow_empty.get(), scene.headlight_shadow_empty.get(), scene.depth_empty.get()}; + std::vector const standins{scene.shadow_empty.get(), scene.env_empty.get(), scene.volume_empty.get(), scene.cube_shadow_empty.get(), + scene.headlight_shadow_empty.get(), scene.depth_empty.get(), scene.cabprobe_empty.get()}; std::vector clears; for (auto *image : standins) { @@ -715,6 +717,8 @@ bool vulkan14_renderer::Render() vk14::barriers(ctx.commands, uses); // no light is assigned to any cluster until the culling pass runs ctx.commands->fill_buffer(scene.clusters.ranges.handle(), 0, VK_WHOLE_SIZE, 0); + // nor does the cab light itself until its probe is drawn + ctx.commands->fill_buffer(scene.cabprobe_bounce.handle(), 0, VK_WHOLE_SIZE, 0); VkMemoryBarrier2 barrier{}; barrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER_2; barrier.srcStageMask = VK_PIPELINE_STAGE_2_CLEAR_BIT; @@ -1500,6 +1504,8 @@ void vulkan14_renderer::render_viewport(std::size_t const Index) render_lamp_shadows(); render_headlight_shadows(); ready_lamp_maps(); + // the probe of the cab, which reads the map of the cab lamp + render_cab_probe(); } // the scenery around the camera the reflections are traced through, before the passes which trace them auto const raytracing{raytracing_wanted(vp)}; @@ -1557,6 +1563,11 @@ void vulkan14_renderer::render_viewport(std::size_t const Index) upload_weather_block(); } + // the ambient occlusion the shading may read (SCENE_AO_TEX) is in the layout it's read in from the first frame on + if (targets.ssao_blur && (targets.ssao_blur->state.layout == VK_IMAGE_LAYOUT_UNDEFINED)) + { + vk14::barriers(commands, {vk14::use_of(*targets.ssao_blur, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, sampled_stage, sampled_access)}); + } // the colour of the scene: drawn to the images with the samples, resolved to the single ones auto const uses{colour_target_uses(targets)}; ctx.passes.begin_pass("sky", uses); @@ -1579,11 +1590,25 @@ void vulkan14_renderer::render_viewport(std::size_t const Index) setup_shadow_matrices(); upload_scene_block(); + // the probe of the cab, which the parts of the cab drawn in the colour pass of the main view read + if (vp.main) + { + scene.cabprobe.setup_scene(scene_ubs, cab_probe_vehicle(), m_colorpass.pass_camera.position(), glm::mat3{m_colorpass.pass_camera.modelview()}); + m_cabprobelit = (scene_ubs.cab_probe_box.w > 0.f); + if (m_cabprobelit) + { + upload_scene_block(); + } + } + // the depth of the whole opaque scene before its colour, which the contact shadows need m_indirectcolourpending = false; + m_aolighting = false; auto const contactshadows{contact_shadows_wanted(vp)}; auto const reflections{m_ssrframe && (false == Global.bWireFrame)}; - if ((Global.gfx_depthprepass_scene || contactshadows || reflections) && vp.main && (false == Global.bWireFrame)) + // and the ambient occlusion in the light of the surfaces + auto const aolighting{ao_lighting_wanted(vp)}; + if ((Global.gfx_depthprepass_scene || contactshadows || reflections || aolighting) && vp.main && (false == Global.bWireFrame)) { render_scene_depth(Index, future); if (contactshadows) @@ -1594,6 +1619,10 @@ void vulkan14_renderer::render_viewport(std::size_t const Index) { setup_screen_reflections(Index, taa); } + if (m_aolighting) + { + setup_ao_lighting(); + } } // opaque parts... @@ -1668,14 +1697,17 @@ void vulkan14_renderer::render_viewport(std::size_t const Index) } commands->end_rendering(); end_draws(); - if (m_contactshadows || m_ssractive) + if (m_contactshadows || m_ssractive || m_aolighting || m_cabprobelit) { - // the depth they read is of this view only + // the depth they read is of this view only, and so is the probe of the cab m_contactshadows = false; m_ssractive = false; + m_cabprobelit = false; scene_ubs.contact_shadow_params = glm::vec4{0.f}; scene_ubs.contact_shadow_lamps = glm::vec4{0.f}; + scene_ubs.ao_params = glm::vec4{0.f}; gfx::screen_reflections::clear(scene_ubs); + gfx::cab_probe::clear_scene(scene_ubs); } // the images resolved and drawn are read by the post-processing m_colorpass.draw_stats = m_renderpass.draw_stats; @@ -1698,8 +1730,10 @@ 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}; - // 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}; + // the depth copied apart from the one the colour pass goes on with, for the contact shadows, the screen space reflections and the + // ambient occlusion in the light of the surfaces + auto const aolighting{ao_lighting_wanted(m_viewports[Index])}; + auto const contact{contact_shadows_wanted(m_viewports[Index]) || m_ssrframe || aolighting}; commands->end_rendering(); // what the draws go on with auto const set{draw.set}; @@ -1794,6 +1828,11 @@ void vulkan14_renderer::render_scene_depth(std::size_t const Index, glm::mat4 co copy.pRegions = ®ion; commands->copy_image2(©); vk14::barriers(commands, {vk14::use_of(*targets.scene_depth, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, sampled_stage, sampled_access)}); + // the ambient occlusion of the depth, between the renderings (compute) + if (aolighting) + { + m_aolighting = compute_ao_lighting(Index); + } } // the colour pass goes on where the sky left it vk14::barriers(commands, colour_target_uses(targets)); @@ -3478,8 +3517,9 @@ void vulkan14_renderer::render_post(std::size_t const Index, bool const Motionbl auto const commands{ctx.commands}; // the occlusion goes into the image before the blur, which smears both alike. the targets are there only when it was on as they - // were made - auto const aomode{(targets.ssao != nullptr) ? Global.gfx_postfx_ao : 0}; + // were made. in the light of the surfaces it's in the image already + auto const aomode{((targets.ssao != nullptr) && (false == m_aolighting)) ? Global.gfx_postfx_ao : 0}; + m_aolighting = false; if (aomode != 0) { ctx.passes.begin_pass("ambient occlusion"); diff --git a/rendering/vulkan14/vulkan14renderer.h b/rendering/vulkan14/vulkan14renderer.h index c02010c6..06c2fc01 100644 --- a/rendering/vulkan14/vulkan14renderer.h +++ b/rendering/vulkan14/vulkan14renderer.h @@ -389,6 +389,19 @@ class vulkan14_renderer : public gfx_renderer, public gfx::scene_walk bool init_ao(std::string &Reason); void destroy_ao(); void render_ao(std::size_t const Viewport, int const Mode); + // the ambient occlusion in the light of the surfaces (gfx.postfx.ao.lighting, the stage A3): whether the colour pass of the + // viewport takes it, the occlusion worked out from the depth of the scene drawn before the colour (outside a rendering; false: it + // couldn't be), and the block of the scene set up for the colour pass to read it + bool ao_lighting_wanted(viewport_config const &Viewport) const; + bool compute_ao_lighting(std::size_t const Viewport); + void setup_ao_lighting(); + bool m_aolighting{false}; + // the probe of the occupied cab (vulkan14cabprobe.cpp, the stage A4; opengl46_renderer::Render_cab_probe()): the resources, the + // occupied vehicle whose cab is drawn (nullptr: none), the drawing of the probe when it needs it in this frame + bool init_cab_probe(std::string &Reason); + void destroy_cab_probe(); + TDynamicObject *cab_probe_vehicle() const; + void render_cab_probe(); // the particles of the gpu: what the colour pass under way draws them with, and the depth of the scene drawn so far copied for // the soft particles (false: they aren't soft) gfx::gpu_particles::view particles_view() const; diff --git a/rendering/vulkan14/vulkan14scene.cpp b/rendering/vulkan14/vulkan14scene.cpp index 8193d5ae..aaad743b 100644 --- a/rendering/vulkan14/vulkan14scene.cpp +++ b/rendering/vulkan14/vulkan14scene.cpp @@ -151,6 +151,7 @@ VkDescriptorType pass_binding_type(std::uint32_t const Binding) case CLUSTER_INDEX_SSBO: case SKY_SSBO: case WATER_SSBO: + case CAB_PROBE_SSBO: return VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; case ATMOSPHERE_UBO: case WEATHER_HORIZON_UBO: @@ -193,6 +194,14 @@ std::array, 3> pass_ 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()); + // the ambient occlusion of the main view the shading takes into the ambient light (the stage A3) + views[SCENE_AO_TEX] = (colour && (false == scene.targets.empty()) && scene.targets.front().ssao_blur ? scene.targets.front().ssao_blur->view() : scene.white->view()); + // the probe of the cab, once it was drawn (the stage A4) + auto const *probelight{scene.cabprobe.light_map()}; + auto const *probealbedo{scene.cabprobe.albedo_map()}; + auto const probeempty{scene.cabprobe_empty ? scene.cabprobe_empty->view() : scene.env_empty->view()}; + views[CAB_PROBE_TEX] = (colour && (probelight != nullptr) ? probelight->view() : probeempty); + views[CAB_PROBE_ALBEDO_TEX] = (colour && (probealbedo != nullptr) ? probealbedo->view() : probeempty); } return result; } @@ -650,7 +659,7 @@ bool vulkan14_renderer::init_scene(std::string &Reason) } // the light of the sky, the exposure, the weather - if ((false == init_sky(Reason)) || (false == init_weather(Reason)) || (false == init_ao(Reason))) + if ((false == init_sky(Reason)) || (false == init_weather(Reason)) || (false == init_ao(Reason)) || (false == init_cab_probe(Reason))) { return false; } @@ -822,6 +831,7 @@ void vulkan14_renderer::destroy_scene() destroy_sky(); destroy_weather(); destroy_ao(); + destroy_cab_probe(); for (auto *pool : scene.occlusion_pools) { vkDestroyQueryPool(device, pool, nullptr); @@ -968,6 +978,7 @@ void vulkan14_renderer::write_pass_sets(vk14::instance_cull *Cull) VkDescriptorBufferInfo const weatherblock{ctx.ring.handle(), 0, offsetof(gl::weather_ubs, fog_horizon)}; VkDescriptorBufferInfo const horizonblock{scene.weather_horizon.handle(), 0, sizeof(glm::vec4) * gl::FOG_HORIZON_SAMPLES}; VkDescriptorBufferInfo const water{scene.water.storage(), 0, VK_WHOLE_SIZE}; + VkDescriptorBufferInfo const cabprobe{scene.cabprobe_bounce.handle(), 0, sizeof(gl::cab_probe_data)}; VkDescriptorImageInfo const headlightsinfo{scene.headlights_sampler, scene.headlights_view, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL}; std::vector writes; std::vector> images; @@ -1017,6 +1028,10 @@ void vulkan14_renderer::write_pass_sets(vk14::instance_cull *Cull) 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])); + write(handle, SCENE_AO_TEX, nullptr, image(ctx.sampling.clamp_nearest, views[SCENE_AO_TEX])); + write(handle, CAB_PROBE_TEX, nullptr, image(ctx.sampling.clamp_linear, views[CAB_PROBE_TEX])); + write(handle, CAB_PROBE_ALBEDO_TEX, nullptr, image(ctx.sampling.clamp_linear, views[CAB_PROBE_ALBEDO_TEX])); + write(handle, CAB_PROBE_SSBO, &cabprobe, nullptr); } vkUpdateDescriptorSets(device, static_cast(writes.size()), writes.data(), 0, nullptr); } @@ -1403,7 +1418,7 @@ void vulkan14_renderer::walk_material(material_handle const Material, TSubModel // materials which declare texture variants provide a different texture set per model instance auto const &materialtextures{material.texture_variants.empty() ? material.textures : material.GetTextures(TSubModel::VariantId(Submodel))}; auto &draw{*m_draw}; - if (draw.depthpass) + if (draw.depthpass || draw.probepass) { // the depth of the whole scene: the programs of the depth prepass. what they can't draw (blended, materials of other programs) // is left to the colour pass @@ -1418,6 +1433,27 @@ void vulkan14_renderer::walk_material(material_handle const Material, TSubModel return; } auto &scene{m_context->scene}; + if (draw.probepass) + { + // the probe of the cab: the programs of the probe, of the same materials, the albedo from the diffuse texture (the white + // stand-in without it), alpha tested as the depth prepass tests it + if (prepassclass == 2) + { + if (materialtextures[0] == null_handle) + { + return; + } + auto &texture{m_context->textures->mark_as_used(materialtextures[0])}; + if ((false == texture.create()) || (m_context->images.find(texture.get_id()) == nullptr)) + { + return; + } + } + walk_texture(0, materialtextures[0]); + draw.program = (prepassclass == 2 ? scene.cabprobe_alpha_program.get() : scene.cabprobe_program.get()); + draw.depthskip = (draw.program == nullptr); + return; + } if (prepassclass == 2) { // a texture which isn't there (yet) would leave the one bound before to the alpha test @@ -1769,7 +1805,7 @@ void vulkan14_renderer::draw_chunk(gfx::geometry_handle const &Geometry, bool co } auto const &chunk{static_cast(m_banks[Geometry.bank - 1])->last()}; - if (draw.depthpass && (draw.depthskip || (topology_class(chunk.topology) != VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST))) + if ((draw.depthpass || draw.probepass) && (draw.depthskip || (topology_class(chunk.topology) != VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST))) { // the depth of the scene: what the programs of the prepass don't draw, and no points or lines return; diff --git a/rendering/vulkan14/vulkan14shaders.cpp b/rendering/vulkan14/vulkan14shaders.cpp index eb24cadf..97e36ba4 100644 --- a/rendering/vulkan14/vulkan14shaders.cpp +++ b/rendering/vulkan14/vulkan14shaders.cpp @@ -803,6 +803,10 @@ std::string binding_preamble(bool const Raytracing) define("SCENE_DEPTH_TEX", SCENE_DEPTH_TEX); define("SSR_HISTORY_TEX", SSR_HISTORY_TEX); define("WATER_SSBO", WATER_SSBO); + define("SCENE_AO_TEX", SCENE_AO_TEX); + define("CAB_PROBE_TEX", CAB_PROBE_TEX); + define("CAB_PROBE_ALBEDO_TEX", CAB_PROBE_ALBEDO_TEX); + define("CAB_PROBE_SSBO", CAB_PROBE_SSBO); define("PASS_BINDING_COUNT", PASS_BINDING_COUNT); define("MATERIAL_TEXTURE_FIRST", MATERIAL_TEXTURE_FIRST); define("CL_CLUSTER_UBO", CL_CLUSTER_UBO); @@ -873,6 +877,8 @@ std::string binding_preamble(bool const Raytracing) define("WATER_TRAIL", gl::WATER_TRAIL); define("WATER_MAP", gl::WATER_MAP); define("WATER_GROUP_SIZE", gl::WATER_GROUP_SIZE); + define("CAB_PROBE_SH_SIZE", static_cast(gl::CAB_PROBE_SH_SIZE)); + define("CAB_PROBE_SH_GROUP_SIZE", static_cast(gl::CAB_PROBE_SH_GROUP_SIZE)); return preamble; } diff --git a/shaders/ogl46/cabprobe.frag b/shaders/ogl46/cabprobe.frag new file mode 100644 index 00000000..3e9fdf9e --- /dev/null +++ b/shaders/ogl46/cabprobe.frag @@ -0,0 +1,20 @@ +#version 460 core +#define STAGE_FRAGMENT 1 +// the probe of the occupied cab (gfx::cab_probe), the materials without the alpha test (cabprobe.glsl) +layout(location = 0) in vec3 f_normal; +layout(location = 2) in vec2 f_coord; +layout(location = 3) in vec4 f_pos; + +#include "common.glsl" + +#pragma texture(diffuse, 0, sRGB_A) +layout(binding = 0) uniform sampler2D diffuse; +// the cube shadow map of the cab lamp, see gl::LOCAL_SHADOW_TEX +layout(binding = LOCAL_SHADOW_TEX) uniform samplerCubeArrayShadow probe_shadowmap; + +#include "cabprobe.glsl" + +void main() +{ + probe_shade(texture(diffuse, f_coord)); +} diff --git a/shaders/ogl46/cabprobe.geom b/shaders/ogl46/cabprobe.geom new file mode 100644 index 00000000..9cc70ad8 --- /dev/null +++ b/shaders/ogl46/cabprobe.geom @@ -0,0 +1,52 @@ +#version 460 core +#define STAGE_GEOMETRY 1 +// draws the cab into the six faces of its probe at once, one invocation per face (as shadowcube.geom draws the cube shadow maps of +// the lamps). the input is in the space of the probe; the faces go in the order of the faces of a cube map (+x, -x, +y, -y, +z, +// -z), each looks the way glm::lookAt() with the usual up vectors of cube maps sets it, with a 90 degree projection of infinite +// reach and reversed depth like the one of the scene: the depth is near / d, d the distance along the axis of the face +layout(triangles, invocations = 6) in; +layout(triangle_strip, max_vertices = 3) out; + +layout(location = 20) in vec3 g_pos[]; +layout(location = 21) in vec3 g_normal[]; +layout(location = 18) in vec2 g_coord[]; +layout(location = 0) out vec3 f_normal; +layout(location = 2) out vec2 f_coord; +layout(location = 3) out vec4 f_pos; + +// near plane of the probe, metres +const float PROBE_NEAR = 0.05; + +// right, up and forward axes of the faces +const vec3 face_right[6] = vec3[](vec3(0.0, 0.0, -1.0), vec3(0.0, 0.0, 1.0), vec3(1.0, 0.0, 0.0), vec3(1.0, 0.0, 0.0), vec3(1.0, 0.0, 0.0), vec3(-1.0, 0.0, 0.0)); +const vec3 face_up[6] = vec3[](vec3(0.0, -1.0, 0.0), vec3(0.0, -1.0, 0.0), vec3(0.0, 0.0, 1.0), vec3(0.0, 0.0, -1.0), vec3(0.0, -1.0, 0.0), vec3(0.0, -1.0, 0.0)); +const vec3 face_forward[6] = vec3[](vec3(1.0, 0.0, 0.0), vec3(-1.0, 0.0, 0.0), vec3(0.0, 1.0, 0.0), vec3(0.0, -1.0, 0.0), vec3(0.0, 0.0, 1.0), vec3(0.0, 0.0, -1.0)); + +void main() +{ + int face = gl_InvocationID; + vec4 clip[3]; + for (int i = 0; i < 3; i++) + clip[i] = vec4(dot(g_pos[i], face_right[face]), dot(g_pos[i], face_up[face]), PROBE_NEAR, dot(g_pos[i], face_forward[face])); + // triangles wholly outside of the face are not sent further + if ((clip[0].w < PROBE_NEAR) && (clip[1].w < PROBE_NEAR) && (clip[2].w < PROBE_NEAR)) + return; + if ((clip[0].x > clip[0].w) && (clip[1].x > clip[1].w) && (clip[2].x > clip[2].w)) + return; + if ((clip[0].x < -clip[0].w) && (clip[1].x < -clip[1].w) && (clip[2].x < -clip[2].w)) + return; + if ((clip[0].y > clip[0].w) && (clip[1].y > clip[1].w) && (clip[2].y > clip[2].w)) + return; + if ((clip[0].y < -clip[0].w) && (clip[1].y < -clip[1].w) && (clip[2].y < -clip[2].w)) + return; + for (int i = 0; i < 3; i++) + { + gl_Layer = face; + gl_Position = clip[i]; + f_normal = g_normal[i]; + f_coord = g_coord[i]; + f_pos = vec4(g_pos[i], 1.0); + EmitVertex(); + } + EndPrimitive(); +} diff --git a/shaders/ogl46/cabprobe.glsl b/shaders/ogl46/cabprobe.glsl new file mode 100644 index 00000000..284510af --- /dev/null +++ b/shaders/ogl46/cabprobe.glsl @@ -0,0 +1,61 @@ +// the shading of the probe of the occupied cab (gfx::cab_probe), for cabprobe.frag and cabprobe_alpha.frag: what the cab shows +// towards the middle of its box lit by the cab lamp alone, with the cube shadow map of the lamp (and what glows in it, the lights +// of the desk), and its albedo, each with 1 in the alpha: the probe is cleared to nothing, so the alpha of a direction tells how +// much of it the cab fills, and both are premultiplied by it. the light of the cab lamp is the one the shading gives it +// (calc_local_light() in light_common.glsl); the ambient light and the sun are left out, the cab gets those as before. the +// parameters of the lamp are in the block of the scene (gl::scene_ubs::cab_probe_lamp), in the space of the probe + +layout(location = 0) out vec4 out_light; +layout(location = 1) out vec4 out_albedo; + +// closest distance (squared) the light of the lamp is computed for, as LOCAL_LIGHT_MIN_DISTANCE2 of light_common.glsl +const float PROBE_LIGHT_MIN_DISTANCE2 = 0.01; + +// the diffuse colour of the submodel or shape, as material_diffuse() of light_common.glsl +vec3 probe_material_diffuse() +{ + vec3 diffusecolor = pow(max(param[0].rgb, vec3(0.0)), vec3(2.2)); + float value = max(param[0].r, max(param[0].g, param[0].b)); + return (value > 0.0 ? diffusecolor * (param[1].x / value) : vec3(0.0)); +} + +// share of the light of the cab lamp held back before the point of the space of its cube shadow map (origin at the lamp), 0..1, as +// local_light_shadow() of light_common.glsl takes it with a single sample +float probe_lamp_shadow(vec3 position, vec3 normal) +{ + vec4 weights = cab_probe_lamp_shadow; + float distance = length(position); + float texel = weights.z * distance; + vec3 tolamp = -position / max(distance, 1e-4); + float NdotL = clamp(dot(normal, tolamp), 0.0, 1.0); + position += normal * (texel * (0.5 + 1.5 * (1.0 - NdotL))); + vec3 axes = abs(position); + float major = max(axes.x, max(axes.y, axes.z)); + float reference = weights.y / max(major - texel * 1.5, weights.y); + return texture(probe_shadowmap, vec4(position, weights.x), reference); +} + +void probe_shade(vec4 tex_color) +{ + vec3 albedo = tex_color.rgb * probe_material_diffuse(); + vec3 position = f_pos.xyz; + vec3 normal = normalize(f_normal); + // the walls are drawn from both sides: the side the probe sees + if (dot(normal, position) > 0.0) + normal = -normal; + vec3 light = vec3(emission); + if (cab_probe_lamp.w > 0.0) + { + vec3 tolight = cab_probe_lamp.xyz - position; + float distance2 = dot(tolight, tolight); + vec3 light_dir = tolight * inversesqrt(max(distance2, 1e-8)); + float edge = clamp(1.0 - distance2 * distance2 * (cab_probe_lamp.w * cab_probe_lamp.w), 0.0, 1.0); + float atten = edge * edge / max(distance2, PROBE_LIGHT_MIN_DISTANCE2); + float NdotL = max(dot(normal, light_dir), 0.0); + if ((atten > 0.0) && (NdotL > 0.0) && (cab_probe_lamp_color.a > 0.5)) + atten *= 1.0 - probe_lamp_shadow(-tolight, normal); + light += cab_probe_lamp_color.rgb * (NdotL * atten); + } + out_light = vec4(albedo * light, 1.0); + out_albedo = vec4(albedo, 1.0); +} diff --git a/shaders/ogl46/cabprobe.vert b/shaders/ogl46/cabprobe.vert new file mode 100644 index 00000000..39d9a16e --- /dev/null +++ b/shaders/ogl46/cabprobe.vert @@ -0,0 +1,21 @@ +#version 460 core +#define STAGE_VERTEX 1 +// the occupied cab drawn into the six faces of its probe at once (gfx::cab_probe, cabprobe.geom picks the faces). the modelview +// takes the cab model to the space of the probe: the axes of the model, the origin in the middle of the box of the cab +layout(location = 0) in vec3 v_vert; +layout(location = 1) in vec3 v_normal; +layout(location = 2) in vec2 v_coord; + +layout(location = 20) out vec3 g_pos; +layout(location = 21) out vec3 g_normal; +layout(location = 18) out vec2 g_coord; + +#include "common.glsl" + +void main() +{ + g_pos = (effective_modelview() * vec4(v_vert, 1.0)).xyz; + g_normal = effective_modelviewnormal() * v_normal; + g_coord = v_coord; + gl_Position = vec4(g_pos, 1.0); +} diff --git a/shaders/ogl46/cabprobe_alpha.frag b/shaders/ogl46/cabprobe_alpha.frag new file mode 100644 index 00000000..283c9edb --- /dev/null +++ b/shaders/ogl46/cabprobe_alpha.frag @@ -0,0 +1,24 @@ +#version 460 core +#define STAGE_FRAGMENT 1 +// the probe of the occupied cab (gfx::cab_probe), the materials whose colour shader tests the alpha of the diffuse texture against +// the opacity of the material, with the test of depthprepass_alpha.frag (cabprobe.glsl) +layout(location = 0) in vec3 f_normal; +layout(location = 2) in vec2 f_coord; +layout(location = 3) in vec4 f_pos; + +#include "common.glsl" + +#pragma texture(diffuse, 0, sRGB_A) +layout(binding = 0) uniform sampler2D diffuse; +// the cube shadow map of the cab lamp, see gl::LOCAL_SHADOW_TEX +layout(binding = LOCAL_SHADOW_TEX) uniform samplerCubeArrayShadow probe_shadowmap; + +#include "cabprobe.glsl" + +void main() +{ + vec4 tex_color = texture(diffuse, f_coord); + if (tex_color.a < opacity + 1.0 / 255.0) + discard; + probe_shade(tex_color); +} diff --git a/shaders/ogl46/cabprobe_sh.comp b/shaders/ogl46/cabprobe_sh.comp new file mode 100644 index 00000000..100f817d --- /dev/null +++ b/shaders/ogl46/cabprobe_sh.comp @@ -0,0 +1,93 @@ +#version 460 core +#define STAGE_COMPUTE 1 +// the light the occupied cab lights itself with again, from its probe (gfx::cab_probe), in one work group: the light of its lamps the +// cab reflects towards the middle of its box, as spherical harmonics of bands 0..2, convolved with the cosine lobe and divided by +// pi, so their value for a direction is the light a white lambertian surface facing it gets, in the units of the ambient light +// (light_common.glsl, cab_probe_bounce()). the light is taken from a level of the probe of at most CAB_PROBE_SH_SIZE pixels a +// face, each pixel weighed by its solid angle. the basis is the one of the sky (gl::sky_data) + +#include "common.glsl" + +layout(local_size_x = CAB_PROBE_SH_GROUP_SIZE) in; + +layout(binding = 0) uniform samplerCube probe_light; + +layout(std430, binding = CAB_PROBE_SSBO_BINDING) writeonly buffer cab_probe_result_ssbo +{ + vec4 result_sh[9]; +}; + +// x: the level of the probe read, y: its size, pixels +layout(location = 0) uniform ivec2 level; + +shared vec3 partial[CAB_PROBE_SH_GROUP_SIZE][9]; +shared float partialweight[CAB_PROBE_SH_GROUP_SIZE]; + +// the direction through the middle of the pixel (s, t) of the face, -1..1 across it, in the order of the faces of a cube map +vec3 face_direction(int face, float s, float t) +{ + switch (face) + { + case 0: return vec3(1.0, -t, -s); + case 1: return vec3(-1.0, -t, s); + case 2: return vec3(s, 1.0, t); + case 3: return vec3(s, -1.0, -t); + case 4: return vec3(s, -t, 1.0); + default: return vec3(-s, -t, -1.0); + } +} + +void main() +{ + uint invocation = gl_LocalInvocationID.x; + int size = max(level.y, 1); + int texels = 6 * size * size; + vec3 sum[9]; + for (int i = 0; i < 9; ++i) + sum[i] = vec3(0.0); + float weights = 0.0; + for (int index = int(invocation); index < texels; index += CAB_PROBE_SH_GROUP_SIZE) + { + int face = index / (size * size); + int texel = index - face * size * size; + float s = (float(texel % size) + 0.5) / float(size) * 2.0 - 1.0; + float t = (float(texel / size) + 0.5) / float(size) * 2.0 - 1.0; + vec3 direction = face_direction(face, s, t); + // the solid angle of the pixel + float lengthsquared = dot(direction, direction); + float weight = 1.0 / (lengthsquared * sqrt(lengthsquared)); + vec3 n = direction * inversesqrt(lengthsquared); + vec3 light = textureLod(probe_light, n, float(level.x)).rgb * weight; + sum[0] += light * 0.282095; + sum[1] += light * (0.488603 * n.y); + sum[2] += light * (0.488603 * n.z); + sum[3] += light * (0.488603 * n.x); + sum[4] += light * (1.092548 * n.x * n.y); + sum[5] += light * (1.092548 * n.y * n.z); + sum[6] += light * (0.315392 * (3.0 * n.z * n.z - 1.0)); + sum[7] += light * (1.092548 * n.x * n.z); + sum[8] += light * (0.546274 * (n.x * n.x - n.y * n.y)); + weights += weight; + } + for (int i = 0; i < 9; ++i) + partial[invocation][i] = sum[i]; + partialweight[invocation] = weights; + barrier(); + for (uint stride = CAB_PROBE_SH_GROUP_SIZE / 2; stride > 0U; stride /= 2U) + { + if (invocation < stride) + { + for (int i = 0; i < 9; ++i) + partial[invocation][i] += partial[invocation + stride][i]; + partialweight[invocation] += partialweight[invocation + stride]; + } + barrier(); + } + if (invocation < 9U) + { + // the weights add up to the whole sphere, 4 pi; the cosine lobe over pi: 1 for band 0, 2/3 for band 1, 1/4 for band 2 + float sphere = 12.566371 / max(partialweight[0], 1e-6); + float band = (invocation == 0U) ? 1.0 : (invocation < 4U) ? (2.0 / 3.0) : 0.25; + result_sh[invocation] = vec4(partial[0][invocation] * (sphere * band), 0.0); + } +} diff --git a/shaders/ogl46/common.glsl b/shaders/ogl46/common.glsl index b30a0873..98cdfbbe 100644 --- a/shaders/ogl46/common.glsl +++ b/shaders/ogl46/common.glsl @@ -7,7 +7,7 @@ // the locations of the inputs and outputs between the stages, the same for a name in every shader (SPIR-V links the stages by // the locations, not by the names): 0 f_normal, 1 f_normal_raw, 2 f_coord, 3 f_pos, 4 f_clip_pos, 5 f_clip_future_pos, // 6 TangentFragPos, 7 UserData, 8-10 f_tbn, 11 f_model_draw, 12 f_albedo, 13 f_color, 14 f_coords, 15 f_local, 16 f_soft, -// 17 f_streak, 18 g_coord, 19 g_coords, 20 g_pos +// 17 f_streak, 18 g_coord, 19 g_coords, 20 g_pos, 21 g_normal #ifndef COMMON_GLSL #define COMMON_GLSL @@ -106,6 +106,12 @@ #define CLOUD_HISTORY_TEX 22 #define SCENE_DEPTH_TEX 23 #define SSR_HISTORY_TEX 24 +#define SCENE_AO_TEX 26 +#define CAB_PROBE_TEX 27 +#define CAB_PROBE_ALBEDO_TEX 28 +#define CAB_PROBE_SSBO_BINDING 17 +#define CAB_PROBE_SH_SIZE 16 +#define CAB_PROBE_SH_GROUP_SIZE 64 #define WEATHER_UBO_BINDING 5 #define WEATHER_SCRATCH_SSBO_BINDING 9 #define MAX_CLOUD_LAYERS 4 @@ -239,6 +245,8 @@ struct model_draw_s float fog_density; float alpha_mult; float shadow_tone; + // 1 for the parts of the occupied cab, see gl::model_ubs + float cab_interior; }; layout(std430, binding = MODEL_DRAW_SSBO_BINDING) readonly buffer model_draw_ssbo @@ -265,6 +273,7 @@ layout(location = 11) flat in uint f_model_draw; #define fog_density model_draws[MODEL_DRAW].fog_density #define alpha_mult model_draws[MODEL_DRAW].alpha_mult #define shadow_tone model_draws[MODEL_DRAW].shadow_tone +#define cab_interior model_draws[MODEL_DRAW].cab_interior #else layout(std140, binding = MODEL_UBO_BINDING) uniform model_ubo { @@ -278,6 +287,8 @@ layout(std140, binding = MODEL_UBO_BINDING) uniform model_ubo float fog_density; float alpha_mult; float shadow_tone; + // 1 for the parts of the occupied cab, see gl::model_ubs + float cab_interior; }; #endif @@ -309,6 +320,15 @@ layout(std140, binding = SCENE_UBO_BINDING) uniform scene_ubo vec4 env_params; // the air against the vehicle of the camera, see gl::scene_ubs vec4 rain_wind; + // the ambient occlusion in the light of the surfaces, see gl::scene_ubs + vec4 ao_params; + // the probe of the occupied cab, and the lamp it's drawn with, see gl::scene_ubs + mat4 cab_probe_view; + vec4 cab_probe_box; + vec4 cab_probe_params; + vec4 cab_probe_lamp; + vec4 cab_probe_lamp_color; + vec4 cab_probe_lamp_shadow; }; #ifdef STAGE_VERTEX diff --git a/shaders/ogl46/light_common.glsl b/shaders/ogl46/light_common.glsl index cbae4a0f..ebd83acc 100644 --- a/shaders/ogl46/light_common.glsl +++ b/shaders/ogl46/light_common.glsl @@ -8,6 +8,17 @@ layout(binding = LOCAL_SHADOW_LAMPS_TEX) uniform samplerCubeArrayShadow local_sh layout(binding = HEADLIGHT_SHADOW_TEX) uniform sampler2DArrayShadow headlight_shadowmap; // depth of the opaque scene drawn before the colour, for the contact shadows (gl::scene_ubs::contact_shadow_params) layout(binding = SCENE_DEPTH_TEX) uniform sampler2D scene_depth; +// the ambient occlusion of the main view, half its size, for the ambient light (gl::scene_ubs::ao_params) +layout(binding = SCENE_AO_TEX) uniform sampler2D scene_ao; +// the probe of the occupied cab (gl::scene_ubs::cab_probe_box, gfx::cab_probe): the light of its lamps the cab reflects and its +// albedo, each premultiplied by the share of the direction the cab fills, which is in their alpha, and the light the cab lights +// itself with again (gl::cab_probe_data) +layout(binding = CAB_PROBE_TEX) uniform samplerCube cab_probe_light; +layout(binding = CAB_PROBE_ALBEDO_TEX) uniform samplerCube cab_probe_albedo; +layout(std430, binding = CAB_PROBE_SSBO_BINDING) readonly buffer cab_probe_ssbo +{ + vec4 cab_probe_sh[9]; +}; #include "envmapping.glsl" #include "ssr.glsl" @@ -45,6 +56,10 @@ float length2(vec3 v) return dot(v, v); } +// the largest difference of the distance of a texel of the ambient occlusion from the one of the fragment (a share of it) the +// texel is taken to be of the surface of the fragment at +const float AO_SURFACE_MATCH = 0.05; + // how many more lamps and headlights the fragment traces contact shadows to, see add_headlights() int contact_shadow_budget = 0; @@ -707,6 +722,103 @@ vec4 reflection_scene(vec3 fragnormal, float roughness) return result; } +// the ambient occlusion of the view in the ambient light of the fragment, 0..1 (1: none): the occlusion of the main view worked out +// from the depth of the opaque scene drawn before its colour (gfx.postfx.ao.lighting), half its size. the four nearest texels are +// weighed by how close they are and by how close their distance is to the one of the fragment (as in postfx_ssao_apply.frag); where +// none of them is of the surface of the fragment (what the depth before the colour doesn't hold: the blended surfaces, the grass, +// the cab drawn after the precipitation, the edges of objects) there's no occlusion. the distance is the one along the axis of the +// view, near / depth with the far plane at infinity +float ambient_occlusion() +{ + if (ao_params.x <= 0.0) + return 1.0; + ivec2 full_size = textureSize(scene_depth, 0); + ivec2 half_size = textureSize(scene_ao, 0); + ivec2 pixel = ivec2(gl_FragCoord.xy); + float distance = max(-f_pos.z, 1e-3); + vec2 position = vec2(pixel) * 0.5; + ivec2 base = ivec2(floor(position)); + vec2 fraction = position - vec2(base); + float sum = 0.0; + float weights = 0.0; + float closest = 1e30; + for (int y = 0; y < 2; ++y) + { + for (int x = 0; x < 2; ++x) + { + ivec2 texel = clamp(base + ivec2(x, y), ivec2(0), half_size - 1); + float depth = texelFetch(scene_depth, min(texel * 2, full_size - 1), 0).r; + float difference = (depth > 0.0) ? abs(projection[3][2] / depth - distance) / distance : 1e30; + closest = min(closest, difference); + float bilinear = (x == 0 ? 1.0 - fraction.x : fraction.x) * (y == 0 ? 1.0 - fraction.y : fraction.y); + float weight = (bilinear + 1e-3) / (1e-3 + difference); + sum += texelFetch(scene_ao, texel, 0).r * weight; + weights += weight; + } + } + if (closest > AO_SURFACE_MATCH) + return 1.0; + return pow(clamp(sum / max(weights, 1e-6), 0.0, 1.0), ao_params.y); +} + +// the light which comes back after more than one bounce off surfaces of the given albedo, for the occlusion of the ambient light +// (Jimenez et al. 2016), for each channel +vec3 ao_multi_bounce(float visibility, vec3 albedo) +{ + vec3 a = 2.0404 * albedo - 0.3324; + vec3 b = -4.7951 * albedo + 0.6417; + vec3 c = 2.7552 * albedo + 0.6903; + return max(vec3(visibility), ((visibility * a + b) * visibility + c) * visibility); +} + +// the share of the reflection of the surroundings the occlusion leaves to a surface of the perceptual roughness seen at the angle +// NdotV (Lagarde and de Rousiers 2014) +float ao_specular(float visibility, float NdotV, float roughness) +{ + return clamp(pow(NdotV + visibility, exp2(-16.0 * roughness - 1.0)) - 1.0 + visibility, 0.0, 1.0); +} + +// whether the fragment is a part of the occupied cab with its probe (gl::scene_ubs::cab_probe_box, gl::model_ubs::cab_interior) +bool cab_probe_active() +{ + return (cab_probe_box.w > 0.0) && (cab_interior > 0.5); +} + +// the light of the lamps the cab reflects which reaches a surface of the cab facing the view space Fragnormal, in the units of the +// ambient light: the spherical harmonics cabprobe_sh.comp worked out from the probe, in the space of the probe +vec3 cab_probe_bounce(vec3 fragnormal) +{ + vec3 n = normalize(mat3(cab_probe_view) * fragnormal); + vec3 light = cab_probe_sh[0].rgb * 0.282095 + + cab_probe_sh[1].rgb * (0.488603 * n.y) + + cab_probe_sh[2].rgb * (0.488603 * n.z) + + cab_probe_sh[3].rgb * (0.488603 * n.x) + + cab_probe_sh[4].rgb * (1.092548 * n.x * n.y) + + cab_probe_sh[5].rgb * (1.092548 * n.y * n.z) + + cab_probe_sh[6].rgb * (0.315392 * (3.0 * n.z * n.z - 1.0)) + + cab_probe_sh[7].rgb * (1.092548 * n.x * n.z) + + cab_probe_sh[8].rgb * (0.546274 * (n.x * n.x - n.y * n.y)); + return max(light, vec3(0.0)) * cab_probe_params.x; +} + +// the reflection of the cab in a surface of the cab of the perceptual roughness, over the reflection of the surroundings, where the +// probe holds the cab: the reflected ray from the fragment to the box of the cab, looked up from the middle of the box, where the +// probe was drawn from (the box stands for the walls of the cab). the cab shows the light of its lamps it reflects (the probe) and +// its albedo lit by the ambient light of the fragment (Ambientlight), the light the cab gets from the outside +vec3 cab_probe_reflection(vec3 surroundings, vec3 fragnormal, float roughness, vec3 ambientlight) +{ + vec3 position = (cab_probe_view * vec4(f_pos.xyz, 1.0)).xyz; + vec3 direction = normalize(mat3(cab_probe_view) * reflect(normalize(f_pos.xyz), fragnormal)); + vec3 invdir = (step(0.0, direction) * 2.0 - 1.0) / max(abs(direction), vec3(1e-5)); + vec3 far = max((cab_probe_box.xyz - position) * invdir, (-cab_probe_box.xyz - position) * invdir); + float reach = max(min(min(far.x, far.y), far.z), 0.0); + vec3 lookup = position + direction * reach; + float level = roughness * cab_probe_params.z; + vec4 light = textureLod(cab_probe_light, lookup, level); + vec4 albedo = textureLod(cab_probe_albedo, lookup, level); + return surroundings * (1.0 - albedo.a) + light.rgb + albedo.rgb * ambientlight; +} + // 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 @@ -761,6 +873,23 @@ vec3 shade_surface(vec3 fragcolor, vec3 fragnormal, vec3 texturecolor, float ref spec_f0 = f0; spec_roughness = roughness; + // the light of the lamps the cab reflects, which lights the cab again (the probe of the cab), joins the ambient light + bool cabprobe = cab_probe_active(); + vec3 cabambient = fragcolor; + if (cabprobe) + fragcolor += cab_probe_bounce(fragnormal); + // the ambient occlusion of the view holds back the ambient light of the diffuse layer (with the light coming back from more + // bounces off surfaces of the albedo) and the reflection of the surroundings; not the light of the sun and the lamps, nor the + // reflection of the scene, which the rays find as it is + float occlusion = ambient_occlusion(); + vec3 diffuse_occlusion = vec3(1.0); + float specular_occlusion = 1.0; + if (occlusion < 1.0) + { + diffuse_occlusion = ao_multi_bounce(occlusion, texturecolor); + specular_occlusion = ao_specular(occlusion, NdotV, roughness); + } + // Pre-integrated env BRDF: how much of the surroundings the surface reflects, for its F0, roughness and the view angle. // the surroundings are the cubemap where the material asks for it (reflectivity) and the ambient light elsewhere, so a // surface reflects them always, the rough one barely @@ -791,15 +920,18 @@ vec3 shade_surface(vec3 fragcolor, vec3 fragnormal, vec3 texturecolor, float ref if (!matte) { envcolor = reflection_surroundings(fragnormal, env_roughness); + // inside the cab the cab is what the surroundings are, where the probe holds it + if (cabprobe && (cab_probe_params.y > 0.0)) + envcolor = cab_probe_reflection(envcolor, fragnormal, env_roughness, cabambient); 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 + return fragcolor * diffuse_occlusion * diffuse_albedo + emissioncolor * texturecolor - + fragcolor * env_spec * ambient_share - + (scenecolor.rgb + envcolor * (1.0 - scenecolor.a)) * cubemap_spec * cubemap_share; + + fragcolor * env_spec * ambient_share * specular_occlusion + + (scenecolor.rgb + envcolor * ((1.0 - scenecolor.a) * specular_occlusion)) * cubemap_spec * cubemap_share; vec3 light_dir = normalize(-lights[0].dir); float NdotL = max(dot(fragnormal, light_dir), 0.0); @@ -830,8 +962,10 @@ vec3 shade_surface(vec3 fragcolor, vec3 fragnormal, vec3 texturecolor, float ref cubemap_shadow = 1.0 - (1.0 - shadowtone) * shadow * 0.5; // 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 - + (scenecolor.rgb + envcolor * ((1.0 - scenecolor.a) * cubemap_shadow)) * cubemap_spec * cubemap_share; + vec3 env_reflection = fragcolor * env_spec * ambient_share * specular_occlusion + + (scenecolor.rgb + envcolor * ((1.0 - scenecolor.a) * cubemap_shadow * specular_occlusion)) * cubemap_spec * cubemap_share; + // the occlusion of the ambient light, before the light of the sun and the lamps joins it + fragcolor *= diffuse_occlusion; // 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/cabprobe.frag b/shaders/vk14/cabprobe.frag new file mode 100644 index 00000000..cd86b5ce --- /dev/null +++ b/shaders/vk14/cabprobe.frag @@ -0,0 +1,19 @@ +#version 460 core +#define STAGE_FRAGMENT 1 +#include "common.glsl" +// the probe of the occupied cab (gfx::cab_probe), the materials without the alpha test (cabprobe.glsl) +layout(location = 0) in vec3 f_normal; +layout(location = 2) in vec2 f_coord; +layout(location = 3) in vec4 f_pos; + +#pragma texture(diffuse, 0, sRGB_A) +layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 0) uniform sampler2D diffuse; +// the cube shadow map of the cab lamp +layout(set = PASS_SET, binding = LOCAL_SHADOW_TEX) uniform samplerCubeArrayShadow probe_shadowmap; + +#include "cabprobe.glsl" + +void main() +{ + probe_shade(texture(diffuse, f_coord)); +} diff --git a/shaders/vk14/cabprobe.glsl b/shaders/vk14/cabprobe.glsl new file mode 100644 index 00000000..dae2699b --- /dev/null +++ b/shaders/vk14/cabprobe.glsl @@ -0,0 +1,61 @@ +// the shading of the probe of the occupied cab (gfx::cab_probe, as shaders/ogl46/cabprobe.glsl), for cabprobe.frag and cabprobe_alpha.frag: what the cab shows +// towards the middle of its box lit by the cab lamp alone, with the cube shadow map of the lamp (and what glows in it, the lights +// of the desk), and its albedo, each with 1 in the alpha: the probe is cleared to nothing, so the alpha of a direction tells how +// much of it the cab fills, and both are premultiplied by it. the light of the cab lamp is the one the shading gives it +// (calc_local_light() in light_common.glsl); the ambient light and the sun are left out, the cab gets those as before. the +// parameters of the lamp are in the block of the scene (gl::scene_ubs::cab_probe_lamp), in the space of the probe + +layout(location = 0) out vec4 out_light; +layout(location = 1) out vec4 out_albedo; + +// closest distance (squared) the light of the lamp is computed for, as LOCAL_LIGHT_MIN_DISTANCE2 of light_common.glsl +const float PROBE_LIGHT_MIN_DISTANCE2 = 0.01; + +// the diffuse colour of the submodel or shape, as material_diffuse() of light_common.glsl +vec3 probe_material_diffuse() +{ + vec3 diffusecolor = pow(max(param[0].rgb, vec3(0.0)), vec3(2.2)); + float value = max(param[0].r, max(param[0].g, param[0].b)); + return (value > 0.0 ? diffusecolor * (param[1].x / value) : vec3(0.0)); +} + +// share of the light of the cab lamp held back before the point of the space of its cube shadow map (origin at the lamp), 0..1, as +// local_light_shadow() of light_common.glsl takes it with a single sample +float probe_lamp_shadow(vec3 position, vec3 normal) +{ + vec4 weights = cab_probe_lamp_shadow; + float distance = length(position); + float texel = weights.z * distance; + vec3 tolamp = -position / max(distance, 1e-4); + float NdotL = clamp(dot(normal, tolamp), 0.0, 1.0); + position += normal * (texel * (0.5 + 1.5 * (1.0 - NdotL))); + vec3 axes = abs(position); + float major = max(axes.x, max(axes.y, axes.z)); + float reference = weights.y / max(major - texel * 1.5, weights.y); + return texture(probe_shadowmap, vec4(position, weights.x), reference); +} + +void probe_shade(vec4 tex_color) +{ + vec3 albedo = tex_color.rgb * probe_material_diffuse(); + vec3 position = f_pos.xyz; + vec3 normal = normalize(f_normal); + // the walls are drawn from both sides: the side the probe sees + if (dot(normal, position) > 0.0) + normal = -normal; + vec3 light = vec3(emission); + if (cab_probe_lamp.w > 0.0) + { + vec3 tolight = cab_probe_lamp.xyz - position; + float distance2 = dot(tolight, tolight); + vec3 light_dir = tolight * inversesqrt(max(distance2, 1e-8)); + float edge = clamp(1.0 - distance2 * distance2 * (cab_probe_lamp.w * cab_probe_lamp.w), 0.0, 1.0); + float atten = edge * edge / max(distance2, PROBE_LIGHT_MIN_DISTANCE2); + float NdotL = max(dot(normal, light_dir), 0.0); + if ((atten > 0.0) && (NdotL > 0.0) && (cab_probe_lamp_color.a > 0.5)) + atten *= 1.0 - probe_lamp_shadow(-tolight, normal); + light += cab_probe_lamp_color.rgb * (NdotL * atten); + } + out_light = vec4(albedo * light, 1.0); + out_albedo = vec4(albedo, 1.0); +} diff --git a/shaders/vk14/cabprobe.vert b/shaders/vk14/cabprobe.vert new file mode 100644 index 00000000..40c47778 --- /dev/null +++ b/shaders/vk14/cabprobe.vert @@ -0,0 +1,38 @@ +#version 460 core +#extension GL_EXT_multiview : require +#define STAGE_VERTEX 1 +// the occupied cab drawn into the six faces of its probe at once (gfx::cab_probe), a view of the multiview rendering per face (the +// geometry shader cabprobe.geom of the opengl renderer). the modelview takes the cab model to the space of the probe: the axes of +// the model, the origin in the middle of the box of the cab. the faces go in the order of the faces of a cube map (+x, -x, +y, -y, +// +z, -z), each looks the way glm::lookAt() with the usual up vectors of cube maps sets it, with a 90 degree projection of infinite +// reach and reversed depth like the one of the scene: the depth is near / d, d the distance along the axis of the face +#include "common.glsl" +layout(location = 0) in vec3 v_vert; +layout(location = 1) in vec3 v_normal; +layout(location = 2) in vec2 v_coord; + +layout(location = 0) out vec3 f_normal; +layout(location = 2) out vec2 f_coord; +layout(location = 3) out vec4 f_pos; + +// near plane of the probe, metres +const float PROBE_NEAR = 0.05; + +// right, up and forward axes of the faces +const vec3 face_right[6] = vec3[](vec3(0.0, 0.0, -1.0), vec3(0.0, 0.0, 1.0), vec3(1.0, 0.0, 0.0), vec3(1.0, 0.0, 0.0), vec3(1.0, 0.0, 0.0), vec3(-1.0, 0.0, 0.0)); +const vec3 face_up[6] = vec3[](vec3(0.0, -1.0, 0.0), vec3(0.0, -1.0, 0.0), vec3(0.0, 0.0, 1.0), vec3(0.0, 0.0, -1.0), vec3(0.0, -1.0, 0.0), vec3(0.0, -1.0, 0.0)); +const vec3 face_forward[6] = vec3[](vec3(1.0, 0.0, 0.0), vec3(-1.0, 0.0, 0.0), vec3(0.0, 1.0, 0.0), vec3(0.0, -1.0, 0.0), vec3(0.0, 0.0, 1.0), vec3(0.0, 0.0, -1.0)); + +void main() +{ + vec3 position = (effective_modelview() * vec4(v_vert, 1.0)).xyz; + int face = int(gl_ViewIndex); + gl_Position = vec4(dot(position, face_right[face]), dot(position, face_up[face]), PROBE_NEAR, dot(position, face_forward[face])); + f_normal = effective_modelviewnormal() * v_normal; + f_coord = v_coord; + f_pos = vec4(position, 1.0); +#ifdef MODEL_DRAWS + // which block of the multi draw the fragments read (common.glsl) + f_model_draw = MODEL_DRAW; +#endif +} diff --git a/shaders/vk14/cabprobe_alpha.frag b/shaders/vk14/cabprobe_alpha.frag new file mode 100644 index 00000000..0ab78275 --- /dev/null +++ b/shaders/vk14/cabprobe_alpha.frag @@ -0,0 +1,23 @@ +#version 460 core +#define STAGE_FRAGMENT 1 +#include "common.glsl" +// the probe of the occupied cab (gfx::cab_probe), the materials whose colour shader tests the alpha of the diffuse texture against +// the opacity of the material, with the test of depthprepass_alpha.frag (cabprobe.glsl) +layout(location = 0) in vec3 f_normal; +layout(location = 2) in vec2 f_coord; +layout(location = 3) in vec4 f_pos; + +#pragma texture(diffuse, 0, sRGB_A) +layout(set = MATERIAL_SET, binding = MATERIAL_TEXTURE_FIRST + 0) uniform sampler2D diffuse; +// the cube shadow map of the cab lamp +layout(set = PASS_SET, binding = LOCAL_SHADOW_TEX) uniform samplerCubeArrayShadow probe_shadowmap; + +#include "cabprobe.glsl" + +void main() +{ + vec4 tex_color = texture(diffuse, f_coord); + if (tex_color.a < opacity + 1.0 / 255.0) + discard; + probe_shade(tex_color); +} diff --git a/shaders/vk14/cabprobe_sh.comp b/shaders/vk14/cabprobe_sh.comp new file mode 100644 index 00000000..03035563 --- /dev/null +++ b/shaders/vk14/cabprobe_sh.comp @@ -0,0 +1,96 @@ +#version 460 core +#define STAGE_COMPUTE 1 +#define LAYOUT_COMPUTE 1 +#include "common.glsl" +// the light the occupied cab lights itself with again, from its probe (gfx::cab_probe), in one work group: the light of its lamps the +// cab reflects towards the middle of its box, as spherical harmonics of bands 0..2, convolved with the cosine lobe and divided by +// pi, so their value for a direction is the light a white lambertian surface facing it gets, in the units of the ambient light +// (light_common.glsl, cab_probe_bounce()). the light is taken from a level of the probe of at most CAB_PROBE_SH_SIZE pixels a +// face, each pixel weighed by its solid angle. the basis is the one of the sky (gl::sky_data) + +layout(local_size_x = CAB_PROBE_SH_GROUP_SIZE) in; + +layout(set = COMPUTE_SET, binding = 0) uniform samplerCube probe_light; + +layout(std430, set = COMPUTE_SET, binding = 1) writeonly buffer cab_probe_result_ssbo +{ + vec4 result_sh[9]; +}; + +// the parameters of the kernel (push constants): x the level of the probe read, y its size, pixels +layout(push_constant) uniform kernel_constants +{ + ivec2 level; +}; + +shared vec3 partial[CAB_PROBE_SH_GROUP_SIZE][9]; +shared float partialweight[CAB_PROBE_SH_GROUP_SIZE]; + +// the direction through the middle of the pixel (s, t) of the face, -1..1 across it, in the order of the faces of a cube map +vec3 face_direction(int face, float s, float t) +{ + switch (face) + { + case 0: return vec3(1.0, -t, -s); + case 1: return vec3(-1.0, -t, s); + case 2: return vec3(s, 1.0, t); + case 3: return vec3(s, -1.0, -t); + case 4: return vec3(s, -t, 1.0); + default: return vec3(-s, -t, -1.0); + } +} + +void main() +{ + uint invocation = gl_LocalInvocationID.x; + int size = max(level.y, 1); + int texels = 6 * size * size; + vec3 sum[9]; + for (int i = 0; i < 9; ++i) + sum[i] = vec3(0.0); + float weights = 0.0; + for (int index = int(invocation); index < texels; index += CAB_PROBE_SH_GROUP_SIZE) + { + int face = index / (size * size); + int texel = index - face * size * size; + float s = (float(texel % size) + 0.5) / float(size) * 2.0 - 1.0; + float t = (float(texel / size) + 0.5) / float(size) * 2.0 - 1.0; + vec3 direction = face_direction(face, s, t); + // the solid angle of the pixel + float lengthsquared = dot(direction, direction); + float weight = 1.0 / (lengthsquared * sqrt(lengthsquared)); + vec3 n = direction * inversesqrt(lengthsquared); + vec3 light = textureLod(probe_light, n, float(level.x)).rgb * weight; + sum[0] += light * 0.282095; + sum[1] += light * (0.488603 * n.y); + sum[2] += light * (0.488603 * n.z); + sum[3] += light * (0.488603 * n.x); + sum[4] += light * (1.092548 * n.x * n.y); + sum[5] += light * (1.092548 * n.y * n.z); + sum[6] += light * (0.315392 * (3.0 * n.z * n.z - 1.0)); + sum[7] += light * (1.092548 * n.x * n.z); + sum[8] += light * (0.546274 * (n.x * n.x - n.y * n.y)); + weights += weight; + } + for (int i = 0; i < 9; ++i) + partial[invocation][i] = sum[i]; + partialweight[invocation] = weights; + barrier(); + for (uint stride = CAB_PROBE_SH_GROUP_SIZE / 2; stride > 0U; stride /= 2U) + { + if (invocation < stride) + { + for (int i = 0; i < 9; ++i) + partial[invocation][i] += partial[invocation + stride][i]; + partialweight[invocation] += partialweight[invocation + stride]; + } + barrier(); + } + if (invocation < 9U) + { + // the weights add up to the whole sphere, 4 pi; the cosine lobe over pi: 1 for band 0, 2/3 for band 1, 1/4 for band 2 + float sphere = 12.566371 / max(partialweight[0], 1e-6); + float band = (invocation == 0U) ? 1.0 : (invocation < 4U) ? (2.0 / 3.0) : 0.25; + result_sh[invocation] = vec4(partial[0][invocation] * (sphere * band), 0.0); + } +} diff --git a/shaders/vk14/common.glsl b/shaders/vk14/common.glsl index 0fc5ae11..ebbdadc9 100644 --- a/shaders/vk14/common.glsl +++ b/shaders/vk14/common.glsl @@ -61,7 +61,11 @@ #define SCENE_DEPTH_TEX 24 #define SSR_HISTORY_TEX 25 #define WATER_SSBO 26 -#define PASS_BINDING_COUNT 27 +#define SCENE_AO_TEX 27 +#define CAB_PROBE_TEX 28 +#define CAB_PROBE_ALBEDO_TEX 29 +#define CAB_PROBE_SSBO 30 +#define PASS_BINDING_COUNT 31 #define MATERIAL_TEXTURE_FIRST 0 #define CL_CLUSTER_UBO 0 #define CL_AABB_SSBO 1 @@ -129,6 +133,8 @@ #define WATER_TRAIL 1024 #define WATER_MAP 256 #define WATER_GROUP_SIZE 64 +#define CAB_PROBE_SH_SIZE 16 +#define CAB_PROBE_SH_GROUP_SIZE 64 #endif #include "settings.glsl" @@ -281,6 +287,15 @@ layout(std140, set = PASS_SET, binding = SCENE_UBO) uniform scene_ubo vec4 env_params; // the air against the vehicle of the camera, see gl::scene_ubs vec4 rain_wind; + // the ambient occlusion in the light of the surfaces, see gl::scene_ubs + vec4 ao_params; + // the probe of the occupied cab, and the lamp it's drawn with, see gl::scene_ubs + mat4 cab_probe_view; + vec4 cab_probe_box; + vec4 cab_probe_params; + vec4 cab_probe_lamp; + vec4 cab_probe_lamp_color; + vec4 cab_probe_lamp_shadow; }; #ifndef LAYOUT_COMPUTE @@ -298,6 +313,8 @@ struct model_draw_s float fog_density; float alpha_mult; float shadow_tone; + // 1 for the parts of the occupied cab, see gl::model_ubs + float cab_interior; }; layout(std430, set = PASS_SET, binding = DRAW_SSBO) readonly buffer model_draw_ssbo @@ -331,6 +348,7 @@ layout(location = 11) flat in uint f_model_draw; #define fog_density model_draws[MODEL_DRAW].fog_density #define alpha_mult model_draws[MODEL_DRAW].alpha_mult #define shadow_tone model_draws[MODEL_DRAW].shadow_tone +#define cab_interior model_draws[MODEL_DRAW].cab_interior // per-instance modelview matrices of the instanced draws, element 0 the identity. a draw which isn't instanced is drawn with // firstInstance 0, an instanced one from its base instance on, with modelview set to the submodel-local chain (computed from diff --git a/shaders/vk14/light_common.glsl b/shaders/vk14/light_common.glsl index fc4b189d..5f1dc464 100644 --- a/shaders/vk14/light_common.glsl +++ b/shaders/vk14/light_common.glsl @@ -12,6 +12,17 @@ layout(set = PASS_SET, binding = LOCAL_SHADOW_LAMPS_TEX) uniform samplerCubeArra layout(set = PASS_SET, binding = HEADLIGHT_SHADOW_TEX) uniform sampler2DArrayShadow headlight_shadowmap; // depth of the opaque scene drawn before the colour, for the contact shadows (gl::scene_ubs::contact_shadow_params) layout(set = PASS_SET, binding = SCENE_DEPTH_TEX) uniform sampler2D scene_depth; +// the ambient occlusion of the main view, half its size, for the ambient light (gl::scene_ubs::ao_params) +layout(set = PASS_SET, binding = SCENE_AO_TEX) uniform sampler2D scene_ao; +// the probe of the occupied cab (gl::scene_ubs::cab_probe_box, gfx::cab_probe): the light of its lamps the cab reflects and its +// albedo, each premultiplied by the share of the direction the cab fills, which is in their alpha, and the light the cab lights +// itself with again (gl::cab_probe_data) +layout(set = PASS_SET, binding = CAB_PROBE_TEX) uniform samplerCube cab_probe_light; +layout(set = PASS_SET, binding = CAB_PROBE_ALBEDO_TEX) uniform samplerCube cab_probe_albedo; +layout(std430, set = PASS_SET, binding = CAB_PROBE_SSBO) readonly buffer cab_probe_ssbo +{ + vec4 cab_probe_sh[9]; +}; #include "envmapping.glsl" #include "ssr.glsl" @@ -49,6 +60,10 @@ float length2(vec3 v) return dot(v, v); } +// the largest difference of the distance of a texel of the ambient occlusion from the one of the fragment (a share of it) the +// texel is taken to be of the surface of the fragment at +const float AO_SURFACE_MATCH = 0.05; + // how many more lamps and headlights the fragment traces contact shadows to, see add_headlights() int contact_shadow_budget = 0; @@ -720,6 +735,103 @@ vec4 reflection_scene(vec3 fragnormal, float roughness, float weight) return result; } +// the ambient occlusion of the view in the ambient light of the fragment, 0..1 (1: none): the occlusion of the main view worked out +// from the depth of the opaque scene drawn before its colour (gfx.postfx.ao.lighting), half its size. the four nearest texels are +// weighed by how close they are and by how close their distance is to the one of the fragment (as in postfx_ssao_apply.frag); where +// none of them is of the surface of the fragment (what the depth before the colour doesn't hold: the blended surfaces, the grass, +// the cab drawn after the precipitation, the edges of objects) there's no occlusion. the distance is the one along the axis of the +// view, near / depth with the far plane at infinity +float ambient_occlusion() +{ + if (ao_params.x <= 0.0) + return 1.0; + ivec2 full_size = textureSize(scene_depth, 0); + ivec2 half_size = textureSize(scene_ao, 0); + ivec2 pixel = ivec2(gl_FragCoord.xy); + float distance = max(-f_pos.z, 1e-3); + vec2 position = vec2(pixel) * 0.5; + ivec2 base = ivec2(floor(position)); + vec2 fraction = position - vec2(base); + float sum = 0.0; + float weights = 0.0; + float closest = 1e30; + for (int y = 0; y < 2; ++y) + { + for (int x = 0; x < 2; ++x) + { + ivec2 texel = clamp(base + ivec2(x, y), ivec2(0), half_size - 1); + float depth = texelFetch(scene_depth, min(texel * 2, full_size - 1), 0).r; + float difference = (depth > 0.0) ? abs(projection[3][2] / depth - distance) / distance : 1e30; + closest = min(closest, difference); + float bilinear = (x == 0 ? 1.0 - fraction.x : fraction.x) * (y == 0 ? 1.0 - fraction.y : fraction.y); + float weight = (bilinear + 1e-3) / (1e-3 + difference); + sum += texelFetch(scene_ao, texel, 0).r * weight; + weights += weight; + } + } + if (closest > AO_SURFACE_MATCH) + return 1.0; + return pow(clamp(sum / max(weights, 1e-6), 0.0, 1.0), ao_params.y); +} + +// the light which comes back after more than one bounce off surfaces of the given albedo, for the occlusion of the ambient light +// (Jimenez et al. 2016), for each channel +vec3 ao_multi_bounce(float visibility, vec3 albedo) +{ + vec3 a = 2.0404 * albedo - 0.3324; + vec3 b = -4.7951 * albedo + 0.6417; + vec3 c = 2.7552 * albedo + 0.6903; + return max(vec3(visibility), ((visibility * a + b) * visibility + c) * visibility); +} + +// the share of the reflection of the surroundings the occlusion leaves to a surface of the perceptual roughness seen at the angle +// NdotV (Lagarde and de Rousiers 2014) +float ao_specular(float visibility, float NdotV, float roughness) +{ + return clamp(pow(NdotV + visibility, exp2(-16.0 * roughness - 1.0)) - 1.0 + visibility, 0.0, 1.0); +} + +// whether the fragment is a part of the occupied cab with its probe (gl::scene_ubs::cab_probe_box, gl::model_ubs::cab_interior) +bool cab_probe_active() +{ + return (cab_probe_box.w > 0.0) && (cab_interior > 0.5); +} + +// the light of the lamps the cab reflects which reaches a surface of the cab facing the view space Fragnormal, in the units of the +// ambient light: the spherical harmonics cabprobe_sh.comp worked out from the probe, in the space of the probe +vec3 cab_probe_bounce(vec3 fragnormal) +{ + vec3 n = normalize(mat3(cab_probe_view) * fragnormal); + vec3 light = cab_probe_sh[0].rgb * 0.282095 + + cab_probe_sh[1].rgb * (0.488603 * n.y) + + cab_probe_sh[2].rgb * (0.488603 * n.z) + + cab_probe_sh[3].rgb * (0.488603 * n.x) + + cab_probe_sh[4].rgb * (1.092548 * n.x * n.y) + + cab_probe_sh[5].rgb * (1.092548 * n.y * n.z) + + cab_probe_sh[6].rgb * (0.315392 * (3.0 * n.z * n.z - 1.0)) + + cab_probe_sh[7].rgb * (1.092548 * n.x * n.z) + + cab_probe_sh[8].rgb * (0.546274 * (n.x * n.x - n.y * n.y)); + return max(light, vec3(0.0)) * cab_probe_params.x; +} + +// the reflection of the cab in a surface of the cab of the perceptual roughness, over the reflection of the surroundings, where the +// probe holds the cab: the reflected ray from the fragment to the box of the cab, looked up from the middle of the box, where the +// probe was drawn from (the box stands for the walls of the cab). the cab shows the light of its lamps it reflects (the probe) and +// its albedo lit by the ambient light of the fragment (Ambientlight), the light the cab gets from the outside +vec3 cab_probe_reflection(vec3 surroundings, vec3 fragnormal, float roughness, vec3 ambientlight) +{ + vec3 position = (cab_probe_view * vec4(f_pos.xyz, 1.0)).xyz; + vec3 direction = normalize(mat3(cab_probe_view) * reflect(normalize(f_pos.xyz), fragnormal)); + vec3 invdir = (step(0.0, direction) * 2.0 - 1.0) / max(abs(direction), vec3(1e-5)); + vec3 far = max((cab_probe_box.xyz - position) * invdir, (-cab_probe_box.xyz - position) * invdir); + float reach = max(min(min(far.x, far.y), far.z), 0.0); + vec3 lookup = position + direction * reach; + float level = roughness * cab_probe_params.z; + vec4 light = textureLod(cab_probe_light, lookup, level); + vec4 albedo = textureLod(cab_probe_albedo, lookup, level); + return surroundings * (1.0 - albedo.a) + light.rgb + albedo.rgb * ambientlight; +} + // 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 @@ -774,6 +886,23 @@ vec3 shade_surface(vec3 fragcolor, vec3 fragnormal, vec3 texturecolor, float ref spec_f0 = f0; spec_roughness = roughness; + // the light of the lamps the cab reflects, which lights the cab again (the probe of the cab), joins the ambient light + bool cabprobe = cab_probe_active(); + vec3 cabambient = fragcolor; + if (cabprobe) + fragcolor += cab_probe_bounce(fragnormal); + // the ambient occlusion of the view holds back the ambient light of the diffuse layer (with the light coming back from more + // bounces off surfaces of the albedo) and the reflection of the surroundings; not the light of the sun and the lamps, nor the + // reflection of the scene, which the rays find as it is + float occlusion = ambient_occlusion(); + vec3 diffuse_occlusion = vec3(1.0); + float specular_occlusion = 1.0; + if (occlusion < 1.0) + { + diffuse_occlusion = ao_multi_bounce(occlusion, texturecolor); + specular_occlusion = ao_specular(occlusion, NdotV, roughness); + } + // Pre-integrated env BRDF: how much of the surroundings the surface reflects, for its F0, roughness and the view angle. // the surroundings are the cubemap where the material asks for it (reflectivity) and the ambient light elsewhere, so a // surface reflects them always, the rough one barely @@ -804,16 +933,19 @@ vec3 shade_surface(vec3 fragcolor, vec3 fragnormal, vec3 texturecolor, float ref if (!matte) { envcolor = reflection_surroundings(fragnormal, env_roughness); + // inside the cab the cab is what the surroundings are, where the probe holds it + if (cabprobe && (cab_probe_params.y > 0.0)) + envcolor = cab_probe_reflection(envcolor, fragnormal, env_roughness, cabambient); float sceneweight = cubemap_share * dot(cubemap_spec, vec3(1.0 / 3.0)); if (((ssr_params.x > 0.0) || (ssr_params2.z > 0.0)) && (sceneweight > REFLECTION_SCENE_WEIGHT)) scenecolor = reflection_scene(fragnormal, env_roughness, sceneweight); } if (lights_count == 0U) - return fragcolor * diffuse_albedo + return fragcolor * diffuse_occlusion * diffuse_albedo + emissioncolor * texturecolor - + fragcolor * env_spec * ambient_share - + (scenecolor.rgb + envcolor * (1.0 - scenecolor.a)) * cubemap_spec * cubemap_share; + + fragcolor * env_spec * ambient_share * specular_occlusion + + (scenecolor.rgb + envcolor * ((1.0 - scenecolor.a) * specular_occlusion)) * cubemap_spec * cubemap_share; vec3 light_dir = normalize(-lights[0].dir); float NdotL = max(dot(fragnormal, light_dir), 0.0); @@ -844,8 +976,10 @@ vec3 shade_surface(vec3 fragcolor, vec3 fragnormal, vec3 texturecolor, float ref cubemap_shadow = 1.0 - (1.0 - shadowtone) * shadow * 0.5; // 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 - + (scenecolor.rgb + envcolor * ((1.0 - scenecolor.a) * cubemap_shadow)) * cubemap_spec * cubemap_share; + vec3 env_reflection = fragcolor * env_spec * ambient_share * specular_occlusion + + (scenecolor.rgb + envcolor * ((1.0 - scenecolor.a) * cubemap_shadow * specular_occlusion)) * cubemap_spec * cubemap_share; + // the occlusion of the ambient light, before the light of the sun and the lamps joins it + fragcolor *= diffuse_occlusion; // 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/utilities/Globals.cpp b/utilities/Globals.cpp index 0958959c..841b2ec3 100644 --- a/utilities/Globals.cpp +++ b/utilities/Globals.cpp @@ -1893,6 +1893,7 @@ global_settings::ConfigParse_gfx_sky( cParser &Parser, std::string_view const To Parser >> gfx_postfx_ao_slices >> gfx_postfx_ao_steps; gfx_postfx_ao_slices = std::clamp( gfx_postfx_ao_slices, 1, 8 ); gfx_postfx_ao_steps = std::clamp( gfx_postfx_ao_steps, 1, 16 ); } + else if( Token == "gfx.postfx.ao.lighting" ) { readswitch( gfx_postfx_ao_lighting ); } else if( Token == "gfx.lights.scenery" ) { readswitch( gfx_lights_scenery ); } else if( Token == "gfx.lights.vehicles" ) { readswitch( gfx_lights_vehicles ); } else if( Token == "gfx.lights.headlights" ) { readswitch( gfx_lights_headlights ); } @@ -1901,6 +1902,22 @@ global_settings::ConfigParse_gfx_sky( cParser &Parser, std::string_view const To else if( Token == "gfx.lights.compartment.intensity" ) { readvalue( gfx_lights_compartment_intensity, 0.f, 100.f ); } else if( Token == "gfx.lights.cab.intensity" ) { readvalue( gfx_lights_cab_intensity, 0.f, 100.f ); } else if( Token == "gfx.lights.cab.bounce" ) { readvalue( gfx_lights_cab_bounce, 0.f, 1.f ); } + else if( Token == "gfx.cabprobe" ) { + // no/low/medium/high, or the number of the level + Parser.getTokens( 1 ); + std::string level; + Parser >> level; + gfx_cabprobe = + ( level == "low" || level == "1" ) ? 1 : + ( level == "medium" || level == "yes" || level == "true" || level == "2" ) ? 2 : + ( level == "high" || level == "3" ) ? 3 : + 0; } + else if( Token == "gfx.cabprobe.interval" ) { + Parser.getTokens( 1 ); + Parser >> gfx_cabprobe_interval; + gfx_cabprobe_interval = std::clamp( gfx_cabprobe_interval, 1, 120 ); } + else if( Token == "gfx.cabprobe.bounce" ) { readvalue( gfx_cabprobe_bounce, 0.f, 4.f ); } + else if( Token == "gfx.cabprobe.reflections" ) { readswitch( gfx_cabprobe_reflections ); } else if( Token == "gfx.lights.range" ) { readvalue( gfx_lights_range, 0.f, 2000.f ); } else if( Token == "gfx.lights.shadows" ) { // no/low/medium/high, or the number of the level @@ -2364,6 +2381,7 @@ global_settings::export_as_text( std::ostream &Output ) const { export_as_text( Output, "gfx.postfx.ao.strength", gfx_postfx_ao_strength ); export_as_text( Output, "gfx.postfx.ao.bounce", gfx_postfx_ao_bounce ); Output << "gfx.postfx.ao.quality " << gfx_postfx_ao_slices << " " << gfx_postfx_ao_steps << "\n"; + export_as_text( Output, "gfx.postfx.ao.lighting", gfx_postfx_ao_lighting ); export_as_text( Output, "gfx.lights.scenery", gfx_lights_scenery ); export_as_text( Output, "gfx.lights.vehicles", gfx_lights_vehicles ); export_as_text( Output, "gfx.lights.headlights", gfx_lights_headlights ); @@ -2372,6 +2390,10 @@ global_settings::export_as_text( std::ostream &Output ) const { export_as_text( Output, "gfx.lights.compartment.intensity", gfx_lights_compartment_intensity ); export_as_text( Output, "gfx.lights.cab.intensity", gfx_lights_cab_intensity ); export_as_text( Output, "gfx.lights.cab.bounce", gfx_lights_cab_bounce ); + export_as_text( Output, "gfx.cabprobe", std::string{ gfx_cabprobe == 3 ? "high" : gfx_cabprobe == 2 ? "medium" : gfx_cabprobe == 1 ? "low" : "no" } ); + export_as_text( Output, "gfx.cabprobe.interval", gfx_cabprobe_interval ); + export_as_text( Output, "gfx.cabprobe.bounce", gfx_cabprobe_bounce ); + export_as_text( Output, "gfx.cabprobe.reflections", gfx_cabprobe_reflections ); export_as_text( Output, "gfx.lights.range", gfx_lights_range ); export_as_text( Output, "gfx.lights.shadows", std::string{ gfx_lights_shadows == 3 ? "high" : gfx_lights_shadows == 2 ? "medium" : gfx_lights_shadows == 1 ? "low" : "no" } ); export_as_text( Output, "gfx.lights.shadows.lamps", gfx_lights_shadows_lamps ); diff --git a/utilities/Globals.h b/utilities/Globals.h index 694e62a1..d9ddef40 100644 --- a/utilities/Globals.h +++ b/utilities/Globals.h @@ -472,6 +472,10 @@ struct global_settings { float gfx_postfx_ao_bounce { 0.3f }; // albedo of the surfaces assumed for the light coming back after more bounces int gfx_postfx_ao_slices { 3 }; // directions searched around each pixel int gfx_postfx_ao_steps { 4 }; // samples on each side of a direction + // with gfx_postfx_ao 2 (opengl 4.6 and vulkan): the occlusion is worked out from the depth of the scene drawn before its colour + // (which it turns on, see gfx_depthprepass_scene) and darkens the ambient light and the reflection of the surroundings in the + // shading, not the light of the sun and the lamps. false: it darkens the finished image, as before + bool gfx_postfx_ao_lighting { true }; // local lights of the opengl 4.6 renderer, besides the sun and the vehicle headlights (debug window "Light clusters"). // they go through the light clusters, so they need gfx_lights_clustered bool gfx_lights_scenery { true }; // light points (freespot lights) of scenery models light their surroundings @@ -484,6 +488,15 @@ struct global_settings { float gfx_lights_compartment_intensity { 1.f }; // brightness of the light of a lit vehicle compartment float gfx_lights_cab_intensity { 1.5f }; // brightness of the cab lamp float gfx_lights_cab_bounce { 0.3f }; // share of the former ambient light of the cab lamp kept, for the light which bounces around the cab + // the probe of the occupied cab (opengl 4.6 and vulkan, gfx::cab_probe): cube maps of the cab lit by its lamp (and what glows in + // it) and of its albedo, drawn every few frames: the light of the lamp bounces around the cab from it (in place of + // gfx_lights_cab_bounce) and the cab reflects itself in its glasses and glossy parts. 0 off, 1 low, 2 medium, 3 high (faces of + // 64, 128, 256 pixels); the frames between its updates, the share of the reflected light which lights the cab again, and + // whether the cab reflects itself (where the screen space reflections don't find it) instead of the sky + int gfx_cabprobe { 2 }; + int gfx_cabprobe_interval { 4 }; + float gfx_cabprobe_bounce { 1.f }; + bool gfx_cabprobe_reflections { true }; float gfx_lights_range { 300.f }; // distance from the camera the lights of models and vehicles are taken from, metres // shadows of the lamps (cube shadow maps), 0 none, 1 low, 2 medium, 3 high; see opengl46_local_shadows. for now the lamp of the // occupied cab, its shadows cast by the cab