Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tonemapper options #111

Merged
merged 5 commits into from
Mar 28, 2021
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified demo/assets/shaders/bloom_combine.frag.cookedshaderpackage
Binary file not shown.
107 changes: 105 additions & 2 deletions demo/shaders/generated_msl/bloom_combine.frag.metal
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
#pragma clang diagnostic ignored "-Wmissing-prototypes"

#include <metal_stdlib>
#include <simd/simd.h>

using namespace metal;

struct Config
{
int tonemapper_type;
};

struct spvDescriptorSetBuffer0
{
texture2d<float> in_color [[id(0)]];
texture2d<float> in_blur [[id(1)]];
constant Config* config [[id(3)]];
};

struct main0_out
Expand All @@ -19,13 +27,108 @@ struct main0_in
float2 inUV [[user(locn0)]];
};

static inline __attribute__((always_inline))
float3 RRT_and_ODT_fit(thread const float3& v)
{
float3 a = (v * (v + float3(0.02457859925925731658935546875))) - float3(9.0537003416102379560470581054688e-05);
float3 b = (v * ((v * 0.98372900485992431640625) + float3(0.4329510033130645751953125))) + float3(0.23808099329471588134765625);
return a / b;
}

static inline __attribute__((always_inline))
float3 tonemap_aces_fitted(thread float3& color)
{
color = float3x3(float3(0.59719002246856689453125, 0.354579985141754150390625, 0.048229999840259552001953125), float3(0.075999997556209564208984375, 0.908339977264404296875, 0.0156599991023540496826171875), float3(0.0284000001847743988037109375, 0.13382999598979949951171875, 0.837769985198974609375)) * color;
float3 param = color;
color = RRT_and_ODT_fit(param);
color = float3x3(float3(1.60475003719329833984375, -0.5310800075531005859375, -0.0736699998378753662109375), float3(-0.10208000242710113525390625, 1.108129978179931640625, -0.00604999996721744537353515625), float3(-0.00326999998651444911956787109375, -0.07276000082492828369140625, 1.0760200023651123046875)) * color;
color = fast::clamp(color, float3(0.0), float3(1.0));
return color;
}

static inline __attribute__((always_inline))
float3 tonemap_aces_film_simple(thread const float3& x)
{
float a = 2.5099999904632568359375;
float b = 0.02999999932944774627685546875;
float c = 2.4300000667572021484375;
float d = 0.589999973773956298828125;
float e = 0.14000000059604644775390625;
return fast::clamp((x * ((x * a) + float3(b))) / ((x * ((x * c) + float3(d))) + float3(e)), float3(0.0), float3(1.0));
}

static inline __attribute__((always_inline))
float3 visualize_value(thread const float& val)
{
float g = 1.0 - ((0.20000000298023223876953125 * (val - 3.2360498905181884765625)) * (val - 3.2360498905181884765625));
float b = 1.0 - ((1.0 * (val - 1.0)) * (val - 1.0));
float r = 1.0 - (1.0 / ((0.5 * val) - 0.5));
if (val > 1.0)
{
b = 0.0;
}
if (val < 3.0)
{
r = 0.0;
}
return fast::clamp(float3(r, g, b), float3(0.0), float3(1.0));
}

static inline __attribute__((always_inline))
float luma(thread const float3& color)
{
return dot(color, float3(0.2989999949932098388671875, 0.58700001239776611328125, 0.114000000059604644775390625));
}

fragment main0_out main0(main0_in in [[stage_in]], constant spvDescriptorSetBuffer0& spvDescriptorSet0 [[buffer(0)]])
{
constexpr sampler smp(mip_filter::linear, compare_func::never, max_anisotropy(1));
main0_out out = {};
float4 color = spvDescriptorSet0.in_color.sample(smp, in.inUV) + spvDescriptorSet0.in_blur.sample(smp, in.inUV);
float3 mapped = color.xyz / (color.xyz + float3(1.0));
out.out_sdr = float4(mapped, color.w);
if ((*spvDescriptorSet0.config).tonemapper_type == 1)
{
float3 param = color.xyz;
float3 _227 = tonemap_aces_fitted(param);
out.out_sdr = float4(_227, color.w);
}
else
{
if ((*spvDescriptorSet0.config).tonemapper_type == 2)
{
float3 param_1 = color.xyz;
out.out_sdr = float4(tonemap_aces_film_simple(param_1), color.w);
}
else
{
if ((*spvDescriptorSet0.config).tonemapper_type == 3)
{
out.out_sdr = float4(color.xyz / (color.xyz + float3(1.0)), color.w);
}
else
{
if ((*spvDescriptorSet0.config).tonemapper_type == 4)
{
float max_val = fast::max(color.x, fast::max(color.y, color.z));
float param_2 = max_val;
out.out_sdr = float4(visualize_value(param_2), color.w);
}
else
{
if ((*spvDescriptorSet0.config).tonemapper_type == 5)
{
float3 param_3 = color.xyz;
float l = luma(param_3);
float param_4 = l;
out.out_sdr = float4(visualize_value(param_4), color.w);
}
else
{
out.out_sdr = color;
}
}
}
}
}
return out;
}

