Skip to content

Commit 912fb58

Browse files
DGriffin91JMS55
andcommitted
Initial tonemapping options (#7594)
# Objective Splits tone mapping from #6677 into a separate PR. Address #2264. Adds tone mapping options: - None: Bypasses tonemapping for instances where users want colors output to match those set. - Reinhard - Reinhard Luminance: Bevy's exiting tonemapping - [ACES](https://github.com/TheRealMJP/BakingLab/blob/master/BakingLab/ACES.hlsl) (Fitted version, based on the same implementation that Godot 4 uses) see #2264 - [AgX](https://github.com/sobotka/AgX) - SomewhatBoringDisplayTransform - TonyMcMapface - Blender Filmic This PR also adds support for EXR images so they can be used to compare tonemapping options with reference images. ## Migration Guide - Tonemapping is now an enum with NONE and the various tonemappers. - The DebandDither is now a separate component. Co-authored-by: JMS55 <[email protected]>
1 parent d46427b commit 912fb58

37 files changed

+1849
-154
lines changed

Cargo.toml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ default = [
5050
"x11",
5151
"filesystem_watcher",
5252
"android_shared_stdcxx",
53+
"tonemapping_luts"
5354
]
5455

5556
# Force dynamic linking, which improves iterative compile times
@@ -78,6 +79,7 @@ trace = ["bevy_internal/trace"]
7879
wgpu_trace = ["bevy_internal/wgpu_trace"]
7980

8081
# Image format support for texture loading (PNG and HDR are enabled by default)
82+
exr = ["bevy_internal/exr"]
8183
hdr = ["bevy_internal/hdr"]
8284
png = ["bevy_internal/png"]
8385
tga = ["bevy_internal/tga"]
@@ -131,6 +133,9 @@ android_shared_stdcxx = ["bevy_internal/android_shared_stdcxx"]
131133
# These trace events are expensive even when off, thus they require compile time opt-in.
132134
detailed_trace = ["bevy_internal/detailed_trace"]
133135

136+
# Include tonemapping LUT KTX2 files.
137+
tonemapping_luts = ["bevy_internal/tonemapping_luts"]
138+
134139
[dependencies]
135140
bevy_dylib = { path = "crates/bevy_dylib", version = "0.9.0", default-features = false, optional = true }
136141
bevy_internal = { path = "crates/bevy_internal", version = "0.9.0", default-features = false }
@@ -389,6 +394,17 @@ description = "Loads and renders a glTF file as a scene"
389394
category = "3D Rendering"
390395
wasm = true
391396

397+
[[example]]
398+
name = "tonemapping"
399+
path = "examples/3d/tonemapping.rs"
400+
required-features = ["ktx2", "zstd"]
401+
402+
[package.metadata.example.tonemapping]
403+
name = "Tonemapping"
404+
description = "Compares tonemapping options"
405+
category = "3D Rendering"
406+
wasm = true
407+
392408
[[example]]
393409
name = "fxaa"
394410
path = "examples/3d/fxaa.rs"
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
#import bevy_pbr::mesh_view_bindings
2+
#import bevy_pbr::mesh_bindings
3+
#import bevy_pbr::utils
4+
5+
#ifdef TONEMAP_IN_SHADER
6+
#import bevy_core_pipeline::tonemapping
7+
#endif
8+
9+
struct FragmentInput {
10+
@builtin(front_facing) is_front: bool,
11+
@builtin(position) frag_coord: vec4<f32>,
12+
#import bevy_pbr::mesh_vertex_output
13+
};
14+
15+
// Sweep across hues on y axis with value from 0.0 to +15EV across x axis
16+
// quantized into 24 steps for both axis.
17+
fn color_sweep(uv: vec2<f32>) -> vec3<f32> {
18+
var uv = uv;
19+
let steps = 24.0;
20+
uv.y = uv.y * (1.0 + 1.0 / steps);
21+
let ratio = 2.0;
22+
23+
let h = PI * 2.0 * floor(1.0 + steps * uv.y) / steps;
24+
let L = floor(uv.x * steps * ratio) / (steps * ratio) - 0.5;
25+
26+
var color = vec3(0.0);
27+
if uv.y < 1.0 {
28+
color = cos(h + vec3(0.0, 1.0, 2.0) * PI * 2.0 / 3.0);
29+
let maxRGB = max(color.r, max(color.g, color.b));
30+
let minRGB = min(color.r, min(color.g, color.b));
31+
color = exp(15.0 * L) * (color - minRGB) / (maxRGB - minRGB);
32+
} else {
33+
color = vec3(exp(15.0 * L));
34+
}
35+
return color;
36+
}
37+
38+
fn hsv_to_srgb(c: vec3<f32>) -> vec3<f32> {
39+
let K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
40+
let p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
41+
return c.z * mix(K.xxx, clamp(p - K.xxx, vec3(0.0), vec3(1.0)), c.y);
42+
}
43+
44+
// Generates a continuous sRGB sweep.
45+
fn continuous_hue(uv: vec2<f32>) -> vec3<f32> {
46+
return hsv_to_srgb(vec3(uv.x, 1.0, 1.0)) * max(0.0, exp2(uv.y * 9.0) - 1.0);
47+
}
48+
49+
@fragment
50+
fn fragment(in: FragmentInput) -> @location(0) vec4<f32> {
51+
var uv = in.uv;
52+
var out = vec3(0.0);
53+
if uv.y > 0.5 {
54+
uv.y = 1.0 - uv.y;
55+
out = color_sweep(vec2(uv.x, uv.y * 2.0));
56+
} else {
57+
out = continuous_hue(vec2(uv.y * 2.0, uv.x));
58+
}
59+
var color = vec4(out, 1.0);
60+
#ifdef TONEMAP_IN_SHADER
61+
color = tone_mapping(color);
62+
#endif
63+
return color;
64+
}

crates/bevy_core_pipeline/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ keywords = ["bevy"]
1515
[features]
1616
trace = []
1717
webgl = []
18+
tonemapping_luts = []
1819

1920
[dependencies]
2021
# bevy

crates/bevy_core_pipeline/src/core_2d/camera_2d.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
use crate::{clear_color::ClearColorConfig, tonemapping::Tonemapping};
1+
use crate::{
2+
clear_color::ClearColorConfig,
3+
tonemapping::{DebandDither, Tonemapping},
4+
};
25
use bevy_ecs::prelude::*;
36
use bevy_reflect::Reflect;
47
use bevy_render::{
@@ -27,6 +30,7 @@ pub struct Camera2dBundle {
2730
pub global_transform: GlobalTransform,
2831
pub camera_2d: Camera2d,
2932
pub tonemapping: Tonemapping,
33+
pub deband_dither: DebandDither,
3034
}
3135

3236
impl Default for Camera2dBundle {
@@ -67,7 +71,8 @@ impl Camera2dBundle {
6771
global_transform: Default::default(),
6872
camera: Camera::default(),
6973
camera_2d: Camera2d::default(),
70-
tonemapping: Tonemapping::Disabled,
74+
tonemapping: Tonemapping::None,
75+
deband_dither: DebandDither::Disabled,
7176
}
7277
}
7378
}

crates/bevy_core_pipeline/src/core_3d/camera_3d.rs

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
1-
use crate::{clear_color::ClearColorConfig, tonemapping::Tonemapping};
1+
use crate::{
2+
clear_color::ClearColorConfig,
3+
tonemapping::{DebandDither, Tonemapping},
4+
};
25
use bevy_ecs::prelude::*;
36
use bevy_reflect::{Reflect, ReflectDeserialize, ReflectSerialize};
47
use bevy_render::{
58
camera::{Camera, CameraRenderGraph, Projection},
69
extract_component::ExtractComponent,
710
primitives::Frustum,
811
render_resource::LoadOp,
9-
view::VisibleEntities,
12+
view::{ColorGrading, VisibleEntities},
1013
};
1114
use bevy_transform::prelude::{GlobalTransform, Transform};
1215
use serde::{Deserialize, Serialize};
@@ -59,23 +62,25 @@ pub struct Camera3dBundle {
5962
pub global_transform: GlobalTransform,
6063
pub camera_3d: Camera3d,
6164
pub tonemapping: Tonemapping,
65+
pub dither: DebandDither,
66+
pub color_grading: ColorGrading,
6267
}
6368

6469
// NOTE: ideally Perspective and Orthographic defaults can share the same impl, but sadly it breaks rust's type inference
6570
impl Default for Camera3dBundle {
6671
fn default() -> Self {
6772
Self {
6873
camera_render_graph: CameraRenderGraph::new(crate::core_3d::graph::NAME),
69-
tonemapping: Tonemapping::Enabled {
70-
deband_dither: true,
71-
},
7274
camera: Default::default(),
7375
projection: Default::default(),
7476
visible_entities: Default::default(),
7577
frustum: Default::default(),
7678
transform: Default::default(),
7779
global_transform: Default::default(),
7880
camera_3d: Default::default(),
81+
tonemapping: Tonemapping::ReinhardLuminance,
82+
dither: DebandDither::Enabled,
83+
color_grading: ColorGrading::default(),
7984
}
8085
}
8186
}
Binary file not shown.
Binary file not shown.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
--- Process for recreating AgX-default_contrast.ktx2 ---
2+
Download:
3+
https://github.com/MrLixm/AgXc/blob/898198e0490b0551ed81412a0c22e0b72fffb7cd/obs/obs-script/AgX-default_contrast.lut.png
4+
Convert to vertical strip exr with:
5+
https://gist.github.com/DGriffin91/fc8e0cfd55aaa175ac10199403bc19b8
6+
Convert exr to 3D ktx2 with:
7+
https://gist.github.com/DGriffin91/49401c43378b58bce32059291097d4ca
8+
9+
--- Process for recreating tony_mc_mapface.ktx2 ---
10+
Download:
11+
https://github.com/h3r2tic/tony-mc-mapface/blob/909e51c8a74251fd828770248476cb084081e08c/tony_mc_mapface.dds
12+
Convert dds to 3D ktx2 with:
13+
https://gist.github.com/DGriffin91/49401c43378b58bce32059291097d4ca
14+
15+
--- Process for recreating Blender_-11_12.ktx2 ---
16+
Create LUT stimulus with:
17+
https://gist.github.com/DGriffin91/e119bf32b520e219f6e102a6eba4a0cf
18+
Open LUT image in Blender's image editor and make sure color space is set to linear.
19+
Export from Blender as 32bit EXR, override color space to Filmic sRGB.
20+
Import EXR back into blender set color space to sRGB, then export as 32bit EXR override color space to linear.
21+
Convert exr to 3D ktx2 with:
22+
https://gist.github.com/DGriffin91/49401c43378b58bce32059291097d4ca
Binary file not shown.

0 commit comments

Comments
 (0)