mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-11-01 06:52:45 +01:00
Cull lights into clusters in a compute pass
This commit is contained in:
151
shaders/cluster_cull.comp
Normal file
151
shaders/cluster_cull.comp
Normal file
@@ -0,0 +1,151 @@
|
||||
// assigns the lights to the light clusters, see gl/cluster_grid.h
|
||||
// one invocation per cluster: tests every light of light_ssbo (the sun, slot 0, is handled apart from the clusters)
|
||||
// against the bounding box of the cluster in view space, and writes the indices of the lights which reach it.
|
||||
// run every frame after the lights were updated, for the grid of the pass about to be drawn
|
||||
|
||||
#include <common>
|
||||
|
||||
layout(local_size_x = CLUSTER_CULL_GROUP_SIZE) in;
|
||||
|
||||
// a light is taken to end where its brightest possible contribution drops under this, a fraction of
|
||||
// what the display can tell apart. no light reaches further than MAX_RANGE, as with the 1 km filter on the cpu side
|
||||
const float MIN_CONTRIBUTION = 1.0 / 1024.0;
|
||||
const float MAX_RANGE = 1000.0;
|
||||
// the boxes and the light data are floats: planes of the headlight projection let this much (in meters) of a box through
|
||||
const float PLANE_MARGIN = 0.05;
|
||||
|
||||
// brightest value the light adds to a surface it hits, as the shading computes it
|
||||
float light_peak(light_s light)
|
||||
{
|
||||
float peak = max(light.color.r, max(light.color.g, light.color.b)) * light.intensity;
|
||||
if (light.type == LIGHT_HEADLIGHTS)
|
||||
// weights are the state of the individual lamps and the beam texture, which is not brighter than 1, picks from them
|
||||
return peak * max(light.headlight_weights.r, max(light.headlight_weights.g, light.headlight_weights.b));
|
||||
return peak * (1.0 + light.ambient);
|
||||
}
|
||||
|
||||
// distance from the light at which its contribution falls under MIN_CONTRIBUTION, 0 for a light which has none
|
||||
float light_range(light_s light)
|
||||
{
|
||||
float peak = light_peak(light);
|
||||
if (!(peak > MIN_CONTRIBUTION))
|
||||
return 0.0;
|
||||
// attenuation which the contribution can drop to without being lost: 1 / k
|
||||
float k = peak / MIN_CONTRIBUTION;
|
||||
|
||||
if (light.type == LIGHT_HEADLIGHTS)
|
||||
{
|
||||
// 1 + linear * d + quadratic * d^2 <= k, the positive root written the way which has no cancellation for a small quadratic
|
||||
float a = light.quadratic;
|
||||
float b = light.linear;
|
||||
float c = 1.0 - k;
|
||||
float denominator = b + sqrt(b * b - 4.0 * a * c);
|
||||
return (denominator > 1e-12) ? min(-2.0 * c / denominator, MAX_RANGE) : MAX_RANGE;
|
||||
}
|
||||
// point and spot lights fall off with the square of the distance
|
||||
return min(sqrt(k), MAX_RANGE);
|
||||
}
|
||||
|
||||
bool sphere_touches_box(vec3 center, float radius, vec3 lower, vec3 upper)
|
||||
{
|
||||
vec3 offset = center - clamp(center, lower, upper);
|
||||
return dot(offset, offset) <= radius * radius;
|
||||
}
|
||||
|
||||
// whether any part of the box, given by its center and half extents, is on the positive side of the plane
|
||||
bool box_reaches_plane(vec4 plane, vec3 center, vec3 extent)
|
||||
{
|
||||
return dot(plane.xyz, center) + plane.w + dot(abs(plane.xyz), extent) + PLANE_MARGIN * length(plane.xyz) >= 0.0;
|
||||
}
|
||||
|
||||
// planes bounding the beam of a headlight: the projection takes a view space position to the texture coordinates
|
||||
// of the beam, and the beam is where those fall in 0..1 and the position is in front of the projector. clip z of that
|
||||
// projection is a constant (reverse z, no far plane), so only the four sides and the near plane are real planes
|
||||
bool box_in_headlight_beam(mat4 projection, vec3 center, vec3 extent)
|
||||
{
|
||||
vec4 row_x = vec4(projection[0][0], projection[1][0], projection[2][0], projection[3][0]);
|
||||
vec4 row_y = vec4(projection[0][1], projection[1][1], projection[2][1], projection[3][1]);
|
||||
vec4 row_z = vec4(projection[0][2], projection[1][2], projection[2][2], projection[3][2]);
|
||||
vec4 row_w = vec4(projection[0][3], projection[1][3], projection[2][3], projection[3][3]);
|
||||
|
||||
return box_reaches_plane(row_x, center, extent)
|
||||
&& box_reaches_plane(row_w - row_x, center, extent)
|
||||
&& box_reaches_plane(row_y, center, extent)
|
||||
&& box_reaches_plane(row_w - row_y, center, extent)
|
||||
&& box_reaches_plane(row_z, center, extent)
|
||||
&& box_reaches_plane(row_w - row_z, center, extent);
|
||||
}
|
||||
|
||||
// whether the sphere touches the infinite cone with the apex at the origin, the unit axis and the cosine of the half angle
|
||||
bool sphere_touches_cone(vec3 center, float radius, vec3 axis, float cosine)
|
||||
{
|
||||
float sine = max(sqrt(max(1.0 - cosine * cosine, 0.0)), 1e-4);
|
||||
float along = dot(center, axis);
|
||||
float across = sqrt(max(dot(center, center) - along * along, 0.0));
|
||||
// distance to the boundary ray if the closest point of the cone is on it, and to the apex otherwise
|
||||
float distance_to_cone = (along * cosine + across * sine > 0.0) ? across * cosine - along * sine : length(center);
|
||||
return distance_to_cone <= radius;
|
||||
}
|
||||
|
||||
bool light_reaches_box(light_s light, float range, vec3 lower, vec3 upper)
|
||||
{
|
||||
vec3 center = 0.5 * (lower + upper);
|
||||
vec3 extent = 0.5 * (upper - lower);
|
||||
|
||||
if (light.type == LIGHT_DIR)
|
||||
return true;
|
||||
|
||||
if (!sphere_touches_box(light.pos, range, lower, upper))
|
||||
return false;
|
||||
|
||||
if (light.type == LIGHT_HEADLIGHTS)
|
||||
return box_in_headlight_beam(light.headlight_projection, center, extent);
|
||||
|
||||
if (light.type == LIGHT_SPOT)
|
||||
// the cone is tested against the sphere around the box, which lets some of the corners through
|
||||
return sphere_touches_cone(center - light.pos, length(extent), normalize(light.dir), light.out_cutoff);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
uint index = gl_GlobalInvocationID.x;
|
||||
if (index >= uint(CLUSTER_COUNT))
|
||||
return;
|
||||
|
||||
vec3 lower = cluster_aabbs[index].lower.xyz;
|
||||
vec3 upper = cluster_aabbs[index].upper.xyz;
|
||||
|
||||
// the lights are in the order of their importance, so when a cluster is full the least important are the ones left out
|
||||
uint found[MAX_LIGHTS_PER_CLUSTER];
|
||||
uint found_count = 0U;
|
||||
uint dropped = 0U;
|
||||
|
||||
uint lights_end = min(lights_total, uint(lights.length()));
|
||||
for (uint i = 1U; i < lights_end; ++i)
|
||||
{
|
||||
float range = light_range(lights[i]);
|
||||
if (range <= 0.0 && lights[i].type != LIGHT_DIR)
|
||||
continue;
|
||||
if (!light_reaches_box(lights[i], range, lower, upper))
|
||||
continue;
|
||||
|
||||
if (found_count < uint(MAX_LIGHTS_PER_CLUSTER))
|
||||
found[found_count++] = i;
|
||||
else
|
||||
++dropped;
|
||||
}
|
||||
|
||||
uint offset = 0U;
|
||||
if (found_count > 0U)
|
||||
{
|
||||
offset = atomicAdd(cluster_index_count, found_count);
|
||||
for (uint k = 0U; k < found_count; ++k)
|
||||
cluster_light_indices[offset + k] = found[k];
|
||||
}
|
||||
if (dropped > 0U)
|
||||
atomicAdd(cluster_overflow_count, dropped);
|
||||
|
||||
cluster_ranges[index] = cluster_range_s(offset, found_count);
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
// 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
|
||||
// debug view of the light clusters: tints every pixel of the scene with the colour of the number of lights which
|
||||
// were assigned to its cluster (see gl/cluster_grid.h), pixels of clusters without lights stay as they are.
|
||||
// drawn over the resolved image
|
||||
|
||||
in vec2 f_coords;
|
||||
|
||||
@@ -12,7 +13,7 @@ layout(location = 0) out vec4 out_color;
|
||||
|
||||
const float TINT = 0.55;
|
||||
|
||||
// blue (near) through green to red (far)
|
||||
// blue (few) through green to red (many)
|
||||
vec3 ramp(float t)
|
||||
{
|
||||
return clamp(vec3(1.5) - abs(4.0 * t - vec3(3.0, 2.0, 1.0)), 0.0, 1.0);
|
||||
@@ -30,9 +31,14 @@ void main()
|
||||
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;
|
||||
uint count = cluster_ranges[tile.x + CLUSTER_GRID_X * (tile.y + CLUSTER_GRID_Y * slice)].count;
|
||||
if (count == 0U)
|
||||
discard;
|
||||
|
||||
// a full cluster (lights beyond its capacity are dropped) is white; the rest goes by the logarithm of the count
|
||||
vec3 color = (count >= uint(MAX_LIGHTS_PER_CLUSTER))
|
||||
? vec3(1.0)
|
||||
: ramp(log2(1.0 + float(count)) / log2(1.0 + float(MAX_LIGHTS_PER_CLUSTER)));
|
||||
|
||||
out_color = vec4(color, TINT);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user