obs-filters: Add HDR Tonemap filter
Allow per-source tonemapping to override default tonemapping.
This commit is contained in:
parent
94189402c2
commit
535d4141cb
@ -121,6 +121,7 @@ target_sources(
|
||||
color-correction-filter.c
|
||||
async-delay-filter.c
|
||||
gpu-delay.c
|
||||
hdr-tonemap-filter.c
|
||||
crop-filter.c
|
||||
scale-filter.c
|
||||
scroll-filter.c
|
||||
@ -145,11 +146,13 @@ if(NOT OS_MACOS)
|
||||
data/blend_sub_filter.effect
|
||||
data/chroma_key_filter.effect
|
||||
data/chroma_key_filter_v2.effect
|
||||
data/color.effect
|
||||
data/color_correction_filter.effect
|
||||
data/color_grade_filter.effect
|
||||
data/color_key_filter.effect
|
||||
data/color_key_filter_v2.effect
|
||||
data/crop_filter.effect
|
||||
data/hdr_tonemap_filter.effect
|
||||
data/luma_key_filter.effect
|
||||
data/luma_key_filter_v2.effect
|
||||
data/mask_alpha_filter.effect
|
||||
|
@ -34,3 +34,53 @@ float3 reinhard(float3 rgb)
|
||||
return float3(reinhard_channel(rgb.r), reinhard_channel(rgb.g), reinhard_channel(rgb.b));
|
||||
}
|
||||
|
||||
float linear_to_st2084_channel(float x)
|
||||
{
|
||||
float c = pow(abs(x), 0.1593017578);
|
||||
return pow((0.8359375 + 18.8515625 * c) / (1. + 18.6875 * c), 78.84375);
|
||||
}
|
||||
|
||||
float st2084_to_linear_channel(float u)
|
||||
{
|
||||
float c = pow(abs(u), 1. / 78.84375);
|
||||
return pow(abs(max(c - 0.8359375, 0.) / (18.8515625 - 18.6875 * c)), 1. / 0.1593017578);
|
||||
}
|
||||
|
||||
float eetf_0_Lmax(float maxRGB1_pq, float Lw, float Lmax)
|
||||
{
|
||||
float Lw_pq = linear_to_st2084_channel(Lw / 10000.);
|
||||
float E1 = saturate(maxRGB1_pq / Lw_pq); // Ensure normalization in case Lw is a lie
|
||||
float maxLum = linear_to_st2084_channel(Lmax / 10000.) / Lw_pq;
|
||||
float KS = (1.5 * maxLum) - 0.5;
|
||||
float E2 = E1;
|
||||
if (E1 > KS)
|
||||
{
|
||||
float T = (E1 - KS) / (1. - KS);
|
||||
float Tsquared = T * T;
|
||||
float Tcubed = Tsquared * T;
|
||||
float P = (2. * Tcubed - 3. * Tsquared + 1.) * KS + (Tcubed - 2. * Tsquared + T) * (1. - KS) + (-2. * Tcubed + 3. * Tsquared) * maxLum;
|
||||
E2 = P;
|
||||
}
|
||||
float E3 = E2;
|
||||
float E4 = E3 * Lw_pq;
|
||||
return E4;
|
||||
}
|
||||
|
||||
float3 maxRGB_eetf_internal(float3 rgb_linear, float maxRGB1_linear, float maxRGB1_pq, float Lw, float Lmax)
|
||||
{
|
||||
float maxRGB2_pq = eetf_0_Lmax(maxRGB1_pq, Lw, Lmax);
|
||||
float maxRGB2_linear = st2084_to_linear_channel(maxRGB2_pq);
|
||||
|
||||
// avoid divide-by-zero possibility
|
||||
maxRGB1_linear = max(6.10352e-5, maxRGB1_linear);
|
||||
|
||||
rgb_linear *= maxRGB2_linear / maxRGB1_linear;
|
||||
return rgb_linear;
|
||||
}
|
||||
|
||||
float3 maxRGB_eetf_linear_to_linear(float3 rgb_linear, float Lw, float Lmax)
|
||||
{
|
||||
float maxRGB1_linear = max(max(rgb_linear.r, rgb_linear.g), rgb_linear.b);
|
||||
float maxRGB1_pq = linear_to_st2084_channel(maxRGB1_linear);
|
||||
return maxRGB_eetf_internal(rgb_linear, maxRGB1_linear, maxRGB1_pq, Lw, Lmax);
|
||||
}
|
||||
|
76
plugins/obs-filters/data/hdr_tonemap_filter.effect
Normal file
76
plugins/obs-filters/data/hdr_tonemap_filter.effect
Normal file
@ -0,0 +1,76 @@
|
||||
#include "color.effect"
|
||||
|
||||
uniform float4x4 ViewProj;
|
||||
uniform texture2d image;
|
||||
|
||||
uniform float multiplier;
|
||||
uniform float hdr_input_maximum_nits;
|
||||
uniform float hdr_output_maximum_nits;
|
||||
|
||||
sampler_state textureSampler {
|
||||
Filter = Linear;
|
||||
AddressU = Clamp;
|
||||
AddressV = Clamp;
|
||||
};
|
||||
|
||||
struct VertData {
|
||||
float4 pos : POSITION;
|
||||
float2 uv : TEXCOORD0;
|
||||
};
|
||||
|
||||
struct VertOut {
|
||||
float2 uv : TEXCOORD0;
|
||||
float4 pos : POSITION;
|
||||
};
|
||||
|
||||
struct FragData {
|
||||
float2 uv : TEXCOORD0;
|
||||
};
|
||||
|
||||
VertOut VSHdrTonemap(VertData v_in)
|
||||
{
|
||||
VertOut vert_out;
|
||||
vert_out.pos = mul(float4(v_in.pos.xyz, 1.0), ViewProj);
|
||||
vert_out.uv = v_in.uv;
|
||||
return vert_out;
|
||||
}
|
||||
|
||||
float4 PSReinhard(FragData f_in) : TARGET
|
||||
{
|
||||
float4 rgba = image.Sample(textureSampler, f_in.uv);
|
||||
rgba.rgb *= multiplier;
|
||||
rgba.rgb = rec709_to_rec2020(rgba.rgb);
|
||||
rgba.rgb = reinhard(rgba.rgb);
|
||||
rgba.rgb = rec2020_to_rec709(rgba.rgb);
|
||||
return rgba;
|
||||
}
|
||||
|
||||
float4 PSMaxrgb(FragData f_in) : TARGET
|
||||
{
|
||||
float4 rgba = image.Sample(textureSampler, f_in.uv);
|
||||
rgba.rgb *= multiplier;
|
||||
rgba.rgb = rec709_to_rec2020(rgba.rgb);
|
||||
rgba.rgb = maxRGB_eetf_linear_to_linear(rgba.rgb, hdr_input_maximum_nits, hdr_output_maximum_nits);
|
||||
rgba.rgb = rec2020_to_rec709(rgba.rgb);
|
||||
float multiplier_i = 1. / multiplier;
|
||||
rgba.rgb *= multiplier_i;
|
||||
return rgba;
|
||||
}
|
||||
|
||||
technique Reinhard
|
||||
{
|
||||
pass
|
||||
{
|
||||
vertex_shader = VSHdrTonemap(v_in);
|
||||
pixel_shader = PSReinhard(f_in);
|
||||
}
|
||||
}
|
||||
|
||||
technique MaxRGB
|
||||
{
|
||||
pass
|
||||
{
|
||||
vertex_shader = VSHdrTonemap(v_in);
|
||||
pixel_shader = PSMaxrgb(f_in);
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@ ColorGradeFilter="Apply LUT"
|
||||
MaskFilter="Image Mask/Blend"
|
||||
AsyncDelayFilter="Video Delay (Async)"
|
||||
CropFilter="Crop/Pad"
|
||||
HdrTonemapFilter="HDR Tone Mapping (Override)"
|
||||
ScrollFilter="Scroll"
|
||||
ChromaKeyFilter="Chroma Key"
|
||||
ColorKeyFilter="Color Key"
|
||||
@ -45,6 +46,12 @@ Crop.Bottom="Bottom"
|
||||
Crop.Width="Width"
|
||||
Crop.Height="Height"
|
||||
Crop.Relative="Relative"
|
||||
HdrTonemap.ToneTransform="Tone Transform"
|
||||
HdrTonemap.SdrReinhard="SDR: Reinhard"
|
||||
HdrTonemap.HdrMaxrgb="HDR: maxRGB"
|
||||
HdrTonemap.SdrWhiteLevel="SDR White Level"
|
||||
HdrTonemap.HdrInputMaximum="HDR Input Maximum"
|
||||
HdrTonemap.HdrOutputMaximum="HDR Output Maximum"
|
||||
ScrollFilter.SpeedX="Horizontal Speed"
|
||||
ScrollFilter.SpeedY="Vertical Speed"
|
||||
ScrollFilter.LimitWidth="Limit Width"
|
||||
|
234
plugins/obs-filters/hdr-tonemap-filter.c
Normal file
234
plugins/obs-filters/hdr-tonemap-filter.c
Normal file
@ -0,0 +1,234 @@
|
||||
#include <obs-module.h>
|
||||
|
||||
enum hdr_tonemap_transform {
|
||||
TRANSFORM_SDR_REINHARD,
|
||||
TRANSFORM_HDR_MAXRGB,
|
||||
};
|
||||
|
||||
struct hdr_tonemap_filter_data {
|
||||
obs_source_t *context;
|
||||
|
||||
gs_effect_t *effect;
|
||||
gs_eparam_t *param_multiplier;
|
||||
gs_eparam_t *param_hdr_input_maximum_nits;
|
||||
gs_eparam_t *param_hdr_output_maximum_nits;
|
||||
|
||||
enum hdr_tonemap_transform transform;
|
||||
float sdr_white_level_nits_i;
|
||||
float hdr_input_maximum_nits;
|
||||
float hdr_output_maximum_nits;
|
||||
};
|
||||
|
||||
static const char *hdr_tonemap_filter_get_name(void *unused)
|
||||
{
|
||||
UNUSED_PARAMETER(unused);
|
||||
return obs_module_text("HdrTonemapFilter");
|
||||
}
|
||||
|
||||
static void *hdr_tonemap_filter_create(obs_data_t *settings,
|
||||
obs_source_t *context)
|
||||
{
|
||||
struct hdr_tonemap_filter_data *filter = bzalloc(sizeof(*filter));
|
||||
char *effect_path = obs_module_file("hdr_tonemap_filter.effect");
|
||||
|
||||
filter->context = context;
|
||||
|
||||
obs_enter_graphics();
|
||||
filter->effect = gs_effect_create_from_file(effect_path, NULL);
|
||||
obs_leave_graphics();
|
||||
|
||||
bfree(effect_path);
|
||||
|
||||
if (!filter->effect) {
|
||||
bfree(filter);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
filter->param_multiplier =
|
||||
gs_effect_get_param_by_name(filter->effect, "multiplier");
|
||||
filter->param_hdr_input_maximum_nits = gs_effect_get_param_by_name(
|
||||
filter->effect, "hdr_input_maximum_nits");
|
||||
filter->param_hdr_output_maximum_nits = gs_effect_get_param_by_name(
|
||||
filter->effect, "hdr_output_maximum_nits");
|
||||
|
||||
obs_source_update(context, settings);
|
||||
return filter;
|
||||
}
|
||||
|
||||
static void hdr_tonemap_filter_destroy(void *data)
|
||||
{
|
||||
struct hdr_tonemap_filter_data *filter = data;
|
||||
|
||||
obs_enter_graphics();
|
||||
gs_effect_destroy(filter->effect);
|
||||
obs_leave_graphics();
|
||||
|
||||
bfree(filter);
|
||||
}
|
||||
|
||||
static void hdr_tonemap_filter_update(void *data, obs_data_t *settings)
|
||||
{
|
||||
struct hdr_tonemap_filter_data *filter = data;
|
||||
|
||||
filter->transform = obs_data_get_int(settings, "transform");
|
||||
filter->sdr_white_level_nits_i =
|
||||
1.f / (float)obs_data_get_int(settings, "sdr_white_level_nits");
|
||||
filter->hdr_input_maximum_nits =
|
||||
(float)obs_data_get_int(settings, "hdr_input_maximum_nits");
|
||||
filter->hdr_output_maximum_nits =
|
||||
(float)obs_data_get_int(settings, "hdr_output_maximum_nits");
|
||||
}
|
||||
|
||||
static bool transform_changed(obs_properties_t *props, obs_property_t *p,
|
||||
obs_data_t *settings)
|
||||
{
|
||||
enum hdr_tonemap_transform transform =
|
||||
obs_data_get_int(settings, "transform");
|
||||
|
||||
const bool reinhard = transform == TRANSFORM_SDR_REINHARD;
|
||||
const bool maxrgb = transform == TRANSFORM_HDR_MAXRGB;
|
||||
obs_property_set_visible(
|
||||
obs_properties_get(props, "sdr_white_level_nits"), reinhard);
|
||||
obs_property_set_visible(
|
||||
obs_properties_get(props, "hdr_input_maximum_nits"), maxrgb);
|
||||
obs_property_set_visible(
|
||||
obs_properties_get(props, "hdr_output_maximum_nits"), maxrgb);
|
||||
|
||||
UNUSED_PARAMETER(p);
|
||||
return true;
|
||||
}
|
||||
|
||||
static obs_properties_t *hdr_tonemap_filter_properties(void *data)
|
||||
{
|
||||
obs_properties_t *props = obs_properties_create();
|
||||
|
||||
obs_property_t *p = obs_properties_add_list(
|
||||
props, "transform", obs_module_text("HdrTonemap.ToneTransform"),
|
||||
OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);
|
||||
obs_property_list_add_int(p, obs_module_text("HdrTonemap.SdrReinhard"),
|
||||
TRANSFORM_SDR_REINHARD);
|
||||
obs_property_list_add_int(p, obs_module_text("HdrTonemap.HdrMaxrgb"),
|
||||
TRANSFORM_HDR_MAXRGB);
|
||||
obs_property_set_modified_callback(p, transform_changed);
|
||||
|
||||
p = obs_properties_add_int(props, "sdr_white_level_nits",
|
||||
obs_module_text("HdrTonemap.SdrWhiteLevel"),
|
||||
80, 480, 1);
|
||||
obs_property_int_set_suffix(p, " nits");
|
||||
p = obs_properties_add_int(
|
||||
props, "hdr_input_maximum_nits",
|
||||
obs_module_text("HdrTonemap.HdrInputMaximum"), 0, 10000, 1);
|
||||
obs_property_int_set_suffix(p, " nits");
|
||||
p = obs_properties_add_int(
|
||||
props, "hdr_output_maximum_nits",
|
||||
obs_module_text("HdrTonemap.HdrOutputMaximum"), 0, 10000, 1);
|
||||
obs_property_int_set_suffix(p, " nits");
|
||||
|
||||
UNUSED_PARAMETER(data);
|
||||
return props;
|
||||
}
|
||||
|
||||
static void hdr_tonemap_filter_defaults(obs_data_t *settings)
|
||||
{
|
||||
obs_data_set_default_int(settings, "transform", TRANSFORM_SDR_REINHARD);
|
||||
obs_data_set_default_int(settings, "sdr_white_level_nits", 300);
|
||||
obs_data_set_default_int(settings, "hdr_input_maximum_nits", 4000);
|
||||
obs_data_set_default_int(settings, "hdr_output_maximum_nits", 1000);
|
||||
}
|
||||
|
||||
static void hdr_tonemap_filter_render(void *data, gs_effect_t *effect)
|
||||
{
|
||||
UNUSED_PARAMETER(effect);
|
||||
|
||||
struct hdr_tonemap_filter_data *filter = data;
|
||||
|
||||
const enum gs_color_space preferred_spaces[] = {
|
||||
GS_CS_SRGB,
|
||||
GS_CS_SRGB_16F,
|
||||
GS_CS_709_EXTENDED,
|
||||
};
|
||||
|
||||
enum gs_color_space source_space = obs_source_get_color_space(
|
||||
obs_filter_get_target(filter->context),
|
||||
OBS_COUNTOF(preferred_spaces), preferred_spaces);
|
||||
if (source_space == GS_CS_709_EXTENDED) {
|
||||
float multiplier = obs_get_video_sdr_white_level();
|
||||
multiplier *= (filter->transform == TRANSFORM_SDR_REINHARD)
|
||||
? filter->sdr_white_level_nits_i
|
||||
: 0.0001f;
|
||||
|
||||
const enum gs_color_format format =
|
||||
gs_get_format_from_space(source_space);
|
||||
if (obs_source_process_filter_begin_with_color_space(
|
||||
filter->context, format, source_space,
|
||||
OBS_NO_DIRECT_RENDERING)) {
|
||||
gs_effect_set_float(filter->param_multiplier,
|
||||
multiplier);
|
||||
gs_effect_set_float(
|
||||
filter->param_hdr_input_maximum_nits,
|
||||
filter->hdr_input_maximum_nits);
|
||||
gs_effect_set_float(
|
||||
filter->param_hdr_output_maximum_nits,
|
||||
filter->hdr_output_maximum_nits);
|
||||
|
||||
gs_blend_state_push();
|
||||
gs_blend_function(GS_BLEND_ONE, GS_BLEND_INVSRCALPHA);
|
||||
|
||||
const char *const tech_name =
|
||||
(filter->transform == TRANSFORM_HDR_MAXRGB)
|
||||
? "MaxRGB"
|
||||
: "Reinhard";
|
||||
obs_source_process_filter_tech_end(filter->context,
|
||||
filter->effect, 0, 0,
|
||||
tech_name);
|
||||
|
||||
gs_blend_state_pop();
|
||||
}
|
||||
} else {
|
||||
obs_source_skip_video_filter(filter->context);
|
||||
}
|
||||
}
|
||||
|
||||
static enum gs_color_space
|
||||
hdr_tonemap_filter_get_color_space(void *data, size_t count,
|
||||
const enum gs_color_space *preferred_spaces)
|
||||
{
|
||||
const enum gs_color_space potential_spaces[] = {
|
||||
GS_CS_SRGB,
|
||||
GS_CS_SRGB_16F,
|
||||
GS_CS_709_EXTENDED,
|
||||
};
|
||||
|
||||
struct hdr_tonemap_filter_data *const filter = data;
|
||||
const enum gs_color_space source_space = obs_source_get_color_space(
|
||||
obs_filter_get_target(filter->context),
|
||||
OBS_COUNTOF(potential_spaces), potential_spaces);
|
||||
|
||||
enum gs_color_space space = source_space;
|
||||
if ((source_space == GS_CS_709_EXTENDED) &&
|
||||
(filter->transform == TRANSFORM_SDR_REINHARD)) {
|
||||
space = GS_CS_SRGB;
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
if (preferred_spaces[i] != GS_CS_SRGB) {
|
||||
space = GS_CS_SRGB_16F;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return space;
|
||||
}
|
||||
|
||||
struct obs_source_info hdr_tonemap_filter = {
|
||||
.id = "hdr_tonemap_filter",
|
||||
.type = OBS_SOURCE_TYPE_FILTER,
|
||||
.output_flags = OBS_SOURCE_VIDEO | OBS_SOURCE_SRGB,
|
||||
.get_name = hdr_tonemap_filter_get_name,
|
||||
.create = hdr_tonemap_filter_create,
|
||||
.destroy = hdr_tonemap_filter_destroy,
|
||||
.update = hdr_tonemap_filter_update,
|
||||
.get_properties = hdr_tonemap_filter_properties,
|
||||
.get_defaults = hdr_tonemap_filter_defaults,
|
||||
.video_render = hdr_tonemap_filter_render,
|
||||
.video_get_color_space = hdr_tonemap_filter_get_color_space,
|
||||
};
|
@ -11,6 +11,7 @@ extern struct obs_source_info mask_filter;
|
||||
extern struct obs_source_info mask_filter_v2;
|
||||
extern struct obs_source_info crop_filter;
|
||||
extern struct obs_source_info gain_filter;
|
||||
extern struct obs_source_info hdr_tonemap_filter;
|
||||
extern struct obs_source_info color_filter;
|
||||
extern struct obs_source_info color_filter_v2;
|
||||
extern struct obs_source_info scale_filter;
|
||||
@ -49,6 +50,7 @@ bool obs_module_load(void)
|
||||
obs_register_source(&mask_filter_v2);
|
||||
obs_register_source(&crop_filter);
|
||||
obs_register_source(&gain_filter);
|
||||
obs_register_source(&hdr_tonemap_filter);
|
||||
obs_register_source(&color_filter);
|
||||
obs_register_source(&color_filter_v2);
|
||||
obs_register_source(&scale_filter);
|
||||
|
Loading…
x
Reference in New Issue
Block a user