mirror of
https://github.com/bevyengine/bevy
synced 2024-11-10 07:04:33 +00:00
Callable PBR functions (#4939)
# Objective - Builds on top of #4938 - Make clustered-forward PBR lighting/shadows functionality callable - See #3969 for details ## Solution - Add `PbrInput` struct type containing a `StandardMaterial`, occlusion, world_position, world_normal, and frag_coord - Split functionality to calculate the unit view vector, and normal-mapped normal into `bevy_pbr::pbr_functions` - Split high-level shading flow into `pbr(in: PbrInput, N: vec3<f32>, V: vec3<f32>, is_orthographic: bool)` function in `bevy_pbr::pbr_functions` - Rework `pbr.wgsl` fragment stage entry point to make use of the new functions - This has been benchmarked on an M1 Max using `many_cubes -- sphere`. `main` had a median frame time of 15.88ms, this PR 15.99ms, which is a 0.69% frame time increase, which is within noise in my opinion. --- ## Changelog - Added: PBR shading code is now callable. Import `bevy_pbr::pbr_functions` and its dependencies, create a `PbrInput`, calculate the unit view and normal-mapped normal vectors and whether the projection is orthographic, and call `pbr()`!
This commit is contained in:
parent
c988264180
commit
114d169dce
3 changed files with 233 additions and 143 deletions
|
@ -64,6 +64,8 @@ pub const SHADOWS_HANDLE: HandleUntyped =
|
|||
HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 11350275143789590502);
|
||||
pub const PBR_SHADER_HANDLE: HandleUntyped =
|
||||
HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 4805239651767701046);
|
||||
pub const PBR_FUNCTIONS_HANDLE: HandleUntyped =
|
||||
HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 16550102964439850292);
|
||||
pub const SHADOW_SHADER_HANDLE: HandleUntyped =
|
||||
HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 1836745567947005696);
|
||||
|
||||
|
@ -104,6 +106,12 @@ impl Plugin for PbrPlugin {
|
|||
"render/shadows.wgsl",
|
||||
Shader::from_wgsl
|
||||
);
|
||||
load_internal_asset!(
|
||||
app,
|
||||
PBR_FUNCTIONS_HANDLE,
|
||||
"render/pbr_functions.wgsl",
|
||||
Shader::from_wgsl
|
||||
);
|
||||
load_internal_asset!(app, PBR_SHADER_HANDLE, "render/pbr.wgsl", Shader::from_wgsl);
|
||||
load_internal_asset!(
|
||||
app,
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#import bevy_pbr::clustered_forward
|
||||
#import bevy_pbr::lighting
|
||||
#import bevy_pbr::shadows
|
||||
#import bevy_pbr::pbr_functions
|
||||
|
||||
struct FragmentInput {
|
||||
[[builtin(front_facing)]] is_front: bool;
|
||||
|
@ -24,22 +25,31 @@ struct FragmentInput {
|
|||
[[stage(fragment)]]
|
||||
fn fragment(in: FragmentInput) -> [[location(0)]] vec4<f32> {
|
||||
var output_color: vec4<f32> = material.base_color;
|
||||
#ifdef VERTEX_COLORS
|
||||
#ifdef VERTEX_COLORS
|
||||
output_color = output_color * in.color;
|
||||
#endif
|
||||
#endif
|
||||
if ((material.flags & STANDARD_MATERIAL_FLAGS_BASE_COLOR_TEXTURE_BIT) != 0u) {
|
||||
output_color = output_color * textureSample(base_color_texture, base_color_sampler, in.uv);
|
||||
}
|
||||
|
||||
// // NOTE: Unlit bit not set means == 0 is true, so the true case is if lit
|
||||
// NOTE: Unlit bit not set means == 0 is true, so the true case is if lit
|
||||
if ((material.flags & STANDARD_MATERIAL_FLAGS_UNLIT_BIT) == 0u) {
|
||||
// Prepare a 'processed' StandardMaterial by sampling all textures to resolve
|
||||
// the material members
|
||||
var pbr_input: PbrInput;
|
||||
|
||||
pbr_input.material.base_color = output_color;
|
||||
pbr_input.material.reflectance = material.reflectance;
|
||||
pbr_input.material.flags = material.flags;
|
||||
pbr_input.material.alpha_cutoff = material.alpha_cutoff;
|
||||
|
||||
// TODO use .a for exposure compensation in HDR
|
||||
var emissive: vec4<f32> = material.emissive;
|
||||
if ((material.flags & STANDARD_MATERIAL_FLAGS_EMISSIVE_TEXTURE_BIT) != 0u) {
|
||||
emissive = vec4<f32>(emissive.rgb * textureSample(emissive_texture, emissive_sampler, in.uv).rgb, 1.0);
|
||||
}
|
||||
pbr_input.material.emissive = emissive;
|
||||
|
||||
// calculate non-linear roughness from linear perceptualRoughness
|
||||
var metallic: f32 = material.metallic;
|
||||
var perceptual_roughness: f32 = material.perceptual_roughness;
|
||||
if ((material.flags & STANDARD_MATERIAL_FLAGS_METALLIC_ROUGHNESS_TEXTURE_BIT) != 0u) {
|
||||
|
@ -48,158 +58,34 @@ fn fragment(in: FragmentInput) -> [[location(0)]] vec4<f32> {
|
|||
metallic = metallic * metallic_roughness.b;
|
||||
perceptual_roughness = perceptual_roughness * metallic_roughness.g;
|
||||
}
|
||||
let roughness = perceptualRoughnessToRoughness(perceptual_roughness);
|
||||
pbr_input.material.metallic = metallic;
|
||||
pbr_input.material.perceptual_roughness = perceptual_roughness;
|
||||
|
||||
var occlusion: f32 = 1.0;
|
||||
if ((material.flags & STANDARD_MATERIAL_FLAGS_OCCLUSION_TEXTURE_BIT) != 0u) {
|
||||
occlusion = textureSample(occlusion_texture, occlusion_sampler, in.uv).r;
|
||||
}
|
||||
pbr_input.occlusion = occlusion;
|
||||
|
||||
var N: vec3<f32> = normalize(in.world_normal);
|
||||
pbr_input.frag_coord = in.frag_coord;
|
||||
pbr_input.world_position = in.world_position;
|
||||
pbr_input.world_normal = in.world_normal;
|
||||
|
||||
pbr_input.is_orthographic = view.projection[3].w == 1.0;
|
||||
|
||||
pbr_input.N = prepare_normal(
|
||||
in.world_normal,
|
||||
#ifdef VERTEX_TANGENTS
|
||||
#ifdef STANDARDMATERIAL_NORMAL_MAP
|
||||
// NOTE: The mikktspace method of normal mapping explicitly requires that these NOT be
|
||||
// normalized nor any Gram-Schmidt applied to ensure the vertex normal is orthogonal to the
|
||||
// vertex tangent! Do not change this code unless you really know what you are doing.
|
||||
// http://www.mikktspace.com/
|
||||
var T: vec3<f32> = in.world_tangent.xyz;
|
||||
var B: vec3<f32> = in.world_tangent.w * cross(N, T);
|
||||
in.world_tangent,
|
||||
#endif
|
||||
#endif
|
||||
|
||||
if ((material.flags & STANDARD_MATERIAL_FLAGS_DOUBLE_SIDED_BIT) != 0u) {
|
||||
if (!in.is_front) {
|
||||
N = -N;
|
||||
#ifdef VERTEX_TANGENTS
|
||||
#ifdef STANDARDMATERIAL_NORMAL_MAP
|
||||
T = -T;
|
||||
B = -B;
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef VERTEX_TANGENTS
|
||||
#ifdef STANDARDMATERIAL_NORMAL_MAP
|
||||
let TBN = mat3x3<f32>(T, B, N);
|
||||
// Nt is the tangent-space normal.
|
||||
var Nt: vec3<f32>;
|
||||
if ((material.flags & STANDARD_MATERIAL_FLAGS_TWO_COMPONENT_NORMAL_MAP) != 0u) {
|
||||
// Only use the xy components and derive z for 2-component normal maps.
|
||||
Nt = vec3<f32>(textureSample(normal_map_texture, normal_map_sampler, in.uv).rg * 2.0 - 1.0, 0.0);
|
||||
Nt.z = sqrt(1.0 - Nt.x * Nt.x - Nt.y * Nt.y);
|
||||
} else {
|
||||
Nt = textureSample(normal_map_texture, normal_map_sampler, in.uv).rgb * 2.0 - 1.0;
|
||||
}
|
||||
// Normal maps authored for DirectX require flipping the y component
|
||||
if ((material.flags & STANDARD_MATERIAL_FLAGS_FLIP_NORMAL_MAP_Y) != 0u) {
|
||||
Nt.y = -Nt.y;
|
||||
}
|
||||
// NOTE: The mikktspace method of normal mapping applies maps the tangent-space normal from
|
||||
// the normal map texture in this way to be an EXACT inverse of how the normal map baker
|
||||
// calculates the normal maps so there is no error introduced. Do not change this code
|
||||
// unless you really know what you are doing.
|
||||
// http://www.mikktspace.com/
|
||||
N = normalize(Nt.x * T + Nt.y * B + Nt.z * N);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
if ((material.flags & STANDARD_MATERIAL_FLAGS_ALPHA_MODE_OPAQUE) != 0u) {
|
||||
// NOTE: If rendering as opaque, alpha should be ignored so set to 1.0
|
||||
output_color.a = 1.0;
|
||||
} else if ((material.flags & STANDARD_MATERIAL_FLAGS_ALPHA_MODE_MASK) != 0u) {
|
||||
if (output_color.a >= material.alpha_cutoff) {
|
||||
// NOTE: If rendering as masked alpha and >= the cutoff, render as fully opaque
|
||||
output_color.a = 1.0;
|
||||
} else {
|
||||
// NOTE: output_color.a < material.alpha_cutoff should not is not rendered
|
||||
// NOTE: This and any other discards mean that early-z testing cannot be done!
|
||||
discard;
|
||||
}
|
||||
}
|
||||
|
||||
var V: vec3<f32>;
|
||||
// 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);
|
||||
}
|
||||
|
||||
// Neubelt and Pettineo 2013, "Crafting a Next-gen Material Pipeline for The Order: 1886"
|
||||
let NdotV = max(dot(N, V), 0.0001);
|
||||
|
||||
// Remapping [0,1] reflectance to F0
|
||||
// See https://google.github.io/filament/Filament.html#materialsystem/parameterization/remapping
|
||||
let reflectance = material.reflectance;
|
||||
let F0 = 0.16 * reflectance * reflectance * (1.0 - metallic) + output_color.rgb * metallic;
|
||||
|
||||
// Diffuse strength inversely related to metallicity
|
||||
let diffuse_color = output_color.rgb * (1.0 - metallic);
|
||||
|
||||
let R = reflect(-V, N);
|
||||
|
||||
// accumulate color
|
||||
var light_accum: vec3<f32> = vec3<f32>(0.0);
|
||||
|
||||
let view_z = dot(vec4<f32>(
|
||||
view.inverse_view[0].z,
|
||||
view.inverse_view[1].z,
|
||||
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, is_orthographic);
|
||||
let offset_and_count = unpack_offset_and_count(cluster_index);
|
||||
for (var i: u32 = offset_and_count[0]; i < offset_and_count[0] + offset_and_count[1]; i = i + 1u) {
|
||||
let light_id = get_light_id(i);
|
||||
let light = point_lights.data[light_id];
|
||||
var shadow: f32 = 1.0;
|
||||
if ((mesh.flags & MESH_FLAGS_SHADOW_RECEIVER_BIT) != 0u
|
||||
&& (light.flags & POINT_LIGHT_FLAGS_SHADOWS_ENABLED_BIT) != 0u) {
|
||||
shadow = fetch_point_shadow(light_id, in.world_position, in.world_normal);
|
||||
}
|
||||
let light_contrib = point_light(in.world_position.xyz, light, roughness, NdotV, N, V, R, F0, diffuse_color);
|
||||
light_accum = light_accum + light_contrib * shadow;
|
||||
}
|
||||
|
||||
let n_directional_lights = lights.n_directional_lights;
|
||||
for (var i: u32 = 0u; i < n_directional_lights; i = i + 1u) {
|
||||
let light = lights.directional_lights[i];
|
||||
var shadow: f32 = 1.0;
|
||||
if ((mesh.flags & MESH_FLAGS_SHADOW_RECEIVER_BIT) != 0u
|
||||
&& (light.flags & DIRECTIONAL_LIGHT_FLAGS_SHADOWS_ENABLED_BIT) != 0u) {
|
||||
shadow = fetch_directional_shadow(i, in.world_position, in.world_normal);
|
||||
}
|
||||
let light_contrib = directional_light(light, roughness, NdotV, N, V, R, F0, diffuse_color);
|
||||
light_accum = light_accum + light_contrib * shadow;
|
||||
}
|
||||
|
||||
let diffuse_ambient = EnvBRDFApprox(diffuse_color, 1.0, NdotV);
|
||||
let specular_ambient = EnvBRDFApprox(F0, perceptual_roughness, NdotV);
|
||||
|
||||
output_color = vec4<f32>(
|
||||
light_accum +
|
||||
(diffuse_ambient + specular_ambient) * lights.ambient_color.rgb * occlusion +
|
||||
emissive.rgb * output_color.a,
|
||||
output_color.a);
|
||||
|
||||
output_color = cluster_debug_visualization(
|
||||
output_color,
|
||||
view_z,
|
||||
is_orthographic,
|
||||
offset_and_count,
|
||||
cluster_index,
|
||||
in.uv,
|
||||
in.is_front,
|
||||
);
|
||||
pbr_input.V = calculate_view(in.world_position, pbr_input.is_orthographic);
|
||||
|
||||
// tone_mapping
|
||||
output_color = vec4<f32>(reinhard_luminance(output_color.rgb), output_color.a);
|
||||
// Gamma correction.
|
||||
// Not needed with sRGB buffer
|
||||
// output_color.rgb = pow(output_color.rgb, vec3(1.0 / 2.2));
|
||||
output_color = pbr(pbr_input);
|
||||
}
|
||||
|
||||
return output_color;
|
||||
|
|
196
crates/bevy_pbr/src/render/pbr_functions.wgsl
Normal file
196
crates/bevy_pbr/src/render/pbr_functions.wgsl
Normal file
|
@ -0,0 +1,196 @@
|
|||
#define_import_path bevy_pbr::pbr_functions
|
||||
|
||||
// NOTE: This ensures that the world_normal is normalized and if
|
||||
// vertex tangents and normal maps then normal mapping may be applied.
|
||||
fn prepare_normal(
|
||||
world_normal: vec3<f32>,
|
||||
#ifdef VERTEX_TANGENTS
|
||||
#ifdef STANDARDMATERIAL_NORMAL_MAP
|
||||
world_tangent: vec4<f32>,
|
||||
#endif
|
||||
#endif
|
||||
uv: vec2<f32>,
|
||||
is_front: bool,
|
||||
) -> vec3<f32> {
|
||||
var N: vec3<f32> = normalize(world_normal);
|
||||
|
||||
#ifdef VERTEX_TANGENTS
|
||||
#ifdef STANDARDMATERIAL_NORMAL_MAP
|
||||
// NOTE: The mikktspace method of normal mapping explicitly requires that these NOT be
|
||||
// normalized nor any Gram-Schmidt applied to ensure the vertex normal is orthogonal to the
|
||||
// vertex tangent! Do not change this code unless you really know what you are doing.
|
||||
// http://www.mikktspace.com/
|
||||
var T: vec3<f32> = world_tangent.xyz;
|
||||
var B: vec3<f32> = world_tangent.w * cross(N, T);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
if ((material.flags & STANDARD_MATERIAL_FLAGS_DOUBLE_SIDED_BIT) != 0u) {
|
||||
if (!is_front) {
|
||||
N = -N;
|
||||
#ifdef VERTEX_TANGENTS
|
||||
#ifdef STANDARDMATERIAL_NORMAL_MAP
|
||||
T = -T;
|
||||
B = -B;
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef VERTEX_TANGENTS
|
||||
#ifdef STANDARDMATERIAL_NORMAL_MAP
|
||||
// Nt is the tangent-space normal.
|
||||
var Nt: vec3<f32>;
|
||||
if ((material.flags & STANDARD_MATERIAL_FLAGS_TWO_COMPONENT_NORMAL_MAP) != 0u) {
|
||||
// Only use the xy components and derive z for 2-component normal maps.
|
||||
Nt = vec3<f32>(textureSample(normal_map_texture, normal_map_sampler, uv).rg * 2.0 - 1.0, 0.0);
|
||||
Nt.z = sqrt(1.0 - Nt.x * Nt.x - Nt.y * Nt.y);
|
||||
} else {
|
||||
Nt = textureSample(normal_map_texture, normal_map_sampler, uv).rgb * 2.0 - 1.0;
|
||||
}
|
||||
// Normal maps authored for DirectX require flipping the y component
|
||||
if ((material.flags & STANDARD_MATERIAL_FLAGS_FLIP_NORMAL_MAP_Y) != 0u) {
|
||||
Nt.y = -Nt.y;
|
||||
}
|
||||
// NOTE: The mikktspace method of normal mapping applies maps the tangent-space normal from
|
||||
// the normal map texture in this way to be an EXACT inverse of how the normal map baker
|
||||
// calculates the normal maps so there is no error introduced. Do not change this code
|
||||
// unless you really know what you are doing.
|
||||
// http://www.mikktspace.com/
|
||||
N = normalize(Nt.x * T + Nt.y * B + Nt.z * N);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
return N;
|
||||
}
|
||||
|
||||
// NOTE: Correctly calculates the view vector depending on whether
|
||||
// the projection is orthographic or perspective.
|
||||
fn calculate_view(
|
||||
world_position: vec4<f32>,
|
||||
is_orthographic: bool,
|
||||
) -> vec3<f32> {
|
||||
var V: vec3<f32>;
|
||||
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 - world_position.xyz);
|
||||
}
|
||||
return V;
|
||||
}
|
||||
|
||||
struct PbrInput {
|
||||
material: StandardMaterial;
|
||||
occlusion: f32;
|
||||
frag_coord: vec4<f32>;
|
||||
world_position: vec4<f32>;
|
||||
world_normal: vec3<f32>;
|
||||
N: vec3<f32>;
|
||||
V: vec3<f32>;
|
||||
is_orthographic: bool;
|
||||
};
|
||||
|
||||
fn pbr(
|
||||
in: PbrInput,
|
||||
) -> vec4<f32> {
|
||||
var output_color: vec4<f32> = in.material.base_color;
|
||||
|
||||
// TODO use .a for exposure compensation in HDR
|
||||
let emissive = in.material.emissive;
|
||||
|
||||
// calculate non-linear roughness from linear perceptualRoughness
|
||||
let metallic = in.material.metallic;
|
||||
let perceptual_roughness = in.material.perceptual_roughness;
|
||||
let roughness = perceptualRoughnessToRoughness(perceptual_roughness);
|
||||
|
||||
let occlusion = in.occlusion;
|
||||
|
||||
if ((in.material.flags & STANDARD_MATERIAL_FLAGS_ALPHA_MODE_OPAQUE) != 0u) {
|
||||
// NOTE: If rendering as opaque, alpha should be ignored so set to 1.0
|
||||
output_color.a = 1.0;
|
||||
} else if ((in.material.flags & STANDARD_MATERIAL_FLAGS_ALPHA_MODE_MASK) != 0u) {
|
||||
if (output_color.a >= in.material.alpha_cutoff) {
|
||||
// NOTE: If rendering as masked alpha and >= the cutoff, render as fully opaque
|
||||
output_color.a = 1.0;
|
||||
} else {
|
||||
// NOTE: output_color.a < in.material.alpha_cutoff should not is not rendered
|
||||
// NOTE: This and any other discards mean that early-z testing cannot be done!
|
||||
discard;
|
||||
}
|
||||
}
|
||||
|
||||
// Neubelt and Pettineo 2013, "Crafting a Next-gen Material Pipeline for The Order: 1886"
|
||||
let NdotV = max(dot(in.N, in.V), 0.0001);
|
||||
|
||||
// Remapping [0,1] reflectance to F0
|
||||
// See https://google.github.io/filament/Filament.html#materialsystem/parameterization/remapping
|
||||
let reflectance = in.material.reflectance;
|
||||
let F0 = 0.16 * reflectance * reflectance * (1.0 - metallic) + output_color.rgb * metallic;
|
||||
|
||||
// Diffuse strength inversely related to metallicity
|
||||
let diffuse_color = output_color.rgb * (1.0 - metallic);
|
||||
|
||||
let R = reflect(-in.V, in.N);
|
||||
|
||||
// accumulate color
|
||||
var light_accum: vec3<f32> = vec3<f32>(0.0);
|
||||
|
||||
let view_z = dot(vec4<f32>(
|
||||
view.inverse_view[0].z,
|
||||
view.inverse_view[1].z,
|
||||
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, in.is_orthographic);
|
||||
let offset_and_count = unpack_offset_and_count(cluster_index);
|
||||
for (var i: u32 = offset_and_count[0]; i < offset_and_count[0] + offset_and_count[1]; i = i + 1u) {
|
||||
let light_id = get_light_id(i);
|
||||
let light = point_lights.data[light_id];
|
||||
var shadow: f32 = 1.0;
|
||||
if ((mesh.flags & MESH_FLAGS_SHADOW_RECEIVER_BIT) != 0u
|
||||
&& (light.flags & POINT_LIGHT_FLAGS_SHADOWS_ENABLED_BIT) != 0u) {
|
||||
shadow = fetch_point_shadow(light_id, in.world_position, in.world_normal);
|
||||
}
|
||||
let light_contrib = point_light(in.world_position.xyz, light, roughness, NdotV, in.N, in.V, R, F0, diffuse_color);
|
||||
light_accum = light_accum + light_contrib * shadow;
|
||||
}
|
||||
|
||||
let n_directional_lights = lights.n_directional_lights;
|
||||
for (var i: u32 = 0u; i < n_directional_lights; i = i + 1u) {
|
||||
let light = lights.directional_lights[i];
|
||||
var shadow: f32 = 1.0;
|
||||
if ((mesh.flags & MESH_FLAGS_SHADOW_RECEIVER_BIT) != 0u
|
||||
&& (light.flags & DIRECTIONAL_LIGHT_FLAGS_SHADOWS_ENABLED_BIT) != 0u) {
|
||||
shadow = fetch_directional_shadow(i, in.world_position, in.world_normal);
|
||||
}
|
||||
let light_contrib = directional_light(light, roughness, NdotV, in.N, in.V, R, F0, diffuse_color);
|
||||
light_accum = light_accum + light_contrib * shadow;
|
||||
}
|
||||
|
||||
let diffuse_ambient = EnvBRDFApprox(diffuse_color, 1.0, NdotV);
|
||||
let specular_ambient = EnvBRDFApprox(F0, perceptual_roughness, NdotV);
|
||||
|
||||
output_color = vec4<f32>(
|
||||
light_accum +
|
||||
(diffuse_ambient + specular_ambient) * lights.ambient_color.rgb * occlusion +
|
||||
emissive.rgb * output_color.a,
|
||||
output_color.a);
|
||||
|
||||
output_color = cluster_debug_visualization(
|
||||
output_color,
|
||||
view_z,
|
||||
in.is_orthographic,
|
||||
offset_and_count,
|
||||
cluster_index,
|
||||
);
|
||||
|
||||
// tone_mapping
|
||||
output_color = vec4<f32>(reinhard_luminance(output_color.rgb), output_color.a);
|
||||
// Gamma correction.
|
||||
// Not needed with sRGB buffer
|
||||
// output_color.rgb = pow(output_color.rgb, vec3(1.0 / 2.2));
|
||||
|
||||
return output_color;
|
||||
}
|
Loading…
Reference in a new issue