mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-17 03:59:19 +02:00
Build view-space light cluster grid in a compute pass
This commit is contained in:
@@ -259,6 +259,7 @@ set(SOURCES
|
||||
"gl/pbo.cpp"
|
||||
"gl/query.cpp"
|
||||
"gl/ssbo.cpp"
|
||||
"gl/cluster_grid.cpp"
|
||||
|
||||
"imgui/imgui.cpp"
|
||||
"imgui/imgui_demo.cpp"
|
||||
|
||||
66
gl/cluster_grid.cpp
Normal file
66
gl/cluster_grid.cpp
Normal file
@@ -0,0 +1,66 @@
|
||||
#include "stdafx.h"
|
||||
#include "cluster_grid.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
gl::cluster_grid::cluster_grid()
|
||||
: m_aabbs(sizeof(cluster_aabb) * CLUSTER_COUNT, CLUSTER_AABB_SSBO_BINDING)
|
||||
, m_ranges(sizeof(cluster_range) * CLUSTER_COUNT, CLUSTER_RANGE_SSBO_BINDING)
|
||||
, m_indices(sizeof(uint32_t) * CLUSTER_COUNT * MAX_LIGHTS_PER_CLUSTER, CLUSTER_INDEX_SSBO_BINDING)
|
||||
, m_counters(sizeof(cluster_counters), CLUSTER_COUNTER_SSBO_BINDING)
|
||||
, m_params_buffer(sizeof(cluster_ubs), CLUSTER_UBO_BINDING)
|
||||
{
|
||||
// no light is assigned to any cluster until the culling pass runs
|
||||
std::vector<uint8_t> const empty(sizeof(cluster_range) * CLUSTER_COUNT, 0);
|
||||
m_ranges.update(empty.data(), 0, static_cast<GLsizeiptr>(empty.size()));
|
||||
m_counters.update(cluster_counters{});
|
||||
m_params_buffer.update(m_params);
|
||||
}
|
||||
|
||||
void gl::cluster_grid::bind()
|
||||
{
|
||||
m_aabbs.bind_base();
|
||||
m_ranges.bind_base();
|
||||
m_indices.bind_base();
|
||||
m_counters.bind_base();
|
||||
m_params_buffer.bind_uniform();
|
||||
}
|
||||
|
||||
bool gl::cluster_grid::update(const glm::mat4 &projection, float znear, float zfar, glm::vec2 viewport_size)
|
||||
{
|
||||
cluster_ubs params = m_params;
|
||||
params.viewport_width = viewport_size.x;
|
||||
params.viewport_height = viewport_size.y;
|
||||
|
||||
// clip space w is a function of the position in a perspective projection, and a constant in an orthographic one
|
||||
bool const perspective{glm::length(glm::vec3{projection[0][3], projection[1][3], projection[2][3]}) > 1e-4f};
|
||||
bool const usable{perspective && znear > 0.f && zfar > znear};
|
||||
params.valid = usable ? 1 : 0;
|
||||
|
||||
if (usable && (projection != m_params.projection || znear != m_params.znear || zfar != m_params.zfar))
|
||||
{
|
||||
params.projection = projection;
|
||||
params.znear = znear;
|
||||
params.zfar = zfar;
|
||||
params.slice_scale = static_cast<float>(CLUSTER_GRID_Z) / std::log(zfar / znear);
|
||||
params.slice_bias = -std::log(znear) * params.slice_scale;
|
||||
m_boxes_stale = true;
|
||||
}
|
||||
|
||||
if (std::memcmp(¶ms, &m_params, sizeof(params)) != 0)
|
||||
{
|
||||
m_params = params;
|
||||
m_params_buffer.update(m_params);
|
||||
}
|
||||
|
||||
return usable && m_boxes_stale;
|
||||
}
|
||||
|
||||
void gl::cluster_grid::build(program &build_program)
|
||||
{
|
||||
bind();
|
||||
build_program.dispatch((CLUSTER_COUNT + CLUSTER_BUILD_GROUP_SIZE - 1) / CLUSTER_BUILD_GROUP_SIZE);
|
||||
// the boxes are read by compute passes which follow, from the same shader storage buffer
|
||||
memory_barrier(GL_SHADER_STORAGE_BARRIER_BIT);
|
||||
m_boxes_stale = false;
|
||||
}
|
||||
57
gl/cluster_grid.h
Normal file
57
gl/cluster_grid.h
Normal file
@@ -0,0 +1,57 @@
|
||||
#pragma once
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include "ssbo.h"
|
||||
#include "ubo.h"
|
||||
#include "shader.h"
|
||||
|
||||
namespace gl
|
||||
{
|
||||
// GPU resources of the light cluster grid of one viewport. the grid cuts the view frustum of a pass into
|
||||
// CLUSTER_GRID_X x CLUSTER_GRID_Y screen tiles and CLUSTER_GRID_Z slices of depth, spaced exponentially
|
||||
// between znear and zfar (layout and binding points: ubo.h). depth is the clip space w of a point, which is
|
||||
// also 1.0 / gl_FragCoord.w of its fragment and, unlike the view space z, is right for tilted screens.
|
||||
// the buffers have a fixed size, so the grid survives changes of the resolution, fov and draw range: those
|
||||
// only make its parameters and boxes be recomputed.
|
||||
// bindings of the buffers are context state and the buffers are per viewport, so bind() has to be done in
|
||||
// the context and before every pass which uses the grid
|
||||
class cluster_grid
|
||||
{
|
||||
public:
|
||||
cluster_grid();
|
||||
|
||||
void bind();
|
||||
|
||||
// refreshes the parameters for a pass: its projection, depth range and the size of the render target.
|
||||
// returns true if the boxes of the clusters no longer match them and build() has to run
|
||||
bool update(const glm::mat4 &projection, float znear, float zfar, glm::vec2 viewport_size);
|
||||
// recomputes the boxes of the clusters in view space with the program made from cluster_build.comp.
|
||||
// binds the grid, and leaves the boxes ready to be read by the compute passes which follow
|
||||
void build(program &build_program);
|
||||
|
||||
// false if the last update found no grid possible for the pass (orthographic projection)
|
||||
bool valid() const
|
||||
{
|
||||
return m_params.valid != 0;
|
||||
}
|
||||
const cluster_ubs ¶ms() const
|
||||
{
|
||||
return m_params;
|
||||
}
|
||||
|
||||
private:
|
||||
// cluster_aabb per cluster
|
||||
ssbo m_aabbs;
|
||||
// cluster_range per cluster
|
||||
ssbo m_ranges;
|
||||
// light indices of all clusters, allocated from m_counters.index_count
|
||||
ssbo m_indices;
|
||||
// cluster_counters
|
||||
ssbo m_counters;
|
||||
ubo m_params_buffer;
|
||||
|
||||
cluster_ubs m_params{};
|
||||
bool m_boxes_stale = true;
|
||||
};
|
||||
} // namespace gl
|
||||
@@ -16,6 +16,17 @@ void gl::glsl_common_setup()
|
||||
"#define MAX_PARAMS " + std::to_string(MAX_PARAMS) + "U\n" +
|
||||
"#define MAX_INSTANCES_PER_BATCH " + std::to_string(MAX_INSTANCES_PER_BATCH) + "U\n" +
|
||||
"#define LIGHT_SSBO_BINDING " + std::to_string(LIGHT_SSBO_BINDING) + "\n" +
|
||||
"#define CLUSTER_GRID_X " + std::to_string(CLUSTER_GRID_X) + "\n" +
|
||||
"#define CLUSTER_GRID_Y " + std::to_string(CLUSTER_GRID_Y) + "\n" +
|
||||
"#define CLUSTER_GRID_Z " + std::to_string(CLUSTER_GRID_Z) + "\n" +
|
||||
"#define CLUSTER_COUNT " + std::to_string(CLUSTER_COUNT) + "\n" +
|
||||
"#define MAX_LIGHTS_PER_CLUSTER " + std::to_string(MAX_LIGHTS_PER_CLUSTER) + "\n" +
|
||||
"#define CLUSTER_BUILD_GROUP_SIZE " + std::to_string(CLUSTER_BUILD_GROUP_SIZE) + "\n" +
|
||||
"#define CLUSTER_UBO_BINDING " + std::to_string(CLUSTER_UBO_BINDING) + "\n" +
|
||||
"#define CLUSTER_AABB_SSBO_BINDING " + std::to_string(CLUSTER_AABB_SSBO_BINDING) + "\n" +
|
||||
"#define CLUSTER_RANGE_SSBO_BINDING " + std::to_string(CLUSTER_RANGE_SSBO_BINDING) + "\n" +
|
||||
"#define CLUSTER_INDEX_SSBO_BINDING " + std::to_string(CLUSTER_INDEX_SSBO_BINDING) + "\n" +
|
||||
"#define CLUSTER_COUNTER_SSBO_BINDING " + std::to_string(CLUSTER_COUNTER_SSBO_BINDING) + "\n" +
|
||||
R"STRING(
|
||||
const uint LIGHT_SPOT = 0U;
|
||||
const uint LIGHT_POINT = 1U;
|
||||
@@ -56,6 +67,60 @@ void gl::glsl_common_setup()
|
||||
};
|
||||
#endif
|
||||
|
||||
// light clusters, see gl/cluster_grid.h
|
||||
struct cluster_aabb_s
|
||||
{
|
||||
vec4 lower;
|
||||
vec4 upper;
|
||||
};
|
||||
|
||||
struct cluster_range_s
|
||||
{
|
||||
uint offset;
|
||||
uint count;
|
||||
};
|
||||
|
||||
#if defined(STAGE_FRAGMENT) || defined(STAGE_COMPUTE)
|
||||
// parameters of the grid for the pass being drawn
|
||||
layout(std140, binding = CLUSTER_UBO_BINDING) uniform cluster_ubo
|
||||
{
|
||||
mat4 cluster_projection;
|
||||
float cluster_znear;
|
||||
float cluster_zfar;
|
||||
float cluster_slice_scale;
|
||||
float cluster_slice_bias;
|
||||
vec2 cluster_viewport_size;
|
||||
uint cluster_valid;
|
||||
};
|
||||
|
||||
// the compute passes fill the cluster buffers, shading only reads them
|
||||
#ifdef STAGE_COMPUTE
|
||||
#define CLUSTER_SSBO_ACCESS
|
||||
layout(std430, binding = CLUSTER_AABB_SSBO_BINDING) buffer cluster_aabb_ssbo
|
||||
{
|
||||
cluster_aabb_s cluster_aabbs[];
|
||||
};
|
||||
|
||||
layout(std430, binding = CLUSTER_COUNTER_SSBO_BINDING) buffer cluster_counter_ssbo
|
||||
{
|
||||
uint cluster_index_count;
|
||||
uint cluster_overflow_count;
|
||||
};
|
||||
#else
|
||||
#define CLUSTER_SSBO_ACCESS readonly
|
||||
#endif
|
||||
|
||||
layout(std430, binding = CLUSTER_RANGE_SSBO_BINDING) CLUSTER_SSBO_ACCESS buffer cluster_range_ssbo
|
||||
{
|
||||
cluster_range_s cluster_ranges[];
|
||||
};
|
||||
|
||||
layout(std430, binding = CLUSTER_INDEX_SSBO_BINDING) CLUSTER_SSBO_ACCESS buffer cluster_index_ssbo
|
||||
{
|
||||
uint cluster_light_indices[];
|
||||
};
|
||||
#endif
|
||||
|
||||
layout (std140) uniform model_ubo
|
||||
{
|
||||
mat4 modelview;
|
||||
|
||||
82
gl/ubo.h
82
gl/ubo.h
@@ -177,5 +177,87 @@ namespace gl
|
||||
static_assert(offsetof(light_data, lights) == 32, "bad layout of light_data");
|
||||
static_assert(sizeof(light_data) == 32 + sizeof(light_element) * MAX_LIGHTS, "bad size of light_data");
|
||||
|
||||
// light clusters (see gl/cluster_grid.h): the view frustum is cut into CLUSTER_GRID_X x CLUSTER_GRID_Y screen
|
||||
// tiles and CLUSTER_GRID_Z depth slices, spaced exponentially between the near and far plane of the pass.
|
||||
// cluster index = x + CLUSTER_GRID_X * (y + CLUSTER_GRID_Y * z), with x and y counted from the lower left corner
|
||||
const GLuint CLUSTER_GRID_X = 16;
|
||||
const GLuint CLUSTER_GRID_Y = 9;
|
||||
const GLuint CLUSTER_GRID_Z = 24;
|
||||
const GLuint CLUSTER_COUNT = CLUSTER_GRID_X * CLUSTER_GRID_Y * CLUSTER_GRID_Z;
|
||||
// capacity of the light list of a single cluster
|
||||
const GLuint MAX_LIGHTS_PER_CLUSTER = 64;
|
||||
// work group size of cluster_build.comp, which handles one cluster per invocation
|
||||
const GLuint CLUSTER_BUILD_GROUP_SIZE = 64;
|
||||
|
||||
// binding points of the cluster buffers: layout(binding = ...) in glsl_common.
|
||||
// the uniform block numbers go together with scene (0), model (1) and instance (3) blocks
|
||||
const GLuint CLUSTER_UBO_BINDING = 2;
|
||||
const GLuint CLUSTER_AABB_SSBO_BINDING = 1;
|
||||
const GLuint CLUSTER_RANGE_SSBO_BINDING = 2;
|
||||
const GLuint CLUSTER_INDEX_SSBO_BINDING = 3;
|
||||
const GLuint CLUSTER_COUNTER_SSBO_BINDING = 4;
|
||||
|
||||
// layout std140: grid parameters for the pass it was built for (GLSL: cluster_ubo)
|
||||
struct cluster_ubs
|
||||
{
|
||||
glm::mat4 projection;
|
||||
float znear;
|
||||
float zfar;
|
||||
// depth slice of a point with clip space w: floor(log(w) * slice_scale + slice_bias), znear..zfar covers all slices
|
||||
float slice_scale;
|
||||
float slice_bias;
|
||||
// size of the render target the tiles are laid over, in pixels
|
||||
float viewport_width;
|
||||
float viewport_height;
|
||||
// 0 if there is no grid for the pass (orthographic projection)
|
||||
uint32_t valid;
|
||||
UBS_PAD(4);
|
||||
};
|
||||
|
||||
static_assert(sizeof(cluster_ubs) == 96, "bad size of cluster_ubs");
|
||||
static_assert(offsetof(cluster_ubs, projection) == 0, "bad layout of cluster_ubs");
|
||||
static_assert(offsetof(cluster_ubs, znear) == 64, "bad layout of cluster_ubs");
|
||||
static_assert(offsetof(cluster_ubs, zfar) == 68, "bad layout of cluster_ubs");
|
||||
static_assert(offsetof(cluster_ubs, slice_scale) == 72, "bad layout of cluster_ubs");
|
||||
static_assert(offsetof(cluster_ubs, slice_bias) == 76, "bad layout of cluster_ubs");
|
||||
static_assert(offsetof(cluster_ubs, viewport_width) == 80, "bad layout of cluster_ubs");
|
||||
static_assert(offsetof(cluster_ubs, viewport_height) == 84, "bad layout of cluster_ubs");
|
||||
static_assert(offsetof(cluster_ubs, valid) == 88, "bad layout of cluster_ubs");
|
||||
|
||||
// layout std430: bounding box of a cluster in view space, element of cluster_aabb_ssbo (GLSL: cluster_aabb_s), w unused
|
||||
struct cluster_aabb
|
||||
{
|
||||
glm::vec4 lower;
|
||||
glm::vec4 upper;
|
||||
};
|
||||
|
||||
static_assert(sizeof(cluster_aabb) == 32, "bad size of cluster_aabb");
|
||||
static_assert(offsetof(cluster_aabb, lower) == 0, "bad layout of cluster_aabb");
|
||||
static_assert(offsetof(cluster_aabb, upper) == 16, "bad layout of cluster_aabb");
|
||||
|
||||
// layout std430: lights of a cluster, element of cluster_range_ssbo (GLSL: cluster_range_s). the lights are
|
||||
// light_data.lights indices stored in the cluster_index_ssbo list from offset on
|
||||
struct cluster_range
|
||||
{
|
||||
uint32_t offset;
|
||||
uint32_t count;
|
||||
};
|
||||
|
||||
static_assert(sizeof(cluster_range) == 8, "bad size of cluster_range");
|
||||
static_assert(offsetof(cluster_range, offset) == 0, "bad layout of cluster_range");
|
||||
static_assert(offsetof(cluster_range, count) == 4, "bad layout of cluster_range");
|
||||
|
||||
// layout std430: cluster_counter_ssbo. index_count is the allocator of the shared index list, overflow_count
|
||||
// the number of lights dropped from clusters which were full
|
||||
struct cluster_counters
|
||||
{
|
||||
uint32_t index_count;
|
||||
uint32_t overflow_count;
|
||||
};
|
||||
|
||||
static_assert(sizeof(cluster_counters) == 8, "bad size of cluster_counters");
|
||||
static_assert(offsetof(cluster_counters, index_count) == 0, "bad layout of cluster_counters");
|
||||
static_assert(offsetof(cluster_counters, overflow_count) == 4, "bad layout of cluster_counters");
|
||||
|
||||
#pragma pack(pop)
|
||||
}
|
||||
|
||||
@@ -169,6 +169,11 @@ bool opengl46_renderer::Init(GLFWwindow *Window)
|
||||
m_celestial_shader = make_shader("celestial.vert", "celestial.frag");
|
||||
m_hiddenarea_shader = make_shader("hiddenarea.vert", "hiddenarea.frag");
|
||||
m_copy_shader = make_shader("quad.vert", "copy.frag");
|
||||
{
|
||||
gl::shader cluster_build("cluster_build.comp");
|
||||
m_cluster_build_shader = std::make_unique<gl::program>(std::vector<std::reference_wrapper<const gl::shader>>{cluster_build});
|
||||
}
|
||||
m_cluster_heatmap_shader = make_shader("quad.vert", "cluster_heatmap.frag");
|
||||
m_invalid_material = Fetch_Material("invalid");
|
||||
}
|
||||
catch (gl::shader_exception const &e)
|
||||
@@ -456,6 +461,8 @@ bool opengl46_renderer::init_viewport(viewport_config &vp)
|
||||
scene_ubo->bind_uniform();
|
||||
light_ssbo->bind_base();
|
||||
|
||||
vp.clusters = std::make_unique<gl::cluster_grid>();
|
||||
|
||||
if (!Global.gfx_skippipeline)
|
||||
{
|
||||
vp.msaa_rbc = std::make_unique<gl::renderbuffer>();
|
||||
@@ -713,6 +720,18 @@ void opengl46_renderer::draw_debug_ui()
|
||||
}
|
||||
ImGui::End();
|
||||
|
||||
if (ImGui::Begin("Light clusters"))
|
||||
{
|
||||
ImGui::Checkbox("heatmap", &Global.gfx_lights_clusterheatmap);
|
||||
if (auto const &clusters{m_viewports.front()->clusters})
|
||||
{
|
||||
auto const ¶ms{clusters->params()};
|
||||
ImGui::Text("grid %u x %u x %u, %s", gl::CLUSTER_GRID_X, gl::CLUSTER_GRID_Y, gl::CLUSTER_GRID_Z, params.valid ? "built" : "none (orthographic view)");
|
||||
ImGui::Text("near %.3f m, far %.1f m", params.znear, params.zfar);
|
||||
}
|
||||
}
|
||||
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));
|
||||
@@ -897,6 +916,7 @@ void opengl46_renderer::Render_pass(viewport_config &vp, rendermode const Mode)
|
||||
future = glm::translate(mv, -glm::vec3(vehicle->get_future_movement())) * glm::inverse(mv);
|
||||
}
|
||||
|
||||
Update_Clusters( vp );
|
||||
Update_Lights( simulation::Lights );
|
||||
|
||||
glDebug("render environment");
|
||||
@@ -1049,6 +1069,9 @@ if (!Global.gfx_skippipeline)
|
||||
m_pfx_tonemapping->apply( *vp.main2_tex, target );
|
||||
}
|
||||
|
||||
if (Global.gfx_lights_clusterheatmap && vp.clusters->valid())
|
||||
Render_cluster_heatmap(vp, target, target_size);
|
||||
|
||||
if (vr) {
|
||||
if (vp.proj_type == viewport_config::vr_left)
|
||||
vr->submit(vr_interface::eye_left, vp.backbuffer_tex.get());
|
||||
@@ -1587,6 +1610,8 @@ void opengl46_renderer::setup_pass(viewport_config &Viewport, renderpass_config
|
||||
auto const znear = ( Znear > 1.f ? Znear : Znear > 0.f ? Znear * zfar : 0.1f * Global.ZoomFactor);
|
||||
if (EditorModeFlag && Viewport.main)
|
||||
zfar = editor_track_view_range(zfar, true);
|
||||
Config.znear = znear;
|
||||
Config.zfar = zfar;
|
||||
|
||||
camera.projection() = perspective_projection(Viewport.projection, znear, zfar, frustumtest_proj);
|
||||
editor_ortho_projection(Viewport, zfar, camera.projection(), frustumtest_proj);
|
||||
@@ -6004,6 +6029,78 @@ void opengl46_renderer::Update_Lights(light_array &Lights)
|
||||
light_ssbo->update(light_data);
|
||||
}
|
||||
|
||||
void opengl46_renderer::Update_Clusters(viewport_config &vp)
|
||||
{
|
||||
glDebug("Update_Clusters");
|
||||
|
||||
auto &grid{*vp.clusters};
|
||||
grid.bind();
|
||||
// the boxes of the clusters depend only on the projection and the depth range, so they are rebuilt only when those change
|
||||
if (grid.update(OpenGLMatrices.data(GL_PROJECTION), m_renderpass.znear, m_renderpass.zfar, glm::vec2{static_cast<float>(vp.width), static_cast<float>(vp.height)}))
|
||||
{
|
||||
m_clusterstimer.begin();
|
||||
grid.build(*m_cluster_build_shader);
|
||||
m_clusterstimer.end();
|
||||
}
|
||||
}
|
||||
|
||||
void opengl46_renderer::Render_cluster_heatmap(viewport_config &vp, gl::framebuffer *Target, glm::ivec2 const &Targetsize)
|
||||
{
|
||||
glDebug("Render_cluster_heatmap");
|
||||
|
||||
if (!vp.msaa_fb)
|
||||
return;
|
||||
|
||||
if (!vp.clusters_depth_fb)
|
||||
{
|
||||
auto texture{std::make_unique<opengl_texture>()};
|
||||
texture->alloc_rendertarget(Global.gfx_format_depth, GL_DEPTH_COMPONENT, vp.width, vp.height, 1, 1, GL_CLAMP_TO_EDGE);
|
||||
// render target textures of depth are made for the shadow sampler; here the values themselves are read
|
||||
glTextureParameteri(texture->id, GL_TEXTURE_COMPARE_MODE, GL_NONE);
|
||||
glTextureParameteri(texture->id, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTextureParameteri(texture->id, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
|
||||
auto framebuffer{std::make_unique<gl::framebuffer>()};
|
||||
framebuffer->attach(*texture, GL_DEPTH_ATTACHMENT);
|
||||
// depth only
|
||||
framebuffer->setup_drawing(0);
|
||||
if (!framebuffer->is_complete())
|
||||
{
|
||||
ErrorLog("cluster heatmap framebuffer setup failed");
|
||||
Global.gfx_lights_clusterheatmap = false;
|
||||
return;
|
||||
}
|
||||
vp.clusters_depth_tex = std::move(texture);
|
||||
vp.clusters_depth_fb = std::move(framebuffer);
|
||||
}
|
||||
|
||||
// the depth of the scene is still in the msaa buffer, the post processing worked on its colour
|
||||
vp.msaa_fb->blit_to(vp.clusters_depth_fb.get(), vp.width, vp.height, GL_DEPTH_BUFFER_BIT, GL_DEPTH_ATTACHMENT);
|
||||
|
||||
if (Target)
|
||||
Target->bind();
|
||||
else
|
||||
gl::framebuffer::unbind();
|
||||
glViewport(0, 0, Targetsize.x, Targetsize.y);
|
||||
|
||||
vp.clusters->bind();
|
||||
m_cluster_heatmap_shader->bind();
|
||||
m_empty_vao->bind();
|
||||
vp.clusters_depth_tex->bind(0);
|
||||
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
glDepthMask(GL_FALSE);
|
||||
auto const blending{glIsEnabled(GL_BLEND) == GL_TRUE};
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
|
||||
|
||||
if (!blending)
|
||||
glDisable(GL_BLEND);
|
||||
vp.clusters_depth_tex->unbind(0);
|
||||
}
|
||||
|
||||
bool opengl46_renderer::Init_caps()
|
||||
{
|
||||
std::string gl_renderer((char *)glGetString(GL_RENDERER));
|
||||
|
||||
@@ -22,6 +22,7 @@ http://mozilla.org/MPL/2.0/.
|
||||
#include "vr/vr_interface.h"
|
||||
#include "gl/ubo.h"
|
||||
#include "gl/ssbo.h"
|
||||
#include "gl/cluster_grid.h"
|
||||
#include "gl/framebuffer.h"
|
||||
#include "gl/renderbuffer.h"
|
||||
#include "gl/postfx.h"
|
||||
@@ -206,6 +207,10 @@ class opengl46_renderer : public gfx_renderer {
|
||||
opengl_camera viewport_camera;
|
||||
rendermode draw_mode{rendermode::none};
|
||||
float draw_range{0.0f};
|
||||
// depth range of the projection of a color pass; the far plane is only the end of the range which is drawn,
|
||||
// as the projection matrix itself has none
|
||||
float znear{0.0f};
|
||||
float zfar{0.0f};
|
||||
debug_stats draw_stats;
|
||||
};
|
||||
|
||||
@@ -256,6 +261,12 @@ class opengl46_renderer : public gfx_renderer {
|
||||
std::unique_ptr<gl::framebuffer> backbuffer_fb;
|
||||
std::unique_ptr<opengl_texture> backbuffer_tex;
|
||||
|
||||
// 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
|
||||
std::unique_ptr<gl::framebuffer> clusters_depth_fb;
|
||||
std::unique_ptr<opengl_texture> clusters_depth_tex;
|
||||
|
||||
bool initialized = false;
|
||||
};
|
||||
|
||||
@@ -331,6 +342,11 @@ class opengl46_renderer : public gfx_renderer {
|
||||
void Render_Alpha(TSubModel *Submodel);
|
||||
void Render_Alpha_text(TSubModel *Submodel); // characters of a text display
|
||||
void Update_Lights(light_array &Lights);
|
||||
// brings the light cluster grid of the viewport up to date with the projection of the color pass being drawn
|
||||
void Update_Clusters(viewport_config &vp);
|
||||
// debug view: tints the finished image of the viewport with the cluster of every pixel. Target is the framebuffer
|
||||
// holding the image (null for the default one), Targetsize its size
|
||||
void Render_cluster_heatmap(viewport_config &vp, gl::framebuffer *Target, glm::ivec2 const &Targetsize);
|
||||
glm::vec3 pick_color(std::size_t const Index);
|
||||
std::size_t pick_index(glm::ivec3 const &Color);
|
||||
|
||||
@@ -500,6 +516,8 @@ class opengl46_renderer : public gfx_renderer {
|
||||
std::unique_ptr<gl::program> m_celestial_shader;
|
||||
std::unique_ptr<gl::program> m_hiddenarea_shader;
|
||||
std::unique_ptr<gl::program> m_copy_shader;
|
||||
std::unique_ptr<gl::program> m_cluster_build_shader;
|
||||
std::unique_ptr<gl::program> m_cluster_heatmap_shader;
|
||||
|
||||
std::unique_ptr<gl::vao> m_empty_vao;
|
||||
|
||||
|
||||
57
shaders/cluster_build.comp
Normal file
57
shaders/cluster_build.comp
Normal file
@@ -0,0 +1,57 @@
|
||||
// bounding boxes of the light clusters in view space, see gl/cluster_grid.h
|
||||
// one invocation per cluster, run when the projection or the depth range of the pass changes
|
||||
|
||||
#include <common>
|
||||
|
||||
layout(local_size_x = CLUSTER_BUILD_GROUP_SIZE) in;
|
||||
|
||||
// clip space w at the lower boundary of slice k: the inverse of the slice formula used when shading
|
||||
float slice_depth(uint k)
|
||||
{
|
||||
return exp((float(k) - cluster_slice_bias) / cluster_slice_scale);
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
uint index = gl_GlobalInvocationID.x;
|
||||
if (index >= uint(CLUSTER_COUNT))
|
||||
return;
|
||||
|
||||
uvec3 cell = uvec3(
|
||||
index % uint(CLUSTER_GRID_X),
|
||||
(index / uint(CLUSTER_GRID_X)) % uint(CLUSTER_GRID_Y),
|
||||
index / uint(CLUSTER_GRID_X * CLUSTER_GRID_Y));
|
||||
|
||||
// clip x, y and w are affine functions of the view space position, while clip z is constant (reverse z with
|
||||
// the far plane at infinity), which makes the projection matrix singular. the position is recovered from
|
||||
// those three alone, so off-axis and rotated screens of custom viewports and vr eyes work too
|
||||
mat3 linear = mat3(
|
||||
vec3(cluster_projection[0][0], cluster_projection[0][1], cluster_projection[0][3]),
|
||||
vec3(cluster_projection[1][0], cluster_projection[1][1], cluster_projection[1][3]),
|
||||
vec3(cluster_projection[2][0], cluster_projection[2][1], cluster_projection[2][3]));
|
||||
vec3 translation = vec3(cluster_projection[3][0], cluster_projection[3][1], cluster_projection[3][3]);
|
||||
mat3 unproject = inverse(linear);
|
||||
|
||||
// the tile in clip space xy, the slice in clip space w
|
||||
vec2 tiles = vec2(CLUSTER_GRID_X, CLUSTER_GRID_Y);
|
||||
vec2 ndc_min = vec2(cell.xy) / tiles * 2.0 - 1.0;
|
||||
vec2 ndc_max = vec2(cell.xy + 1U) / tiles * 2.0 - 1.0;
|
||||
float w_near = slice_depth(cell.z);
|
||||
float w_far = slice_depth(cell.z + 1U);
|
||||
|
||||
vec3 lower = vec3(3.402823e38);
|
||||
vec3 upper = vec3(-3.402823e38);
|
||||
for (int corner = 0; corner < 8; ++corner)
|
||||
{
|
||||
vec2 ndc = vec2(
|
||||
(corner & 1) != 0 ? ndc_max.x : ndc_min.x,
|
||||
(corner & 2) != 0 ? ndc_max.y : ndc_min.y);
|
||||
float w = (corner & 4) != 0 ? w_far : w_near;
|
||||
vec3 position = unproject * (vec3(ndc * w, w) - translation);
|
||||
lower = min(lower, position);
|
||||
upper = max(upper, position);
|
||||
}
|
||||
|
||||
cluster_aabbs[index].lower = vec4(lower, 0.0);
|
||||
cluster_aabbs[index].upper = vec4(upper, 0.0);
|
||||
}
|
||||
38
shaders/cluster_heatmap.frag
Normal file
38
shaders/cluster_heatmap.frag
Normal file
@@ -0,0 +1,38 @@
|
||||
// debug view of the light clusters: tints every pixel of the scene with the colour of its depth slice,
|
||||
// and darkens every other screen tile to show the grid. drawn over the resolved image, see gl/cluster_grid.h
|
||||
|
||||
in vec2 f_coords;
|
||||
|
||||
#include <common>
|
||||
|
||||
#texture (depth_tex, 0, R)
|
||||
uniform sampler2D depth_tex;
|
||||
|
||||
layout(location = 0) out vec4 out_color;
|
||||
|
||||
const float TINT = 0.55;
|
||||
|
||||
// blue (near) through green to red (far)
|
||||
vec3 ramp(float t)
|
||||
{
|
||||
return clamp(vec3(1.5) - abs(4.0 * t - vec3(3.0, 2.0, 1.0)), 0.0, 1.0);
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
// reverse z: 0 is the far plane at infinity, i.e. the sky, which has no cluster
|
||||
float depth = texelFetch(depth_tex, ivec2(f_coords * vec2(textureSize(depth_tex, 0))), 0).r;
|
||||
if (depth <= 0.0)
|
||||
discard;
|
||||
|
||||
// depth is znear / w
|
||||
float w = cluster_znear / depth;
|
||||
int slice = clamp(int(floor(log(w) * cluster_slice_scale + cluster_slice_bias)), 0, CLUSTER_GRID_Z - 1);
|
||||
ivec2 tile = clamp(ivec2(floor(f_coords * vec2(CLUSTER_GRID_X, CLUSTER_GRID_Y))), ivec2(0), ivec2(CLUSTER_GRID_X - 1, CLUSTER_GRID_Y - 1));
|
||||
|
||||
vec3 color = ramp(float(slice) / float(CLUSTER_GRID_Z - 1));
|
||||
if (((tile.x + tile.y) & 1) != 0)
|
||||
color *= 0.8;
|
||||
|
||||
out_color = vec4(color, TINT);
|
||||
}
|
||||
@@ -1421,6 +1421,11 @@ global_settings::ConfigParse_gfx( cParser &Parser, std::string_view const Token
|
||||
Parser.getTokens(1);
|
||||
Parser >> gfx_envmap_enabled;
|
||||
}
|
||||
else if (Token == "gfx.lights.clusterheatmap")
|
||||
{
|
||||
Parser.getTokens(1);
|
||||
Parser >> gfx_lights_clusterheatmap;
|
||||
}
|
||||
else if (Token == "gfx.postfx.motionblur.enabled")
|
||||
{
|
||||
Parser.getTokens(1);
|
||||
@@ -1754,6 +1759,7 @@ global_settings::export_as_text( std::ostream &Output ) const {
|
||||
// TODO: export fpslimit
|
||||
export_as_text( Output, "randomseed", Global.random_seed );
|
||||
export_as_text( Output, "gfx.envmap.enabled", gfx_envmap_enabled );
|
||||
export_as_text( Output, "gfx.lights.clusterheatmap", gfx_lights_clusterheatmap );
|
||||
export_as_text( Output, "gfx.postfx.motionblur.enabled", gfx_postfx_motionblur_enabled );
|
||||
export_as_text( Output, "gfx.postfx.motionblur.shutter", gfx_postfx_motionblur_shutter );
|
||||
// TODO: export gfx_postfx_motionblur_format
|
||||
|
||||
@@ -283,6 +283,7 @@ struct global_settings {
|
||||
int gfx_framebuffer_height = -1;
|
||||
bool gfx_shadowmap_enabled = true;
|
||||
bool gfx_envmap_enabled = true;
|
||||
bool gfx_lights_clusterheatmap = false; // debug view of the light clusters
|
||||
bool gfx_postfx_motionblur_enabled = true;
|
||||
float gfx_postfx_motionblur_shutter = 0.01f;
|
||||
GLenum gfx_postfx_motionblur_format = GL_RG16F;
|
||||
|
||||
Reference in New Issue
Block a user