mirror of
https://github.com/bevyengine/bevy
synced 2024-11-25 22:20:20 +00:00
bevy_pbr2: Fix clustering for orthographic projections (#3316)
# Objective PBR lighting was broken in the new renderer when using orthographic projections due to the way the depth slicing works for the clusters. Fix it. ## Solution - The default orthographic projection near plane is 0.0. The perspective projection depth slicing does a division by the near plane which gives a floating point NaN and the clustering all breaks down. - Orthographic projections have a linear depth mapping, so it made intuitive sense to me to do depth slicing with a linear mapping too. The alternative I saw was to try to handle the near plane being at 0.0 and using the exponential depth slicing, but that felt like a hack that didn't make sense. - As such, I have added code that detects whether the projection is orthographic based on `projection[3][3] == 1.0` and then implemented the orthographic mapping case throughout (when computing cluster AABBs, and when mapping a view space position (or light) to a cluster id in both the rust and shader code). ## Screenshots Before: ![before](https://user-images.githubusercontent.com/302146/145847278-5b1bca74-fbad-4cc5-8b49-384f6a377fdc.png) After: <img width="1392" alt="Screenshot 2021-12-13 at 16 36 53" src="https://user-images.githubusercontent.com/302146/145847314-6f3a2035-5d87-4896-8032-0c3e35e15b7d.png"> Old renderer (slightly lighter due to slight difference in configured intensity): <img width="1392" alt="Screenshot 2021-12-13 at 16 42 23" src="https://user-images.githubusercontent.com/302146/145847391-6a5e6fe0-22da-4fc1-a6c7-440543689a63.png">
This commit is contained in:
parent
c825fda74a
commit
c061ec33c8
5 changed files with 165 additions and 75 deletions
|
@ -12,7 +12,8 @@ use bevy_transform::components::GlobalTransform;
|
|||
use bevy_window::Windows;
|
||||
|
||||
use crate::{
|
||||
CubeMapFace, CubemapVisibleEntities, ViewClusterBindings, CUBE_MAP_FACES, POINT_LIGHT_NEAR_Z,
|
||||
calculate_cluster_factors, CubeMapFace, CubemapVisibleEntities, ViewClusterBindings,
|
||||
CUBE_MAP_FACES, POINT_LIGHT_NEAR_Z,
|
||||
};
|
||||
|
||||
/// A light that emits light in all directions from a central point.
|
||||
|
@ -265,12 +266,14 @@ fn line_intersection_to_z_plane(origin: Vec3, p: Vec3, z: f32) -> Vec3 {
|
|||
origin + t * v
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn compute_aabb_for_cluster(
|
||||
z_near: f32,
|
||||
z_far: f32,
|
||||
tile_size: Vec2,
|
||||
screen_size: Vec2,
|
||||
inverse_projection: Mat4,
|
||||
is_orthographic: bool,
|
||||
cluster_dimensions: UVec3,
|
||||
ijk: UVec3,
|
||||
) -> Aabb {
|
||||
|
@ -280,6 +283,31 @@ fn compute_aabb_for_cluster(
|
|||
let p_min = ijk.xy() * tile_size;
|
||||
let p_max = p_min + tile_size;
|
||||
|
||||
let cluster_min;
|
||||
let cluster_max;
|
||||
if is_orthographic {
|
||||
// Use linear depth slicing for orthographic
|
||||
|
||||
// Convert to view space at the cluster near and far planes
|
||||
// NOTE: 1.0 is the near plane due to using reverse z projections
|
||||
let p_min = screen_to_view(
|
||||
screen_size,
|
||||
inverse_projection,
|
||||
p_min,
|
||||
1.0 - (ijk.z / cluster_dimensions.z as f32),
|
||||
)
|
||||
.xyz();
|
||||
let p_max = screen_to_view(
|
||||
screen_size,
|
||||
inverse_projection,
|
||||
p_max,
|
||||
1.0 - ((ijk.z + 1.0) / cluster_dimensions.z as f32),
|
||||
)
|
||||
.xyz();
|
||||
|
||||
cluster_min = p_min.min(p_max);
|
||||
cluster_max = p_min.max(p_max);
|
||||
} else {
|
||||
// Convert to view space at the near plane
|
||||
// NOTE: 1.0 is the near plane due to using reverse z projections
|
||||
let p_min = screen_to_view(screen_size, inverse_projection, p_min, 1.0);
|
||||
|
@ -288,8 +316,9 @@ fn compute_aabb_for_cluster(
|
|||
let z_far_over_z_near = -z_far / -z_near;
|
||||
let cluster_near = -z_near * z_far_over_z_near.powf(ijk.z / cluster_dimensions.z as f32);
|
||||
// NOTE: This could be simplified to:
|
||||
// let cluster_far = cluster_near * z_far_over_z_near;
|
||||
let cluster_far = -z_near * z_far_over_z_near.powf((ijk.z + 1.0) / cluster_dimensions.z as f32);
|
||||
// cluster_far = cluster_near * z_far_over_z_near;
|
||||
let cluster_far =
|
||||
-z_near * z_far_over_z_near.powf((ijk.z + 1.0) / cluster_dimensions.z as f32);
|
||||
|
||||
// Calculate the four intersection points of the min and max points with the cluster near and far planes
|
||||
let p_min_near = line_intersection_to_z_plane(Vec3::ZERO, p_min.xyz(), cluster_near);
|
||||
|
@ -297,8 +326,9 @@ fn compute_aabb_for_cluster(
|
|||
let p_max_near = line_intersection_to_z_plane(Vec3::ZERO, p_max.xyz(), cluster_near);
|
||||
let p_max_far = line_intersection_to_z_plane(Vec3::ZERO, p_max.xyz(), cluster_far);
|
||||
|
||||
let cluster_min = p_min_near.min(p_min_far).min(p_max_near.min(p_max_far));
|
||||
let cluster_max = p_min_near.max(p_min_far).max(p_max_near.max(p_max_far));
|
||||
cluster_min = p_min_near.min(p_min_far).min(p_max_near.min(p_max_far));
|
||||
cluster_max = p_min_near.max(p_min_far).max(p_max_near.max(p_max_far));
|
||||
}
|
||||
|
||||
Aabb::from_min_max(cluster_min, cluster_max)
|
||||
}
|
||||
|
@ -322,6 +352,7 @@ pub fn add_clusters(
|
|||
|
||||
pub fn update_clusters(windows: Res<Windows>, mut views: Query<(&Camera, &mut Clusters)>) {
|
||||
for (camera, mut clusters) in views.iter_mut() {
|
||||
let is_orthographic = camera.projection_matrix.w_axis.w == 1.0;
|
||||
let inverse_projection = camera.projection_matrix.inverse();
|
||||
let window = windows.get(camera.window).unwrap();
|
||||
let screen_size_u32 = UVec2::new(window.physical_width(), window.physical_height());
|
||||
|
@ -348,6 +379,7 @@ pub fn update_clusters(windows: Res<Windows>, mut views: Query<(&Camera, &mut Cl
|
|||
tile_size,
|
||||
screen_size,
|
||||
inverse_projection,
|
||||
is_orthographic,
|
||||
clusters.axis_slices,
|
||||
UVec3::new(x, y, z),
|
||||
));
|
||||
|
@ -383,14 +415,20 @@ impl VisiblePointLights {
|
|||
}
|
||||
}
|
||||
|
||||
fn view_z_to_z_slice(cluster_factors: Vec2, view_z: f32) -> u32 {
|
||||
fn view_z_to_z_slice(cluster_factors: Vec2, view_z: f32, is_orthographic: bool) -> u32 {
|
||||
if is_orthographic {
|
||||
// NOTE: view_z is correct in the orthographic case
|
||||
((view_z - cluster_factors.x) * cluster_factors.y).floor() as u32
|
||||
} else {
|
||||
// NOTE: had to use -view_z to make it positive else log(negative) is nan
|
||||
((-view_z).ln() * cluster_factors.x - cluster_factors.y).floor() as u32
|
||||
}
|
||||
}
|
||||
|
||||
fn ndc_position_to_cluster(
|
||||
cluster_dimensions: UVec3,
|
||||
cluster_factors: Vec2,
|
||||
is_orthographic: bool,
|
||||
ndc_p: Vec3,
|
||||
view_z: f32,
|
||||
) -> UVec3 {
|
||||
|
@ -398,7 +436,7 @@ fn ndc_position_to_cluster(
|
|||
let frag_coord =
|
||||
(ndc_p.xy() * Vec2::new(0.5, -0.5) + Vec2::splat(0.5)).clamp(Vec2::ZERO, Vec2::ONE);
|
||||
let xy = (frag_coord * cluster_dimensions_f32.xy()).floor();
|
||||
let z_slice = view_z_to_z_slice(cluster_factors, view_z);
|
||||
let z_slice = view_z_to_z_slice(cluster_factors, view_z, is_orthographic);
|
||||
xy.as_uvec2()
|
||||
.extend(z_slice)
|
||||
.clamp(UVec3::ZERO, cluster_dimensions - UVec3::ONE)
|
||||
|
@ -421,11 +459,12 @@ pub fn assign_lights_to_clusters(
|
|||
let view_transform = view_transform.compute_matrix();
|
||||
let inverse_view_transform = view_transform.inverse();
|
||||
let cluster_count = clusters.aabbs.len();
|
||||
let z_slices_of_ln_zfar_over_znear =
|
||||
clusters.axis_slices.z as f32 / (camera.far / camera.near).ln();
|
||||
let cluster_factors = Vec2::new(
|
||||
z_slices_of_ln_zfar_over_znear,
|
||||
camera.near.ln() * z_slices_of_ln_zfar_over_znear,
|
||||
let is_orthographic = camera.projection_matrix.w_axis.w == 1.0;
|
||||
let cluster_factors = calculate_cluster_factors(
|
||||
camera.near,
|
||||
camera.far,
|
||||
clusters.axis_slices.z as f32,
|
||||
is_orthographic,
|
||||
);
|
||||
|
||||
let mut clusters_lights =
|
||||
|
@ -501,12 +540,14 @@ pub fn assign_lights_to_clusters(
|
|||
let min_cluster = ndc_position_to_cluster(
|
||||
clusters.axis_slices,
|
||||
cluster_factors,
|
||||
is_orthographic,
|
||||
light_aabb_ndc_min,
|
||||
light_aabb_view_min.z,
|
||||
);
|
||||
let max_cluster = ndc_position_to_cluster(
|
||||
clusters.axis_slices,
|
||||
cluster_factors,
|
||||
is_orthographic,
|
||||
light_aabb_ndc_max,
|
||||
light_aabb_view_max.z,
|
||||
);
|
||||
|
|
|
@ -10,7 +10,7 @@ use bevy_ecs::{
|
|||
prelude::*,
|
||||
system::{lifetimeless::*, SystemParamItem},
|
||||
};
|
||||
use bevy_math::{const_vec3, Mat4, UVec3, UVec4, Vec3, Vec4, Vec4Swizzles};
|
||||
use bevy_math::{const_vec3, Mat4, UVec3, UVec4, Vec2, Vec3, Vec4, Vec4Swizzles};
|
||||
use bevy_render::{
|
||||
camera::{Camera, CameraProjection},
|
||||
color::Color,
|
||||
|
@ -540,6 +540,22 @@ pub enum LightEntity {
|
|||
face_index: usize,
|
||||
},
|
||||
}
|
||||
pub fn calculate_cluster_factors(
|
||||
near: f32,
|
||||
far: f32,
|
||||
z_slices: f32,
|
||||
is_orthographic: bool,
|
||||
) -> Vec2 {
|
||||
if is_orthographic {
|
||||
Vec2::new(-near, z_slices / (-far - -near))
|
||||
} else {
|
||||
let z_slices_of_ln_zfar_over_znear = z_slices / (far / near).ln();
|
||||
Vec2::new(
|
||||
z_slices_of_ln_zfar_over_znear,
|
||||
near.ln() * z_slices_of_ln_zfar_over_znear,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn prepare_lights(
|
||||
|
@ -644,8 +660,14 @@ pub fn prepare_lights(
|
|||
);
|
||||
let mut view_lights = Vec::new();
|
||||
|
||||
let z_times_ln_far_over_near =
|
||||
clusters.axis_slices.z as f32 / (extracted_view.far / extracted_view.near).ln();
|
||||
let is_orthographic = extracted_view.projection.w_axis.w == 1.0;
|
||||
let cluster_factors_zw = calculate_cluster_factors(
|
||||
extracted_view.near,
|
||||
extracted_view.far,
|
||||
clusters.axis_slices.z as f32,
|
||||
is_orthographic,
|
||||
);
|
||||
|
||||
let mut gpu_lights = GpuLights {
|
||||
directional_lights: [GpuDirectionalLight::default(); MAX_DIRECTIONAL_LIGHTS],
|
||||
ambient_color: Vec4::from_slice(&ambient_light.color.as_linear_rgba_f32())
|
||||
|
@ -653,8 +675,8 @@ pub fn prepare_lights(
|
|||
cluster_factors: Vec4::new(
|
||||
clusters.axis_slices.x as f32 / extracted_view.width as f32,
|
||||
clusters.axis_slices.y as f32 / extracted_view.height as f32,
|
||||
z_times_ln_far_over_near,
|
||||
extracted_view.near.ln() * z_times_ln_far_over_near,
|
||||
cluster_factors_zw.x,
|
||||
cluster_factors_zw.y,
|
||||
),
|
||||
cluster_dimensions: clusters.axis_slices.extend(0),
|
||||
n_directional_lights: directional_lights.iter().len() as u32,
|
||||
|
@ -855,15 +877,16 @@ const CLUSTER_COUNT_MASK: u32 = (1 << 8) - 1;
|
|||
const POINT_LIGHT_INDEX_MASK: u32 = (1 << 8) - 1;
|
||||
|
||||
// NOTE: With uniform buffer max binding size as 16384 bytes
|
||||
// that means we can fit say 128 point lights in one uniform
|
||||
// buffer, which means the count can be at most 128 so it
|
||||
// needs 7 bits, use 8 for convenience.
|
||||
// that means we can fit say 256 point lights in one uniform
|
||||
// buffer, which means the count can be at most 256 so it
|
||||
// needs 8 bits.
|
||||
// The array of indices can also use u8 and that means the
|
||||
// offset in to the array of indices needs to be able to address
|
||||
// 16384 values. lod2(16384) = 21 bits.
|
||||
// 16384 values. log2(16384) = 14 bits.
|
||||
// This means we can pack the offset into the upper 24 bits of a u32
|
||||
// and the count into the lower 8 bits.
|
||||
// FIXME: Probably there are endianness concerns here????!!!!!
|
||||
// NOTE: This assumes CPU and GPU endianness are the same which is true
|
||||
// for all common and tested x86/ARM CPUs and AMD/NVIDIA/Intel/Apple/etc GPUs
|
||||
fn pack_offset_and_count(offset: usize, count: usize) -> u32 {
|
||||
((offset as u32 & CLUSTER_OFFSET_MASK) << CLUSTER_COUNT_SIZE)
|
||||
| (count as u32 & CLUSTER_COUNT_MASK)
|
||||
|
|
|
@ -245,7 +245,7 @@ impl FromWorld for MeshPipeline {
|
|||
ty: BufferBindingType::Uniform,
|
||||
has_dynamic_offset: false,
|
||||
// NOTE: Static size for uniform buffers. GpuPointLight has a padded
|
||||
// size of 128 bytes, so 16384 / 128 = 128 point lights max
|
||||
// size of 64 bytes, so 16384 / 64 = 256 point lights max
|
||||
min_binding_size: BufferSize::new(16384),
|
||||
},
|
||||
count: None,
|
||||
|
@ -257,8 +257,7 @@ impl FromWorld for MeshPipeline {
|
|||
ty: BindingType::Buffer {
|
||||
ty: BufferBindingType::Uniform,
|
||||
has_dynamic_offset: false,
|
||||
// NOTE: With 128 point lights max, indices need 7 bits. Use u8 for
|
||||
// convenience.
|
||||
// NOTE: With 256 point lights max, indices need 8 bits so use u8
|
||||
min_binding_size: BufferSize::new(16384),
|
||||
},
|
||||
count: None,
|
||||
|
@ -270,10 +269,10 @@ impl FromWorld for MeshPipeline {
|
|||
ty: BindingType::Buffer {
|
||||
ty: BufferBindingType::Uniform,
|
||||
has_dynamic_offset: false,
|
||||
// NOTE: The offset needs to address 16384 indices, which needs 21 bits.
|
||||
// The count can be at most all 128 lights so 7 bits.
|
||||
// NOTE: The offset needs to address 16384 indices, which needs 14 bits.
|
||||
// The count can be at most all 256 lights so 8 bits.
|
||||
// Pack the offset into the upper 24 bits and the count into the
|
||||
// lower 8 bits for convenience.
|
||||
// lower 8 bits.
|
||||
min_binding_size: BufferSize::new(16384),
|
||||
},
|
||||
count: None,
|
||||
|
|
|
@ -43,8 +43,15 @@ struct Lights {
|
|||
// x/y/z dimensions
|
||||
cluster_dimensions: vec4<u32>;
|
||||
// xy are vec2<f32>(cluster_dimensions.xy) / vec2<f32>(view.width, view.height)
|
||||
//
|
||||
// For perspective projections:
|
||||
// z is cluster_dimensions.z / log(far / near)
|
||||
// w is cluster_dimensions.z * log(near) / log(far / near)
|
||||
//
|
||||
// For orthographic projections:
|
||||
// NOTE: near and far are +ve but -z is infront of the camera
|
||||
// z is -near
|
||||
// w is cluster_dimensions.z / (-far - -near)
|
||||
cluster_factors: vec4<f32>;
|
||||
n_directional_lights: u32;
|
||||
};
|
||||
|
|
|
@ -239,14 +239,19 @@ fn reinhard_extended_luminance(color: vec3<f32>, max_white_l: f32) -> vec3<f32>
|
|||
return change_luminance(color, l_new);
|
||||
}
|
||||
|
||||
fn view_z_to_z_slice(view_z: f32) -> u32 {
|
||||
fn view_z_to_z_slice(view_z: f32, is_orthographic: bool) -> u32 {
|
||||
if (is_orthographic) {
|
||||
// NOTE: view_z is correct in the orthographic case
|
||||
return u32(floor((view_z - lights.cluster_factors.z) * lights.cluster_factors.w));
|
||||
} else {
|
||||
// NOTE: had to use -view_z to make it positive else log(negative) is nan
|
||||
return u32(floor(log(-view_z) * lights.cluster_factors.z - lights.cluster_factors.w));
|
||||
}
|
||||
}
|
||||
|
||||
fn fragment_cluster_index(frag_coord: vec2<f32>, view_z: f32) -> u32 {
|
||||
fn fragment_cluster_index(frag_coord: vec2<f32>, view_z: f32, is_orthographic: bool) -> u32 {
|
||||
let xy = vec2<u32>(floor(frag_coord * lights.cluster_factors.xy));
|
||||
let z_slice = view_z_to_z_slice(view_z);
|
||||
let z_slice = view_z_to_z_slice(view_z, is_orthographic);
|
||||
return (xy.y * lights.cluster_dimensions.x + xy.x) * lights.cluster_dimensions.z + z_slice;
|
||||
}
|
||||
|
||||
|
@ -423,6 +428,10 @@ fn hsv2rgb(hue: f32, saturation: f32, value: f32) -> vec3<f32> {
|
|||
return value * mix( vec3<f32>(1.0), rgb, vec3<f32>(saturation));
|
||||
}
|
||||
|
||||
fn random1D(s: f32) -> f32 {
|
||||
return fract(sin(s * 12.9898) * 43758.5453123);
|
||||
}
|
||||
|
||||
struct FragmentInput {
|
||||
[[builtin(front_facing)]] is_front: bool;
|
||||
[[builtin(position)]] frag_coord: vec4<f32>;
|
||||
|
@ -508,12 +517,14 @@ fn fragment(in: FragmentInput) -> [[location(0)]] vec4<f32> {
|
|||
}
|
||||
|
||||
var V: vec3<f32>;
|
||||
if (view.projection[3].w != 1.0) { // If the projection is not orthographic
|
||||
// If the projection is not orthographic
|
||||
let is_orthographic = view.projection[3].w == 1.0;
|
||||
if (is_orthographic) {
|
||||
// Orthographic view vector
|
||||
V = normalize(vec3<f32>(view.view_proj[0].z, view.view_proj[1].z, view.view_proj[2].z));
|
||||
} else {
|
||||
// Only valid for a perpective projection
|
||||
V = normalize(view.world_position.xyz - in.world_position.xyz);
|
||||
} else {
|
||||
// Ortho view vec
|
||||
V = normalize(vec3<f32>(view.view_proj[0].z, view.view_proj[1].z, view.view_proj[2].z));
|
||||
}
|
||||
|
||||
// Neubelt and Pettineo 2013, "Crafting a Next-gen Material Pipeline for The Order: 1886"
|
||||
|
@ -538,7 +549,7 @@ fn fragment(in: FragmentInput) -> [[location(0)]] vec4<f32> {
|
|||
view.inverse_view[2].z,
|
||||
view.inverse_view[3].z
|
||||
), in.world_position);
|
||||
let cluster_index = fragment_cluster_index(in.frag_coord.xy, view_z);
|
||||
let cluster_index = fragment_cluster_index(in.frag_coord.xy, view_z, is_orthographic);
|
||||
let offset_and_count = unpack_offset_and_count(cluster_index);
|
||||
for (var i: u32 = offset_and_count.offset; i < offset_and_count.offset + offset_and_count.count; i = i + 1u) {
|
||||
let light_id = get_light_id(i);
|
||||
|
@ -573,13 +584,11 @@ fn fragment(in: FragmentInput) -> [[location(0)]] vec4<f32> {
|
|||
emissive.rgb * output_color.a,
|
||||
output_color.a);
|
||||
|
||||
#ifdef CLUSTERED_FORWARD_DEBUG
|
||||
// Cluster allocation debug (using 'over' alpha blending)
|
||||
let cluster_debug_mode = 1;
|
||||
let cluster_overlay_alpha = 1.0;
|
||||
if (cluster_debug_mode == 0) {
|
||||
#ifdef CLUSTERED_FORWARD_DEBUG_Z_SLICES
|
||||
// NOTE: This debug mode visualises the z-slices
|
||||
var z_slice: u32 = view_z_to_z_slice(view_z);
|
||||
let cluster_overlay_alpha = 0.1;
|
||||
var z_slice: u32 = view_z_to_z_slice(view_z, is_orthographic);
|
||||
// A hack to make the colors alternate a bit more
|
||||
if ((z_slice & 1u) == 1u) {
|
||||
z_slice = z_slice + lights.cluster_dimensions.z / 2u;
|
||||
|
@ -589,15 +598,26 @@ fn fragment(in: FragmentInput) -> [[location(0)]] vec4<f32> {
|
|||
(1.0 - cluster_overlay_alpha) * output_color.rgb + cluster_overlay_alpha * slice_color,
|
||||
output_color.a
|
||||
);
|
||||
} elseif (cluster_debug_mode == 1) {
|
||||
#endif // CLUSTERED_FORWARD_DEBUG_Z_SLICES
|
||||
#ifdef CLUSTERED_FORWARD_DEBUG_CLUSTER_LIGHT_COMPLEXITY
|
||||
// NOTE: This debug mode visualises the number of lights within the cluster that contains
|
||||
// the fragment. It shows a sort of lighting complexity measure.
|
||||
let cluster_overlay_alpha = 0.1;
|
||||
let max_light_complexity_per_cluster = 64.0;
|
||||
output_color.r = (1.0 - cluster_overlay_alpha) * output_color.r
|
||||
+ cluster_overlay_alpha * smoothStep(0.0, 16.0, f32(offset_and_count.count));
|
||||
+ cluster_overlay_alpha * smoothStep(0.0, max_light_complexity_per_cluster, f32(offset_and_count.count));
|
||||
output_color.g = (1.0 - cluster_overlay_alpha) * output_color.g
|
||||
+ cluster_overlay_alpha * (1.0 - smoothStep(0.0, 16.0, f32(offset_and_count.count)));
|
||||
}
|
||||
#endif
|
||||
+ cluster_overlay_alpha * (1.0 - smoothStep(0.0, max_light_complexity_per_cluster, f32(offset_and_count.count)));
|
||||
#endif // CLUSTERED_FORWARD_DEBUG_CLUSTER_LIGHT_COMPLEXITY
|
||||
#ifdef CLUSTERED_FORWARD_DEBUG_CLUSTER_COHERENCY
|
||||
// NOTE: Visualizes the cluster to which the fragment belongs
|
||||
let cluster_overlay_alpha = 0.1;
|
||||
let cluster_color = hsv2rgb(random1D(f32(cluster_index)), 1.0, 0.5);
|
||||
output_color = vec4<f32>(
|
||||
(1.0 - cluster_overlay_alpha) * output_color.rgb + cluster_overlay_alpha * cluster_color,
|
||||
output_color.a
|
||||
);
|
||||
#endif // CLUSTERED_FORWARD_DEBUG_CLUSTER_COHERENCY
|
||||
|
||||
// tone_mapping
|
||||
output_color = vec4<f32>(reinhard_luminance(output_color.rgb), output_color.a);
|
||||
|
|
Loading…
Reference in a new issue