/* 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 "rendering/common/skyocclusion.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; m_masked = false; } bool cab_probe::update_wanted(TDynamicObject const *Vehicle, TModel3d const *Cab, glm::vec3 const &Center, glm::vec3 const &Extent, std::vector const &Lights, 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; m_masked = 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; m_masked = false; return false; } // the lights the probe is drawn with, together: one of them switched or dimmed changes it glm::vec3 lamp{0.f}; for (std::size_t idx = 0; idx < std::min(Lights.size(), gl::MAX_CAB_PROBE_LAMPS); ++idx) { lamp += Lights[idx].source.color; } 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)) { // the mask is of another cab m_valid = false; m_masked = false; } if (lampchanged) { m_valid = false; } // the moving parts of the cab, the lights of the desk and the sun are followed every few frames; a probe drawn without its mask // is drawn again at once if (m_valid && (false == m_settle) && (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, std::vector const &Lights, gfx::local_shadows const &Shadows, TDynamicObject const *Vehicle) const { for (std::size_t idx = 0; idx < gl::MAX_CAB_PROBE_LAMPS; ++idx) { if (idx >= Lights.size()) { // the end of the lights Scene.cab_probe_lamp[idx] = glm::vec4{0.f}; Scene.cab_probe_lamp_color[idx] = glm::vec4{0.f}; continue; } auto const &light{Lights[idx]}; // as the light buffer has it (Write_Local_Light()): the shading takes the light to end at 1 / sqrt(quadratic) Scene.cab_probe_lamp[idx] = glm::vec4{light.local - m_center, 1.f / sq(std::max(light.source.range, 0.01f))}; Scene.cab_probe_lamp_cone[idx] = glm::vec4{light.local_direction, light.source.cos_outer}; Scene.cab_probe_lamp_shape[idx] = glm::vec4{light.source.cos_inner, (light.source.soft_falloff ? -light.source.radius : light.source.radius), 0.f, 0.f}; // with the shadow map of the light, or of the closest light with one auto const slot{Shadows.cab_slot_of(Vehicle, light)}; Scene.cab_probe_lamp_color[idx] = glm::vec4{light.source.color, (slot >= 0 ? 1.f : 0.f)}; Scene.cab_probe_lamp_shadow[idx] = (slot >= 0 ? Shadows.weights(static_cast(slot)) : glm::vec4{0.f}); Scene.cab_probe_lamp_map[idx] = glm::vec4{(slot >= 0 ? Shadows.cab(static_cast(slot)).lamp : light.local) - m_center, 0.f}; } } void cab_probe::setup_sun(gl::scene_ubs &Scene, TDynamicObject const *Vehicle, glm::vec3 const &Tosun, glm::vec3 const &Sunlight, glm::dvec3 const &Camera) const { Scene.cab_probe_sun = glm::vec4{0.f}; Scene.cab_probe_sun_color = glm::vec4{0.f}; Scene.cab_probe_sun_box = glm::vec4{m_extent, 0.f}; Scene.cab_probe_sun_world = glm::vec4{0.f}; if ((Vehicle == nullptr) || (false == m_masked) || (Global.gfx_cabprobe_sun <= 0.f) || (glm::length(Tosun) <= 0.f)) return; auto const rotation{glm::dmat3{Vehicle->mMatrix}}; auto const middle{Vehicle->GetPosition() + rotation * glm::dvec3{m_center}}; // the scenery around the cab holds the sun back as it does in the shading (the vehicle's light level of the environment flags with // Global.gfx_tunnelflags, as walk_sunlight() takes it in Render_cab()) auto share{1.f}; if (Global.gfx_tunnelflags) { auto const shade{vehicle_shade(Vehicle)}; share = (shade > 0.f ? shade : 1.f); } else if (auto const *occlusion{sky_occlusion::current()}) { share = std::clamp(occlusion->sample(middle).y, 0.f, 1.f); } auto const light{Sunlight * (share * Global.gfx_cabprobe_sun)}; if (max_component(light) <= 0.f) return; auto const tosun{glm::normalize(glm::vec3{glm::inverse(rotation) * glm::dvec3{Tosun}})}; Scene.cab_probe_sun = glm::vec4{tosun, 1.f}; Scene.cab_probe_sun_color = glm::vec4{light, 0.f}; Scene.cab_probe_sun_world = glm::vec4{glm::vec3{middle - Camera}, 0.f}; } void cab_probe::begin() { m_cpustart = std::chrono::steady_clock::now(); gpu_begin(); } void cab_probe::end(std::uint32_t const Drawcalls) { gpu_end(); // the mask was copied from this drawing: a drawing without it is made again with it m_settle = (false == m_masked); m_masked = true; 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); } int cab_probe::mask_level() const { auto level{0}; while ((m_size >> level) > static_cast(gl::CAB_PROBE_MASK_SIZE)) ++level; return level; } int cab_probe::mask_size() const { return std::max(m_size >> mask_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::SliderFloat("sun through the windows (gfx.cabprobe.sun)", &Global.gfx_cabprobe_sun, 0.f, 4.f); ImGui::TextUnformatted(info().c_str()); } } // namespace gfx