mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-11-06 04:38:37 +01:00
Add sky light, physical atmosphere, auto exposure and bloom to the GL 4.6 renderer
This commit is contained in:
175
rendering/opengl46/opengl46exposure.cpp
Normal file
175
rendering/opengl46/opengl46exposure.cpp
Normal file
@@ -0,0 +1,175 @@
|
||||
/*
|
||||
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/opengl46exposure.h"
|
||||
|
||||
#include "model/Texture.h"
|
||||
#include "utilities/Globals.h"
|
||||
#include "utilities/utilities.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
// luminance range of the histogram, log2: 2^-12 .. 2^6. darker pixels count as black, brighter ones as the brightest
|
||||
float const HISTOGRAM_LOG_MIN{-12.f};
|
||||
float const HISTOGRAM_LOG_MAX{6.f};
|
||||
// the bloom chain goes down this many levels at most, and not below this size
|
||||
std::size_t const BLOOM_LEVELS{6};
|
||||
int const BLOOM_MIN_SIZE{8};
|
||||
// format of the levels: no alpha, no negative values, half the memory of rgba16f
|
||||
GLint const BLOOM_FORMAT{GL_R11F_G11F_B10F};
|
||||
// soft knee of the threshold, a fraction of it
|
||||
float const BLOOM_KNEE{0.5f};
|
||||
|
||||
} // namespace
|
||||
|
||||
void opengl46_exposure::init()
|
||||
{
|
||||
auto const make_compute = [](std::string const &Name) {
|
||||
gl::shader shader(Name);
|
||||
return std::make_unique<gl::program>(std::vector<std::reference_wrapper<const gl::shader>>{shader});
|
||||
};
|
||||
m_histogram_program = make_compute("exposure_histogram.comp");
|
||||
m_average_program = make_compute("exposure_average.comp");
|
||||
m_bloom_down = std::make_unique<gl::postfx>("bloom_down");
|
||||
m_bloom_up = std::make_unique<gl::postfx>("bloom_up");
|
||||
|
||||
m_exposure_ssbo = std::make_unique<gl::ssbo>(sizeof(gl::exposure_data), gl::EXPOSURE_SSBO_BINDING);
|
||||
gl::exposure_data data{};
|
||||
data.adapted_luminance = Global.gfx_postfx_autoexposure_key;
|
||||
data.exposure = 1.f;
|
||||
data.average_luminance = Global.gfx_postfx_autoexposure_key;
|
||||
data.valid = 0;
|
||||
m_exposure_ssbo->update(data);
|
||||
}
|
||||
|
||||
void opengl46_exposure::bind()
|
||||
{
|
||||
m_exposure_ssbo->bind_base();
|
||||
}
|
||||
|
||||
std::unique_ptr<opengl46_exposure::bloom_chain> opengl46_exposure::make_bloom_chain(int const Width, int const Height)
|
||||
{
|
||||
auto chain{std::make_unique<bloom_chain>()};
|
||||
glm::ivec2 size{Width, Height};
|
||||
for (std::size_t level = 0; level < BLOOM_LEVELS; ++level)
|
||||
{
|
||||
size = glm::max(size / 2, glm::ivec2{1});
|
||||
if (level > 0 && std::min(size.x, size.y) < BLOOM_MIN_SIZE)
|
||||
break;
|
||||
chain->sizes.push_back(size);
|
||||
auto &texture{chain->down.emplace_back(std::make_unique<opengl_texture>())};
|
||||
texture->alloc_rendertarget(BLOOM_FORMAT, GL_RGB, size.x, size.y, 1, 1, GL_CLAMP_TO_EDGE);
|
||||
auto &framebuffer{chain->down_fb.emplace_back(std::make_unique<gl::framebuffer>())};
|
||||
framebuffer->attach(*texture, GL_COLOR_ATTACHMENT0);
|
||||
if (!framebuffer->is_complete())
|
||||
return nullptr;
|
||||
}
|
||||
for (std::size_t level = 0; level + 1 < chain->sizes.size(); ++level)
|
||||
{
|
||||
auto const &size{chain->sizes[level]};
|
||||
auto &texture{chain->up.emplace_back(std::make_unique<opengl_texture>())};
|
||||
texture->alloc_rendertarget(BLOOM_FORMAT, GL_RGB, size.x, size.y, 1, 1, GL_CLAMP_TO_EDGE);
|
||||
auto &framebuffer{chain->up_fb.emplace_back(std::make_unique<gl::framebuffer>())};
|
||||
framebuffer->attach(*texture, GL_COLOR_ATTACHMENT0);
|
||||
if (!framebuffer->is_complete())
|
||||
return nullptr;
|
||||
}
|
||||
return chain->sizes.empty() ? nullptr : std::move(chain);
|
||||
}
|
||||
|
||||
void opengl46_exposure::measure(opengl_texture &Image, double const Deltatime)
|
||||
{
|
||||
float data[4];
|
||||
if (m_readback.pop(data))
|
||||
std::copy(std::begin(data), std::end(data), std::begin(m_readback_data));
|
||||
|
||||
m_exposure_timer.begin();
|
||||
m_exposure_ssbo->bind_base();
|
||||
Image.bind(0);
|
||||
auto const bins{static_cast<float>(gl::EXPOSURE_HISTOGRAM_BINS - 2)};
|
||||
glProgramUniform2f(*m_histogram_program, 0, HISTOGRAM_LOG_MIN, bins / (HISTOGRAM_LOG_MAX - HISTOGRAM_LOG_MIN));
|
||||
auto const groupsize{2 * static_cast<int>(gl::EXPOSURE_HISTOGRAM_GROUP_SIZE)};
|
||||
m_histogram_program->dispatch((Image.get_width() + groupsize - 1) / groupsize, (Image.get_height() + groupsize - 1) / groupsize);
|
||||
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
|
||||
|
||||
glProgramUniform2f(*m_average_program, 0, HISTOGRAM_LOG_MIN, (HISTOGRAM_LOG_MAX - HISTOGRAM_LOG_MIN) / bins);
|
||||
glProgramUniform4f(*m_average_program, 1,
|
||||
static_cast<float>(std::clamp(Deltatime, 0.0, 1.0)),
|
||||
Global.gfx_postfx_autoexposure_speed_dark,
|
||||
Global.gfx_postfx_autoexposure_speed_bright,
|
||||
Global.gfx_postfx_autoexposure_key);
|
||||
glProgramUniform2f(*m_average_program, 2, std::exp2(Global.gfx_postfx_autoexposure_min), std::exp2(Global.gfx_postfx_autoexposure_max));
|
||||
m_average_program->dispatch(1);
|
||||
// read by the tonemapping, and copied for the debug window
|
||||
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT | GL_BUFFER_UPDATE_BARRIER_BIT);
|
||||
m_exposure_timer.end();
|
||||
|
||||
m_readback.push(*m_exposure_ssbo, offsetof(gl::exposure_data, adapted_luminance));
|
||||
opengl_texture::reset_unit_cache();
|
||||
}
|
||||
|
||||
opengl_texture *opengl46_exposure::bloom(bloom_chain &Chain, opengl_texture &Image, gl::ubo &Modelubo, gl::model_ubs &Model)
|
||||
{
|
||||
m_bloom_timer.begin();
|
||||
// down, the first step from the image itself
|
||||
for (std::size_t level = 0; level < Chain.sizes.size(); ++level)
|
||||
{
|
||||
Model.param[0] = glm::vec4{(level == 0 ? 1.f : 0.f), Global.gfx_postfx_bloom_threshold, BLOOM_KNEE, 0.f};
|
||||
Modelubo.update(Model);
|
||||
glViewport(0, 0, Chain.sizes[level].x, Chain.sizes[level].y);
|
||||
m_bloom_down->apply(level == 0 ? Image : *Chain.down[level - 1], Chain.down_fb[level].get());
|
||||
}
|
||||
// up, from the lowest level
|
||||
for (std::size_t level = Chain.up.size(); level-- > 0;)
|
||||
{
|
||||
auto &lower{(level + 1 < Chain.up.size()) ? *Chain.up[level + 1] : *Chain.down[level + 1]};
|
||||
glViewport(0, 0, Chain.sizes[level].x, Chain.sizes[level].y);
|
||||
m_bloom_up->apply({&lower, Chain.down[level].get()}, Chain.up_fb[level].get());
|
||||
}
|
||||
m_bloom_timer.end();
|
||||
opengl_texture::reset_unit_cache();
|
||||
return Chain.up.empty() ? Chain.down.front().get() : Chain.up.front().get();
|
||||
}
|
||||
|
||||
glm::vec4 opengl46_exposure::tonemap_params(std::size_t const Bloomlevels) const
|
||||
{
|
||||
auto const bloom{(Global.gfx_postfx_bloom && Bloomlevels > 0) ? Global.gfx_postfx_bloom_intensity : 0.f};
|
||||
return glm::vec4{
|
||||
bloom,
|
||||
Global.gfx_postfx_autoexposure ? 1.f : 0.f,
|
||||
std::exp2(Global.gfx_postfx_autoexposure_compensation),
|
||||
Bloomlevels > 0 ? 1.f / static_cast<float>(Bloomlevels) : 0.f};
|
||||
}
|
||||
|
||||
std::string opengl46_exposure::info_times()
|
||||
{
|
||||
std::string text;
|
||||
if (auto const time = m_exposure_timer.result())
|
||||
text += " exposure: " + to_string(static_cast<double>(*time) / 1000000.0, 3) + " ms\n";
|
||||
if (auto const time = m_bloom_timer.result())
|
||||
text += " bloom: " + to_string(static_cast<double>(*time) / 1000000.0, 3) + " ms\n";
|
||||
return text;
|
||||
}
|
||||
|
||||
void opengl46_exposure::draw_debug_ui()
|
||||
{
|
||||
ImGui::Checkbox("automatic exposure (gfx.postfx.autoexposure)", &Global.gfx_postfx_autoexposure);
|
||||
ImGui::SliderFloat("key", &Global.gfx_postfx_autoexposure_key, 0.02f, 2.f, "%.3f", ImGuiSliderFlags_Logarithmic);
|
||||
ImGui::SliderFloat("min EV", &Global.gfx_postfx_autoexposure_min, -4.f, 0.f);
|
||||
ImGui::SliderFloat("max EV", &Global.gfx_postfx_autoexposure_max, 0.f, 4.f);
|
||||
ImGui::SliderFloat("adaptation to dark (1/s)", &Global.gfx_postfx_autoexposure_speed_dark, 0.05f, 10.f);
|
||||
ImGui::SliderFloat("adaptation to bright (1/s)", &Global.gfx_postfx_autoexposure_speed_bright, 0.05f, 10.f);
|
||||
ImGui::SliderFloat("compensation EV", &Global.gfx_postfx_autoexposure_compensation, -4.f, 4.f);
|
||||
ImGui::Text("average luminance %.3f, adapted %.3f, exposure %.3f (%+.2f EV)", m_readback_data[2], m_readback_data[0], m_readback_data[1], std::log2(std::max(m_readback_data[1], 1e-6f)));
|
||||
ImGui::Checkbox("bloom (gfx.postfx.bloom)", &Global.gfx_postfx_bloom);
|
||||
ImGui::SliderFloat("bloom intensity", &Global.gfx_postfx_bloom_intensity, 0.f, 0.5f);
|
||||
ImGui::SliderFloat("bloom threshold", &Global.gfx_postfx_bloom_threshold, 0.f, 10.f);
|
||||
}
|
||||
78
rendering/opengl46/opengl46exposure.h
Normal file
78
rendering/opengl46/opengl46exposure.h
Normal file
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
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 <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "gl/ubo.h"
|
||||
#include "gl/ssbo.h"
|
||||
#include "gl/shader.h"
|
||||
#include "gl/postfx.h"
|
||||
#include "gl/framebuffer.h"
|
||||
#include "gl/query.h"
|
||||
#include "gl/readback.h"
|
||||
|
||||
class opengl_texture;
|
||||
|
||||
// automatic exposure and bloom of the post processing of the opengl 4.6 renderer:
|
||||
// - a luminance histogram of the final hdr image in a compute pass, and the exposure adapted to its average over time,
|
||||
// within the limits of gfx.postfx.autoexposure.* (exposure_*.comp). the exposure stays on the gpu, the tonemapping reads it
|
||||
// - bloom: the image taken down a chain of half size levels and back up, each level added (postfx_bloom_*.frag)
|
||||
// both are switched by Global.gfx_postfx_autoexposure and gfx_postfx_bloom; off, the tonemapping gets the image as before
|
||||
class opengl46_exposure
|
||||
{
|
||||
public:
|
||||
// the levels of the bloom chain of one viewport
|
||||
struct bloom_chain
|
||||
{
|
||||
std::vector<std::unique_ptr<opengl_texture>> down;
|
||||
std::vector<std::unique_ptr<gl::framebuffer>> down_fb;
|
||||
// one fewer than down: the lowest level goes up as it is
|
||||
std::vector<std::unique_ptr<opengl_texture>> up;
|
||||
std::vector<std::unique_ptr<gl::framebuffer>> up_fb;
|
||||
std::vector<glm::ivec2> sizes;
|
||||
};
|
||||
|
||||
opengl46_exposure() = default;
|
||||
opengl46_exposure(const opengl46_exposure &) = delete;
|
||||
opengl46_exposure &operator=(const opengl46_exposure &) = delete;
|
||||
|
||||
// creates the programs and the buffer. throws gl::shader_exception
|
||||
void init();
|
||||
// binds the buffer the tonemapping reads; bindings are context state, so done in every context
|
||||
void bind();
|
||||
// the bloom chain for an image of the given size; null if it could not be made
|
||||
static std::unique_ptr<bloom_chain> make_bloom_chain(int const Width, int const Height);
|
||||
// measures the luminance of the image and adapts the exposure to it. Deltatime: seconds since the last measurement
|
||||
void measure(opengl_texture &Image, double const Deltatime);
|
||||
// takes the image down and up the chain; returns the result, which the tonemapping blends in. leaves the viewport
|
||||
// and the framebuffer binding changed. Model is the model_ubs the post processing shaders read the parameters from
|
||||
opengl_texture *bloom(bloom_chain &Chain, opengl_texture &Image, gl::ubo &Modelubo, gl::model_ubs &Model);
|
||||
// parameters of the tonemapping (postfx_tonemapping.frag), with bloom from a chain of Bloomlevels levels (0: none)
|
||||
glm::vec4 tonemap_params(std::size_t const Bloomlevels) const;
|
||||
// lines for the gpu times of the debug panel
|
||||
std::string info_times();
|
||||
// controls of the debug window
|
||||
void draw_debug_ui();
|
||||
|
||||
private:
|
||||
std::unique_ptr<gl::program> m_histogram_program;
|
||||
std::unique_ptr<gl::program> m_average_program;
|
||||
std::unique_ptr<gl::postfx> m_bloom_down;
|
||||
std::unique_ptr<gl::postfx> m_bloom_up;
|
||||
std::unique_ptr<gl::ssbo> m_exposure_ssbo;
|
||||
// the exposure and luminances, read back for the debug window only
|
||||
gl::readback m_readback{4 * sizeof(float)};
|
||||
float m_readback_data[4]{1.f, 1.f, 1.f, 0.f};
|
||||
gl::gpu_timer m_exposure_timer;
|
||||
gl::gpu_timer m_bloom_timer;
|
||||
};
|
||||
@@ -181,6 +181,17 @@ bool opengl46_renderer::Init(GLFWwindow *Window)
|
||||
light_ssbo->bind_base();
|
||||
instance_ubo = std::make_unique<gl::ubo>(sizeof(gl::instance_ubs), 3, GL_STREAM_DRAW);
|
||||
|
||||
try
|
||||
{
|
||||
m_sky.init();
|
||||
m_exposure.init();
|
||||
}
|
||||
catch (gl::shader_exception const &e)
|
||||
{
|
||||
ErrorLog("invalid shader: " + std::string(e.what()));
|
||||
return false;
|
||||
}
|
||||
|
||||
// better initialize with 0 to not crash driver/whole system
|
||||
// when we forget
|
||||
memset(&light_data, 0, sizeof(light_data));
|
||||
@@ -462,6 +473,8 @@ bool opengl46_renderer::init_viewport(viewport_config &vp)
|
||||
model_ubo->bind_uniform();
|
||||
scene_ubo->bind_uniform();
|
||||
light_ssbo->bind_base();
|
||||
m_sky.bind();
|
||||
m_exposure.bind();
|
||||
|
||||
vp.clusters = std::make_unique<gl::cluster_grid>();
|
||||
// the material shaders declare the cluster blocks whether they read them or not, and drawing with an active storage
|
||||
@@ -546,6 +559,11 @@ bool opengl46_renderer::init_viewport(viewport_config &vp)
|
||||
|
||||
if (!vp.main2_fb->is_complete())
|
||||
return false;
|
||||
|
||||
// made whether the bloom is on or not, it can be switched on in the debug window
|
||||
vp.bloom = opengl46_exposure::make_bloom_chain(vp.width, vp.height);
|
||||
if (!vp.bloom)
|
||||
ErrorLog("bloom framebuffer setup failed, no bloom");
|
||||
}
|
||||
|
||||
if (vp.custom_backbuffer)
|
||||
@@ -588,6 +606,10 @@ bool opengl46_renderer::Render()
|
||||
// quantize sun angle to reduce shadow crawl
|
||||
auto const quantizationstep{0.004f};
|
||||
m_sunlight.direction = glm::normalize(quantizationstep * glm::roundEven(m_sunlight.direction * (1.f / quantizationstep)));
|
||||
// the light of the sky for the render: colours of the sun, ambient light and fog from the atmosphere where it is
|
||||
// used, the directional part of the ambient light
|
||||
m_fogcolor = Global.FogColor;
|
||||
m_sky.update(m_sunlight, m_fogcolor, simulation::Environment.skydome(), simulation::Environment.sun().getDirection(), simulation::Environment.sun().getAngle());
|
||||
}
|
||||
// generate new frame
|
||||
opengl_texture::reset_unit_cache();
|
||||
@@ -669,6 +691,8 @@ void opengl46_renderer::SwapBuffers()
|
||||
m_debugtimestext += " clusters: " + to_string(static_cast<double>(*clusterstime) / 1000000.0, 3) + " ms\n";
|
||||
if (auto const culltime = m_cullstimer.result())
|
||||
m_debugtimestext += " cull: " + to_string(static_cast<double>(*culltime) / 1000000.0, 3) + " ms\n";
|
||||
m_debugtimestext += m_sky.info_times();
|
||||
m_debugtimestext += m_exposure.info_times();
|
||||
m_debugtimestext += "uilayer: " + to_string( Timer::subsystem.gfx_gui.average(), 2 ) + " ms\n";
|
||||
if( DebugModeFlag )
|
||||
m_debugtimestext += m_textures.info();
|
||||
@@ -756,6 +780,14 @@ void opengl46_renderer::draw_debug_ui()
|
||||
}
|
||||
ImGui::End();
|
||||
|
||||
if (ImGui::Begin("Sky and exposure"))
|
||||
{
|
||||
m_sky.draw_debug_ui();
|
||||
ImGui::Separator();
|
||||
m_exposure.draw_debug_ui();
|
||||
}
|
||||
ImGui::End();
|
||||
|
||||
ImGui::SetNextWindowSize(ImVec2S(400, 400));
|
||||
if (ImGui::Begin("Pickbuffer") && m_pick_tex) {
|
||||
ImGui::Image((ImTextureID)(intptr_t)(m_pick_tex->id), ImGui::GetContentRegionAvail(), ImVec2(0, 1.0), ImVec2(1.0, 0));
|
||||
@@ -873,6 +905,9 @@ void opengl46_renderer::Render_pass(viewport_config &vp, rendermode const Mode)
|
||||
Timer::subsystem.gfx_reflections.start();
|
||||
if (Render_reflections(vp))
|
||||
m_renderpass = m_colorpass; // restore color pass settings
|
||||
// the prefiltered map is made with every update of the environment map, or once the switch is turned on
|
||||
if (Global.gfx_envmap_ggx && m_environmentready && (m_sky.prefiltered() == nullptr))
|
||||
m_sky.prefilter(*m_env_tex);
|
||||
Timer::subsystem.gfx_reflections.stop();
|
||||
setup_env_map(m_env_tex.get());
|
||||
}
|
||||
@@ -956,6 +991,8 @@ void opengl46_renderer::Render_pass(viewport_config &vp, rendermode const Mode)
|
||||
glDebug("render environment");
|
||||
|
||||
scene_ubs.projection = OpenGLMatrices.data(GL_PROJECTION);
|
||||
// back to the rotation of the camera after the faces of the environment map, which are drawn with their own
|
||||
scene_ubs.inv_view = glm::inverse(glm::mat4{glm::mat3{m_colorpass.pass_camera.modelview()}});
|
||||
scene_ubo->update(scene_ubs);
|
||||
Render(&simulation::Environment);
|
||||
|
||||
@@ -1083,6 +1120,22 @@ if (!Global.gfx_skippipeline)
|
||||
m_pfx_ssao_apply->apply({ vp.main_tex.get(), vp.ssao_blur_tex.get() }, vp.main2_fb.get());
|
||||
}
|
||||
|
||||
// the exposure follows the main view; the other viewports use it too
|
||||
if (Global.gfx_postfx_autoexposure && vp.main)
|
||||
m_exposure.measure(*vp.main2_tex, Timer::GetDeltaRenderTime());
|
||||
opengl_texture *bloomtexture{nullptr};
|
||||
std::size_t bloomlevels{0};
|
||||
if (Global.gfx_postfx_bloom && vp.bloom)
|
||||
{
|
||||
bloomtexture = m_exposure.bloom(*vp.bloom, *vp.main2_tex, *model_ubo, model_ubs);
|
||||
bloomlevels = vp.bloom->sizes.size();
|
||||
}
|
||||
model_ubs.param[0] = m_exposure.tonemap_params(bloomlevels);
|
||||
model_ubo->update(model_ubs);
|
||||
std::vector<opengl_texture *> tonemapinput{vp.main2_tex.get()};
|
||||
if (bloomtexture != nullptr)
|
||||
tonemapinput.push_back(bloomtexture);
|
||||
|
||||
if (!Global.gfx_shadergamma)
|
||||
glEnable(GL_FRAMEBUFFER_SRGB);
|
||||
|
||||
@@ -1093,14 +1146,14 @@ if (!Global.gfx_skippipeline)
|
||||
if( Global.gfx_postfx_chromaticaberration_enabled ) {
|
||||
glViewport(0, 0, vp.width, vp.height);
|
||||
vp.main_fb->setup_drawing(1);
|
||||
m_pfx_tonemapping->apply( *vp.main2_tex, vp.main_fb.get() );
|
||||
m_pfx_tonemapping->apply( tonemapinput, vp.main_fb.get() );
|
||||
|
||||
glViewport(0, 0, target_size.x, target_size.y);
|
||||
m_pfx_chromaticaberration->apply( *vp.main_tex, target );
|
||||
}
|
||||
else {
|
||||
glViewport(0, 0, target_size.x, target_size.y);
|
||||
m_pfx_tonemapping->apply( *vp.main2_tex, target );
|
||||
m_pfx_tonemapping->apply( tonemapinput, target );
|
||||
}
|
||||
|
||||
if (Global.gfx_lights_clusterheatmap && vp.clusters->valid())
|
||||
@@ -1235,6 +1288,8 @@ if (!Global.gfx_skippipeline)
|
||||
|
||||
setup_shadow_empty_map();
|
||||
scene_ubs.projection = OpenGLMatrices.data(GL_PROJECTION);
|
||||
// world space directions of the shading of the face (the sky, the directional ambient light)
|
||||
scene_ubs.inv_view = glm::inverse(glm::mat4{glm::mat3{m_renderpass.pass_camera.modelview()}});
|
||||
scene_ubo->update(scene_ubs);
|
||||
Render(&simulation::Environment);
|
||||
// opaque parts...
|
||||
@@ -1478,6 +1533,8 @@ bool opengl46_renderer::Render_reflections(viewport_config &vp)
|
||||
{
|
||||
m_env_tex->generate_mipmaps();
|
||||
m_environmentready = true;
|
||||
if (Global.gfx_envmap_ggx)
|
||||
m_sky.prefilter(*m_env_tex);
|
||||
}
|
||||
m_env_fb->detach(GL_COLOR_ATTACHMENT0);
|
||||
|
||||
@@ -1899,6 +1956,15 @@ void opengl46_renderer::setup_env_map(gl::cubemap *tex)
|
||||
glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
}
|
||||
// rough reflections read the prefiltered map along with the real environment map; with the empty stand-in (the reflection
|
||||
// pass) or without the map the shading keeps to the mipmaps of what is bound
|
||||
auto const *prefiltered{(tex != nullptr && tex == m_env_tex.get()) ? m_sky.prefiltered() : nullptr};
|
||||
m_envmap_bound = (prefiltered != nullptr);
|
||||
if (prefiltered != nullptr)
|
||||
prefiltered->bind(GL_TEXTURE0 + gl::ENV_GGX_TEX);
|
||||
else if (m_empty_cubemap)
|
||||
m_empty_cubemap->bind(GL_TEXTURE0 + gl::ENV_GGX_TEX);
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
opengl_texture::reset_unit_cache();
|
||||
}
|
||||
|
||||
@@ -1937,6 +2003,7 @@ void opengl46_renderer::setup_sunlight_intensity( float const Factor ) {
|
||||
m_sunlight.apply_intensity( Factor );
|
||||
light_data.lights[ 0 ].intensity = m_sunlight.factor * (1.0f - Global.Overcast * 0.33f);
|
||||
light_data.ambient = m_sunlight.ambient * m_sunlight.factor;
|
||||
light_data.sky_ambient_factor = ( Global.gfx_ambient_sh ? m_sunlight.factor : 0.f );
|
||||
upload_light_header();
|
||||
}
|
||||
|
||||
@@ -1967,10 +2034,14 @@ bool opengl46_renderer::Render(world_environment *Environment)
|
||||
{
|
||||
glScalef( 500.0f, 500.0f, 500.0f );
|
||||
model_ubs.set_modelview( OpenGLMatrices.data( GL_MODELVIEW ) );
|
||||
auto const atmosphere{ m_sky.atmosphere_weight() > 0.f };
|
||||
if( atmosphere ) {
|
||||
m_sky.setup_sky( model_ubs );
|
||||
}
|
||||
model_ubo->update( model_ubs );
|
||||
|
||||
m_skydomerenderer.update();
|
||||
m_skydomerenderer.render();
|
||||
m_skydomerenderer.render( atmosphere );
|
||||
// skydome uses a custom vbo which could potentially confuse the main geometry system. hardly elegant but, eh
|
||||
gfx::opengl46_vaogeometrybank::reset();
|
||||
}
|
||||
@@ -2002,6 +2073,7 @@ bool opengl46_renderer::Render(world_environment *Environment)
|
||||
auto const fogfactor{std::clamp(Global.fFogEnd / 2000.f, 0.f, 1.f)}; // stronger fog reduces opacity of the celestial bodies
|
||||
float const duskfactor = 1.0f - std::clamp(std::abs(Environment->m_sun.getAngle()), 0.0f, 12.0f) / 12.0f;
|
||||
glm::vec3 suncolor = glm::mix(glm::vec3(1.0f, 242.0f / 255.0f, 231.0f / 255.0f), glm::vec3(235.0f / 255.0f, 140.0f / 255.0f, 36.0f / 255.0f), duskfactor);
|
||||
suncolor = m_sky.sun_disc_color(suncolor);
|
||||
|
||||
// sun
|
||||
{
|
||||
@@ -5643,6 +5715,9 @@ void opengl46_renderer::Preview_Color(TAnimModel *Instance, float const Squaredi
|
||||
light_data.lights_total = 1;
|
||||
light_data.lights_count = 1;
|
||||
light_data.lights_clustered = 0;
|
||||
// a uniform ambient light, as the preview always had
|
||||
light_data.sky_ambient_factor = 0.f;
|
||||
light_data.env_ggx = 0;
|
||||
light_data.lights[0].type = gl::light_element::DIR;
|
||||
light_data.lights[0].dir = modelview * glm::vec4(m_sunlight.direction, 0.0f);
|
||||
light_data.lights[0].color = m_sunlight.diffuse;
|
||||
@@ -5855,6 +5930,9 @@ bool opengl46_renderer::Render_Preview(TAnimModel *Instance, int const Size, int
|
||||
glViewport(0, 0, targetsize, targetsize);
|
||||
if (!Global.gfx_shadergamma)
|
||||
glEnable(GL_FRAMEBUFFER_SRGB);
|
||||
// no bloom, no automatic exposure
|
||||
model_ubs.param[0] = glm::vec4{0.f, 0.f, 1.f, 0.f};
|
||||
model_ubo->update(model_ubs);
|
||||
m_pfx_tonemapping->apply(*m_preview.hdr_tex, m_preview.ldr_fb.get());
|
||||
if (!Global.gfx_shadergamma)
|
||||
glDisable(GL_FRAMEBUFFER_SRGB);
|
||||
@@ -6067,15 +6145,17 @@ void opengl46_renderer::Update_Lights(light_array &Lights, bool const Headlights
|
||||
m_lights_lit = static_cast<uint32_t>(light_i - 1);
|
||||
light_data.lights_count = 1 + std::min<uint32_t>(static_cast<uint32_t>(light_i - 1), static_cast<uint32_t>(std::max(Global.DynamicLightCount, 0)));
|
||||
light_data.lights_clustered = Clustered ? 1 : 0;
|
||||
light_data.env_ggx = ( Global.gfx_envmap_ggx && m_envmap_bound ) ? 1 : 0;
|
||||
// fill sunlight data
|
||||
light_data.ambient = m_sunlight.ambient * m_sunlight.factor;// *simulation::Environment.light_intensity();
|
||||
light_data.sky_ambient_factor = ( Global.gfx_ambient_sh ? m_sunlight.factor : 0.f );
|
||||
light_data.lights[0].type = gl::light_element::DIR;
|
||||
light_data.lights[0].dir = mv * glm::vec4(m_sunlight.direction, 0.0f);
|
||||
light_data.lights[0].color = m_sunlight.diffuse * m_sunlight.factor * simulation::Environment.light_intensity();
|
||||
light_data.lights[0].ambient = 0.0f;
|
||||
light_data.lights[0].intensity = 1.0f;
|
||||
// fill fog data
|
||||
light_data.fog_color = Global.FogColor;
|
||||
light_data.fog_color = m_fogcolor;
|
||||
if (Global.fFogEnd > 0)
|
||||
{
|
||||
m_fogrange = Global.fFogEnd / std::max(1.f, Global.Overcast * 2.f);
|
||||
|
||||
@@ -32,6 +32,8 @@ http://mozilla.org/MPL/2.0/.
|
||||
#include "gl/pbo.h"
|
||||
#include "gl/query.h"
|
||||
#include "rendering/opengl46/opengl46terrain.h"
|
||||
#include "rendering/opengl46/opengl46sky.h"
|
||||
#include "rendering/opengl46/opengl46exposure.h"
|
||||
|
||||
// bare-bones render controller, in lack of anything better yet
|
||||
class opengl46_renderer : public gfx_renderer {
|
||||
@@ -261,6 +263,9 @@ class opengl46_renderer : public gfx_renderer {
|
||||
std::unique_ptr<gl::framebuffer> backbuffer_fb;
|
||||
std::unique_ptr<opengl_texture> backbuffer_tex;
|
||||
|
||||
// levels of the bloom of the post processing; null without the pipeline
|
||||
std::unique_ptr<opengl46_exposure::bloom_chain> bloom;
|
||||
|
||||
// light cluster grid of the viewport
|
||||
std::unique_ptr<gl::cluster_grid> clusters;
|
||||
// depth of the finished scene resolved from the msaa buffer, for the cluster heatmap; made when it is first needed
|
||||
@@ -380,6 +385,14 @@ class opengl46_renderer : public gfx_renderer {
|
||||
texture_manager m_textures;
|
||||
opengl46_terrain_materials m_terrainmaterials{m_materials, m_textures};
|
||||
opengl46_light m_sunlight;
|
||||
// fog colour of the frame: the one of the simulation, or of the atmosphere (see opengl46_sky::update())
|
||||
glm::vec3 m_fogcolor{0.f};
|
||||
// light of the sky: directional ambient light, atmosphere, prefiltered environment map
|
||||
opengl46_sky m_sky;
|
||||
// automatic exposure and bloom of the post processing
|
||||
opengl46_exposure m_exposure;
|
||||
// true while the environment map bound for the shading is the real one (not the empty stand-in of the reflection pass)
|
||||
bool m_envmap_bound{false};
|
||||
/*
|
||||
float m_sunandviewangle; // cached dot product of sunlight and camera vectors
|
||||
*/
|
||||
|
||||
417
rendering/opengl46/opengl46sky.cpp
Normal file
417
rendering/opengl46/opengl46sky.cpp
Normal file
@@ -0,0 +1,417 @@
|
||||
/*
|
||||
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/opengl46sky.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include "environment/skydome.h"
|
||||
#include "model/Texture.h"
|
||||
#include "utilities/Globals.h"
|
||||
#include "utilities/utilities.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
// irradiance of a radiance given as spherical harmonics, divided by pi (the units of the ambient light of the shading:
|
||||
// under a uniform sky of radiance L it is L): the coefficients of band l are scaled by A_l / pi (Ramamoorthi, Hanrahan 2001)
|
||||
float const SH_IRRADIANCE_BAND[3]{1.f, 2.f / 3.f, 0.25f};
|
||||
int const SH_BAND[gl::SKY_SH_COEFFICIENTS]{0, 1, 1, 1, 2, 2, 2, 2, 2};
|
||||
float const SH_Y00{0.282095f};
|
||||
float const SH_Y1{0.488603f};
|
||||
float const PI{3.14159265359f};
|
||||
|
||||
// real spherical harmonics of order 2 in a world space direction, in the order of light_common.glsl
|
||||
std::array<float, gl::SKY_SH_COEFFICIENTS> sh_basis(glm::vec3 const &n)
|
||||
{
|
||||
return {SH_Y00,
|
||||
SH_Y1 * n.y,
|
||||
SH_Y1 * n.z,
|
||||
SH_Y1 * n.x,
|
||||
1.092548f * n.x * n.y,
|
||||
1.092548f * n.y * n.z,
|
||||
0.315392f * (3.f * n.z * n.z - 1.f),
|
||||
1.092548f * n.x * n.z,
|
||||
0.546274f * (n.x * n.x - n.y * n.y)};
|
||||
}
|
||||
|
||||
float luminance(glm::vec3 const &Color)
|
||||
{
|
||||
return glm::dot(Color, glm::vec3{0.2126f, 0.7152f, 0.0722f});
|
||||
}
|
||||
|
||||
glm::vec3 desaturate(glm::vec3 const &Color, float const Amount)
|
||||
{
|
||||
return glm::mix(Color, glm::vec3{luminance(Color)}, Amount);
|
||||
}
|
||||
|
||||
// the response of color.frag to the vertex colours of the dome, so the light taken from the dome is the light it shows
|
||||
glm::vec3 sky_shoulder(glm::vec3 const &Color)
|
||||
{
|
||||
float const knee{0.25f};
|
||||
float const range{0.95f - knee};
|
||||
auto const excess{glm::max(Color - glm::vec3{knee}, glm::vec3{0.f})};
|
||||
return glm::min(Color, glm::vec3{knee}) + range * (glm::vec3{1.f} - glm::exp(-excess / range));
|
||||
}
|
||||
|
||||
// the atmosphere is calibrated against the old sky: these scales make a sky of the atmosphere, with the sun 15 to 60 degrees
|
||||
// high on a clear day, as bright as the old Perez dome is drawn on average, and the ambient light and the sunlight as strong
|
||||
// as the old ones, with the default ground albedo (measured on the tables worked out by the same shaders on a software
|
||||
// renderer, see the notes of the plan, stage S2)
|
||||
float const ATMOSPHERE_SKY_SCALE{16.f};
|
||||
float const ATMOSPHERE_AMBIENT_SCALE{44.f};
|
||||
float const ATMOSPHERE_SUN_SCALE{1.45f};
|
||||
// sun elevations (degrees) between which the sky goes over from the old model (night, twilight) to the atmosphere. the
|
||||
// atmosphere darkens at dusk much faster than the old model, which the rest of the lighting was tuned to
|
||||
float const ATMOSPHERE_FADE_START{-6.f};
|
||||
float const ATMOSPHERE_FADE_END{10.f};
|
||||
// the shading scales the sunlight and the ambient light by these (light_common.glsl), so the light the sun gives the
|
||||
// ground, in the units of the ambient light, is the diffuse colour times their ratio
|
||||
float const SHADING_SUN_TO_AMBIENT{1.5f / 0.65f};
|
||||
|
||||
// parameters of the earth's atmosphere after Hillaire 2020 (km, 1/km)
|
||||
glm::vec3 const RAYLEIGH_SCATTERING{5.802e-3f, 13.558e-3f, 33.1e-3f};
|
||||
float const RAYLEIGH_SCALE_HEIGHT{8.f};
|
||||
float const MIE_SCATTERING{3.996e-3f};
|
||||
float const MIE_EXTINCTION{4.440e-3f};
|
||||
float const MIE_SCALE_HEIGHT{1.2f};
|
||||
float const MIE_ASYMMETRY{0.8f};
|
||||
glm::vec3 const OZONE_ABSORPTION{0.650e-3f, 1.881e-3f, 0.085e-3f};
|
||||
float const OZONE_ALTITUDE{25.f};
|
||||
float const OZONE_HALF_WIDTH{15.f};
|
||||
float const BOTTOM_RADIUS{6360.f};
|
||||
float const TOP_RADIUS{6460.f};
|
||||
|
||||
} // namespace
|
||||
|
||||
opengl46_sky::~opengl46_sky()
|
||||
{
|
||||
GLuint const tables[]{m_transmittance_table, m_multiscattering_table, m_skyview_table};
|
||||
for (auto const table : tables)
|
||||
{
|
||||
if (table != 0)
|
||||
glDeleteTextures(1, &table);
|
||||
}
|
||||
}
|
||||
|
||||
GLuint opengl46_sky::create_table(GLsizei const Width, GLsizei const Height)
|
||||
{
|
||||
GLuint table{0};
|
||||
glCreateTextures(GL_TEXTURE_2D, 1, &table);
|
||||
glTextureStorage2D(table, 1, GL_RGBA16F, Width, Height);
|
||||
glTextureParameteri(table, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTextureParameteri(table, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTextureParameteri(table, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTextureParameteri(table, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
float const black[4]{0.f, 0.f, 0.f, 0.f};
|
||||
glClearTexImage(table, 0, GL_RGBA, GL_FLOAT, black);
|
||||
return table;
|
||||
}
|
||||
|
||||
void opengl46_sky::init()
|
||||
{
|
||||
auto const make_compute = [](std::string const &Name) {
|
||||
gl::shader shader(Name);
|
||||
return std::make_unique<gl::program>(std::vector<std::reference_wrapper<const gl::shader>>{shader});
|
||||
};
|
||||
m_transmittance_program = make_compute("atmosphere_transmittance.comp");
|
||||
m_multiscattering_program = make_compute("atmosphere_multiscattering.comp");
|
||||
m_skyview_program = make_compute("atmosphere_skyview.comp");
|
||||
m_ambient_program = make_compute("atmosphere_ambient.comp");
|
||||
m_prefilter_program = make_compute("envmap_prefilter.comp");
|
||||
|
||||
m_sky_ssbo = std::make_unique<gl::ssbo>(sizeof(gl::sky_data), gl::SKY_SSBO_BINDING);
|
||||
m_sky_ssbo->update(m_sky_data);
|
||||
m_atmosphere_ubo = std::make_unique<gl::ubo>(sizeof(gl::atmosphere_ubs), gl::ATMOSPHERE_UBO_BINDING);
|
||||
m_atmosphere_result_ssbo = std::make_unique<gl::ssbo>(sizeof(gl::atmosphere_result), gl::ATMOSPHERE_RESULT_SSBO_BINDING);
|
||||
|
||||
m_transmittance_table = create_table(gl::ATMOSPHERE_TRANSMITTANCE_WIDTH, gl::ATMOSPHERE_TRANSMITTANCE_HEIGHT);
|
||||
m_multiscattering_table = create_table(gl::ATMOSPHERE_MULTISCATTERING_SIZE, gl::ATMOSPHERE_MULTISCATTERING_SIZE);
|
||||
m_skyview_table = create_table(gl::ATMOSPHERE_SKYVIEW_WIDTH, gl::ATMOSPHERE_SKYVIEW_HEIGHT);
|
||||
|
||||
// a valid atmosphere for the sky shader from the start, the tables stay black until the first update
|
||||
m_atmosphere.rayleigh = glm::vec4{RAYLEIGH_SCATTERING, RAYLEIGH_SCALE_HEIGHT};
|
||||
m_atmosphere.mie = glm::vec4{MIE_SCATTERING, MIE_EXTINCTION, MIE_SCALE_HEIGHT, MIE_ASYMMETRY};
|
||||
m_atmosphere.ozone = glm::vec4{OZONE_ABSORPTION, OZONE_ALTITUDE};
|
||||
m_atmosphere.ground = glm::vec4{glm::vec3{Global.gfx_ambient_groundalbedo}, OZONE_HALF_WIDTH};
|
||||
m_atmosphere.sun_direction = glm::vec4{0.f, 1.f, 0.f, 0.f};
|
||||
m_atmosphere.bottom_radius = BOTTOM_RADIUS;
|
||||
m_atmosphere.top_radius = TOP_RADIUS;
|
||||
m_atmosphere.observer_radius = BOTTOM_RADIUS + 0.2f;
|
||||
m_atmosphere_ubo->update(m_atmosphere);
|
||||
}
|
||||
|
||||
void opengl46_sky::bind()
|
||||
{
|
||||
m_sky_ssbo->bind_base();
|
||||
m_atmosphere_ubo->bind_uniform();
|
||||
m_atmosphere_result_ssbo->bind_base();
|
||||
}
|
||||
|
||||
opengl46_sky::sh_coefficients opengl46_sky::dome_directional(CSkyDome const &Skydome, glm::vec3 const &Ambient, glm::vec3 const &Sunlight, float const Sunheight) const
|
||||
{
|
||||
sh_coefficients radiance{};
|
||||
auto const &vertices{Skydome.vertices()};
|
||||
auto const &colors{Skydome.colors()};
|
||||
// the vertices make a grid even in the elevation and the azimuth, so the solid angle of a vertex goes with the cosine of
|
||||
// its elevation. the bottom ring lies a little under the horizon and is taken as the horizon
|
||||
float totalweight{0.f};
|
||||
for (std::size_t i = 0; i < vertices.size() && i < colors.size(); ++i)
|
||||
{
|
||||
auto direction{glm::normalize(vertices[i])};
|
||||
// the dome darkens its colours towards the top and brightens the band above the horizon for the looks
|
||||
// (CSkyDome::RebuildColors), which would make the zenith almost black and the horizon the main source of light.
|
||||
// both are undone here, so the light comes from the sky the Perez model describes
|
||||
auto const height{std::max(direction.y, 0.05f)};
|
||||
auto const horizonband{std::clamp(1.f - height / 0.2f, 0.f, 1.f)};
|
||||
auto const looks{std::clamp(1.f - height * 0.75f, 0.25f, 1.f) * std::lerp(1.f, 1.5f + 0.75f * m_overcast, horizonband)};
|
||||
direction.y = std::max(direction.y, 0.f);
|
||||
direction = glm::normalize(direction);
|
||||
auto const weight{std::sqrt(std::max(1.f - direction.y * direction.y, 0.f))};
|
||||
auto const color{sky_shoulder(glm::pow(glm::max(colors[i] / looks, glm::vec3{0.f}), glm::vec3{2.2f}))};
|
||||
auto const basis{sh_basis(direction)};
|
||||
for (std::size_t c = 0; c < radiance.size(); ++c)
|
||||
radiance[c] += color * (basis[c] * weight);
|
||||
totalweight += weight;
|
||||
}
|
||||
if (totalweight <= 0.f)
|
||||
return {};
|
||||
for (auto &coefficient : radiance)
|
||||
coefficient *= 2.f * PI / totalweight;
|
||||
|
||||
// the ground, lit by the sky and the sun, fills the lower hemisphere with uniform light (see atmosphere_ambient.comp).
|
||||
// the dome is in its own units: the light of the sky on an upward facing surface is taken to be the ambient light,
|
||||
// which converts the sunlight to them
|
||||
auto const up{sh_basis(glm::vec3{0.f, 1.f, 0.f})};
|
||||
glm::vec3 skyup{0.f};
|
||||
for (std::size_t c = 0; c < radiance.size(); ++c)
|
||||
skyup += radiance[c] * (SH_IRRADIANCE_BAND[SH_BAND[c]] * up[c]);
|
||||
auto const tosky{luminance(skyup) / std::max(luminance(Ambient), 1e-4f)};
|
||||
auto const groundradiance{Global.gfx_ambient_groundalbedo * (skyup + Sunlight * SHADING_SUN_TO_AMBIENT * std::max(Sunheight, 0.f) * tosky)};
|
||||
radiance[0] += groundradiance * (SH_Y00 * 2.f * PI);
|
||||
radiance[1] += groundradiance * (SH_Y1 * -PI);
|
||||
|
||||
// to the irradiance, scaled so its average over all directions is the ambient light, channel by channel
|
||||
sh_coefficients directional{};
|
||||
glm::vec3 const average{radiance[0] * SH_Y00};
|
||||
auto const scale{Ambient / glm::max(average, glm::vec3{1e-4f})};
|
||||
for (std::size_t c = 1; c < radiance.size(); ++c)
|
||||
directional[c] = radiance[c] * SH_IRRADIANCE_BAND[SH_BAND[c]] * scale;
|
||||
return directional;
|
||||
}
|
||||
|
||||
void opengl46_sky::update_atmosphere(glm::vec3 const &Sundirection)
|
||||
{
|
||||
// aerosols grow with the turbidity the weather sets (3 clear .. 9 overcast)
|
||||
auto const turbidity{std::clamp(Global.fTurbidity, 3.f, 9.f)};
|
||||
auto const miescale{Global.gfx_atmosphere_haze * (1.f + 0.5f * (turbidity - 3.f))};
|
||||
glm::vec4 const medium{miescale, Global.gfx_ambient_groundalbedo, Global.gfx_atmosphere_altitude, 0.f};
|
||||
auto const mediumchanged{!m_tables_medium || *m_tables_medium != medium};
|
||||
// the sky-view table follows the sun when it moved by more than about a twentieth of a degree
|
||||
auto const sunchanged{!m_tables_sun || glm::dot(*m_tables_sun, Sundirection) < 0.9999995f};
|
||||
if (!mediumchanged && !sunchanged)
|
||||
return;
|
||||
|
||||
m_atmosphere.mie = glm::vec4{MIE_SCATTERING * miescale, MIE_EXTINCTION * miescale, MIE_SCALE_HEIGHT, MIE_ASYMMETRY};
|
||||
m_atmosphere.ground = glm::vec4{glm::vec3{Global.gfx_ambient_groundalbedo}, OZONE_HALF_WIDTH};
|
||||
m_atmosphere.sun_direction = glm::vec4{Sundirection, 0.f};
|
||||
m_atmosphere.observer_radius = BOTTOM_RADIUS + std::max(Global.gfx_atmosphere_altitude, 0.f) * 0.001f + 0.001f;
|
||||
m_atmosphere_ubo->update(m_atmosphere);
|
||||
m_atmosphere_ubo->bind_uniform();
|
||||
m_atmosphere_result_ssbo->bind_base();
|
||||
|
||||
m_atmosphere_timer.begin();
|
||||
if (mediumchanged)
|
||||
{
|
||||
glBindImageTexture(gl::ATMOSPHERE_TRANSMITTANCE_UNIT, m_transmittance_table, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA16F);
|
||||
m_transmittance_program->dispatch((gl::ATMOSPHERE_TRANSMITTANCE_WIDTH + 7) / 8, (gl::ATMOSPHERE_TRANSMITTANCE_HEIGHT + 7) / 8);
|
||||
glMemoryBarrier(GL_TEXTURE_FETCH_BARRIER_BIT | GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
|
||||
|
||||
glBindTextureUnit(gl::ATMOSPHERE_TRANSMITTANCE_UNIT, m_transmittance_table);
|
||||
glBindImageTexture(gl::ATMOSPHERE_MULTISCATTERING_UNIT, m_multiscattering_table, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA16F);
|
||||
m_multiscattering_program->dispatch(gl::ATMOSPHERE_MULTISCATTERING_SIZE, gl::ATMOSPHERE_MULTISCATTERING_SIZE);
|
||||
glMemoryBarrier(GL_TEXTURE_FETCH_BARRIER_BIT | GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
|
||||
}
|
||||
|
||||
glBindTextureUnit(gl::ATMOSPHERE_TRANSMITTANCE_UNIT, m_transmittance_table);
|
||||
glBindTextureUnit(gl::ATMOSPHERE_MULTISCATTERING_UNIT, m_multiscattering_table);
|
||||
glBindImageTexture(gl::ATMOSPHERE_SKYVIEW_UNIT, m_skyview_table, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA16F);
|
||||
m_skyview_program->dispatch((gl::ATMOSPHERE_SKYVIEW_WIDTH + 7) / 8, (gl::ATMOSPHERE_SKYVIEW_HEIGHT + 7) / 8);
|
||||
glMemoryBarrier(GL_TEXTURE_FETCH_BARRIER_BIT | GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
|
||||
|
||||
glBindTextureUnit(gl::ATMOSPHERE_SKYVIEW_UNIT, m_skyview_table);
|
||||
m_ambient_program->dispatch(1);
|
||||
glMemoryBarrier(GL_BUFFER_UPDATE_BARRIER_BIT | GL_SHADER_STORAGE_BARRIER_BIT);
|
||||
m_atmosphere_timer.end();
|
||||
|
||||
// the units the passes used are below the ones of the texture cache, which is told they changed
|
||||
for (GLuint unit : {gl::ATMOSPHERE_TRANSMITTANCE_UNIT, gl::ATMOSPHERE_MULTISCATTERING_UNIT, gl::ATMOSPHERE_SKYVIEW_UNIT})
|
||||
glBindTextureUnit(unit, 0);
|
||||
opengl_texture::reset_unit_cache();
|
||||
|
||||
m_result_readback.push(*m_atmosphere_result_ssbo);
|
||||
m_tables_medium = medium;
|
||||
m_tables_sun = Sundirection;
|
||||
}
|
||||
|
||||
void opengl46_sky::update(opengl46_light &Sunlight, glm::vec3 &Fogcolor, CSkyDome const &Skydome, glm::vec3 const &Sundirection, float const Sunangle)
|
||||
{
|
||||
// the light the simulation set, which is kept where the atmosphere is not used
|
||||
glm::vec3 const oldambient{Sunlight.ambient};
|
||||
glm::vec3 const olddiffuse{Sunlight.diffuse};
|
||||
glm::vec3 const oldfog{Fogcolor};
|
||||
m_overcast = std::clamp(Global.Overcast, 0.f, 1.f);
|
||||
|
||||
// atmosphere
|
||||
m_weight = 0.f;
|
||||
if (Global.gfx_atmosphere)
|
||||
{
|
||||
update_atmosphere(glm::normalize(Sundirection));
|
||||
gl::atmosphere_result result;
|
||||
if (m_result_readback.pop(&result))
|
||||
m_result = result;
|
||||
if (m_result)
|
||||
m_weight = glm::smoothstep(ATMOSPHERE_FADE_START, ATMOSPHERE_FADE_END, Sunangle);
|
||||
}
|
||||
|
||||
sh_coefficients directional{};
|
||||
if (Global.gfx_ambient_sh)
|
||||
{
|
||||
// the dome changes with the sun, the weather and the light the simulation works out from them
|
||||
std::array<glm::vec4, 3> const key{glm::vec4{Sundirection, Sunlight.direction.y}, glm::vec4{oldambient, m_overcast}, glm::vec4{olddiffuse, Global.gfx_ambient_groundalbedo}};
|
||||
if (!m_dome_key || *m_dome_key != key)
|
||||
{
|
||||
m_dome_sh = dome_directional(Skydome, oldambient, olddiffuse, -Sunlight.direction.y);
|
||||
m_dome_key = key;
|
||||
}
|
||||
directional = m_dome_sh;
|
||||
}
|
||||
|
||||
m_sunlight = olddiffuse;
|
||||
m_ambient = oldambient;
|
||||
m_fog = oldfog;
|
||||
m_sun_disc_color = glm::vec3{1.f};
|
||||
if (m_weight > 0.f)
|
||||
{
|
||||
auto const &result{*m_result};
|
||||
// the camera's white balance: the sun high in the sky is white
|
||||
auto const balance{1.f / glm::max(glm::vec3{result.zenith_transmittance}, glm::vec3{1e-3f})};
|
||||
m_white_balance = balance / std::max(luminance(balance), 1e-4f);
|
||||
|
||||
// the atmosphere has no clouds: the overcast takes away the sunlight, greys the sky and evens out its light
|
||||
auto const sun{glm::vec3{result.sun_transmittance} * m_white_balance * (ATMOSPHERE_SUN_SCALE * Global.gfx_atmosphere_sun) * (1.f - 0.65f * m_overcast)};
|
||||
auto const ambientscale{ATMOSPHERE_AMBIENT_SCALE * Global.gfx_atmosphere_ambient};
|
||||
auto const ambient{desaturate(glm::vec3{result.radiance_sh[0]} * SH_Y00 * m_white_balance * ambientscale, 0.5f * m_overcast)};
|
||||
m_sky_scale = ATMOSPHERE_SKY_SCALE * Global.gfx_atmosphere_sky;
|
||||
auto const fog{desaturate(glm::vec3{result.horizon} * m_white_balance * m_sky_scale, 0.5f * m_overcast)};
|
||||
m_sky_flat_luminance = luminance(glm::vec3{result.average} * m_white_balance * m_sky_scale);
|
||||
|
||||
m_sunlight = glm::mix(olddiffuse, sun, m_weight);
|
||||
m_ambient = glm::mix(oldambient, ambient, m_weight);
|
||||
m_fog = glm::mix(oldfog, fog, m_weight);
|
||||
auto const sundisc{glm::vec3{result.sun_transmittance} * m_white_balance};
|
||||
m_sun_disc_color = sundisc / std::max({sundisc.r, sundisc.g, sundisc.b, 1e-4f});
|
||||
|
||||
if (Global.gfx_ambient_sh)
|
||||
{
|
||||
auto const evenness{1.f - 0.75f * m_overcast};
|
||||
for (std::size_t c = 1; c < directional.size(); ++c)
|
||||
{
|
||||
auto const atmosphere{desaturate(glm::vec3{result.radiance_sh[c]} * m_white_balance * (SH_IRRADIANCE_BAND[SH_BAND[c]] * ambientscale * evenness), 0.5f * m_overcast)};
|
||||
directional[c] = glm::mix(directional[c], atmosphere, m_weight);
|
||||
}
|
||||
}
|
||||
|
||||
Sunlight.diffuse = glm::vec4{m_sunlight, Sunlight.diffuse.a};
|
||||
Sunlight.ambient = glm::vec4{m_ambient, Sunlight.ambient.a};
|
||||
Fogcolor = m_fog;
|
||||
}
|
||||
|
||||
// directional part of the ambient light for the shading
|
||||
gl::sky_data skydata{};
|
||||
for (std::size_t c = 1; c < directional.size(); ++c)
|
||||
skydata.ambient_sh[c] = glm::vec4{directional[c] * Global.gfx_ambient_sh_strength, 0.f};
|
||||
if (!m_sky_data_uploaded || std::memcmp(&skydata, &m_sky_data, sizeof(skydata)) != 0)
|
||||
{
|
||||
m_sky_data = skydata;
|
||||
m_sky_ssbo->update(m_sky_data);
|
||||
m_sky_data_uploaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
void opengl46_sky::setup_sky(gl::model_ubs &Model) const
|
||||
{
|
||||
glBindTextureUnit(gl::SKY_VIEW_TEX, m_skyview_table);
|
||||
Model.param[0] = glm::vec4{m_weight, m_sky_scale, 0.75f * m_overcast, m_sky_flat_luminance};
|
||||
Model.param[1] = glm::vec4{m_white_balance, 0.f};
|
||||
}
|
||||
|
||||
glm::vec3 opengl46_sky::sun_disc_color(glm::vec3 const &Oldcolor) const
|
||||
{
|
||||
return glm::mix(Oldcolor, m_sun_disc_color, m_weight);
|
||||
}
|
||||
|
||||
void opengl46_sky::prefilter(gl::cubemap const &Source)
|
||||
{
|
||||
if (!m_prefiltered)
|
||||
{
|
||||
m_prefiltered = std::make_unique<gl::cubemap>();
|
||||
m_prefiltered->alloc_levels(GL_RGBA16F, static_cast<int>(gl::ENVMAP_GGX_SIZE), static_cast<int>(gl::ENVMAP_GGX_LEVELS));
|
||||
}
|
||||
|
||||
m_prefilter_timer.begin();
|
||||
Source.bind(GL_TEXTURE0 + gl::ENV_TEX);
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
for (std::size_t level = 0; level < gl::ENVMAP_GGX_LEVELS; ++level)
|
||||
{
|
||||
auto const size{static_cast<int>(gl::ENVMAP_GGX_SIZE >> level)};
|
||||
auto const roughness{gl::ENVMAP_GGX_ROUGHNESS_MIN + (1.f - gl::ENVMAP_GGX_ROUGHNESS_MIN) * static_cast<float>(level) / static_cast<float>(gl::ENVMAP_GGX_LEVELS - 1)};
|
||||
glBindImageTexture(0, *m_prefiltered, static_cast<GLint>(level), GL_TRUE, 0, GL_WRITE_ONLY, GL_RGBA16F);
|
||||
glProgramUniform1f(*m_prefilter_program, 0, roughness);
|
||||
glProgramUniform1i(*m_prefilter_program, 1, size);
|
||||
glProgramUniform1f(*m_prefilter_program, 2, static_cast<float>(gl::ENVMAP_SIZE));
|
||||
glProgramUniform1i(*m_prefilter_program, 3, 64);
|
||||
auto const groups{static_cast<GLuint>((size + gl::ENVMAP_GGX_GROUP_SIZE - 1) / gl::ENVMAP_GGX_GROUP_SIZE)};
|
||||
m_prefilter_program->dispatch(groups, groups, 6);
|
||||
}
|
||||
glMemoryBarrier(GL_TEXTURE_FETCH_BARRIER_BIT | GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
|
||||
m_prefilter_timer.end();
|
||||
glBindImageTexture(0, 0, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA16F);
|
||||
m_prefiltered_ready = true;
|
||||
}
|
||||
|
||||
std::string opengl46_sky::info_times()
|
||||
{
|
||||
std::string text;
|
||||
if (auto const time = m_atmosphere_timer.result())
|
||||
text += " atmosphere: " + to_string(static_cast<double>(*time) / 1000000.0, 3) + " ms\n";
|
||||
if (auto const time = m_prefilter_timer.result())
|
||||
text += " envmap ggx: " + to_string(static_cast<double>(*time) / 1000000.0, 3) + " ms\n";
|
||||
return text;
|
||||
}
|
||||
|
||||
void opengl46_sky::draw_debug_ui()
|
||||
{
|
||||
ImGui::Checkbox("directional ambient light (gfx.ambient.sh)", &Global.gfx_ambient_sh);
|
||||
ImGui::SliderFloat("directional strength", &Global.gfx_ambient_sh_strength, 0.f, 2.f);
|
||||
ImGui::SliderFloat("ground albedo", &Global.gfx_ambient_groundalbedo, 0.f, 1.f);
|
||||
ImGui::Checkbox("GGX prefiltered reflections (gfx.envmap.ggx)", &Global.gfx_envmap_ggx);
|
||||
ImGui::Checkbox("physical atmosphere (gfx.atmosphere)", &Global.gfx_atmosphere);
|
||||
ImGui::SliderFloat("sky", &Global.gfx_atmosphere_sky, 0.f, 4.f);
|
||||
ImGui::SliderFloat("ambient", &Global.gfx_atmosphere_ambient, 0.f, 4.f);
|
||||
ImGui::SliderFloat("sun", &Global.gfx_atmosphere_sun, 0.f, 4.f);
|
||||
ImGui::SliderFloat("haze", &Global.gfx_atmosphere_haze, 0.f, 8.f);
|
||||
ImGui::SliderFloat("altitude (m)", &Global.gfx_atmosphere_altitude, 0.f, 3000.f);
|
||||
ImGui::Text("atmosphere share %.2f%s", m_weight, (Global.gfx_atmosphere && !m_result) ? " (waiting for the first read back)" : "");
|
||||
ImGui::Text("sun %.3f %.3f %.3f", m_sunlight.r, m_sunlight.g, m_sunlight.b);
|
||||
ImGui::Text("ambient %.3f %.3f %.3f", m_ambient.r, m_ambient.g, m_ambient.b);
|
||||
ImGui::Text("fog %.3f %.3f %.3f", m_fog.r, m_fog.g, m_fog.b);
|
||||
ImGui::Text("up / down ambient %.3f / %.3f", luminance(m_ambient + glm::vec3{m_sky_data.ambient_sh[1]} * SH_Y1), luminance(m_ambient - glm::vec3{m_sky_data.ambient_sh[1]} * SH_Y1));
|
||||
}
|
||||
128
rendering/opengl46/opengl46sky.h
Normal file
128
rendering/opengl46/opengl46sky.h
Normal file
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
This Source Code Form is subject to the
|
||||
terms of the Mozilla Public License, v.
|
||||
2.0. If a copy of the MPL was not
|
||||
distributed with this file, You can
|
||||
obtain one at
|
||||
http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
#include "gl/ubo.h"
|
||||
#include "gl/ssbo.h"
|
||||
#include "gl/shader.h"
|
||||
#include "gl/cubemap.h"
|
||||
#include "gl/query.h"
|
||||
#include "gl/readback.h"
|
||||
#include "rendering/opengl46/opengl46light.h"
|
||||
|
||||
class CSkyDome;
|
||||
|
||||
// light of the sky for the opengl 4.6 renderer:
|
||||
// - the directional part of the ambient light, as spherical harmonics in sky_ssbo, worked out from the colours of the sky
|
||||
// dome or from the physical atmosphere. the shading adds it to the uniform ambient light (light_common.glsl), so the
|
||||
// average of the ambient light stays what the rest of the renderer sets
|
||||
// - the physical atmosphere (Hillaire 2020, atmosphere_*.comp): look-up tables made in compute passes, the sky drawn from
|
||||
// them (sky_atmosphere.frag), and the sunlight, ambient light and fog colour read back from them a few frames later.
|
||||
// these are values for the render only: the simulation (Global.DayLight, fLuminance) keeps its own
|
||||
// - the environment map prefiltered with the GGX lobe, for rough reflections (envmap_prefilter.comp)
|
||||
// all of it is switched by Global.gfx_ambient_sh, gfx_atmosphere and gfx_envmap_ggx; off, the renderer works as before
|
||||
class opengl46_sky
|
||||
{
|
||||
public:
|
||||
opengl46_sky() = default;
|
||||
~opengl46_sky();
|
||||
opengl46_sky(const opengl46_sky &) = delete;
|
||||
opengl46_sky &operator=(const opengl46_sky &) = delete;
|
||||
|
||||
// creates the buffers, tables and programs. throws gl::shader_exception
|
||||
void init();
|
||||
// binds the buffers the shaders read; bindings are context state, so done in every context
|
||||
void bind();
|
||||
// works out the light of the sky for the frame and replaces with it the colours of Sunlight (diffuse, ambient) and
|
||||
// Fogcolor, which come in as the simulation set them. runs the compute passes, so it is called in the main context,
|
||||
// before the passes of the frame. Sundirection and Sunangle (degrees) are those of the sun, also when the moon is the key light
|
||||
void update(opengl46_light &Sunlight, glm::vec3 &Fogcolor, CSkyDome const &Skydome, glm::vec3 const &Sundirection, float const Sunangle);
|
||||
// share of the atmosphere in the sky of the frame; 0 when the old sky is drawn alone
|
||||
float atmosphere_weight() const
|
||||
{
|
||||
return m_weight;
|
||||
}
|
||||
// sets the shader parameters of the sky drawn with the atmosphere (sky_atmosphere.frag) and binds its table
|
||||
void setup_sky(gl::model_ubs &Model) const;
|
||||
// colour of the sun disc, the old one blended with the colour the atmosphere gives it
|
||||
glm::vec3 sun_disc_color(glm::vec3 const &Oldcolor) const;
|
||||
// prefilters the levels of the GGX map from the environment map, which has to have its mipmaps
|
||||
void prefilter(gl::cubemap const &Source);
|
||||
// the prefiltered map, null until it was made once
|
||||
gl::cubemap const *prefiltered() const
|
||||
{
|
||||
return m_prefiltered_ready ? m_prefiltered.get() : nullptr;
|
||||
}
|
||||
// lines for the gpu times of the debug panel
|
||||
std::string info_times();
|
||||
// controls of the debug window
|
||||
void draw_debug_ui();
|
||||
|
||||
private:
|
||||
// spherical harmonics of order 2, rgb
|
||||
using sh_coefficients = std::array<glm::vec3, gl::SKY_SH_COEFFICIENTS>;
|
||||
|
||||
// directional part of the ambient light from the colours of the dome, scaled so its average is Ambient
|
||||
sh_coefficients dome_directional(CSkyDome const &Skydome, glm::vec3 const &Ambient, glm::vec3 const &Sunlight, float const Sunheight) const;
|
||||
// sets up the parameters of the atmosphere for the frame and runs the passes whose input changed
|
||||
void update_atmosphere(glm::vec3 const &Sundirection);
|
||||
// creates an immutable 2d texture for a look-up table
|
||||
static GLuint create_table(GLsizei Width, GLsizei Height);
|
||||
|
||||
// buffers the shaders read
|
||||
std::unique_ptr<gl::ssbo> m_sky_ssbo;
|
||||
std::unique_ptr<gl::ubo> m_atmosphere_ubo;
|
||||
std::unique_ptr<gl::ssbo> m_atmosphere_result_ssbo;
|
||||
gl::sky_data m_sky_data{};
|
||||
bool m_sky_data_uploaded{false};
|
||||
|
||||
// atmosphere
|
||||
std::unique_ptr<gl::program> m_transmittance_program;
|
||||
std::unique_ptr<gl::program> m_multiscattering_program;
|
||||
std::unique_ptr<gl::program> m_skyview_program;
|
||||
std::unique_ptr<gl::program> m_ambient_program;
|
||||
GLuint m_transmittance_table{0};
|
||||
GLuint m_multiscattering_table{0};
|
||||
GLuint m_skyview_table{0};
|
||||
gl::atmosphere_ubs m_atmosphere{};
|
||||
// inputs the tables were last made for: the transmittance and multiple scattering tables depend on the medium only,
|
||||
// the sky-view table also on the sun
|
||||
std::optional<glm::vec4> m_tables_medium;
|
||||
std::optional<glm::vec3> m_tables_sun;
|
||||
gl::readback m_result_readback{sizeof(gl::atmosphere_result)};
|
||||
std::optional<gl::atmosphere_result> m_result;
|
||||
gl::gpu_timer m_atmosphere_timer;
|
||||
|
||||
// what update() made of the frame, for the sky, the sun disc and the debug window
|
||||
float m_weight{0.f};
|
||||
glm::vec3 m_white_balance{1.f};
|
||||
float m_sky_scale{0.f};
|
||||
float m_overcast{0.f};
|
||||
float m_sky_flat_luminance{0.f};
|
||||
glm::vec3 m_sun_disc_color{1.f};
|
||||
glm::vec3 m_sunlight{0.f};
|
||||
glm::vec3 m_ambient{0.f};
|
||||
glm::vec3 m_fog{0.f};
|
||||
|
||||
// cache of the directional ambient light of the dome, remade when its inputs change
|
||||
std::optional<std::array<glm::vec4, 3>> m_dome_key;
|
||||
sh_coefficients m_dome_sh{};
|
||||
|
||||
// prefiltered environment map
|
||||
std::unique_ptr<gl::program> m_prefilter_program;
|
||||
std::unique_ptr<gl::cubemap> m_prefiltered;
|
||||
bool m_prefiltered_ready{false};
|
||||
gl::gpu_timer m_prefilter_timer;
|
||||
};
|
||||
@@ -19,6 +19,8 @@ void opengl46_skydome::update() {
|
||||
gl::shader vert("vbocolor.vert");
|
||||
gl::shader frag("color.frag");
|
||||
m_shader.emplace(std::vector<std::reference_wrapper<const gl::shader>>({vert, frag}));
|
||||
gl::shader atmospherefrag("sky_atmosphere.frag");
|
||||
m_atmosphereshader.emplace(std::vector<std::reference_wrapper<const gl::shader>>({vert, atmospherefrag}));
|
||||
}
|
||||
|
||||
auto &skydome { simulation::Environment.skydome() };
|
||||
@@ -55,11 +57,16 @@ void opengl46_skydome::update() {
|
||||
}
|
||||
|
||||
// render skydome to screen
|
||||
void opengl46_skydome::render() {
|
||||
void opengl46_skydome::render( bool const Atmosphere ) {
|
||||
|
||||
if( ( !m_shader) || ( !m_vao ) ) { return; }
|
||||
|
||||
m_shader->bind();
|
||||
if( Atmosphere && m_atmosphereshader ) {
|
||||
m_atmosphereshader->bind();
|
||||
}
|
||||
else {
|
||||
m_shader->bind();
|
||||
}
|
||||
m_vao->bind();
|
||||
|
||||
auto const &skydome { simulation::Environment.skydome() };
|
||||
|
||||
@@ -23,8 +23,9 @@ public:
|
||||
// methods
|
||||
// updates data stores on the opengl end. NOTE: unbinds buffers
|
||||
void update();
|
||||
// draws the skydome
|
||||
void render();
|
||||
// draws the skydome. with Atmosphere the colours come from the tables of the physical atmosphere (sky_atmosphere.frag),
|
||||
// which the caller binds along with the parameters of the shader
|
||||
void render( bool const Atmosphere = false );
|
||||
|
||||
private:
|
||||
// members
|
||||
@@ -32,5 +33,6 @@ private:
|
||||
std::optional<gl::buffer> m_indexbuffer;
|
||||
std::optional<gl::buffer> m_coloursbuffer;
|
||||
std::optional<gl::program> m_shader;
|
||||
std::optional<gl::program> m_atmosphereshader;
|
||||
std::optional<gl::vao> m_vao;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user