mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-28 16:57:29 +01:00
Use clustered light lists in forward shading
This commit is contained in:
@@ -64,8 +64,10 @@ void gl::glsl_common_setup()
|
||||
uint lights_total;
|
||||
|
||||
vec3 fog_color;
|
||||
// lights the shading loop goes through
|
||||
// lights the shading loop goes through when the lights are not clustered
|
||||
uint lights_count;
|
||||
// 1 if the shading takes the lights from the list of the cluster of a fragment, see cluster_ubo
|
||||
uint lights_clustered;
|
||||
|
||||
light_s lights[];
|
||||
};
|
||||
|
||||
12
gl/ubo.h
12
gl/ubo.h
@@ -168,8 +168,13 @@ namespace gl
|
||||
uint32_t lights_total;
|
||||
|
||||
packed_vec3 fog_color;
|
||||
// number of lights (the sun included) the shading loop over lights[] goes through
|
||||
// number of lights (the sun included) the shading loop over lights[] goes through when the lights are not clustered
|
||||
uint32_t lights_count;
|
||||
// 1 if the shading takes the lights of a fragment from the list of its cluster (lights_count is not used then), 0 if not.
|
||||
// set per pass: the cluster grid exists only for the color pass of a perspective view
|
||||
uint32_t lights_clustered;
|
||||
// lights[] begins at a multiple of 16 bytes
|
||||
uint32_t padding[3];
|
||||
|
||||
light_element lights[MAX_LIGHTS];
|
||||
};
|
||||
@@ -178,8 +183,9 @@ namespace gl
|
||||
static_assert(offsetof(light_data, lights_total) == 12, "bad layout of light_data");
|
||||
static_assert(offsetof(light_data, fog_color) == 16, "bad layout of light_data");
|
||||
static_assert(offsetof(light_data, lights_count) == 28, "bad layout of light_data");
|
||||
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");
|
||||
static_assert(offsetof(light_data, lights_clustered) == 32, "bad layout of light_data");
|
||||
static_assert(offsetof(light_data, lights) == 48, "bad layout of light_data");
|
||||
static_assert(sizeof(light_data) == 48 + 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.
|
||||
|
||||
@@ -694,8 +694,9 @@ void opengl46_renderer::SwapBuffers()
|
||||
+ " lines: " + to_string( m_colorpass.draw_stats.lines, 7 ) + "\n"
|
||||
+ "particles: " + to_string( m_colorpass.draw_stats.particles, 7 )
|
||||
+ "\nlights: " + to_string( static_cast<int>( m_lights_lit ), 7 )
|
||||
+ " (" + std::to_string( m_lights_dropped ) + " over capacity)";
|
||||
if( auto const cullstats = ( m_viewports.empty() ? std::nullopt : m_viewports.front()->clusters->stats() ) ) {
|
||||
+ " (" + std::to_string( m_lights_dropped ) + " over capacity), "
|
||||
+ ( Global.gfx_lights_clustered ? "clustered" : "first " + std::to_string( std::max( Global.DynamicLightCount, 0 ) ) );
|
||||
if( auto const cullstats = ( m_viewports.empty() || !( Global.gfx_lights_clustered || Global.gfx_lights_clusterheatmap ) ? std::nullopt : m_viewports.front()->clusters->stats() ) ) {
|
||||
m_debugstatstext +=
|
||||
"\ncluster lists: " + std::to_string( cullstats->index_count )
|
||||
+ ", overflow " + std::to_string( cullstats->overflow_count )
|
||||
@@ -726,6 +727,7 @@ void opengl46_renderer::draw_debug_ui()
|
||||
|
||||
if (ImGui::Begin("Light clusters"))
|
||||
{
|
||||
ImGui::Checkbox("clustered lights (off: the first dynamiclights headlights)", &Global.gfx_lights_clustered);
|
||||
ImGui::Checkbox("heatmap", &Global.gfx_lights_clusterheatmap);
|
||||
if (auto const &clusters{m_viewports.front()->clusters})
|
||||
{
|
||||
@@ -926,9 +928,17 @@ 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 );
|
||||
Cull_Lights( vp );
|
||||
// the cluster grid and its lists of lights are kept up only while something reads them: the shading or the heatmap.
|
||||
// the lists exist for a perspective view; without them the shading goes through the first few lights, as does the pass
|
||||
// the environment map is drawn in
|
||||
auto const clusterlists{ Global.gfx_lights_clustered || Global.gfx_lights_clusterheatmap };
|
||||
if( clusterlists ) {
|
||||
Update_Clusters( vp );
|
||||
}
|
||||
Update_Lights( simulation::Lights, true, Global.gfx_lights_clustered && vp.clusters->valid() );
|
||||
if( clusterlists ) {
|
||||
Cull_Lights( vp );
|
||||
}
|
||||
|
||||
glDebug("render environment");
|
||||
|
||||
@@ -1201,6 +1211,10 @@ if (!Global.gfx_skippipeline)
|
||||
setup_env_map(m_empty_cubemap.get());
|
||||
|
||||
setup_matrices();
|
||||
// the light buffer holds what the last color pass left in it, in the view space of its camera, while the face is drawn
|
||||
// in its own. the lights go in anew; the map is a cache of the surroundings which lasts for seconds, so the headlights
|
||||
// of the vehicles around are left out of it and the cost of the pass does not depend on how many of them are on
|
||||
Update_Lights(simulation::Lights, false);
|
||||
|
||||
// render
|
||||
// environment...
|
||||
@@ -5612,6 +5626,7 @@ void opengl46_renderer::Preview_Color(TAnimModel *Instance, float const Squaredi
|
||||
light_data.fog_color = glm::vec3{1.f};
|
||||
light_data.lights_total = 1;
|
||||
light_data.lights_count = 1;
|
||||
light_data.lights_clustered = 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;
|
||||
@@ -5893,7 +5908,7 @@ bool opengl46_renderer::Render_Preview(TAnimModel *Instance, int const Size, int
|
||||
return true;
|
||||
}
|
||||
|
||||
void opengl46_renderer::Update_Lights(light_array &Lights)
|
||||
void opengl46_renderer::Update_Lights(light_array &Lights, bool const Headlights, bool const Clustered)
|
||||
{
|
||||
glDebug("Update_Lights");
|
||||
|
||||
@@ -5901,15 +5916,18 @@ void opengl46_renderer::Update_Lights(light_array &Lights)
|
||||
|
||||
// arrange the light array from closest to farthest from current position of the camera
|
||||
auto const camera = m_colorpass.pass_camera.position();
|
||||
std::sort(
|
||||
std::begin(Lights.data), std::end(Lights.data),
|
||||
[&camera](light_array::light_record const &Left, light_array::light_record const &Right) {
|
||||
// move lights which are off at the end...
|
||||
if (Left.intensity == 0.f) { return false; }
|
||||
if (Right.intensity == 0.f) { return true; }
|
||||
// ...otherwise prefer closer and/or brigher light sources
|
||||
return (glm::length2(camera - Left.position) / Left.intensity) < (glm::length2(camera - Right.position) / Right.intensity);
|
||||
});
|
||||
if (Headlights)
|
||||
{
|
||||
std::sort(
|
||||
std::begin(Lights.data), std::end(Lights.data),
|
||||
[&camera](light_array::light_record const &Left, light_array::light_record const &Right) {
|
||||
// move lights which are off at the end...
|
||||
if (Left.intensity == 0.f) { return false; }
|
||||
if (Right.intensity == 0.f) { return true; }
|
||||
// ...otherwise prefer closer and/or brigher light sources
|
||||
return (glm::length2(camera - Left.position) / Left.intensity) < (glm::length2(camera - Right.position) / Right.intensity);
|
||||
});
|
||||
}
|
||||
|
||||
// set up helpers
|
||||
glm::mat4 coordmove;
|
||||
@@ -5931,7 +5949,7 @@ void opengl46_renderer::Update_Lights(light_array &Lights)
|
||||
|
||||
for (auto const &scenelight : Lights.data)
|
||||
{
|
||||
if (scenelight.intensity == 0.f) {
|
||||
if (!Headlights || scenelight.intensity == 0.f) {
|
||||
// all lights past this one are bound to be off
|
||||
break;
|
||||
}
|
||||
@@ -6032,6 +6050,7 @@ void opengl46_renderer::Update_Lights(light_array &Lights)
|
||||
light_data.lights_total = static_cast<uint32_t>(light_i);
|
||||
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;
|
||||
// fill sunlight data
|
||||
light_data.ambient = m_sunlight.ambient * m_sunlight.factor;// *simulation::Environment.light_intensity();
|
||||
light_data.lights[0].type = gl::light_element::DIR;
|
||||
|
||||
@@ -341,7 +341,10 @@ class opengl46_renderer : public gfx_renderer {
|
||||
bool Render_Alpha(TModel3d *Model, material_data const *Material, float const Squaredistance);
|
||||
void Render_Alpha(TSubModel *Submodel);
|
||||
void Render_Alpha_text(TSubModel *Submodel); // characters of a text display
|
||||
void Update_Lights(light_array &Lights);
|
||||
// writes the lighting of the pass about to be drawn to the light buffer: the sun, the ambient light and the fog, and the headlights of Lights
|
||||
// unless Headlights is false, as in the pass which draws the environment map, where lights of vehicles would only get baked into the map.
|
||||
// Clustered tells the shading to take the headlights from the lists of the clusters (see Cull_Lights()) and not to go through the first few
|
||||
void Update_Lights(light_array &Lights, bool const Headlights = true, bool const Clustered = false);
|
||||
// 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);
|
||||
// assigns the lights set up by Update_Lights() to the clusters of the viewport; run after it, before the scene is drawn
|
||||
|
||||
@@ -194,7 +194,10 @@ vec2 calc_dir_light(light_s light, vec3 fragnormal)
|
||||
return calc_light(light_dir, fragnormal);
|
||||
}
|
||||
|
||||
vec2 calc_headlights(light_s light, vec3 fragnormal)
|
||||
// dpdx and dpdy are the change of the view space position of the fragment from a pixel to the next one. the beam texture is
|
||||
// sampled with gradients worked out from them instead of the ones of the hardware: those are undefined where the fragments of
|
||||
// a quad do not go through the same lights, which is what happens at the edges of the clusters
|
||||
vec2 calc_headlights(light_s light, vec3 fragnormal, vec3 dpdx, vec3 dpdy)
|
||||
{
|
||||
vec4 headlightpos = light.headlight_projection * f_pos;
|
||||
vec3 coords = headlightpos.xyz / headlightpos.w;
|
||||
@@ -203,6 +206,10 @@ vec2 calc_headlights(light_s light, vec3 fragnormal)
|
||||
return vec2(0.0);
|
||||
if (coords.z < 0.0)
|
||||
return vec2(0.0);
|
||||
// the beam is what the projection takes in. beyond its sides the texture would be clamped to its edge, and near the projector
|
||||
// (where a pixel spans many texels) its coarsest levels spill the average of the whole beam over the surroundings
|
||||
if (coords.x < 0.0 || coords.x > 1.0 || coords.y < 0.0 || coords.y > 1.0)
|
||||
return vec2(0.0);
|
||||
|
||||
vec3 light_dir = normalize(light.pos - f_pos.xyz);
|
||||
// Tighter wrap (was +0.25): faces angled away from the headlight cone
|
||||
@@ -212,11 +219,58 @@ vec2 calc_headlights(light_s light, vec3 fragnormal)
|
||||
float distance = length(light.pos - f_pos.xyz);
|
||||
float atten = 1.0f / (1.0f + light.linear * distance + light.quadratic * (distance * distance));
|
||||
atten *= mix(1.0, 0.0, clamp((coords.z - 0.998) * 500.0, 0.0, 1.0));
|
||||
vec3 lights = textureProj(headlightmap, headlightpos).rgb * light.headlight_weights.rgb;
|
||||
vec2 beamcoords = headlightpos.xy / headlightpos.w;
|
||||
vec4 dheadlightposdx = light.headlight_projection * vec4(dpdx, 0.0);
|
||||
vec4 dheadlightposdy = light.headlight_projection * vec4(dpdy, 0.0);
|
||||
vec2 dbeamdx = (dheadlightposdx.xy - beamcoords * dheadlightposdx.w) / headlightpos.w;
|
||||
vec2 dbeamdy = (dheadlightposdy.xy - beamcoords * dheadlightposdy.w) / headlightpos.w;
|
||||
vec3 lights = textureGrad(headlightmap, beamcoords, dbeamdx, dbeamdy).rgb * light.headlight_weights.rgb;
|
||||
float lightintensity = max(max(lights.r, lights.g), lights.b);
|
||||
return part * atten * lightintensity;
|
||||
}
|
||||
|
||||
// index of the light cluster the fragment is in, see gl/cluster_grid.h: its screen tile and slice of depth. the depth is the clip
|
||||
// space w, which is 1 / gl_FragCoord.w. a tile is kept in the grid, as the far edge of the render target and rounding
|
||||
// right below it give one past the last
|
||||
uint fragment_cluster()
|
||||
{
|
||||
ivec2 tile = ivec2(gl_FragCoord.xy * (vec2(CLUSTER_GRID_X, CLUSTER_GRID_Y) / cluster_viewport_size));
|
||||
tile = clamp(tile, ivec2(0), ivec2(CLUSTER_GRID_X - 1, CLUSTER_GRID_Y - 1));
|
||||
int slice = clamp(int(floor(cluster_slice_bias - log(gl_FragCoord.w) * cluster_slice_scale)), 0, CLUSTER_GRID_Z - 1);
|
||||
return uint(tile.x + CLUSTER_GRID_X * (tile.y + CLUSTER_GRID_Y * slice));
|
||||
}
|
||||
|
||||
// adds the light of the headlights which reach the fragment to its colour. these are the ones in the list of its cluster
|
||||
// when the lights are clustered (the sun is not on the list, the callers take care of it), and the first lights_count - 1 of
|
||||
// lights[] otherwise
|
||||
vec3 add_headlights(vec3 fragcolor, vec3 fragnormal)
|
||||
{
|
||||
// worked out before the loops, which the fragments of a quad do not leave together
|
||||
vec3 dpdx = dFdx(f_pos.xyz);
|
||||
vec3 dpdy = dFdy(f_pos.xyz);
|
||||
|
||||
if (lights_clustered != 0U)
|
||||
{
|
||||
cluster_range_s range = cluster_ranges[fragment_cluster()];
|
||||
for (uint i = 0U; i < range.count; i++)
|
||||
{
|
||||
light_s light = lights[cluster_light_indices[range.offset + i]];
|
||||
vec2 part = calc_headlights(light, fragnormal, dpdx, dpdy);
|
||||
fragcolor += light.color * (part.x + part.y * param[1].y) * light.intensity;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (uint i = 1U; i < lights_count; i++)
|
||||
{
|
||||
light_s light = lights[i];
|
||||
vec2 part = calc_headlights(light, fragnormal, dpdx, dpdy);
|
||||
fragcolor += light.color * (part.x + part.y * param[1].y) * light.intensity;
|
||||
}
|
||||
}
|
||||
return fragcolor;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Split-sum environment BRDF (Karis / UE4 analytic approximation).
|
||||
//
|
||||
@@ -312,12 +366,7 @@ vec3 apply_lights(vec3 fragcolor, vec3 fragnormal, vec3 texturecolor, float refl
|
||||
// direct sun read as too hot/burnt; raise it for more punch.
|
||||
fragcolor += lights[0].color * SUN_DIFFUSE_SCALE * (1.0 - shadow1) * diffuseamount;
|
||||
|
||||
for (uint i = 1U; i < lights_count; i++)
|
||||
{
|
||||
light_s light = lights[i];
|
||||
vec2 part = calc_headlights(light, fragnormal);
|
||||
fragcolor += light.color * (part.x + part.y * param[1].y) * light.intensity;
|
||||
}
|
||||
fragcolor = add_headlights(fragcolor, fragnormal);
|
||||
|
||||
float specularamount = sunlight.y * param[1].y * specularity * lights[0].intensity
|
||||
* clamp(1.0 - shadowtone, 0.0, 1.0);
|
||||
|
||||
@@ -66,22 +66,7 @@ vec3 apply_lights_sunless(vec3 fragcolor, vec3 fragnormal, vec3 texturecolor, fl
|
||||
float specularamount = (sunlight.y * param[1].y * specularity) * lights[0].intensity;
|
||||
glossiness = abs(param[1].w);
|
||||
|
||||
for (uint i = 1U; i < lights_count; i++)
|
||||
{
|
||||
light_s light = lights[i];
|
||||
vec2 part = vec2(0.0);
|
||||
|
||||
// if (light.type == LIGHT_SPOT)
|
||||
// part = calc_spot_light(light, fragnormal);
|
||||
// else if (light.type == LIGHT_POINT)
|
||||
// part = calc_point_light(light, fragnormal);
|
||||
// else if (light.type == LIGHT_DIR)
|
||||
// part = calc_dir_light(light, fragnormal);
|
||||
// else if (light.type == LIGHT_HEADLIGHTS)
|
||||
part = calc_headlights(light, fragnormal);
|
||||
|
||||
fragcolor += light.color * (part.x + part.y * param[1].y) * light.intensity;
|
||||
}
|
||||
fragcolor = add_headlights(fragcolor, fragnormal);
|
||||
|
||||
if (shadowtone < 1.0)
|
||||
{
|
||||
|
||||
@@ -76,12 +76,7 @@ vec3 apply_lights_sunless(vec3 fragcolor, vec3 fragnormal, vec3 texturecolor, fl
|
||||
|
||||
float specularamount = sunlight.y * param[1].y * specularity * lights[0].intensity;
|
||||
|
||||
for (uint i = 1U; i < lights_count; i++)
|
||||
{
|
||||
light_s light = lights[i];
|
||||
vec2 part = calc_headlights(light, fragnormal);
|
||||
fragcolor += light.color * (part.x + part.y * param[1].y) * light.intensity;
|
||||
}
|
||||
fragcolor = add_headlights(fragcolor, fragnormal);
|
||||
|
||||
if (shadowtone < 1.0)
|
||||
{
|
||||
|
||||
@@ -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.clustered")
|
||||
{
|
||||
Parser.getTokens(1);
|
||||
Parser >> gfx_lights_clustered;
|
||||
}
|
||||
else if (Token == "gfx.lights.clusterheatmap")
|
||||
{
|
||||
Parser.getTokens(1);
|
||||
@@ -1759,6 +1764,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.clustered", gfx_lights_clustered );
|
||||
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 );
|
||||
|
||||
@@ -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_clustered = true; // headlights from the light lists of the clusters (no limit of lit headlights) or the first dynamiclights of them
|
||||
bool gfx_lights_clusterheatmap = false; // debug view of the light clusters
|
||||
bool gfx_postfx_motionblur_enabled = true;
|
||||
float gfx_postfx_motionblur_shutter = 0.01f;
|
||||
|
||||
Reference in New Issue
Block a user