mirror of
https://github.com/bevyengine/bevy.git
synced 2026-05-06 06:06:42 -04:00
Add PBR Neutral tone mapping (#23761)
# Objective Add a tonemapping option for for e-commerce, architecture and CAD applications. ## Solution Implemented [PBR Neutral](https://github.com/KhronosGroup/ToneMapping/tree/main/PBR_Neutral) tone mapping - Added to 2d and 3d materials, deferred and forward rendering - Added to bloom_2d example - Added to tonemapping example ## Testing Run bloom_2d and tonemapping examples --------- Co-authored-by: Brian Chirls <brian.chirls@ambr.net> Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com> Co-authored-by: IceSentry <IceSentry@users.noreply.github.com>
This commit is contained in:
@@ -160,6 +160,9 @@ pub enum Tonemapping {
|
||||
/// Somewhat neutral. Suffers from hue shifting. Brights desaturate across the spectrum.
|
||||
/// NOTE: Requires the `tonemapping_luts` cargo feature.
|
||||
BlenderFilmic,
|
||||
/// Designed to faithfully reproduce base color under neutral lighting. Suitable for e-commerce, architecture and CAD applications.
|
||||
/// See [the KhronosGroup spec](https://github.com/KhronosGroup/ToneMapping/tree/main/PBR_Neutral) for more information.
|
||||
PbrNeutral,
|
||||
}
|
||||
|
||||
impl Tonemapping {
|
||||
@@ -265,6 +268,7 @@ impl SpecializedRenderPipeline for TonemappingPipeline {
|
||||
);
|
||||
shader_defs.push("TONEMAP_METHOD_BLENDER_FILMIC".into());
|
||||
}
|
||||
Tonemapping::PbrNeutral => shader_defs.push("TONEMAP_METHOD_PBR_NEUTRAL".into()),
|
||||
}
|
||||
RenderPipelineDescriptor {
|
||||
label: Some("tonemapping pipeline".into()),
|
||||
@@ -392,6 +396,7 @@ pub fn get_lut_bindings<'a>(
|
||||
| Tonemapping::ReinhardLuminance
|
||||
| Tonemapping::AcesFitted
|
||||
| Tonemapping::AgX
|
||||
| Tonemapping::PbrNeutral
|
||||
| Tonemapping::SomewhatBoringDisplayTransform => &tonemapping_luts.agx,
|
||||
Tonemapping::TonyMcMapface => &tonemapping_luts.tony_mc_mapface,
|
||||
Tonemapping::BlenderFilmic => &tonemapping_luts.blender_filmic,
|
||||
|
||||
@@ -255,6 +255,54 @@ fn rgb_to_srgb_simple(color: vec3<f32>) -> vec3<f32> {
|
||||
return pow(color, vec3<f32>(1.0 / 2.2));
|
||||
}
|
||||
|
||||
// PBR Neutral tone mapping
|
||||
// https://github.com/KhronosGroup/ToneMapping/blob/main/PBR_Neutral/pbrNeutral.glsl
|
||||
// Adapted under Apache 2.0, per https://github.com/KhronosGroup/ToneMapping/tree/main/LICENSES
|
||||
fn tonemapping_pbr_neutral(color_in: vec3<f32>) -> vec3<f32> {
|
||||
// Parameter controlling when highlight compression starts
|
||||
// (`K_s` in specification)
|
||||
const start_compression: f32 = 0.8 - 0.04;
|
||||
|
||||
// Parameter controlling the speed of desaturation
|
||||
// (`K-d` in specification)
|
||||
const desaturation: f32 = 0.15;
|
||||
|
||||
// `x` in the specification equation
|
||||
let min_channel = min(color_in.r, min(color_in.g, color_in.b));
|
||||
|
||||
// The amount that the "toe" adjustment reduces the color
|
||||
// `f` in the specification equation
|
||||
let offset = select(0.04, min_channel - 6.25 * min_channel * min_channel, min_channel < 0.08);
|
||||
|
||||
// The original color, minus the "toe" reduction
|
||||
// `c_in - f` in the specification equation
|
||||
let offset_color = color_in - offset;
|
||||
|
||||
// Maximum of all offset color channels
|
||||
// `p` in the specification equation
|
||||
let max_channel = max(offset_color.r, max(offset_color.g, offset_color.b));
|
||||
if max_channel < start_compression {
|
||||
// "toe" at the low-end; or uncompressed, offset color for most of the range
|
||||
return offset_color;
|
||||
}
|
||||
|
||||
// This doesn't exist in the specification equation. It is part of optimizing
|
||||
// the math for computation.
|
||||
let d = 1.0 - start_compression;
|
||||
|
||||
// Maximum color channel, scaled to asymptotically approach 1.0
|
||||
let new_max_channel = 1.0 - d * d / (max_channel + d - start_compression);
|
||||
|
||||
// Full color, from offset color, with the same scale applied as `new_max_channel`
|
||||
// `p_n` in the specification equation
|
||||
let color = offset_color * (new_max_channel / max_channel);
|
||||
|
||||
// Amount to desaturate, used as the blend factor when mixing between
|
||||
// full color and desaturated.
|
||||
let g = 1.0 - 1.0 / (desaturation * (max_channel - new_max_channel) + 1.0);
|
||||
return mix(color, vec3(new_max_channel), g);
|
||||
}
|
||||
|
||||
// Source: Advanced VR Rendering, GDC 2015, Alex Vlachos, Valve, Slide 49
|
||||
// https://media.steampowered.com/apps/valve/2015/Alex_Vlachos_Advanced_VR_Rendering_GDC2015.pdf
|
||||
fn screen_space_dither(frag_coord: vec2<f32>) -> vec3<f32> {
|
||||
@@ -372,6 +420,8 @@ fn tone_mapping(in: vec4<f32>, in_color_grading: ColorGrading) -> vec4<f32> {
|
||||
color = sample_tony_mc_mapface_lut(color);
|
||||
#else ifdef TONEMAP_METHOD_BLENDER_FILMIC
|
||||
color = sample_blender_filmic_lut(color.rgb);
|
||||
#else ifdef TONEMAP_METHOD_PBR_NEUTRAL
|
||||
color = tonemapping_pbr_neutral(color.rgb);
|
||||
#endif
|
||||
|
||||
// Perceptual post tonemapping grading
|
||||
|
||||
@@ -260,6 +260,8 @@ impl SpecializedRenderPipeline for DeferredLightingLayout {
|
||||
shader_defs.push("TONEMAP_METHOD_BLENDER_FILMIC".into());
|
||||
} else if method == MeshPipelineKey::TONEMAP_METHOD_TONY_MC_MAPFACE {
|
||||
shader_defs.push("TONEMAP_METHOD_TONY_MC_MAPFACE".into());
|
||||
} else if method == MeshPipelineKey::TONEMAP_METHOD_PBR_NEUTRAL {
|
||||
shader_defs.push("TONEMAP_METHOD_PBR_NEUTRAL".into());
|
||||
}
|
||||
|
||||
// Debanding is tied to tonemapping in the shader, cannot run without it.
|
||||
@@ -502,6 +504,7 @@ pub fn prepare_deferred_lighting_pipelines(
|
||||
}
|
||||
Tonemapping::TonyMcMapface => MeshPipelineKey::TONEMAP_METHOD_TONY_MC_MAPFACE,
|
||||
Tonemapping::BlenderFilmic => MeshPipelineKey::TONEMAP_METHOD_BLENDER_FILMIC,
|
||||
Tonemapping::PbrNeutral => MeshPipelineKey::TONEMAP_METHOD_PBR_NEUTRAL,
|
||||
};
|
||||
}
|
||||
if let Some(DebandDither::Enabled) = dither {
|
||||
|
||||
@@ -630,6 +630,7 @@ pub const fn tonemapping_pipeline_key(tonemapping: Tonemapping) -> MeshPipelineK
|
||||
}
|
||||
Tonemapping::TonyMcMapface => MeshPipelineKey::TONEMAP_METHOD_TONY_MC_MAPFACE,
|
||||
Tonemapping::BlenderFilmic => MeshPipelineKey::TONEMAP_METHOD_BLENDER_FILMIC,
|
||||
Tonemapping::PbrNeutral => MeshPipelineKey::TONEMAP_METHOD_PBR_NEUTRAL,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3093,6 +3093,7 @@ bitflags::bitflags! {
|
||||
const TONEMAP_METHOD_SOMEWHAT_BORING_DISPLAY_TRANSFORM = 5 << Self::TONEMAP_METHOD_SHIFT_BITS;
|
||||
const TONEMAP_METHOD_TONY_MC_MAPFACE = 6 << Self::TONEMAP_METHOD_SHIFT_BITS;
|
||||
const TONEMAP_METHOD_BLENDER_FILMIC = 7 << Self::TONEMAP_METHOD_SHIFT_BITS;
|
||||
const TONEMAP_METHOD_PBR_NEUTRAL = 8 << Self::TONEMAP_METHOD_SHIFT_BITS;
|
||||
const SHADOW_FILTER_METHOD_RESERVED_BITS = Self::SHADOW_FILTER_METHOD_MASK_BITS << Self::SHADOW_FILTER_METHOD_SHIFT_BITS;
|
||||
const SHADOW_FILTER_METHOD_HARDWARE_2X2 = 0 << Self::SHADOW_FILTER_METHOD_SHIFT_BITS;
|
||||
const SHADOW_FILTER_METHOD_GAUSSIAN = 1 << Self::SHADOW_FILTER_METHOD_SHIFT_BITS;
|
||||
@@ -3127,7 +3128,7 @@ impl MeshPipelineKey {
|
||||
const BLEND_MASK_BITS: u64 = 0b111;
|
||||
const BLEND_SHIFT_BITS: u64 = Self::MSAA_MASK_BITS.count_ones() as u64 + Self::MSAA_SHIFT_BITS;
|
||||
|
||||
const TONEMAP_METHOD_MASK_BITS: u64 = 0b111;
|
||||
const TONEMAP_METHOD_MASK_BITS: u64 = 0b1111;
|
||||
const TONEMAP_METHOD_SHIFT_BITS: u64 =
|
||||
Self::BLEND_MASK_BITS.count_ones() as u64 + Self::BLEND_SHIFT_BITS;
|
||||
|
||||
@@ -3546,6 +3547,8 @@ impl SpecializedMeshPipeline for MeshPipeline {
|
||||
shader_defs.push("TONEMAP_METHOD_BLENDER_FILMIC".into());
|
||||
} else if method == MeshPipelineKey::TONEMAP_METHOD_TONY_MC_MAPFACE {
|
||||
shader_defs.push("TONEMAP_METHOD_TONY_MC_MAPFACE".into());
|
||||
} else if method == MeshPipelineKey::TONEMAP_METHOD_PBR_NEUTRAL {
|
||||
shader_defs.push("TONEMAP_METHOD_PBR_NEUTRAL".into());
|
||||
}
|
||||
|
||||
// Debanding is tied to tonemapping in the shader, cannot run without it.
|
||||
|
||||
@@ -579,6 +579,7 @@ pub const fn tonemapping_pipeline_key(tonemapping: Tonemapping) -> Mesh2dPipelin
|
||||
}
|
||||
Tonemapping::TonyMcMapface => Mesh2dPipelineKey::TONEMAP_METHOD_TONY_MC_MAPFACE,
|
||||
Tonemapping::BlenderFilmic => Mesh2dPipelineKey::TONEMAP_METHOD_BLENDER_FILMIC,
|
||||
Tonemapping::PbrNeutral => Mesh2dPipelineKey::TONEMAP_METHOD_PBR_NEUTRAL,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -479,6 +479,7 @@ bitflags::bitflags! {
|
||||
const TONEMAP_METHOD_SOMEWHAT_BORING_DISPLAY_TRANSFORM = 5 << Self::TONEMAP_METHOD_SHIFT_BITS;
|
||||
const TONEMAP_METHOD_TONY_MC_MAPFACE = 6 << Self::TONEMAP_METHOD_SHIFT_BITS;
|
||||
const TONEMAP_METHOD_BLENDER_FILMIC = 7 << Self::TONEMAP_METHOD_SHIFT_BITS;
|
||||
const TONEMAP_METHOD_PBR_NEUTRAL = 8 << Self::TONEMAP_METHOD_SHIFT_BITS;
|
||||
const STRIP_INDEX_FORMAT_RESERVED_BITS = Self::INDEX_FORMAT_MASK_BITS << Self::INDEX_FORMAT_SHIFT_BITS;
|
||||
const STRIP_INDEX_FORMAT_NONE = 0 << Self::INDEX_FORMAT_SHIFT_BITS;
|
||||
const STRIP_INDEX_FORMAT_U32 = 1 << Self::INDEX_FORMAT_SHIFT_BITS;
|
||||
@@ -493,7 +494,7 @@ impl Mesh2dPipelineKey {
|
||||
const MSAA_SHIFT_BITS: u32 = 32 - Self::MSAA_MASK_BITS.count_ones();
|
||||
const PRIMITIVE_TOPOLOGY_MASK_BITS: u32 = 0b111;
|
||||
const PRIMITIVE_TOPOLOGY_SHIFT_BITS: u32 = Self::MSAA_SHIFT_BITS - 3;
|
||||
const TONEMAP_METHOD_MASK_BITS: u32 = 0b111;
|
||||
const TONEMAP_METHOD_MASK_BITS: u32 = 0b1111;
|
||||
const TONEMAP_METHOD_SHIFT_BITS: u32 =
|
||||
Self::PRIMITIVE_TOPOLOGY_SHIFT_BITS - Self::TONEMAP_METHOD_MASK_BITS.count_ones();
|
||||
pub const INDEX_FORMAT_MASK_BITS: u32 = 0b11;
|
||||
@@ -652,6 +653,9 @@ impl SpecializedMeshPipeline for Mesh2dPipeline {
|
||||
Mesh2dPipelineKey::TONEMAP_METHOD_TONY_MC_MAPFACE => {
|
||||
shader_defs.push("TONEMAP_METHOD_TONY_MC_MAPFACE".into());
|
||||
}
|
||||
Mesh2dPipelineKey::TONEMAP_METHOD_PBR_NEUTRAL => {
|
||||
shader_defs.push("TONEMAP_METHOD_PBR_NEUTRAL".into());
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
// Debanding is tied to tonemapping in the shader, cannot run without it.
|
||||
|
||||
@@ -112,6 +112,8 @@ bitflags::bitflags! {
|
||||
const TONEMAP_METHOD_SOMEWHAT_BORING_DISPLAY_TRANSFORM = 5 << Self::TONEMAP_METHOD_SHIFT_BITS;
|
||||
const TONEMAP_METHOD_TONY_MC_MAPFACE = 6 << Self::TONEMAP_METHOD_SHIFT_BITS;
|
||||
const TONEMAP_METHOD_BLENDER_FILMIC = 7 << Self::TONEMAP_METHOD_SHIFT_BITS;
|
||||
const TONEMAP_METHOD_PBR_NEUTRAL = 8 << Self::TONEMAP_METHOD_SHIFT_BITS;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,7 +122,7 @@ impl SpritePipelineKey {
|
||||
const COLOR_TARGET_FORMAT_SHIFT_BITS: u32 = 4;
|
||||
const MSAA_MASK_BITS: u32 = 0b111;
|
||||
const MSAA_SHIFT_BITS: u32 = 32 - Self::MSAA_MASK_BITS.count_ones();
|
||||
const TONEMAP_METHOD_MASK_BITS: u32 = 0b111;
|
||||
const TONEMAP_METHOD_MASK_BITS: u32 = 0b1111;
|
||||
const TONEMAP_METHOD_SHIFT_BITS: u32 =
|
||||
Self::MSAA_SHIFT_BITS - Self::TONEMAP_METHOD_MASK_BITS.count_ones();
|
||||
|
||||
@@ -191,6 +193,8 @@ impl SpecializedRenderPipeline for SpritePipeline {
|
||||
shader_defs.push("TONEMAP_METHOD_BLENDER_FILMIC".into());
|
||||
} else if method == SpritePipelineKey::TONEMAP_METHOD_TONY_MC_MAPFACE {
|
||||
shader_defs.push("TONEMAP_METHOD_TONY_MC_MAPFACE".into());
|
||||
} else if method == SpritePipelineKey::TONEMAP_METHOD_PBR_NEUTRAL {
|
||||
shader_defs.push("TONEMAP_METHOD_PBR_NEUTRAL".into());
|
||||
}
|
||||
|
||||
// Debanding is tied to tonemapping in the shader, cannot run without it.
|
||||
@@ -549,6 +553,7 @@ pub fn queue_sprites(
|
||||
}
|
||||
Tonemapping::TonyMcMapface => SpritePipelineKey::TONEMAP_METHOD_TONY_MC_MAPFACE,
|
||||
Tonemapping::BlenderFilmic => SpritePipelineKey::TONEMAP_METHOD_BLENDER_FILMIC,
|
||||
Tonemapping::PbrNeutral => SpritePipelineKey::TONEMAP_METHOD_PBR_NEUTRAL,
|
||||
};
|
||||
}
|
||||
if let Some(DebandDither::Enabled) = dither {
|
||||
|
||||
@@ -209,6 +209,7 @@ fn next_tonemap(tonemapping: &Tonemapping) -> Tonemapping {
|
||||
Tonemapping::Reinhard => Tonemapping::ReinhardLuminance,
|
||||
Tonemapping::ReinhardLuminance => Tonemapping::SomewhatBoringDisplayTransform,
|
||||
Tonemapping::SomewhatBoringDisplayTransform => Tonemapping::TonyMcMapface,
|
||||
Tonemapping::TonyMcMapface => Tonemapping::None,
|
||||
Tonemapping::TonyMcMapface => Tonemapping::PbrNeutral,
|
||||
Tonemapping::PbrNeutral => Tonemapping::None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -310,6 +310,8 @@ fn toggle_tonemapping_method(
|
||||
**tonemapping = Tonemapping::TonyMcMapface;
|
||||
} else if keys.just_pressed(KeyCode::Digit8) {
|
||||
**tonemapping = Tonemapping::BlenderFilmic;
|
||||
} else if keys.just_pressed(KeyCode::Digit9) {
|
||||
**tonemapping = Tonemapping::PbrNeutral;
|
||||
}
|
||||
|
||||
**color_grading = (*per_method_settings
|
||||
@@ -497,6 +499,14 @@ fn update_ui(
|
||||
""
|
||||
}
|
||||
));
|
||||
text.push_str(&format!(
|
||||
"(9) {} PBR Neutral\n",
|
||||
if tonemapping == Tonemapping::PbrNeutral {
|
||||
">"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
));
|
||||
|
||||
text.push_str("\n\nColor Grading:\n");
|
||||
text.push_str("(arrow keys)\n");
|
||||
@@ -588,6 +598,7 @@ impl Default for PerMethodSettings {
|
||||
Tonemapping::SomewhatBoringDisplayTransform,
|
||||
Tonemapping::TonyMcMapface,
|
||||
Tonemapping::BlenderFilmic,
|
||||
Tonemapping::PbrNeutral,
|
||||
] {
|
||||
settings.insert(
|
||||
method,
|
||||
|
||||
Reference in New Issue
Block a user