mirror of
https://github.com/bevyengine/bevy
synced 2024-11-21 20:23:28 +00:00
Implement filmic color grading. (#13121)
This commit expands Bevy's existing tonemapping feature to a complete set of filmic color grading tools, matching those of engines like Unity, Unreal, and Godot. The following features are supported: * White point adjustment. This is inspired by Unity's implementation of the feature, but simplified and optimized. *Temperature* and *tint* control the adjustments to the *x* and *y* chromaticity values of [CIE 1931]. Following Unity, the adjustments are made relative to the [D65 standard illuminant] in the [LMS color space]. * Hue rotation. This simply converts the RGB value to [HSV], alters the hue, and converts back. * Color correction. This allows the *gamma*, *gain*, and *lift* values to be adjusted according to the standard [ASC CDL combined function]. * Separate color correction for shadows, midtones, and highlights. Blender's source code was used as a reference for the implementation of this. The midtone ranges can be adjusted by the user. To avoid abrupt color changes, a small crossfade is used between the different sections of the image, again following Blender's formulas. A new example, `color_grading`, has been added, offering a GUI to change all the color grading settings. It uses the same test scene as the existing `tonemapping` example, which has been factored out into a shared glTF scene. [CIE 1931]: https://en.wikipedia.org/wiki/CIE_1931_color_space [D65 standard illuminant]: https://en.wikipedia.org/wiki/Standard_illuminant#Illuminant_series_D [LMS color space]: https://en.wikipedia.org/wiki/LMS_color_space [HSV]: https://en.wikipedia.org/wiki/HSL_and_HSV [ASC CDL combined function]: https://en.wikipedia.org/wiki/ASC_CDL#Combined_Function ## Changelog ### Added * Many new filmic color grading options have been added to the `ColorGrading` component. ## Migration Guide * `ColorGrading::gamma` and `ColorGrading::pre_saturation` are now set separately for the `shadows`, `midtones`, and `highlights` sections. You can migrate code with the `ColorGrading::all_sections` and `ColorGrading::all_sections_mut` functions, which access and/or update all sections at once. * `ColorGrading::post_saturation` and `ColorGrading::exposure` are now fields of `ColorGrading::global`. ## Screenshots ![Screenshot 2024-04-27 143144](https://github.com/bevyengine/bevy/assets/157897/c1de5894-917d-4101-b5c9-e644d141a941) ![Screenshot 2024-04-27 143216](https://github.com/bevyengine/bevy/assets/157897/da393c8a-d747-42f5-b47c-6465044c788d)
This commit is contained in:
parent
abddbf2d95
commit
961b24deaf
16 changed files with 1969 additions and 184 deletions
11
Cargo.toml
11
Cargo.toml
|
@ -2910,6 +2910,17 @@ description = "Demonstrates FPS overlay"
|
|||
category = "Dev tools"
|
||||
wasm = true
|
||||
|
||||
[[example]]
|
||||
name = "color_grading"
|
||||
path = "examples/3d/color_grading.rs"
|
||||
doc-scrape-examples = true
|
||||
|
||||
[package.metadata.example.color_grading]
|
||||
name = "Color grading"
|
||||
description = "Demonstrates color grading"
|
||||
category = "3D Rendering"
|
||||
wasm = true
|
||||
|
||||
[profile.wasm-release]
|
||||
inherits = "release"
|
||||
opt-level = "z"
|
||||
|
|
BIN
assets/models/TonemappingTest/TestPattern.png
Normal file
BIN
assets/models/TonemappingTest/TestPattern.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 361 B |
BIN
assets/models/TonemappingTest/TonemappingTest.bin
Normal file
BIN
assets/models/TonemappingTest/TonemappingTest.bin
Normal file
Binary file not shown.
679
assets/models/TonemappingTest/TonemappingTest.gltf
Normal file
679
assets/models/TonemappingTest/TonemappingTest.gltf
Normal file
|
@ -0,0 +1,679 @@
|
|||
{
|
||||
"asset":{
|
||||
"generator":"Khronos glTF Blender I/O v4.0.44",
|
||||
"version":"2.0"
|
||||
},
|
||||
"scene":0,
|
||||
"scenes":[
|
||||
{
|
||||
"name":"Scene",
|
||||
"nodes":[
|
||||
0,
|
||||
1,
|
||||
2
|
||||
]
|
||||
}
|
||||
],
|
||||
"nodes":[
|
||||
{
|
||||
"mesh":0,
|
||||
"name":"Plane",
|
||||
"scale":[
|
||||
50,
|
||||
1,
|
||||
50
|
||||
]
|
||||
},
|
||||
{
|
||||
"mesh":1,
|
||||
"name":"Cube",
|
||||
"translation":[
|
||||
-1,
|
||||
0.125,
|
||||
0
|
||||
]
|
||||
},
|
||||
{
|
||||
"mesh":2,
|
||||
"name":"Sphere",
|
||||
"translation":[
|
||||
0,
|
||||
0.125,
|
||||
0
|
||||
]
|
||||
}
|
||||
],
|
||||
"materials":[
|
||||
{
|
||||
"doubleSided":true,
|
||||
"name":"Material.001",
|
||||
"pbrMetallicRoughness":{
|
||||
"baseColorFactor":[
|
||||
0.10000000149011612,
|
||||
0.20000000298023224,
|
||||
0.10000000149011612,
|
||||
1
|
||||
],
|
||||
"metallicFactor":0,
|
||||
"roughnessFactor":0.5
|
||||
}
|
||||
},
|
||||
{
|
||||
"doubleSided":true,
|
||||
"name":"Material.002",
|
||||
"pbrMetallicRoughness":{
|
||||
"baseColorTexture":{
|
||||
"index":0
|
||||
},
|
||||
"metallicFactor":0,
|
||||
"roughnessFactor":0.5
|
||||
}
|
||||
},
|
||||
{
|
||||
"doubleSided":true,
|
||||
"name":"Sphere0",
|
||||
"pbrMetallicRoughness":{
|
||||
"baseColorFactor":[
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
1
|
||||
],
|
||||
"metallicFactor":0,
|
||||
"roughnessFactor":0.08900000154972076
|
||||
}
|
||||
},
|
||||
{
|
||||
"doubleSided":true,
|
||||
"name":"Sphere1",
|
||||
"pbrMetallicRoughness":{
|
||||
"baseColorFactor":[
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
1
|
||||
],
|
||||
"metallicFactor":0,
|
||||
"roughnessFactor":0.08900000154972076
|
||||
}
|
||||
},
|
||||
{
|
||||
"doubleSided":true,
|
||||
"name":"Sphere2",
|
||||
"pbrMetallicRoughness":{
|
||||
"baseColorFactor":[
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1
|
||||
],
|
||||
"metallicFactor":0,
|
||||
"roughnessFactor":0.08900000154972076
|
||||
}
|
||||
},
|
||||
{
|
||||
"doubleSided":true,
|
||||
"name":"Sphere3",
|
||||
"pbrMetallicRoughness":{
|
||||
"baseColorFactor":[
|
||||
0.20000000298023224,
|
||||
0.20000000298023224,
|
||||
1,
|
||||
1
|
||||
],
|
||||
"metallicFactor":0,
|
||||
"roughnessFactor":0.08900000154972076
|
||||
}
|
||||
},
|
||||
{
|
||||
"doubleSided":true,
|
||||
"name":"Sphere4",
|
||||
"pbrMetallicRoughness":{
|
||||
"baseColorFactor":[
|
||||
0.20000000298023224,
|
||||
1,
|
||||
0.20000000298023224,
|
||||
1
|
||||
],
|
||||
"metallicFactor":0,
|
||||
"roughnessFactor":0.08900000154972076
|
||||
}
|
||||
},
|
||||
{
|
||||
"doubleSided":true,
|
||||
"name":"Sphere5",
|
||||
"pbrMetallicRoughness":{
|
||||
"baseColorFactor":[
|
||||
1,
|
||||
0.20000000298023224,
|
||||
0.20000000298023224,
|
||||
1
|
||||
],
|
||||
"metallicFactor":0,
|
||||
"roughnessFactor":0.08900000154972076
|
||||
}
|
||||
}
|
||||
],
|
||||
"meshes":[
|
||||
{
|
||||
"name":"Plane",
|
||||
"primitives":[
|
||||
{
|
||||
"attributes":{
|
||||
"POSITION":0,
|
||||
"NORMAL":1,
|
||||
"TEXCOORD_0":2
|
||||
},
|
||||
"indices":3,
|
||||
"material":0
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name":"Cube.001",
|
||||
"primitives":[
|
||||
{
|
||||
"attributes":{
|
||||
"POSITION":4,
|
||||
"NORMAL":5,
|
||||
"TEXCOORD_0":6
|
||||
},
|
||||
"indices":7,
|
||||
"material":1
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name":"Sphere",
|
||||
"primitives":[
|
||||
{
|
||||
"attributes":{
|
||||
"POSITION":8,
|
||||
"NORMAL":9,
|
||||
"TEXCOORD_0":10
|
||||
},
|
||||
"indices":11,
|
||||
"material":2
|
||||
},
|
||||
{
|
||||
"attributes":{
|
||||
"POSITION":12,
|
||||
"NORMAL":13,
|
||||
"TEXCOORD_0":14
|
||||
},
|
||||
"indices":11,
|
||||
"material":3
|
||||
},
|
||||
{
|
||||
"attributes":{
|
||||
"POSITION":15,
|
||||
"NORMAL":16,
|
||||
"TEXCOORD_0":17
|
||||
},
|
||||
"indices":11,
|
||||
"material":4
|
||||
},
|
||||
{
|
||||
"attributes":{
|
||||
"POSITION":18,
|
||||
"NORMAL":19,
|
||||
"TEXCOORD_0":20
|
||||
},
|
||||
"indices":11,
|
||||
"material":5
|
||||
},
|
||||
{
|
||||
"attributes":{
|
||||
"POSITION":21,
|
||||
"NORMAL":22,
|
||||
"TEXCOORD_0":23
|
||||
},
|
||||
"indices":11,
|
||||
"material":6
|
||||
},
|
||||
{
|
||||
"attributes":{
|
||||
"POSITION":24,
|
||||
"NORMAL":25,
|
||||
"TEXCOORD_0":26
|
||||
},
|
||||
"indices":11,
|
||||
"material":7
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"textures":[
|
||||
{
|
||||
"sampler":0,
|
||||
"source":0
|
||||
}
|
||||
],
|
||||
"images":[
|
||||
{
|
||||
"mimeType":"image/png",
|
||||
"name":"TestPattern",
|
||||
"uri":"TestPattern.png"
|
||||
}
|
||||
],
|
||||
"accessors":[
|
||||
{
|
||||
"bufferView":0,
|
||||
"componentType":5126,
|
||||
"count":4,
|
||||
"max":[
|
||||
1,
|
||||
0,
|
||||
1
|
||||
],
|
||||
"min":[
|
||||
-1,
|
||||
0,
|
||||
-1
|
||||
],
|
||||
"type":"VEC3"
|
||||
},
|
||||
{
|
||||
"bufferView":1,
|
||||
"componentType":5126,
|
||||
"count":4,
|
||||
"type":"VEC3"
|
||||
},
|
||||
{
|
||||
"bufferView":2,
|
||||
"componentType":5126,
|
||||
"count":4,
|
||||
"type":"VEC2"
|
||||
},
|
||||
{
|
||||
"bufferView":3,
|
||||
"componentType":5123,
|
||||
"count":6,
|
||||
"type":"SCALAR"
|
||||
},
|
||||
{
|
||||
"bufferView":4,
|
||||
"componentType":5126,
|
||||
"count":120,
|
||||
"max":[
|
||||
1.125,
|
||||
0.125,
|
||||
0.125
|
||||
],
|
||||
"min":[
|
||||
-0.125,
|
||||
-0.125,
|
||||
-2.125
|
||||
],
|
||||
"type":"VEC3"
|
||||
},
|
||||
{
|
||||
"bufferView":5,
|
||||
"componentType":5126,
|
||||
"count":120,
|
||||
"type":"VEC3"
|
||||
},
|
||||
{
|
||||
"bufferView":6,
|
||||
"componentType":5126,
|
||||
"count":120,
|
||||
"type":"VEC2"
|
||||
},
|
||||
{
|
||||
"bufferView":7,
|
||||
"componentType":5123,
|
||||
"count":180,
|
||||
"type":"SCALAR"
|
||||
},
|
||||
{
|
||||
"bufferView":8,
|
||||
"componentType":5126,
|
||||
"count":625,
|
||||
"max":[
|
||||
-0.42500004172325134,
|
||||
0.125,
|
||||
0.37499991059303284
|
||||
],
|
||||
"min":[
|
||||
-0.6749998927116394,
|
||||
-0.125,
|
||||
0.125
|
||||
],
|
||||
"type":"VEC3"
|
||||
},
|
||||
{
|
||||
"bufferView":9,
|
||||
"componentType":5126,
|
||||
"count":625,
|
||||
"type":"VEC3"
|
||||
},
|
||||
{
|
||||
"bufferView":10,
|
||||
"componentType":5126,
|
||||
"count":625,
|
||||
"type":"VEC2"
|
||||
},
|
||||
{
|
||||
"bufferView":11,
|
||||
"componentType":5123,
|
||||
"count":3264,
|
||||
"type":"SCALAR"
|
||||
},
|
||||
{
|
||||
"bufferView":12,
|
||||
"componentType":5126,
|
||||
"count":625,
|
||||
"max":[
|
||||
-0.17500004172325134,
|
||||
0.125,
|
||||
0.12499991804361343
|
||||
],
|
||||
"min":[
|
||||
-0.4249998927116394,
|
||||
-0.125,
|
||||
-0.125
|
||||
],
|
||||
"type":"VEC3"
|
||||
},
|
||||
{
|
||||
"bufferView":13,
|
||||
"componentType":5126,
|
||||
"count":625,
|
||||
"type":"VEC3"
|
||||
},
|
||||
{
|
||||
"bufferView":14,
|
||||
"componentType":5126,
|
||||
"count":625,
|
||||
"type":"VEC2"
|
||||
},
|
||||
{
|
||||
"bufferView":15,
|
||||
"componentType":5126,
|
||||
"count":625,
|
||||
"max":[
|
||||
0.07499995082616806,
|
||||
0.125,
|
||||
-0.12500005960464478
|
||||
],
|
||||
"min":[
|
||||
-0.1749998927116394,
|
||||
-0.125,
|
||||
-0.3749999701976776
|
||||
],
|
||||
"type":"VEC3"
|
||||
},
|
||||
{
|
||||
"bufferView":16,
|
||||
"componentType":5126,
|
||||
"count":625,
|
||||
"type":"VEC3"
|
||||
},
|
||||
{
|
||||
"bufferView":17,
|
||||
"componentType":5126,
|
||||
"count":625,
|
||||
"type":"VEC2"
|
||||
},
|
||||
{
|
||||
"bufferView":18,
|
||||
"componentType":5126,
|
||||
"count":625,
|
||||
"max":[
|
||||
-0.1250000298023224,
|
||||
0.125,
|
||||
0.6749999523162842
|
||||
],
|
||||
"min":[
|
||||
-0.37499988079071045,
|
||||
-0.125,
|
||||
0.42500001192092896
|
||||
],
|
||||
"type":"VEC3"
|
||||
},
|
||||
{
|
||||
"bufferView":19,
|
||||
"componentType":5126,
|
||||
"count":625,
|
||||
"type":"VEC3"
|
||||
},
|
||||
{
|
||||
"bufferView":20,
|
||||
"componentType":5126,
|
||||
"count":625,
|
||||
"type":"VEC2"
|
||||
},
|
||||
{
|
||||
"bufferView":21,
|
||||
"componentType":5126,
|
||||
"count":625,
|
||||
"max":[
|
||||
0.12499996274709702,
|
||||
0.125,
|
||||
0.4249999225139618
|
||||
],
|
||||
"min":[
|
||||
-0.12499988079071045,
|
||||
-0.125,
|
||||
0.17500001192092896
|
||||
],
|
||||
"type":"VEC3"
|
||||
},
|
||||
{
|
||||
"bufferView":22,
|
||||
"componentType":5126,
|
||||
"count":625,
|
||||
"type":"VEC3"
|
||||
},
|
||||
{
|
||||
"bufferView":23,
|
||||
"componentType":5126,
|
||||
"count":625,
|
||||
"type":"VEC2"
|
||||
},
|
||||
{
|
||||
"bufferView":24,
|
||||
"componentType":5126,
|
||||
"count":625,
|
||||
"max":[
|
||||
0.3749999403953552,
|
||||
0.125,
|
||||
0.1749999225139618
|
||||
],
|
||||
"min":[
|
||||
0.12500008940696716,
|
||||
-0.125,
|
||||
-0.07499998807907104
|
||||
],
|
||||
"type":"VEC3"
|
||||
},
|
||||
{
|
||||
"bufferView":25,
|
||||
"componentType":5126,
|
||||
"count":625,
|
||||
"type":"VEC3"
|
||||
},
|
||||
{
|
||||
"bufferView":26,
|
||||
"componentType":5126,
|
||||
"count":625,
|
||||
"type":"VEC2"
|
||||
}
|
||||
],
|
||||
"bufferViews":[
|
||||
{
|
||||
"buffer":0,
|
||||
"byteLength":48,
|
||||
"byteOffset":0,
|
||||
"target":34962
|
||||
},
|
||||
{
|
||||
"buffer":0,
|
||||
"byteLength":48,
|
||||
"byteOffset":48,
|
||||
"target":34962
|
||||
},
|
||||
{
|
||||
"buffer":0,
|
||||
"byteLength":32,
|
||||
"byteOffset":96,
|
||||
"target":34962
|
||||
},
|
||||
{
|
||||
"buffer":0,
|
||||
"byteLength":12,
|
||||
"byteOffset":128,
|
||||
"target":34963
|
||||
},
|
||||
{
|
||||
"buffer":0,
|
||||
"byteLength":1440,
|
||||
"byteOffset":140,
|
||||
"target":34962
|
||||
},
|
||||
{
|
||||
"buffer":0,
|
||||
"byteLength":1440,
|
||||
"byteOffset":1580,
|
||||
"target":34962
|
||||
},
|
||||
{
|
||||
"buffer":0,
|
||||
"byteLength":960,
|
||||
"byteOffset":3020,
|
||||
"target":34962
|
||||
},
|
||||
{
|
||||
"buffer":0,
|
||||
"byteLength":360,
|
||||
"byteOffset":3980,
|
||||
"target":34963
|
||||
},
|
||||
{
|
||||
"buffer":0,
|
||||
"byteLength":7500,
|
||||
"byteOffset":4340,
|
||||
"target":34962
|
||||
},
|
||||
{
|
||||
"buffer":0,
|
||||
"byteLength":7500,
|
||||
"byteOffset":11840,
|
||||
"target":34962
|
||||
},
|
||||
{
|
||||
"buffer":0,
|
||||
"byteLength":5000,
|
||||
"byteOffset":19340,
|
||||
"target":34962
|
||||
},
|
||||
{
|
||||
"buffer":0,
|
||||
"byteLength":6528,
|
||||
"byteOffset":24340,
|
||||
"target":34963
|
||||
},
|
||||
{
|
||||
"buffer":0,
|
||||
"byteLength":7500,
|
||||
"byteOffset":30868,
|
||||
"target":34962
|
||||
},
|
||||
{
|
||||
"buffer":0,
|
||||
"byteLength":7500,
|
||||
"byteOffset":38368,
|
||||
"target":34962
|
||||
},
|
||||
{
|
||||
"buffer":0,
|
||||
"byteLength":5000,
|
||||
"byteOffset":45868,
|
||||
"target":34962
|
||||
},
|
||||
{
|
||||
"buffer":0,
|
||||
"byteLength":7500,
|
||||
"byteOffset":50868,
|
||||
"target":34962
|
||||
},
|
||||
{
|
||||
"buffer":0,
|
||||
"byteLength":7500,
|
||||
"byteOffset":58368,
|
||||
"target":34962
|
||||
},
|
||||
{
|
||||
"buffer":0,
|
||||
"byteLength":5000,
|
||||
"byteOffset":65868,
|
||||
"target":34962
|
||||
},
|
||||
{
|
||||
"buffer":0,
|
||||
"byteLength":7500,
|
||||
"byteOffset":70868,
|
||||
"target":34962
|
||||
},
|
||||
{
|
||||
"buffer":0,
|
||||
"byteLength":7500,
|
||||
"byteOffset":78368,
|
||||
"target":34962
|
||||
},
|
||||
{
|
||||
"buffer":0,
|
||||
"byteLength":5000,
|
||||
"byteOffset":85868,
|
||||
"target":34962
|
||||
},
|
||||
{
|
||||
"buffer":0,
|
||||
"byteLength":7500,
|
||||
"byteOffset":90868,
|
||||
"target":34962
|
||||
},
|
||||
{
|
||||
"buffer":0,
|
||||
"byteLength":7500,
|
||||
"byteOffset":98368,
|
||||
"target":34962
|
||||
},
|
||||
{
|
||||
"buffer":0,
|
||||
"byteLength":5000,
|
||||
"byteOffset":105868,
|
||||
"target":34962
|
||||
},
|
||||
{
|
||||
"buffer":0,
|
||||
"byteLength":7500,
|
||||
"byteOffset":110868,
|
||||
"target":34962
|
||||
},
|
||||
{
|
||||
"buffer":0,
|
||||
"byteLength":7500,
|
||||
"byteOffset":118368,
|
||||
"target":34962
|
||||
},
|
||||
{
|
||||
"buffer":0,
|
||||
"byteLength":5000,
|
||||
"byteOffset":125868,
|
||||
"target":34962
|
||||
}
|
||||
],
|
||||
"samplers":[
|
||||
{
|
||||
"magFilter":9728,
|
||||
"minFilter":9984
|
||||
}
|
||||
],
|
||||
"buffers":[
|
||||
{
|
||||
"byteLength":130868,
|
||||
"uri":"TonemappingTest.bin"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -11,11 +11,12 @@ use bevy_render::render_resource::binding_types::{
|
|||
};
|
||||
use bevy_render::renderer::RenderDevice;
|
||||
use bevy_render::texture::{CompressedImageFormats, GpuImage, Image, ImageSampler, ImageType};
|
||||
use bevy_render::view::{ViewTarget, ViewUniform};
|
||||
use bevy_render::view::{ExtractedView, ViewTarget, ViewUniform};
|
||||
use bevy_render::{camera::Camera, texture::FallbackImage};
|
||||
use bevy_render::{render_resource::*, Render, RenderApp, RenderSet};
|
||||
#[cfg(not(feature = "tonemapping_luts"))]
|
||||
use bevy_utils::tracing::error;
|
||||
use bitflags::bitflags;
|
||||
|
||||
mod node;
|
||||
|
||||
|
@ -179,10 +180,27 @@ impl Tonemapping {
|
|||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
/// Various flags describing what tonemapping needs to do.
|
||||
///
|
||||
/// This allows the shader to skip unneeded steps.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
|
||||
pub struct TonemappingPipelineKeyFlags: u8 {
|
||||
/// The hue needs to be changed.
|
||||
const HUE_ROTATE = 0x01;
|
||||
/// The white balance needs to be adjusted.
|
||||
const WHITE_BALANCE = 0x02;
|
||||
/// Saturation/contrast/gamma/gain/lift for one or more sections
|
||||
/// (shadows, midtones, highlights) need to be adjusted.
|
||||
const SECTIONAL_COLOR_GRADING = 0x04;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct TonemappingPipelineKey {
|
||||
deband_dither: DebandDither,
|
||||
tonemapping: Tonemapping,
|
||||
flags: TonemappingPipelineKeyFlags,
|
||||
}
|
||||
|
||||
impl SpecializedRenderPipeline for TonemappingPipeline {
|
||||
|
@ -194,6 +212,23 @@ impl SpecializedRenderPipeline for TonemappingPipeline {
|
|||
shader_defs.push("DEBAND_DITHER".into());
|
||||
}
|
||||
|
||||
// Define shader flags depending on the color grading options in use.
|
||||
if key.flags.contains(TonemappingPipelineKeyFlags::HUE_ROTATE) {
|
||||
shader_defs.push("HUE_ROTATE".into());
|
||||
}
|
||||
if key
|
||||
.flags
|
||||
.contains(TonemappingPipelineKeyFlags::WHITE_BALANCE)
|
||||
{
|
||||
shader_defs.push("WHITE_BALANCE".into());
|
||||
}
|
||||
if key
|
||||
.flags
|
||||
.contains(TonemappingPipelineKeyFlags::SECTIONAL_COLOR_GRADING)
|
||||
{
|
||||
shader_defs.push("SECTIONAL_COLOR_GRADING".into());
|
||||
}
|
||||
|
||||
match key.tonemapping {
|
||||
Tonemapping::None => shader_defs.push("TONEMAP_METHOD_NONE".into()),
|
||||
Tonemapping::Reinhard => shader_defs.push("TONEMAP_METHOD_REINHARD".into()),
|
||||
|
@ -292,12 +327,38 @@ pub fn prepare_view_tonemapping_pipelines(
|
|||
pipeline_cache: Res<PipelineCache>,
|
||||
mut pipelines: ResMut<SpecializedRenderPipelines<TonemappingPipeline>>,
|
||||
upscaling_pipeline: Res<TonemappingPipeline>,
|
||||
view_targets: Query<(Entity, Option<&Tonemapping>, Option<&DebandDither>), With<ViewTarget>>,
|
||||
view_targets: Query<
|
||||
(
|
||||
Entity,
|
||||
&ExtractedView,
|
||||
Option<&Tonemapping>,
|
||||
Option<&DebandDither>,
|
||||
),
|
||||
With<ViewTarget>,
|
||||
>,
|
||||
) {
|
||||
for (entity, tonemapping, dither) in view_targets.iter() {
|
||||
for (entity, view, tonemapping, dither) in view_targets.iter() {
|
||||
// As an optimization, we omit parts of the shader that are unneeded.
|
||||
let mut flags = TonemappingPipelineKeyFlags::empty();
|
||||
flags.set(
|
||||
TonemappingPipelineKeyFlags::HUE_ROTATE,
|
||||
view.color_grading.global.hue != 0.0,
|
||||
);
|
||||
flags.set(
|
||||
TonemappingPipelineKeyFlags::WHITE_BALANCE,
|
||||
view.color_grading.global.temperature != 0.0 || view.color_grading.global.tint != 0.0,
|
||||
);
|
||||
flags.set(
|
||||
TonemappingPipelineKeyFlags::SECTIONAL_COLOR_GRADING,
|
||||
view.color_grading
|
||||
.all_sections()
|
||||
.any(|section| *section != default()),
|
||||
);
|
||||
|
||||
let key = TonemappingPipelineKey {
|
||||
deband_dither: *dither.unwrap_or(&DebandDither::Disabled),
|
||||
tonemapping: *tonemapping.unwrap_or(&Tonemapping::None),
|
||||
flags,
|
||||
};
|
||||
let pipeline = pipelines.specialize(&pipeline_cache, &upscaling_pipeline, key);
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#define_import_path bevy_core_pipeline::tonemapping
|
||||
|
||||
#import bevy_render::view::ColorGrading
|
||||
#import bevy_pbr::utils::{PI_2, hsv_to_rgb, rgb_to_hsv};
|
||||
|
||||
// hack !! not sure what to do with this
|
||||
#ifdef TONEMAPPING_PASS
|
||||
|
@ -11,6 +12,15 @@
|
|||
@group(0) @binding(19) var dt_lut_sampler: sampler;
|
||||
#endif
|
||||
|
||||
// Half the size of the crossfade region between shadows and midtones and
|
||||
// between midtones and highlights. This value, 0.1, corresponds to 10% of the
|
||||
// gamut on either side of the cutoff point.
|
||||
const LEVEL_MARGIN: f32 = 0.1;
|
||||
|
||||
// The inverse reciprocal of twice the above, used when scaling the midtone
|
||||
// region.
|
||||
const LEVEL_MARGIN_DIV: f32 = 0.5 / LEVEL_MARGIN;
|
||||
|
||||
fn sample_current_lut(p: vec3<f32>) -> vec3<f32> {
|
||||
// Don't include code that will try to sample from LUTs if tonemap method doesn't require it
|
||||
// Allows this file to be imported without necessarily needing the lut texture bindings
|
||||
|
@ -273,22 +283,92 @@ fn screen_space_dither(frag_coord: vec2<f32>) -> vec3<f32> {
|
|||
return (dither - 0.5) / 255.0;
|
||||
}
|
||||
|
||||
fn tone_mapping(in: vec4<f32>, color_grading: ColorGrading) -> vec4<f32> {
|
||||
// Performs the "sectional" color grading: i.e. the color grading that applies
|
||||
// individually to shadows, midtones, and highlights.
|
||||
fn sectional_color_grading(
|
||||
in: vec3<f32>,
|
||||
color_grading: ptr<function, ColorGrading>,
|
||||
) -> vec3<f32> {
|
||||
var color = in;
|
||||
|
||||
// Determine whether the color is a shadow, midtone, or highlight. Colors
|
||||
// close to the edges are considered a mix of both, to avoid sharp
|
||||
// discontinuities. The formulas are taken from Blender's compositor.
|
||||
|
||||
let level = (color.r + color.g + color.b) / 3.0;
|
||||
|
||||
// Determine whether this color is a shadow, midtone, or highlight. If close
|
||||
// to the cutoff points, blend between the two to avoid sharp color
|
||||
// discontinuities.
|
||||
var levels = vec3(0.0);
|
||||
let midtone_range = (*color_grading).midtone_range;
|
||||
if (level < midtone_range.x - LEVEL_MARGIN) {
|
||||
levels.x = 1.0;
|
||||
} else if (level < midtone_range.x + LEVEL_MARGIN) {
|
||||
levels.y = ((level - midtone_range.x) * LEVEL_MARGIN_DIV) + 0.5;
|
||||
levels.z = 1.0 - levels.y;
|
||||
} else if (level < midtone_range.y - LEVEL_MARGIN) {
|
||||
levels.y = 1.0;
|
||||
} else if (level < midtone_range.y + LEVEL_MARGIN) {
|
||||
levels.z = ((level - midtone_range.y) * LEVEL_MARGIN_DIV) + 0.5;
|
||||
levels.y = 1.0 - levels.z;
|
||||
} else {
|
||||
levels.z = 1.0;
|
||||
}
|
||||
|
||||
// Calculate contrast/saturation/gamma/gain/lift.
|
||||
let contrast = dot(levels, (*color_grading).contrast);
|
||||
let saturation = dot(levels, (*color_grading).saturation);
|
||||
let gamma = dot(levels, (*color_grading).gamma);
|
||||
let gain = dot(levels, (*color_grading).gain);
|
||||
let lift = dot(levels, (*color_grading).lift);
|
||||
|
||||
// Adjust saturation and contrast.
|
||||
let luma = tonemapping_luminance(color);
|
||||
color = luma + saturation * (color - luma);
|
||||
color = 0.5 + (color - 0.5) * contrast;
|
||||
|
||||
// The [ASC CDL] formula for color correction. Given *i*, an input color, we
|
||||
// have:
|
||||
//
|
||||
// out = (i × s + o)ⁿ
|
||||
//
|
||||
// Following the normal photographic naming convention, *gain* is the *s*
|
||||
// factor, *lift* is the *o* term, and the inverse of *gamma* is the *n*
|
||||
// exponent.
|
||||
//
|
||||
// [ASC CDL]: https://en.wikipedia.org/wiki/ASC_CDL#Combined_Function
|
||||
color = powsafe(color * gain + lift, 1.0 / gamma);
|
||||
|
||||
// Account for exposure.
|
||||
color = color * powsafe(vec3(2.0), (*color_grading).exposure);
|
||||
return max(color, vec3(0.0));
|
||||
}
|
||||
|
||||
fn tone_mapping(in: vec4<f32>, in_color_grading: ColorGrading) -> vec4<f32> {
|
||||
var color = max(in.rgb, vec3(0.0));
|
||||
var color_grading = in_color_grading; // So we can take pointers to it.
|
||||
|
||||
// Possible future grading:
|
||||
// Rotate hue if needed, by converting to and from HSV. Remember that hue is
|
||||
// an angle, so it needs to be modulo 2π.
|
||||
#ifdef HUE_ROTATE
|
||||
var hsv = rgb_to_hsv(color);
|
||||
hsv.r = (hsv.r + color_grading.hue) % PI_2;
|
||||
color = hsv_to_rgb(hsv);
|
||||
#endif
|
||||
|
||||
// highlight gain gamma: 0..
|
||||
// let luma = powsafe(vec3(tonemapping_luminance(color)), 1.0);
|
||||
// Perform white balance correction. Conveniently, this is a linear
|
||||
// transform. The matrix was pre-calculated from the temperature and tint
|
||||
// values on the CPU.
|
||||
#ifdef WHITE_BALANCE
|
||||
color = max(color_grading.balance * color, vec3(0.0));
|
||||
#endif
|
||||
|
||||
// highlight gain: 0..
|
||||
// color += color * luma.xxx * 1.0;
|
||||
|
||||
// Linear pre tonemapping grading
|
||||
color = saturation(color, color_grading.pre_saturation);
|
||||
color = powsafe(color, color_grading.gamma);
|
||||
color = color * powsafe(vec3(2.0), color_grading.exposure);
|
||||
color = max(color, vec3(0.0));
|
||||
// Perform the "sectional" color grading: i.e. the color grading that
|
||||
// applies individually to shadows, midtones, and highlights.
|
||||
#ifdef SECTIONAL_COLOR_GRADING
|
||||
color = sectional_color_grading(color, &color_grading);
|
||||
#endif
|
||||
|
||||
// tone_mapping
|
||||
#ifdef TONEMAP_METHOD_NONE
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
#import bevy_pbr::{
|
||||
mesh_view_bindings as bindings,
|
||||
utils::{hsv2rgb, rand_f},
|
||||
utils::{PI_2, hsv_to_rgb, rand_f},
|
||||
}
|
||||
|
||||
// NOTE: Keep in sync with bevy_pbr/src/light.rs
|
||||
|
@ -78,7 +78,11 @@ fn cluster_debug_visualization(
|
|||
if (z_slice & 1u) == 1u {
|
||||
z_slice = z_slice + bindings::lights.cluster_dimensions.z / 2u;
|
||||
}
|
||||
let slice_color = hsv2rgb(f32(z_slice) / f32(bindings::lights.cluster_dimensions.z + 1u), 1.0, 0.5);
|
||||
let slice_color = hsv_to_rgb(
|
||||
f32(z_slice) / f32(bindings::lights.cluster_dimensions.z + 1u) * PI_2,
|
||||
1.0,
|
||||
0.5
|
||||
);
|
||||
output_color = vec4<f32>(
|
||||
(1.0 - cluster_overlay_alpha) * output_color.rgb + cluster_overlay_alpha * slice_color,
|
||||
output_color.a
|
||||
|
@ -96,7 +100,7 @@ fn cluster_debug_visualization(
|
|||
// NOTE: Visualizes the cluster to which the fragment belongs
|
||||
let cluster_overlay_alpha = 0.1;
|
||||
var rng = cluster_index;
|
||||
let cluster_color = hsv2rgb(rand_f(&rng), 1.0, 0.5);
|
||||
let cluster_color = hsv_to_rgb(rand_f(&rng) * PI_2, 1.0, 0.5);
|
||||
output_color = vec4<f32>(
|
||||
(1.0 - cluster_overlay_alpha) * output_color.rgb + cluster_overlay_alpha * cluster_color,
|
||||
output_color.a
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
#import bevy_pbr::{
|
||||
mesh_view_types::POINT_LIGHT_FLAGS_SPOT_LIGHT_Y_NEGATIVE,
|
||||
mesh_view_bindings as view_bindings,
|
||||
utils::hsv2rgb,
|
||||
utils::{hsv_to_rgb, PI_2},
|
||||
shadow_sampling::{SPOT_SHADOW_TEXEL_SIZE, sample_shadow_cubemap, sample_shadow_map}
|
||||
}
|
||||
|
||||
|
@ -170,7 +170,11 @@ fn cascade_debug_visualization(
|
|||
) -> vec3<f32> {
|
||||
let overlay_alpha = 0.95;
|
||||
let cascade_index = get_cascade_index(light_id, view_z);
|
||||
let cascade_color = hsv2rgb(f32(cascade_index) / f32(#{MAX_CASCADES_PER_LIGHT}u + 1u), 1.0, 0.5);
|
||||
let cascade_color = hsv_to_rgb(
|
||||
f32(cascade_index) / f32(#{MAX_CASCADES_PER_LIGHT}u + 1u) * PI_2,
|
||||
1.0,
|
||||
0.5
|
||||
);
|
||||
return vec3<f32>(
|
||||
(1.0 - overlay_alpha) * output_color.rgb + overlay_alpha * cascade_color
|
||||
);
|
||||
|
|
|
@ -2,20 +2,53 @@
|
|||
|
||||
#import bevy_pbr::rgb9e5
|
||||
|
||||
const PI: f32 = 3.141592653589793;
|
||||
const HALF_PI: f32 = 1.57079632679;
|
||||
const E: f32 = 2.718281828459045;
|
||||
const PI: f32 = 3.141592653589793; // π
|
||||
const PI_2: f32 = 6.283185307179586; // 2π
|
||||
const HALF_PI: f32 = 1.57079632679; // π/2
|
||||
const FRAC_PI_3: f32 = 1.0471975512; // π/3
|
||||
const E: f32 = 2.718281828459045; // exp(1)
|
||||
|
||||
fn hsv2rgb(hue: f32, saturation: f32, value: f32) -> vec3<f32> {
|
||||
let rgb = clamp(
|
||||
abs(
|
||||
((hue * 6.0 + vec3<f32>(0.0, 4.0, 2.0)) % 6.0) - 3.0
|
||||
) - 1.0,
|
||||
vec3<f32>(0.0),
|
||||
vec3<f32>(1.0)
|
||||
);
|
||||
// Converts HSV to RGB.
|
||||
//
|
||||
// Input: H ∈ [0, 2π), S ∈ [0, 1], V ∈ [0, 1].
|
||||
// Output: R ∈ [0, 1], G ∈ [0, 1], B ∈ [0, 1].
|
||||
//
|
||||
// <https://en.wikipedia.org/wiki/HSL_and_HSV#HSV_to_RGB_alternative>
|
||||
fn hsv_to_rgb(hsv: vec3<f32>) -> vec3<f32> {
|
||||
let n = vec3(5.0, 3.0, 1.0);
|
||||
let k = (n + hsv.x / FRAC_PI_3) % 6.0;
|
||||
return hsv.z - hsv.z * hsv.y * max(vec3(0.0), min(k, min(4.0 - k, vec3(1.0))));
|
||||
}
|
||||
|
||||
return value * mix(vec3<f32>(1.0), rgb, vec3<f32>(saturation));
|
||||
// Converts RGB to HSV.
|
||||
//
|
||||
// Input: R ∈ [0, 1], G ∈ [0, 1], B ∈ [0, 1].
|
||||
// Output: H ∈ [0, 2π), S ∈ [0, 1], V ∈ [0, 1].
|
||||
//
|
||||
// <https://en.wikipedia.org/wiki/HSL_and_HSV#From_RGB>
|
||||
fn rgb_to_hsv(rgb: vec3<f32>) -> vec3<f32> {
|
||||
let x_max = max(rgb.r, max(rgb.g, rgb.b)); // i.e. V
|
||||
let x_min = min(rgb.r, min(rgb.g, rgb.b));
|
||||
let c = x_max - x_min; // chroma
|
||||
|
||||
var swizzle = vec3<f32>(0.0);
|
||||
if (x_max == rgb.r) {
|
||||
swizzle = vec3(rgb.gb, 0.0);
|
||||
} else if (x_max == rgb.g) {
|
||||
swizzle = vec3(rgb.br, 2.0);
|
||||
} else {
|
||||
swizzle = vec3(rgb.rg, 4.0);
|
||||
}
|
||||
|
||||
let h = FRAC_PI_3 * (((swizzle.x - swizzle.y) / c + swizzle.z) % 6.0);
|
||||
|
||||
// Avoid division by zero.
|
||||
var s = 0.0;
|
||||
if (x_max > 0.0) {
|
||||
s = c / x_max;
|
||||
}
|
||||
|
||||
return vec3(h, s, x_max);
|
||||
}
|
||||
|
||||
// Generates a random u32 in range [0, u32::MAX].
|
||||
|
|
|
@ -853,7 +853,7 @@ pub fn extract_cameras(
|
|||
gpu_culling,
|
||||
) in query.iter()
|
||||
{
|
||||
let color_grading = *color_grading.unwrap_or(&ColorGrading::default());
|
||||
let color_grading = color_grading.unwrap_or(&ColorGrading::default()).clone();
|
||||
|
||||
if !camera.is_active {
|
||||
continue;
|
||||
|
|
|
@ -24,13 +24,16 @@ use crate::{
|
|||
};
|
||||
use bevy_app::{App, Plugin};
|
||||
use bevy_ecs::prelude::*;
|
||||
use bevy_math::{Mat4, UVec4, Vec3, Vec4, Vec4Swizzles};
|
||||
use bevy_math::{mat3, vec2, vec3, Mat3, Mat4, UVec4, Vec2, Vec3, Vec4, Vec4Swizzles};
|
||||
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
|
||||
use bevy_transform::components::GlobalTransform;
|
||||
use bevy_utils::HashMap;
|
||||
use std::sync::{
|
||||
atomic::{AtomicUsize, Ordering},
|
||||
Arc,
|
||||
use std::{
|
||||
ops::Range,
|
||||
sync::{
|
||||
atomic::{AtomicUsize, Ordering},
|
||||
Arc,
|
||||
},
|
||||
};
|
||||
use wgpu::{
|
||||
Extent3d, RenderPassColorAttachment, RenderPassDepthStencilAttachment, StoreOp,
|
||||
|
@ -39,6 +42,55 @@ use wgpu::{
|
|||
|
||||
pub const VIEW_TYPE_HANDLE: Handle<Shader> = Handle::weak_from_u128(15421373904451797197);
|
||||
|
||||
/// The matrix that converts from the RGB to the LMS color space.
|
||||
///
|
||||
/// To derive this, first we convert from RGB to [CIE 1931 XYZ]:
|
||||
///
|
||||
/// ```text
|
||||
/// ⎡ X ⎤ ⎡ 0.490 0.310 0.200 ⎤ ⎡ R ⎤
|
||||
/// ⎢ Y ⎥ = ⎢ 0.177 0.812 0.011 ⎥ ⎢ G ⎥
|
||||
/// ⎣ Z ⎦ ⎣ 0.000 0.010 0.990 ⎦ ⎣ B ⎦
|
||||
/// ```
|
||||
///
|
||||
/// Then we convert to LMS according to the [CAM16 standard matrix]:
|
||||
///
|
||||
/// ```text
|
||||
/// ⎡ L ⎤ ⎡ 0.401 0.650 -0.051 ⎤ ⎡ X ⎤
|
||||
/// ⎢ M ⎥ = ⎢ -0.250 1.204 0.046 ⎥ ⎢ Y ⎥
|
||||
/// ⎣ S ⎦ ⎣ -0.002 0.049 0.953 ⎦ ⎣ Z ⎦
|
||||
/// ```
|
||||
///
|
||||
/// The resulting matrix is just the concatenation of these two matrices, to do
|
||||
/// the conversion in one step.
|
||||
///
|
||||
/// [CIE 1931 XYZ]: https://en.wikipedia.org/wiki/CIE_1931_color_space
|
||||
/// [CAM16 standard matrix]: https://en.wikipedia.org/wiki/LMS_color_space
|
||||
static RGB_TO_LMS: Mat3 = mat3(
|
||||
vec3(0.311692, 0.0905138, 0.00764433),
|
||||
vec3(0.652085, 0.901341, 0.0486554),
|
||||
vec3(0.0362225, 0.00814478, 0.943700),
|
||||
);
|
||||
|
||||
/// The inverse of the [`RGB_TO_LMS`] matrix, converting from the LMS color
|
||||
/// space back to RGB.
|
||||
static LMS_TO_RGB: Mat3 = mat3(
|
||||
vec3(4.06305, -0.40791, -0.0118812),
|
||||
vec3(-2.93241, 1.40437, -0.0486532),
|
||||
vec3(-0.130646, 0.00353630, 1.0605344),
|
||||
);
|
||||
|
||||
/// The [CIE 1931] *xy* chromaticity coordinates of the [D65 white point].
|
||||
///
|
||||
/// [CIE 1931]: https://en.wikipedia.org/wiki/CIE_1931_color_space
|
||||
/// [D65 white point]: https://en.wikipedia.org/wiki/Standard_illuminant#D65_values
|
||||
static D65_XY: Vec2 = vec2(0.31272, 0.32903);
|
||||
|
||||
/// The [D65 white point] in [LMS color space].
|
||||
///
|
||||
/// [LMS color space]: https://en.wikipedia.org/wiki/LMS_color_space
|
||||
/// [D65 white point]: https://en.wikipedia.org/wiki/Standard_illuminant#D65_values
|
||||
static D65_LMS: Vec3 = vec3(0.975538, 1.01648, 1.08475);
|
||||
|
||||
pub struct ViewPlugin;
|
||||
|
||||
impl Plugin for ViewPlugin {
|
||||
|
@ -129,40 +181,217 @@ impl ExtractedView {
|
|||
}
|
||||
}
|
||||
|
||||
/// Configures basic color grading parameters to adjust the image appearance. Grading is applied just before/after tonemapping for a given [`Camera`](crate::camera::Camera) entity.
|
||||
#[derive(Component, Reflect, Debug, Copy, Clone, ShaderType)]
|
||||
/// Configures filmic color grading parameters to adjust the image appearance.
|
||||
///
|
||||
/// Color grading is applied just before tonemapping for a given
|
||||
/// [`Camera`](crate::camera::Camera) entity, with the sole exception of the
|
||||
/// `post_saturation` value in [`ColorGradingGlobal`], which is applied after
|
||||
/// tonemapping.
|
||||
#[derive(Component, Reflect, Debug, Default, Clone)]
|
||||
#[reflect(Component, Default)]
|
||||
pub struct ColorGrading {
|
||||
/// Filmic color grading values applied to the image as a whole (as opposed
|
||||
/// to individual sections, like shadows and highlights).
|
||||
pub global: ColorGradingGlobal,
|
||||
|
||||
/// Color grading values that are applied to the darker parts of the image.
|
||||
///
|
||||
/// The cutoff points can be customized with the
|
||||
/// [`ColorGradingGlobal::midtones_range`] field.
|
||||
pub shadows: ColorGradingSection,
|
||||
|
||||
/// Color grading values that are applied to the parts of the image with
|
||||
/// intermediate brightness.
|
||||
///
|
||||
/// The cutoff points can be customized with the
|
||||
/// [`ColorGradingGlobal::midtones_range`] field.
|
||||
pub midtones: ColorGradingSection,
|
||||
|
||||
/// Color grading values that are applied to the lighter parts of the image.
|
||||
///
|
||||
/// The cutoff points can be customized with the
|
||||
/// [`ColorGradingGlobal::midtones_range`] field.
|
||||
pub highlights: ColorGradingSection,
|
||||
}
|
||||
|
||||
/// Filmic color grading values applied to the image as a whole (as opposed to
|
||||
/// individual sections, like shadows and highlights).
|
||||
#[derive(Clone, Debug, Reflect)]
|
||||
#[reflect(Default)]
|
||||
pub struct ColorGradingGlobal {
|
||||
/// Exposure value (EV) offset, measured in stops.
|
||||
pub exposure: f32,
|
||||
|
||||
/// Non-linear luminance adjustment applied before tonemapping. y = pow(x, gamma)
|
||||
pub gamma: f32,
|
||||
/// An adjustment made to the [CIE 1931] chromaticity *x* value.
|
||||
///
|
||||
/// Positive values make the colors redder. Negative values make the colors
|
||||
/// bluer. This has no effect on luminance (brightness).
|
||||
///
|
||||
/// [CIE 1931]: https://en.wikipedia.org/wiki/CIE_1931_color_space#CIE_xy_chromaticity_diagram_and_the_CIE_xyY_color_space
|
||||
pub temperature: f32,
|
||||
|
||||
/// Saturation adjustment applied before tonemapping.
|
||||
/// Values below 1.0 desaturate, with a value of 0.0 resulting in a grayscale image
|
||||
/// with luminance defined by ITU-R BT.709.
|
||||
/// Values above 1.0 increase saturation.
|
||||
pub pre_saturation: f32,
|
||||
/// An adjustment made to the [CIE 1931] chromaticity *y* value.
|
||||
///
|
||||
/// Positive values make the colors more magenta. Negative values make the
|
||||
/// colors greener. This has no effect on luminance (brightness).
|
||||
///
|
||||
/// [CIE 1931]: https://en.wikipedia.org/wiki/CIE_1931_color_space#CIE_xy_chromaticity_diagram_and_the_CIE_xyY_color_space
|
||||
pub tint: f32,
|
||||
|
||||
/// An adjustment to the [hue], in radians.
|
||||
///
|
||||
/// Adjusting this value changes the perceived colors in the image: red to
|
||||
/// yellow to green to blue, etc. It has no effect on the saturation or
|
||||
/// brightness of the colors.
|
||||
///
|
||||
/// [hue]: https://en.wikipedia.org/wiki/HSL_and_HSV#Formal_derivation
|
||||
pub hue: f32,
|
||||
|
||||
/// Saturation adjustment applied after tonemapping.
|
||||
/// Values below 1.0 desaturate, with a value of 0.0 resulting in a grayscale image
|
||||
/// with luminance defined by ITU-R BT.709
|
||||
/// Values above 1.0 increase saturation.
|
||||
pub post_saturation: f32,
|
||||
|
||||
/// The luminance (brightness) ranges that are considered part of the
|
||||
/// "midtones" of the image.
|
||||
///
|
||||
/// This affects which [`ColorGradingSection`]s apply to which colors. Note
|
||||
/// that the sections smoothly blend into one another, to avoid abrupt
|
||||
/// transitions.
|
||||
///
|
||||
/// The default value is 0.2 to 0.7.
|
||||
pub midtones_range: Range<f32>,
|
||||
}
|
||||
|
||||
impl Default for ColorGrading {
|
||||
/// The [`ColorGrading`] structure, packed into the most efficient form for the
|
||||
/// GPU.
|
||||
#[derive(Clone, Copy, Debug, ShaderType)]
|
||||
struct ColorGradingUniform {
|
||||
balance: Mat3,
|
||||
saturation: Vec3,
|
||||
contrast: Vec3,
|
||||
gamma: Vec3,
|
||||
gain: Vec3,
|
||||
lift: Vec3,
|
||||
midtone_range: Vec2,
|
||||
exposure: f32,
|
||||
hue: f32,
|
||||
post_saturation: f32,
|
||||
}
|
||||
|
||||
/// A section of color grading values that can be selectively applied to
|
||||
/// shadows, midtones, and highlights.
|
||||
#[derive(Reflect, Debug, Copy, Clone, PartialEq)]
|
||||
pub struct ColorGradingSection {
|
||||
/// Values below 1.0 desaturate, with a value of 0.0 resulting in a grayscale image
|
||||
/// with luminance defined by ITU-R BT.709.
|
||||
/// Values above 1.0 increase saturation.
|
||||
pub saturation: f32,
|
||||
|
||||
/// Adjusts the range of colors.
|
||||
///
|
||||
/// A value of 1.0 applies no changes. Values below 1.0 move the colors more
|
||||
/// toward a neutral gray. Values above 1.0 spread the colors out away from
|
||||
/// the neutral gray.
|
||||
pub contrast: f32,
|
||||
|
||||
/// A nonlinear luminance adjustment, mainly affecting the high end of the
|
||||
/// range.
|
||||
///
|
||||
/// This is the *n* exponent in the standard [ASC CDL] formula for color
|
||||
/// correction:
|
||||
///
|
||||
/// ```text
|
||||
/// out = (i × s + o)ⁿ
|
||||
/// ```
|
||||
///
|
||||
/// [ASC CDL]: https://en.wikipedia.org/wiki/ASC_CDL#Combined_Function
|
||||
pub gamma: f32,
|
||||
|
||||
/// A linear luminance adjustment, mainly affecting the middle part of the
|
||||
/// range.
|
||||
///
|
||||
/// This is the *s* factor in the standard [ASC CDL] formula for color
|
||||
/// correction:
|
||||
///
|
||||
/// ```text
|
||||
/// out = (i × s + o)ⁿ
|
||||
/// ```
|
||||
///
|
||||
/// [ASC CDL]: https://en.wikipedia.org/wiki/ASC_CDL#Combined_Function
|
||||
pub gain: f32,
|
||||
|
||||
/// A fixed luminance adjustment, mainly affecting the lower part of the
|
||||
/// range.
|
||||
///
|
||||
/// This is the *o* term in the standard [ASC CDL] formula for color
|
||||
/// correction:
|
||||
///
|
||||
/// ```text
|
||||
/// out = (i × s + o)ⁿ
|
||||
/// ```
|
||||
///
|
||||
/// [ASC CDL]: https://en.wikipedia.org/wiki/ASC_CDL#Combined_Function
|
||||
pub lift: f32,
|
||||
}
|
||||
|
||||
impl Default for ColorGradingGlobal {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
exposure: 0.0,
|
||||
gamma: 1.0,
|
||||
pre_saturation: 1.0,
|
||||
temperature: 0.0,
|
||||
tint: 0.0,
|
||||
hue: 0.0,
|
||||
post_saturation: 1.0,
|
||||
midtones_range: 0.2..0.7,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ColorGradingSection {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
saturation: 1.0,
|
||||
contrast: 1.0,
|
||||
gamma: 1.0,
|
||||
gain: 1.0,
|
||||
lift: 0.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ColorGrading {
|
||||
/// Creates a new [`ColorGrading`] instance in which shadows, midtones, and
|
||||
/// highlights all have the same set of color grading values.
|
||||
pub fn with_identical_sections(
|
||||
global: ColorGradingGlobal,
|
||||
section: ColorGradingSection,
|
||||
) -> ColorGrading {
|
||||
ColorGrading {
|
||||
global,
|
||||
highlights: section,
|
||||
midtones: section,
|
||||
shadows: section,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns an iterator that visits the shadows, midtones, and highlights
|
||||
/// sections, in that order.
|
||||
pub fn all_sections(&self) -> impl Iterator<Item = &ColorGradingSection> {
|
||||
[&self.shadows, &self.midtones, &self.highlights].into_iter()
|
||||
}
|
||||
|
||||
/// Applies the given mutating function to the shadows, midtones, and
|
||||
/// highlights sections, in that order.
|
||||
///
|
||||
/// Returns an array composed of the results of such evaluation, in that
|
||||
/// order.
|
||||
pub fn all_sections_mut(&mut self) -> impl Iterator<Item = &mut ColorGradingSection> {
|
||||
[&mut self.shadows, &mut self.midtones, &mut self.highlights].into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, ShaderType)]
|
||||
pub struct ViewUniform {
|
||||
view_proj: Mat4,
|
||||
|
@ -177,7 +406,7 @@ pub struct ViewUniform {
|
|||
// viewport(x_origin, y_origin, width, height)
|
||||
viewport: Vec4,
|
||||
frustum: [Vec4; 6],
|
||||
color_grading: ColorGrading,
|
||||
color_grading: ColorGradingUniform,
|
||||
mip_bias: f32,
|
||||
render_layers: u32,
|
||||
}
|
||||
|
@ -208,6 +437,85 @@ pub struct PostProcessWrite<'a> {
|
|||
pub destination: &'a TextureView,
|
||||
}
|
||||
|
||||
impl From<ColorGrading> for ColorGradingUniform {
|
||||
fn from(component: ColorGrading) -> Self {
|
||||
// Compute the balance matrix that will be used to apply the white
|
||||
// balance adjustment to an RGB color. Our general approach will be to
|
||||
// convert both the color and the developer-supplied white point to the
|
||||
// LMS color space, apply the conversion, and then convert back.
|
||||
//
|
||||
// First, we start with the CIE 1931 *xy* values of the standard D65
|
||||
// illuminant:
|
||||
// <https://en.wikipedia.org/wiki/Standard_illuminant#D65_values>
|
||||
//
|
||||
// We then adjust them based on the developer's requested white balance.
|
||||
let white_point_xy = D65_XY + vec2(-component.global.temperature, component.global.tint);
|
||||
|
||||
// Convert the white point from CIE 1931 *xy* to LMS. First, we convert to XYZ:
|
||||
//
|
||||
// Y Y
|
||||
// Y = 1 X = ─ x Z = ─ (1 - x - y)
|
||||
// y y
|
||||
//
|
||||
// Then we convert from XYZ to LMS color space, using the CAM16 matrix
|
||||
// from <https://en.wikipedia.org/wiki/LMS_color_space#Later_CIECAMs>:
|
||||
//
|
||||
// ⎡ L ⎤ ⎡ 0.401 0.650 -0.051 ⎤ ⎡ X ⎤
|
||||
// ⎢ M ⎥ = ⎢ -0.250 1.204 0.046 ⎥ ⎢ Y ⎥
|
||||
// ⎣ S ⎦ ⎣ -0.002 0.049 0.953 ⎦ ⎣ Z ⎦
|
||||
//
|
||||
// The following formula is just a simplification of the above.
|
||||
|
||||
let white_point_lms = vec3(0.701634, 1.15856, -0.904175)
|
||||
+ (vec3(-0.051461, 0.045854, 0.953127)
|
||||
+ vec3(0.452749, -0.296122, -0.955206) * white_point_xy.x)
|
||||
/ white_point_xy.y;
|
||||
|
||||
// Now that we're in LMS space, perform the white point scaling.
|
||||
let white_point_adjustment = Mat3::from_diagonal(D65_LMS / white_point_lms);
|
||||
|
||||
// Finally, combine the RGB → LMS → corrected LMS → corrected RGB
|
||||
// pipeline into a single 3×3 matrix.
|
||||
let balance = LMS_TO_RGB * white_point_adjustment * RGB_TO_LMS;
|
||||
|
||||
Self {
|
||||
balance,
|
||||
saturation: vec3(
|
||||
component.shadows.saturation,
|
||||
component.midtones.saturation,
|
||||
component.highlights.saturation,
|
||||
),
|
||||
contrast: vec3(
|
||||
component.shadows.contrast,
|
||||
component.midtones.contrast,
|
||||
component.highlights.contrast,
|
||||
),
|
||||
gamma: vec3(
|
||||
component.shadows.gamma,
|
||||
component.midtones.gamma,
|
||||
component.highlights.gamma,
|
||||
),
|
||||
gain: vec3(
|
||||
component.shadows.gain,
|
||||
component.midtones.gain,
|
||||
component.highlights.gain,
|
||||
),
|
||||
lift: vec3(
|
||||
component.shadows.lift,
|
||||
component.midtones.lift,
|
||||
component.highlights.lift,
|
||||
),
|
||||
midtone_range: vec2(
|
||||
component.global.midtones_range.start,
|
||||
component.global.midtones_range.end,
|
||||
),
|
||||
exposure: component.global.exposure,
|
||||
hue: component.global.hue,
|
||||
post_saturation: component.global.post_saturation,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct GpuCulling;
|
||||
|
||||
|
@ -445,7 +753,7 @@ pub fn prepare_view_uniforms(
|
|||
.unwrap_or_else(|| Exposure::default().exposure()),
|
||||
viewport,
|
||||
frustum,
|
||||
color_grading: extracted_view.color_grading,
|
||||
color_grading: extracted_view.color_grading.clone().into(),
|
||||
mip_bias: mip_bias.unwrap_or(&MipBias(0.0)).0,
|
||||
render_layers: maybe_layers.copied().unwrap_or_default().bits(),
|
||||
}),
|
||||
|
|
|
@ -1,9 +1,15 @@
|
|||
#define_import_path bevy_render::view
|
||||
|
||||
struct ColorGrading {
|
||||
balance: mat3x3<f32>,
|
||||
saturation: vec3<f32>,
|
||||
contrast: vec3<f32>,
|
||||
gamma: vec3<f32>,
|
||||
gain: vec3<f32>,
|
||||
lift: vec3<f32>,
|
||||
midtone_range: vec2<f32>,
|
||||
exposure: f32,
|
||||
gamma: f32,
|
||||
pre_saturation: f32,
|
||||
hue: f32,
|
||||
post_saturation: f32,
|
||||
}
|
||||
|
||||
|
|
680
examples/3d/color_grading.rs
Normal file
680
examples/3d/color_grading.rs
Normal file
|
@ -0,0 +1,680 @@
|
|||
//! Demonstrates color grading with an interactive adjustment UI.
|
||||
|
||||
use std::{
|
||||
f32::consts::PI,
|
||||
fmt::{self, Formatter},
|
||||
};
|
||||
|
||||
use bevy::{
|
||||
ecs::system::EntityCommands,
|
||||
pbr::CascadeShadowConfigBuilder,
|
||||
prelude::*,
|
||||
render::view::{ColorGrading, ColorGradingGlobal, ColorGradingSection},
|
||||
};
|
||||
use std::fmt::Display;
|
||||
|
||||
static FONT_PATH: &str = "fonts/FiraMono-Medium.ttf";
|
||||
|
||||
/// How quickly the value changes per frame.
|
||||
const OPTION_ADJUSTMENT_SPEED: f32 = 0.003;
|
||||
|
||||
/// The color grading section that the user has selected: highlights, midtones,
|
||||
/// or shadows.
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
enum SelectedColorGradingSection {
|
||||
Highlights,
|
||||
Midtones,
|
||||
Shadows,
|
||||
}
|
||||
|
||||
/// The global option that the user has selected.
|
||||
///
|
||||
/// See the documentation of [`ColorGradingGlobal`] for more information about
|
||||
/// each field here.
|
||||
#[derive(Clone, Copy, PartialEq, Default)]
|
||||
enum SelectedGlobalColorGradingOption {
|
||||
#[default]
|
||||
Exposure,
|
||||
Temperature,
|
||||
Tint,
|
||||
Hue,
|
||||
}
|
||||
|
||||
/// The section-specific option that the user has selected.
|
||||
///
|
||||
/// See the documentation of [`ColorGradingSection`] for more information about
|
||||
/// each field here.
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
enum SelectedSectionColorGradingOption {
|
||||
Saturation,
|
||||
Contrast,
|
||||
Gamma,
|
||||
Gain,
|
||||
Lift,
|
||||
}
|
||||
|
||||
/// The color grading option that the user has selected.
|
||||
#[derive(Clone, Copy, PartialEq, Resource)]
|
||||
enum SelectedColorGradingOption {
|
||||
/// The user has selected a global color grading option: one that applies to
|
||||
/// the whole image as opposed to specifically to highlights, midtones, or
|
||||
/// shadows.
|
||||
Global(SelectedGlobalColorGradingOption),
|
||||
|
||||
/// The user has selected a color grading option that applies only to
|
||||
/// highlights, midtones, or shadows.
|
||||
Section(
|
||||
SelectedColorGradingSection,
|
||||
SelectedSectionColorGradingOption,
|
||||
),
|
||||
}
|
||||
|
||||
impl Default for SelectedColorGradingOption {
|
||||
fn default() -> Self {
|
||||
Self::Global(default())
|
||||
}
|
||||
}
|
||||
|
||||
/// Buttons consist of three parts: the button itself, a label child, and a
|
||||
/// value child. This specifies one of the three entities.
|
||||
#[derive(Clone, Copy, PartialEq, Component)]
|
||||
enum ColorGradingOptionWidgetType {
|
||||
/// The parent button.
|
||||
Button,
|
||||
/// The label of the button.
|
||||
Label,
|
||||
/// The numerical value that the button displays.
|
||||
Value,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Component)]
|
||||
struct ColorGradingOptionWidget {
|
||||
widget_type: ColorGradingOptionWidgetType,
|
||||
option: SelectedColorGradingOption,
|
||||
}
|
||||
|
||||
/// A marker component for the help text at the top left of the screen.
|
||||
#[derive(Clone, Copy, Component)]
|
||||
struct HelpText;
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins(DefaultPlugins)
|
||||
.init_resource::<SelectedColorGradingOption>()
|
||||
.add_systems(Startup, setup)
|
||||
.add_systems(
|
||||
Update,
|
||||
(
|
||||
handle_button_presses,
|
||||
adjust_color_grading_option,
|
||||
update_ui_state,
|
||||
)
|
||||
.chain(),
|
||||
)
|
||||
.run();
|
||||
}
|
||||
|
||||
fn setup(
|
||||
mut commands: Commands,
|
||||
currently_selected_option: Res<SelectedColorGradingOption>,
|
||||
asset_server: Res<AssetServer>,
|
||||
) {
|
||||
// Create the scene.
|
||||
add_basic_scene(&mut commands, &asset_server);
|
||||
|
||||
// Create the root UI element.
|
||||
let font = asset_server.load(FONT_PATH);
|
||||
let color_grading = ColorGrading::default();
|
||||
add_buttons(
|
||||
&mut commands,
|
||||
¤tly_selected_option,
|
||||
&font,
|
||||
&color_grading,
|
||||
);
|
||||
|
||||
// Spawn help text.
|
||||
add_help_text(&mut commands, &font, ¤tly_selected_option);
|
||||
|
||||
// Spawn the camera.
|
||||
add_camera(&mut commands, &asset_server, color_grading);
|
||||
}
|
||||
|
||||
/// Adds all the buttons on the bottom of the scene.
|
||||
fn add_buttons(
|
||||
commands: &mut Commands,
|
||||
currently_selected_option: &SelectedColorGradingOption,
|
||||
font: &Handle<Font>,
|
||||
color_grading: &ColorGrading,
|
||||
) {
|
||||
// Spawn the parent node that contains all the buttons.
|
||||
commands
|
||||
.spawn(NodeBundle {
|
||||
style: Style {
|
||||
flex_direction: FlexDirection::Column,
|
||||
position_type: PositionType::Absolute,
|
||||
row_gap: Val::Px(6.0),
|
||||
left: Val::Px(10.0),
|
||||
bottom: Val::Px(10.0),
|
||||
..default()
|
||||
},
|
||||
..default()
|
||||
})
|
||||
.with_children(|parent| {
|
||||
// Create the first row, which contains the global controls.
|
||||
add_buttons_for_global_controls(
|
||||
parent,
|
||||
*currently_selected_option,
|
||||
color_grading,
|
||||
font,
|
||||
);
|
||||
|
||||
// Create the rows for individual controls.
|
||||
for section in [
|
||||
SelectedColorGradingSection::Highlights,
|
||||
SelectedColorGradingSection::Midtones,
|
||||
SelectedColorGradingSection::Shadows,
|
||||
] {
|
||||
add_buttons_for_section(
|
||||
parent,
|
||||
section,
|
||||
*currently_selected_option,
|
||||
color_grading,
|
||||
font,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Adds the buttons for the global controls (those that control the scene as a
|
||||
/// whole as opposed to shadows, midtones, or highlights).
|
||||
fn add_buttons_for_global_controls(
|
||||
parent: &mut ChildBuilder,
|
||||
currently_selected_option: SelectedColorGradingOption,
|
||||
color_grading: &ColorGrading,
|
||||
font: &Handle<Font>,
|
||||
) {
|
||||
// Add the parent node for the row.
|
||||
parent
|
||||
.spawn(NodeBundle {
|
||||
style: Style::default(),
|
||||
..default()
|
||||
})
|
||||
.with_children(|parent| {
|
||||
// Add some placeholder text to fill this column.
|
||||
parent.spawn(NodeBundle {
|
||||
style: Style {
|
||||
width: Val::Px(125.0),
|
||||
..default()
|
||||
},
|
||||
..default()
|
||||
});
|
||||
|
||||
// Add each global color grading option button.
|
||||
for option in [
|
||||
SelectedGlobalColorGradingOption::Exposure,
|
||||
SelectedGlobalColorGradingOption::Temperature,
|
||||
SelectedGlobalColorGradingOption::Tint,
|
||||
SelectedGlobalColorGradingOption::Hue,
|
||||
] {
|
||||
add_button_for_value(
|
||||
parent,
|
||||
SelectedColorGradingOption::Global(option),
|
||||
currently_selected_option,
|
||||
color_grading,
|
||||
font,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Adds the buttons that control color grading for individual sections
|
||||
/// (highlights, midtones, shadows).
|
||||
fn add_buttons_for_section(
|
||||
parent: &mut ChildBuilder,
|
||||
section: SelectedColorGradingSection,
|
||||
currently_selected_option: SelectedColorGradingOption,
|
||||
color_grading: &ColorGrading,
|
||||
font: &Handle<Font>,
|
||||
) {
|
||||
// Spawn the row container.
|
||||
parent
|
||||
.spawn(NodeBundle {
|
||||
style: Style {
|
||||
align_items: AlignItems::Center,
|
||||
..default()
|
||||
},
|
||||
..default()
|
||||
})
|
||||
.with_children(|parent| {
|
||||
// Spawn the label ("Highlights", etc.)
|
||||
add_text(parent, §ion.to_string(), font, Color::WHITE).insert(Style {
|
||||
width: Val::Px(125.0),
|
||||
..default()
|
||||
});
|
||||
|
||||
// Spawn the buttons.
|
||||
for option in [
|
||||
SelectedSectionColorGradingOption::Saturation,
|
||||
SelectedSectionColorGradingOption::Contrast,
|
||||
SelectedSectionColorGradingOption::Gamma,
|
||||
SelectedSectionColorGradingOption::Gain,
|
||||
SelectedSectionColorGradingOption::Lift,
|
||||
] {
|
||||
add_button_for_value(
|
||||
parent,
|
||||
SelectedColorGradingOption::Section(section, option),
|
||||
currently_selected_option,
|
||||
color_grading,
|
||||
font,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Adds a button that controls one of the color grading values.
|
||||
fn add_button_for_value(
|
||||
parent: &mut ChildBuilder,
|
||||
option: SelectedColorGradingOption,
|
||||
currently_selected_option: SelectedColorGradingOption,
|
||||
color_grading: &ColorGrading,
|
||||
font: &Handle<Font>,
|
||||
) {
|
||||
let is_selected = currently_selected_option == option;
|
||||
|
||||
let (bg_color, fg_color) = if is_selected {
|
||||
(Color::WHITE, Color::BLACK)
|
||||
} else {
|
||||
(Color::BLACK, Color::WHITE)
|
||||
};
|
||||
|
||||
// Add the button node.
|
||||
parent
|
||||
.spawn(ButtonBundle {
|
||||
style: Style {
|
||||
border: UiRect::all(Val::Px(1.0)),
|
||||
width: Val::Px(200.0),
|
||||
justify_content: JustifyContent::Center,
|
||||
align_items: AlignItems::Center,
|
||||
padding: UiRect::axes(Val::Px(12.0), Val::Px(6.0)),
|
||||
margin: UiRect::right(Val::Px(12.0)),
|
||||
..default()
|
||||
},
|
||||
border_color: BorderColor(Color::WHITE),
|
||||
border_radius: BorderRadius::MAX,
|
||||
image: UiImage::default().with_color(bg_color),
|
||||
..default()
|
||||
})
|
||||
.insert(ColorGradingOptionWidget {
|
||||
widget_type: ColorGradingOptionWidgetType::Button,
|
||||
option,
|
||||
})
|
||||
.with_children(|parent| {
|
||||
// Add the button label.
|
||||
let label = match option {
|
||||
SelectedColorGradingOption::Global(option) => option.to_string(),
|
||||
SelectedColorGradingOption::Section(_, option) => option.to_string(),
|
||||
};
|
||||
add_text(parent, &label, font, fg_color).insert(ColorGradingOptionWidget {
|
||||
widget_type: ColorGradingOptionWidgetType::Label,
|
||||
option,
|
||||
});
|
||||
|
||||
// Add a spacer.
|
||||
parent.spawn(NodeBundle {
|
||||
style: Style {
|
||||
flex_grow: 1.0,
|
||||
..default()
|
||||
},
|
||||
..default()
|
||||
});
|
||||
|
||||
// Add the value text.
|
||||
add_text(
|
||||
parent,
|
||||
&format!("{:.3}", option.get(color_grading)),
|
||||
font,
|
||||
fg_color,
|
||||
)
|
||||
.insert(ColorGradingOptionWidget {
|
||||
widget_type: ColorGradingOptionWidgetType::Value,
|
||||
option,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// Creates the help text at the top of the screen.
|
||||
fn add_help_text(
|
||||
commands: &mut Commands,
|
||||
font: &Handle<Font>,
|
||||
currently_selected_option: &SelectedColorGradingOption,
|
||||
) {
|
||||
commands
|
||||
.spawn(TextBundle {
|
||||
style: Style {
|
||||
position_type: PositionType::Absolute,
|
||||
left: Val::Px(10.0),
|
||||
top: Val::Px(10.0),
|
||||
..default()
|
||||
},
|
||||
..TextBundle::from_section(
|
||||
create_help_text(currently_selected_option),
|
||||
TextStyle {
|
||||
font: font.clone(),
|
||||
font_size: 24.0,
|
||||
color: Color::WHITE,
|
||||
},
|
||||
)
|
||||
})
|
||||
.insert(HelpText);
|
||||
}
|
||||
|
||||
/// Adds some text to the scene.
|
||||
fn add_text<'a>(
|
||||
parent: &'a mut ChildBuilder,
|
||||
label: &str,
|
||||
font: &Handle<Font>,
|
||||
color: Color,
|
||||
) -> EntityCommands<'a> {
|
||||
parent.spawn(TextBundle::from_section(
|
||||
label,
|
||||
TextStyle {
|
||||
font: font.clone(),
|
||||
font_size: 18.0,
|
||||
color,
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
fn add_camera(commands: &mut Commands, asset_server: &AssetServer, color_grading: ColorGrading) {
|
||||
commands.spawn((
|
||||
Camera3dBundle {
|
||||
camera: Camera {
|
||||
hdr: true,
|
||||
..default()
|
||||
},
|
||||
transform: Transform::from_xyz(0.7, 0.7, 1.0)
|
||||
.looking_at(Vec3::new(0.0, 0.3, 0.0), Vec3::Y),
|
||||
color_grading,
|
||||
..default()
|
||||
},
|
||||
FogSettings {
|
||||
color: Color::srgb_u8(43, 44, 47),
|
||||
falloff: FogFalloff::Linear {
|
||||
start: 1.0,
|
||||
end: 8.0,
|
||||
},
|
||||
..default()
|
||||
},
|
||||
EnvironmentMapLight {
|
||||
diffuse_map: asset_server.load("environment_maps/pisa_diffuse_rgb9e5_zstd.ktx2"),
|
||||
specular_map: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"),
|
||||
intensity: 2000.0,
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
fn add_basic_scene(commands: &mut Commands, asset_server: &AssetServer) {
|
||||
// Spawn the main scene.
|
||||
commands.spawn(SceneBundle {
|
||||
scene: asset_server.load("models/TonemappingTest/TonemappingTest.gltf#Scene0"),
|
||||
..default()
|
||||
});
|
||||
|
||||
// Spawn the flight helmet.
|
||||
commands.spawn(SceneBundle {
|
||||
scene: asset_server.load("models/FlightHelmet/FlightHelmet.gltf#Scene0"),
|
||||
transform: Transform::from_xyz(0.5, 0.0, -0.5)
|
||||
.with_rotation(Quat::from_rotation_y(-0.15 * PI)),
|
||||
..default()
|
||||
});
|
||||
|
||||
// Spawn the light.
|
||||
commands.spawn(DirectionalLightBundle {
|
||||
directional_light: DirectionalLight {
|
||||
illuminance: 15000.0,
|
||||
shadows_enabled: true,
|
||||
..default()
|
||||
},
|
||||
transform: Transform::from_rotation(Quat::from_euler(
|
||||
EulerRot::ZYX,
|
||||
0.0,
|
||||
PI * -0.15,
|
||||
PI * -0.15,
|
||||
)),
|
||||
cascade_shadow_config: CascadeShadowConfigBuilder {
|
||||
maximum_distance: 3.0,
|
||||
first_cascade_far_bound: 0.9,
|
||||
..default()
|
||||
}
|
||||
.into(),
|
||||
..default()
|
||||
});
|
||||
}
|
||||
|
||||
impl Display for SelectedGlobalColorGradingOption {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
let name = match *self {
|
||||
SelectedGlobalColorGradingOption::Exposure => "Exposure",
|
||||
SelectedGlobalColorGradingOption::Temperature => "Temperature",
|
||||
SelectedGlobalColorGradingOption::Tint => "Tint",
|
||||
SelectedGlobalColorGradingOption::Hue => "Hue",
|
||||
};
|
||||
f.write_str(name)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for SelectedColorGradingSection {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
let name = match *self {
|
||||
SelectedColorGradingSection::Highlights => "Highlights",
|
||||
SelectedColorGradingSection::Midtones => "Midtones",
|
||||
SelectedColorGradingSection::Shadows => "Shadows",
|
||||
};
|
||||
f.write_str(name)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for SelectedSectionColorGradingOption {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
let name = match *self {
|
||||
SelectedSectionColorGradingOption::Saturation => "Saturation",
|
||||
SelectedSectionColorGradingOption::Contrast => "Contrast",
|
||||
SelectedSectionColorGradingOption::Gamma => "Gamma",
|
||||
SelectedSectionColorGradingOption::Gain => "Gain",
|
||||
SelectedSectionColorGradingOption::Lift => "Lift",
|
||||
};
|
||||
f.write_str(name)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for SelectedColorGradingOption {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
SelectedColorGradingOption::Global(option) => write!(f, "\"{}\"", option),
|
||||
SelectedColorGradingOption::Section(section, option) => {
|
||||
write!(f, "\"{}\" for \"{}\"", option, section)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SelectedSectionColorGradingOption {
|
||||
/// Returns the appropriate value in the given color grading section.
|
||||
fn get(&self, section: &ColorGradingSection) -> f32 {
|
||||
match *self {
|
||||
SelectedSectionColorGradingOption::Saturation => section.saturation,
|
||||
SelectedSectionColorGradingOption::Contrast => section.contrast,
|
||||
SelectedSectionColorGradingOption::Gamma => section.gamma,
|
||||
SelectedSectionColorGradingOption::Gain => section.gain,
|
||||
SelectedSectionColorGradingOption::Lift => section.lift,
|
||||
}
|
||||
}
|
||||
|
||||
fn set(&self, section: &mut ColorGradingSection, value: f32) {
|
||||
match *self {
|
||||
SelectedSectionColorGradingOption::Saturation => section.saturation = value,
|
||||
SelectedSectionColorGradingOption::Contrast => section.contrast = value,
|
||||
SelectedSectionColorGradingOption::Gamma => section.gamma = value,
|
||||
SelectedSectionColorGradingOption::Gain => section.gain = value,
|
||||
SelectedSectionColorGradingOption::Lift => section.lift = value,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SelectedGlobalColorGradingOption {
|
||||
/// Returns the appropriate value in the given set of global color grading
|
||||
/// values.
|
||||
fn get(&self, global: &ColorGradingGlobal) -> f32 {
|
||||
match *self {
|
||||
SelectedGlobalColorGradingOption::Exposure => global.exposure,
|
||||
SelectedGlobalColorGradingOption::Temperature => global.temperature,
|
||||
SelectedGlobalColorGradingOption::Tint => global.tint,
|
||||
SelectedGlobalColorGradingOption::Hue => global.hue,
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the appropriate value in the given set of global color grading
|
||||
/// values.
|
||||
fn set(&self, global: &mut ColorGradingGlobal, value: f32) {
|
||||
match *self {
|
||||
SelectedGlobalColorGradingOption::Exposure => global.exposure = value,
|
||||
SelectedGlobalColorGradingOption::Temperature => global.temperature = value,
|
||||
SelectedGlobalColorGradingOption::Tint => global.tint = value,
|
||||
SelectedGlobalColorGradingOption::Hue => global.hue = value,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SelectedColorGradingOption {
|
||||
/// Returns the appropriate value in the given set of color grading values.
|
||||
fn get(&self, color_grading: &ColorGrading) -> f32 {
|
||||
match self {
|
||||
SelectedColorGradingOption::Global(option) => option.get(&color_grading.global),
|
||||
SelectedColorGradingOption::Section(
|
||||
SelectedColorGradingSection::Highlights,
|
||||
option,
|
||||
) => option.get(&color_grading.highlights),
|
||||
SelectedColorGradingOption::Section(SelectedColorGradingSection::Midtones, option) => {
|
||||
option.get(&color_grading.midtones)
|
||||
}
|
||||
SelectedColorGradingOption::Section(SelectedColorGradingSection::Shadows, option) => {
|
||||
option.get(&color_grading.shadows)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the appropriate value in the given set of color grading values.
|
||||
fn set(&self, color_grading: &mut ColorGrading, value: f32) {
|
||||
match self {
|
||||
SelectedColorGradingOption::Global(option) => {
|
||||
option.set(&mut color_grading.global, value);
|
||||
}
|
||||
SelectedColorGradingOption::Section(
|
||||
SelectedColorGradingSection::Highlights,
|
||||
option,
|
||||
) => option.set(&mut color_grading.highlights, value),
|
||||
SelectedColorGradingOption::Section(SelectedColorGradingSection::Midtones, option) => {
|
||||
option.set(&mut color_grading.midtones, value);
|
||||
}
|
||||
SelectedColorGradingOption::Section(SelectedColorGradingSection::Shadows, option) => {
|
||||
option.set(&mut color_grading.shadows, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Handles mouse clicks on the buttons when the user clicks on a new one.
|
||||
fn handle_button_presses(
|
||||
mut interactions: Query<(&Interaction, &ColorGradingOptionWidget), Changed<Interaction>>,
|
||||
mut currently_selected_option: ResMut<SelectedColorGradingOption>,
|
||||
) {
|
||||
for (interaction, widget) in interactions.iter_mut() {
|
||||
if widget.widget_type == ColorGradingOptionWidgetType::Button
|
||||
&& *interaction == Interaction::Pressed
|
||||
{
|
||||
*currently_selected_option = widget.option;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Updates the state of the UI based on the current state.
|
||||
fn update_ui_state(
|
||||
mut buttons: Query<(&mut UiImage, &ColorGradingOptionWidget)>,
|
||||
mut button_text: Query<(&mut Text, &ColorGradingOptionWidget), Without<HelpText>>,
|
||||
mut help_text: Query<&mut Text, With<HelpText>>,
|
||||
cameras: Query<&ColorGrading>,
|
||||
currently_selected_option: Res<SelectedColorGradingOption>,
|
||||
) {
|
||||
// The currently-selected option is drawn with inverted colors.
|
||||
for (mut image, widget) in buttons.iter_mut() {
|
||||
image.color = if *currently_selected_option == widget.option {
|
||||
Color::WHITE
|
||||
} else {
|
||||
Color::BLACK
|
||||
};
|
||||
}
|
||||
|
||||
let value_label = cameras
|
||||
.iter()
|
||||
.next()
|
||||
.map(|color_grading| format!("{:.3}", currently_selected_option.get(color_grading)));
|
||||
|
||||
// Update the buttons.
|
||||
for (mut text, widget) in button_text.iter_mut() {
|
||||
// Set the text color.
|
||||
|
||||
let color = if *currently_selected_option == widget.option {
|
||||
Color::BLACK
|
||||
} else {
|
||||
Color::WHITE
|
||||
};
|
||||
|
||||
for section in &mut text.sections {
|
||||
section.style.color = color;
|
||||
}
|
||||
|
||||
// Update the displayed value, if this is the currently-selected option.
|
||||
if widget.widget_type == ColorGradingOptionWidgetType::Value
|
||||
&& *currently_selected_option == widget.option
|
||||
{
|
||||
if let Some(ref value_label) = value_label {
|
||||
for section in &mut text.sections {
|
||||
section.value = value_label.clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update the help text.
|
||||
for mut help_text in help_text.iter_mut() {
|
||||
for section in &mut help_text.sections {
|
||||
section.value = create_help_text(¤tly_selected_option);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates the help text at the top left of the window.
|
||||
fn create_help_text(currently_selected_option: &SelectedColorGradingOption) -> String {
|
||||
format!("Press Left/Right to adjust {}", currently_selected_option)
|
||||
}
|
||||
|
||||
/// Processes keyboard input to change the value of the currently-selected color
|
||||
/// grading option.
|
||||
fn adjust_color_grading_option(
|
||||
mut cameras: Query<&mut ColorGrading>,
|
||||
input: Res<ButtonInput<KeyCode>>,
|
||||
currently_selected_option: Res<SelectedColorGradingOption>,
|
||||
) {
|
||||
let mut delta = 0.0;
|
||||
if input.pressed(KeyCode::ArrowLeft) {
|
||||
delta -= OPTION_ADJUSTMENT_SPEED;
|
||||
}
|
||||
if input.pressed(KeyCode::ArrowRight) {
|
||||
delta += OPTION_ADJUSTMENT_SPEED;
|
||||
}
|
||||
|
||||
for mut color_grading in cameras.iter_mut() {
|
||||
let new_value = currently_selected_option.get(&color_grading) + delta;
|
||||
currently_selected_option.set(&mut color_grading, new_value);
|
||||
}
|
||||
}
|
|
@ -6,10 +6,8 @@ use bevy::{
|
|||
prelude::*,
|
||||
reflect::TypePath,
|
||||
render::{
|
||||
render_asset::RenderAssetUsages,
|
||||
render_resource::{AsBindGroup, Extent3d, ShaderRef, TextureDimension, TextureFormat},
|
||||
texture::{ImageSampler, ImageSamplerDescriptor},
|
||||
view::ColorGrading,
|
||||
render_resource::{AsBindGroup, ShaderRef},
|
||||
view::{ColorGrading, ColorGradingGlobal, ColorGradingSection},
|
||||
},
|
||||
utils::HashMap,
|
||||
};
|
||||
|
@ -98,83 +96,14 @@ fn setup(
|
|||
);
|
||||
}
|
||||
|
||||
fn setup_basic_scene(
|
||||
mut commands: Commands,
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||
mut images: ResMut<Assets<Image>>,
|
||||
asset_server: Res<AssetServer>,
|
||||
) {
|
||||
// plane
|
||||
commands.spawn((
|
||||
PbrBundle {
|
||||
mesh: meshes.add(Plane3d::default().mesh().size(50.0, 50.0)),
|
||||
material: materials.add(Color::srgb(0.1, 0.2, 0.1)),
|
||||
fn setup_basic_scene(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||
// Main scene
|
||||
commands
|
||||
.spawn(SceneBundle {
|
||||
scene: asset_server.load("models/TonemappingTest/TonemappingTest.gltf#Scene0"),
|
||||
..default()
|
||||
},
|
||||
SceneNumber(1),
|
||||
));
|
||||
|
||||
// cubes
|
||||
let cube_material = materials.add(StandardMaterial {
|
||||
base_color_texture: Some(images.add(uv_debug_texture())),
|
||||
..default()
|
||||
});
|
||||
|
||||
let cube_mesh = meshes.add(Cuboid::new(0.25, 0.25, 0.25));
|
||||
for i in 0..5 {
|
||||
commands.spawn((
|
||||
PbrBundle {
|
||||
mesh: cube_mesh.clone(),
|
||||
material: cube_material.clone(),
|
||||
transform: Transform::from_xyz(i as f32 * 0.25 - 1.0, 0.125, -i as f32 * 0.5),
|
||||
..default()
|
||||
},
|
||||
SceneNumber(1),
|
||||
));
|
||||
}
|
||||
|
||||
// spheres
|
||||
let sphere_mesh = meshes.add(Sphere::new(0.125).mesh().uv(32, 18));
|
||||
for i in 0..6 {
|
||||
let j = i % 3;
|
||||
let s_val = if i < 3 { 0.0 } else { 0.2 };
|
||||
let material = if j == 0 {
|
||||
materials.add(StandardMaterial {
|
||||
base_color: Color::srgb(s_val, s_val, 1.0),
|
||||
perceptual_roughness: 0.089,
|
||||
metallic: 0.0,
|
||||
..default()
|
||||
})
|
||||
} else if j == 1 {
|
||||
materials.add(StandardMaterial {
|
||||
base_color: Color::srgb(s_val, 1.0, s_val),
|
||||
perceptual_roughness: 0.089,
|
||||
metallic: 0.0,
|
||||
..default()
|
||||
})
|
||||
} else {
|
||||
materials.add(StandardMaterial {
|
||||
base_color: Color::srgb(1.0, s_val, s_val),
|
||||
perceptual_roughness: 0.089,
|
||||
metallic: 0.0,
|
||||
..default()
|
||||
})
|
||||
};
|
||||
commands.spawn((
|
||||
PbrBundle {
|
||||
mesh: sphere_mesh.clone(),
|
||||
material,
|
||||
transform: Transform::from_xyz(
|
||||
j as f32 * 0.25 + if i < 3 { -0.15 } else { 0.15 } - 0.4,
|
||||
0.125,
|
||||
-j as f32 * 0.25 + if i < 3 { -0.15 } else { 0.15 } + 0.4,
|
||||
),
|
||||
..default()
|
||||
},
|
||||
SceneNumber(1),
|
||||
));
|
||||
}
|
||||
})
|
||||
.insert(SceneNumber(1));
|
||||
|
||||
// Flight Helmet
|
||||
commands.spawn((
|
||||
|
@ -403,10 +332,12 @@ fn toggle_tonemapping_method(
|
|||
*method = Tonemapping::BlenderFilmic;
|
||||
}
|
||||
|
||||
*color_grading = *per_method_settings
|
||||
*color_grading = (*per_method_settings
|
||||
.settings
|
||||
.get::<Tonemapping>(&method)
|
||||
.unwrap();
|
||||
.as_ref()
|
||||
.unwrap())
|
||||
.clone();
|
||||
}
|
||||
|
||||
#[derive(Resource)]
|
||||
|
@ -448,16 +379,20 @@ fn update_color_grading_settings(
|
|||
if keys.pressed(KeyCode::ArrowLeft) || keys.pressed(KeyCode::ArrowRight) {
|
||||
match selected_parameter.value {
|
||||
0 => {
|
||||
color_grading.exposure += dt;
|
||||
color_grading.global.exposure += dt;
|
||||
}
|
||||
1 => {
|
||||
color_grading.gamma += dt;
|
||||
color_grading
|
||||
.all_sections_mut()
|
||||
.for_each(|section| section.gamma += dt);
|
||||
}
|
||||
2 => {
|
||||
color_grading.pre_saturation += dt;
|
||||
color_grading
|
||||
.all_sections_mut()
|
||||
.for_each(|section| section.saturation += dt);
|
||||
}
|
||||
3 => {
|
||||
color_grading.post_saturation += dt;
|
||||
color_grading.global.post_saturation += dt;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
@ -577,24 +512,24 @@ fn update_ui(
|
|||
if selected_parameter.value == 0 {
|
||||
text.push_str("> ");
|
||||
}
|
||||
text.push_str(&format!("Exposure: {}\n", color_grading.exposure));
|
||||
text.push_str(&format!("Exposure: {}\n", color_grading.global.exposure));
|
||||
if selected_parameter.value == 1 {
|
||||
text.push_str("> ");
|
||||
}
|
||||
text.push_str(&format!("Gamma: {}\n", color_grading.gamma));
|
||||
text.push_str(&format!("Gamma: {}\n", color_grading.shadows.gamma));
|
||||
if selected_parameter.value == 2 {
|
||||
text.push_str("> ");
|
||||
}
|
||||
text.push_str(&format!(
|
||||
"PreSaturation: {}\n",
|
||||
color_grading.pre_saturation
|
||||
color_grading.shadows.saturation
|
||||
));
|
||||
if selected_parameter.value == 3 {
|
||||
text.push_str("> ");
|
||||
}
|
||||
text.push_str(&format!(
|
||||
"PostSaturation: {}\n",
|
||||
color_grading.post_saturation
|
||||
color_grading.global.post_saturation
|
||||
));
|
||||
text.push_str("(Space) Reset all to default\n");
|
||||
|
||||
|
@ -614,19 +549,30 @@ impl PerMethodSettings {
|
|||
fn basic_scene_recommendation(method: Tonemapping) -> ColorGrading {
|
||||
match method {
|
||||
Tonemapping::Reinhard | Tonemapping::ReinhardLuminance => ColorGrading {
|
||||
exposure: 0.5,
|
||||
global: ColorGradingGlobal {
|
||||
exposure: 0.5,
|
||||
..default()
|
||||
},
|
||||
..default()
|
||||
},
|
||||
Tonemapping::AcesFitted => ColorGrading {
|
||||
exposure: 0.35,
|
||||
global: ColorGradingGlobal {
|
||||
exposure: 0.35,
|
||||
..default()
|
||||
},
|
||||
..default()
|
||||
},
|
||||
Tonemapping::AgX => ColorGrading {
|
||||
exposure: -0.2,
|
||||
gamma: 1.0,
|
||||
pre_saturation: 1.1,
|
||||
post_saturation: 1.1,
|
||||
},
|
||||
Tonemapping::AgX => ColorGrading::with_identical_sections(
|
||||
ColorGradingGlobal {
|
||||
exposure: -0.2,
|
||||
post_saturation: 1.1,
|
||||
..default()
|
||||
},
|
||||
ColorGradingSection {
|
||||
saturation: 1.1,
|
||||
..default()
|
||||
},
|
||||
),
|
||||
_ => ColorGrading::default(),
|
||||
}
|
||||
}
|
||||
|
@ -656,37 +602,6 @@ impl Default for PerMethodSettings {
|
|||
}
|
||||
}
|
||||
|
||||
/// Creates a colorful test pattern
|
||||
fn uv_debug_texture() -> Image {
|
||||
const TEXTURE_SIZE: usize = 8;
|
||||
|
||||
let mut palette: [u8; 32] = [
|
||||
255, 102, 159, 255, 255, 159, 102, 255, 236, 255, 102, 255, 121, 255, 102, 255, 102, 255,
|
||||
198, 255, 102, 198, 255, 255, 121, 102, 255, 255, 236, 102, 255, 255,
|
||||
];
|
||||
|
||||
let mut texture_data = [0; TEXTURE_SIZE * TEXTURE_SIZE * 4];
|
||||
for y in 0..TEXTURE_SIZE {
|
||||
let offset = TEXTURE_SIZE * y * 4;
|
||||
texture_data[offset..(offset + TEXTURE_SIZE * 4)].copy_from_slice(&palette);
|
||||
palette.rotate_right(4);
|
||||
}
|
||||
|
||||
let mut img = Image::new_fill(
|
||||
Extent3d {
|
||||
width: TEXTURE_SIZE as u32,
|
||||
height: TEXTURE_SIZE as u32,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
TextureDimension::D2,
|
||||
&texture_data,
|
||||
TextureFormat::Rgba8UnormSrgb,
|
||||
RenderAssetUsages::RENDER_WORLD,
|
||||
);
|
||||
img.sampler = ImageSampler::Descriptor(ImageSamplerDescriptor::default());
|
||||
img
|
||||
}
|
||||
|
||||
impl Material for ColorGradientMaterial {
|
||||
fn fragment_shader() -> ShaderRef {
|
||||
"shaders/tonemapping_test_patterns.wgsl".into()
|
||||
|
|
|
@ -30,7 +30,7 @@ use bevy::{
|
|||
prelude::*,
|
||||
render::{
|
||||
camera::{Exposure, TemporalJitter},
|
||||
view::ColorGrading,
|
||||
view::{ColorGrading, ColorGradingGlobal},
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -345,7 +345,10 @@ fn setup(
|
|||
},
|
||||
transform: Transform::from_xyz(1.0, 1.8, 7.0).looking_at(Vec3::ZERO, Vec3::Y),
|
||||
color_grading: ColorGrading {
|
||||
post_saturation: 1.2,
|
||||
global: ColorGradingGlobal {
|
||||
post_saturation: 1.2,
|
||||
..default()
|
||||
},
|
||||
..default()
|
||||
},
|
||||
tonemapping: Tonemapping::TonyMcMapface,
|
||||
|
|
|
@ -129,6 +129,7 @@ Example | Description
|
|||
[Anti-aliasing](../examples/3d/anti_aliasing.rs) | Compares different anti-aliasing methods
|
||||
[Atmospheric Fog](../examples/3d/atmospheric_fog.rs) | A scene showcasing the atmospheric fog effect
|
||||
[Blend Modes](../examples/3d/blend_modes.rs) | Showcases different blend modes
|
||||
[Color grading](../examples/3d/color_grading.rs) | Demonstrates color grading
|
||||
[Deferred Rendering](../examples/3d/deferred_rendering.rs) | Renders meshes with both forward and deferred pipelines
|
||||
[Fog](../examples/3d/fog.rs) | A scene showcasing the distance fog effect
|
||||
[Generate Custom Mesh](../examples/3d/generate_custom_mesh.rs) | Simple showcase of how to generate a custom mesh with a custom texture
|
||||
|
|
Loading…
Reference in a new issue