98 changes: 96 additions & 2 deletions demo/shaders/glsl/bloom_combine.frag
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,109 @@ layout (set = 0, binding = 1) uniform texture2D in_blur;
// ])]
layout (set = 0, binding = 2) uniform sampler smp;

// @[export]
// @[internal_buffer]
layout (set = 0, binding = 3) uniform Config {
int tonemapper_type;
} config;

layout (location = 0) in vec2 inUV;

layout (location = 0) out vec4 out_sdr;

// The code for ACESFitted was originally written by Stephen Hill (@self_shadow), who deserves all
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is a non-trivial amount of code, could we separate it to a separate .glsl? Also I think we should add a link to the original material

// credit for coming up with this fit and implementing it. Buy him a beer next time you see him. :)
// The code is licensed under the MIT license

// sRGB => XYZ => D65_2_D60 => AP1 => RRT_SAT
const mat3 ACESInputMat =
{
{0.59719, 0.35458, 0.04823},
{0.07600, 0.90834, 0.01566},
{0.02840, 0.13383, 0.83777}
};

// ODT_SAT => XYZ => D60_2_D65 => sRGB
const mat3 ACESOutputMat =
{
{ 1.60475, -0.53108, -0.07367},
{-0.10208, 1.10813, -0.00605},
{-0.00327, -0.07276, 1.07602}
};

vec3 RRT_and_ODT_fit(vec3 v)
{
vec3 a = v * (v + 0.0245786f) - 0.000090537f;
vec3 b = v * (0.983729f * v + 0.4329510f) + 0.238081f;
return a / b;
}

vec3 tonemap_aces_fitted(vec3 color)
{
color = ACESInputMat * color;

// Apply RRT and ODT
color = RRT_and_ODT_fit(color);

color = ACESOutputMat * color;

// Clamp to [0, 1]
color = clamp(color, 0, 1);

return color;
}

// source: https://knarkowicz.wordpress.com/2016/01/06/aces-filmic-tone-mapping-curve/
vec3 tonemap_aces_film_simple(vec3 x)
{
float a = 2.51f;
float b = 0.03f;
float c = 2.43f;
float d = 0.59f;
float e = 0.14f;
return clamp((x*(a*x+b))/(x*(c*x+d)+e), 0.0, 1.0);
}

float luma(vec3 color) {
return dot(color, vec3(0.299, 0.587, 0.114));
}

vec3 visualize_value(float val) {
// parabolic curves visualize the range
// blue is used to visualize 0-1 exclusively
// green covers 0-5
// red is 3+
float g = 1 - 0.2 * (val - 3.23605) * (val - 3.23605);
float b = 1 - 1 * (val - 1) * (val - 1);
float r = 1 - 1 / (0.5 * val - 0.5);
if (val > 1.0) {
b = 0;
}
if (val < 3.0) {
r = 0;
}
return clamp(vec3(r, g, b), 0, 1);
}

void main()
{
vec4 color = texture(sampler2D(in_color, smp), inUV) + texture(sampler2D(in_blur, smp), inUV);

// tonemapping.. TODO: implement auto-exposure
vec3 mapped = color.rgb / (color.rgb + vec3(1.0));
out_sdr = vec4(mapped, color.a);
if (config.tonemapper_type == 1) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to use constants here? Will make it more readable.

We should also put comments on the rust/glsl code saying "these indexes must be kept in sync with X"

Long term, it would be great if we could export glsl constants as rust code so that we don't have to define them more than once

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think all of these modes are useful? (I really don't know much about tone mapping!)

Copy link
Collaborator Author

@kabergstrom kabergstrom Mar 28, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll make them constants.
Whether they're useful or not, I'm not sure. I bet all of them have been used in some released game. I mainly wanted to set up a testbed for showing the effects of various tonemapping operators.

out_sdr = vec4(tonemap_aces_fitted(color.rgb), color.a);
} else if (config.tonemapper_type == 2) {
out_sdr = vec4(tonemap_aces_film_simple(color.rgb), color.a);
} else if (config.tonemapper_type == 3) {
out_sdr = vec4(color.rgb / (color.rgb + vec3(1.0)), color.a);
} else if (config.tonemapper_type == 4) {
float max_val = max(color.r, max(color.g, color.b));
out_sdr = vec4(visualize_value(max_val), color.a);
} else if (config.tonemapper_type == 5) {
float l = luma(color.rgb);
out_sdr = vec4(visualize_value(l), color.a);
} else {
out_sdr = color;
}
// out_sdr = vec4(mapped, color.a);
}
10 changes: 0 additions & 10 deletions demo/shaders/glsl/mesh.frag
Original file line number Diff line number Diff line change
Expand Up @@ -885,16 +885,6 @@ vec4 pbr_path(
vec3 color = ambient + total_light + emissive_color.rgb;
#endif
return vec4(color, base_color.a);

// tonemapping
//vec3 mapped = color.rgb / (color.rgb + vec3(1.0));

// gamma correction
//const float gamma = 2.2;
//mapped = pow(mapped, vec3(1.0 / gamma));

// output
//return vec4(mapped, base_color.a);
}


Expand Down
47 changes: 47 additions & 0 deletions demo/shaders/src/bloom_combine_frag.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,37 @@ use rafx_framework::{
ImageViewResource, ResourceArc,
};

#[derive(Copy, Clone, Debug)]
#[repr(C)]
pub struct ConfigStd140 {
pub tonemapper_type: i32, // +0 (size: 4)
pub _padding0: [u8; 12], // +4 (size: 12)
} // 16 bytes

impl Default for ConfigStd140 {
fn default() -> Self {
ConfigStd140 {
tonemapper_type: <i32>::default(),
_padding0: [u8::default(); 12],
}
}
}

pub type ConfigUniform = ConfigStd140;

pub const IN_COLOR_DESCRIPTOR_SET_INDEX: usize = 0;
pub const IN_COLOR_DESCRIPTOR_BINDING_INDEX: usize = 0;
pub const IN_BLUR_DESCRIPTOR_SET_INDEX: usize = 0;
pub const IN_BLUR_DESCRIPTOR_BINDING_INDEX: usize = 1;
pub const SMP_DESCRIPTOR_SET_INDEX: usize = 0;
pub const SMP_DESCRIPTOR_BINDING_INDEX: usize = 2;
pub const CONFIG_DESCRIPTOR_SET_INDEX: usize = 0;
pub const CONFIG_DESCRIPTOR_BINDING_INDEX: usize = 3;

pub struct DescriptorSet0Args<'a> {
pub in_color: &'a ResourceArc<ImageViewResource>,
pub in_blur: &'a ResourceArc<ImageViewResource>,
pub config: &'a ConfigUniform,
}

impl<'a> DescriptorSetInitializer<'a> for DescriptorSet0Args<'a> {
Expand Down Expand Up @@ -53,6 +74,7 @@ impl DescriptorSet0 {
) {
descriptor_set.set_image(IN_COLOR_DESCRIPTOR_BINDING_INDEX as u32, args.in_color);
descriptor_set.set_image(IN_BLUR_DESCRIPTOR_BINDING_INDEX as u32, args.in_blur);
descriptor_set.set_buffer_data(CONFIG_DESCRIPTOR_BINDING_INDEX as u32, args.config);
}

pub fn set_args(
Expand All @@ -61,6 +83,7 @@ impl DescriptorSet0 {
) {
self.set_in_color(args.in_color);
self.set_in_blur(args.in_blur);
self.set_config(args.config);
}

pub fn set_in_color(
Expand All @@ -79,10 +102,34 @@ impl DescriptorSet0 {
.set_image(IN_BLUR_DESCRIPTOR_BINDING_INDEX as u32, in_blur);
}

pub fn set_config(
&mut self,
config: &ConfigUniform,
) {
self.0
.set_buffer_data(CONFIG_DESCRIPTOR_BINDING_INDEX as u32, config);
}

pub fn flush(
&mut self,
descriptor_set_allocator: &mut DescriptorSetAllocator,
) -> RafxResult<()> {
self.0.flush(descriptor_set_allocator)
}
}

#[cfg(test)]
mod test {
use super::*;

#[test]
fn test_struct_config_std140() {
assert_eq!(std::mem::size_of::<ConfigStd140>(), 16);
assert_eq!(std::mem::size_of::<i32>(), 4);
assert_eq!(std::mem::align_of::<i32>(), 4);
assert_eq!(memoffset::offset_of!(ConfigStd140, tonemapper_type), 0);
assert_eq!(std::mem::size_of::<[u8; 12]>(), 12);
assert_eq!(std::mem::align_of::<[u8; 12]>(), 1);
assert_eq!(memoffset::offset_of!(ConfigStd140, _padding0), 4);
}
}
Loading