Browse Source

obs-filters: Add maxRGB tonemapper for SDR

Preserves saturation better than our Reinhard.
jpark37 1 year ago
parent
commit
35f8481498

+ 26 - 5
plugins/obs-filters/data/hdr_tonemap_filter.effect

@@ -4,8 +4,8 @@ uniform float4x4 ViewProj;
 uniform texture2d image;
 uniform texture2d image;
 
 
 uniform float multiplier;
 uniform float multiplier;
-uniform float hdr_input_maximum_nits;
-uniform float hdr_output_maximum_nits;
+uniform float input_maximum_nits;
+uniform float output_maximum_nits;
 
 
 sampler_state textureSampler {
 sampler_state textureSampler {
 	Filter    = Linear;
 	Filter    = Linear;
@@ -50,10 +50,22 @@ float4 PSMaxrgb(FragData f_in) : TARGET
 	float4 rgba = image.Sample(textureSampler, f_in.uv);
 	float4 rgba = image.Sample(textureSampler, f_in.uv);
 	rgba.rgb *= multiplier;
 	rgba.rgb *= multiplier;
 	rgba.rgb = rec709_to_rec2020(rgba.rgb);
 	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 = maxRGB_eetf_linear_to_linear(rgba.rgb, input_maximum_nits, output_maximum_nits);
 	rgba.rgb = rec2020_to_rec709(rgba.rgb);
 	rgba.rgb = rec2020_to_rec709(rgba.rgb);
-	float multiplier_i = 1. / multiplier;
-	rgba.rgb *= multiplier_i;
+	rgba.rgb *= 1. / multiplier;
+	return rgba;
+}
+
+float4 PSMaxrgbSdr(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, input_maximum_nits, output_maximum_nits);
+	rgba.rgb = rec2020_to_rec709(rgba.rgb);
+	rgba.rgb *= 10000. / output_maximum_nits;
+	rgba.rgb = pow(saturate(rgba.rgb), float3(1. / 2.4, 1. / 2.4, 1. / 2.4));
+	rgba.rgb = srgb_nonlinear_to_linear(rgba.rgb);
 	return rgba;
 	return rgba;
 }
 }
 
 
@@ -74,3 +86,12 @@ technique MaxRGB
 		pixel_shader  = PSMaxrgb(f_in);
 		pixel_shader  = PSMaxrgb(f_in);
 	}
 	}
 }
 }
+
+technique MaxRGBSDR
+{
+	pass
+	{
+		vertex_shader = VSHdrTonemap(v_in);
+		pixel_shader  = PSMaxrgbSdr(f_in);
+	}
+}

+ 3 - 0
plugins/obs-filters/data/locale/en-US.ini

@@ -50,9 +50,12 @@ HdrTonemap.Description="OBS can perform HDR to SDR tone mapping automatically. O
 HdrTonemap.ToneTransform="Tone Transform"
 HdrTonemap.ToneTransform="Tone Transform"
 HdrTonemap.SdrReinhard="SDR: Reinhard"
 HdrTonemap.SdrReinhard="SDR: Reinhard"
 HdrTonemap.HdrMaxrgb="HDR: maxRGB"
 HdrTonemap.HdrMaxrgb="HDR: maxRGB"
+HdrTonemap.SdrMaxrgb="SDR: maxRGB"
 HdrTonemap.SdrWhiteLevel="SDR White Level"
 HdrTonemap.SdrWhiteLevel="SDR White Level"
 HdrTonemap.HdrInputMaximum="HDR Input Maximum"
 HdrTonemap.HdrInputMaximum="HDR Input Maximum"
 HdrTonemap.HdrOutputMaximum="HDR Output Maximum"
 HdrTonemap.HdrOutputMaximum="HDR Output Maximum"
+HdrTonemap.SdrInputMaximum="SDR Input Maximum"
+HdrTonemap.SdrOutputMaximum="SDR Output Maximum"
 ScrollFilter.SpeedX="Horizontal Speed"
 ScrollFilter.SpeedX="Horizontal Speed"
 ScrollFilter.SpeedY="Vertical Speed"
 ScrollFilter.SpeedY="Vertical Speed"
 ScrollFilter.LimitWidth="Limit Width"
 ScrollFilter.LimitWidth="Limit Width"

+ 55 - 19
plugins/obs-filters/hdr-tonemap-filter.c

@@ -3,6 +3,7 @@
 enum hdr_tonemap_transform {
 enum hdr_tonemap_transform {
 	TRANSFORM_SDR_REINHARD,
 	TRANSFORM_SDR_REINHARD,
 	TRANSFORM_HDR_MAXRGB,
 	TRANSFORM_HDR_MAXRGB,
+	TRANSFORM_SDR_MAXRGB,
 };
 };
 
 
 struct hdr_tonemap_filter_data {
 struct hdr_tonemap_filter_data {
@@ -10,13 +11,15 @@ struct hdr_tonemap_filter_data {
 
 
 	gs_effect_t *effect;
 	gs_effect_t *effect;
 	gs_eparam_t *param_multiplier;
 	gs_eparam_t *param_multiplier;
-	gs_eparam_t *param_hdr_input_maximum_nits;
-	gs_eparam_t *param_hdr_output_maximum_nits;
+	gs_eparam_t *param_input_maximum_nits;
+	gs_eparam_t *param_output_maximum_nits;
 
 
 	enum hdr_tonemap_transform transform;
 	enum hdr_tonemap_transform transform;
 	float sdr_white_level_nits_i;
 	float sdr_white_level_nits_i;
 	float hdr_input_maximum_nits;
 	float hdr_input_maximum_nits;
 	float hdr_output_maximum_nits;
 	float hdr_output_maximum_nits;
+	float sdr_input_maximum_nits;
+	float sdr_output_maximum_nits;
 };
 };
 
 
 static const char *hdr_tonemap_filter_get_name(void *unused)
 static const char *hdr_tonemap_filter_get_name(void *unused)
@@ -46,10 +49,10 @@ static void *hdr_tonemap_filter_create(obs_data_t *settings,
 
 
 	filter->param_multiplier =
 	filter->param_multiplier =
 		gs_effect_get_param_by_name(filter->effect, "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");
+	filter->param_input_maximum_nits = gs_effect_get_param_by_name(
+		filter->effect, "input_maximum_nits");
+	filter->param_output_maximum_nits = gs_effect_get_param_by_name(
+		filter->effect, "output_maximum_nits");
 
 
 	obs_source_update(context, settings);
 	obs_source_update(context, settings);
 	return filter;
 	return filter;
@@ -77,6 +80,10 @@ static void hdr_tonemap_filter_update(void *data, obs_data_t *settings)
 		(float)obs_data_get_int(settings, "hdr_input_maximum_nits");
 		(float)obs_data_get_int(settings, "hdr_input_maximum_nits");
 	filter->hdr_output_maximum_nits =
 	filter->hdr_output_maximum_nits =
 		(float)obs_data_get_int(settings, "hdr_output_maximum_nits");
 		(float)obs_data_get_int(settings, "hdr_output_maximum_nits");
+	filter->sdr_input_maximum_nits =
+		(float)obs_data_get_int(settings, "sdr_input_maximum_nits");
+	filter->sdr_output_maximum_nits =
+		(float)obs_data_get_int(settings, "sdr_output_maximum_nits");
 }
 }
 
 
 static bool transform_changed(obs_properties_t *props, obs_property_t *p,
 static bool transform_changed(obs_properties_t *props, obs_property_t *p,
@@ -86,13 +93,22 @@ static bool transform_changed(obs_properties_t *props, obs_property_t *p,
 		obs_data_get_int(settings, "transform");
 		obs_data_get_int(settings, "transform");
 
 
 	const bool reinhard = transform == TRANSFORM_SDR_REINHARD;
 	const bool reinhard = transform == TRANSFORM_SDR_REINHARD;
-	const bool maxrgb = transform == TRANSFORM_HDR_MAXRGB;
+	const bool maxrgb_hdr = transform == TRANSFORM_HDR_MAXRGB;
+	const bool maxrgb_sdr = transform == TRANSFORM_SDR_MAXRGB;
 	obs_property_set_visible(
 	obs_property_set_visible(
 		obs_properties_get(props, "sdr_white_level_nits"), reinhard);
 		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);
+	obs_property_set_visible(obs_properties_get(props,
+						    "hdr_input_maximum_nits"),
+				 maxrgb_hdr);
+	obs_property_set_visible(obs_properties_get(props,
+						    "hdr_output_maximum_nits"),
+				 maxrgb_hdr);
+	obs_property_set_visible(obs_properties_get(props,
+						    "sdr_input_maximum_nits"),
+				 maxrgb_sdr);
+	obs_property_set_visible(obs_properties_get(props,
+						    "sdr_output_maximum_nits"),
+				 maxrgb_sdr);
 
 
 	UNUSED_PARAMETER(p);
 	UNUSED_PARAMETER(p);
 	return true;
 	return true;
@@ -113,6 +129,8 @@ static obs_properties_t *hdr_tonemap_filter_properties(void *data)
 				  TRANSFORM_SDR_REINHARD);
 				  TRANSFORM_SDR_REINHARD);
 	obs_property_list_add_int(p, obs_module_text("HdrTonemap.HdrMaxrgb"),
 	obs_property_list_add_int(p, obs_module_text("HdrTonemap.HdrMaxrgb"),
 				  TRANSFORM_HDR_MAXRGB);
 				  TRANSFORM_HDR_MAXRGB);
+	obs_property_list_add_int(p, obs_module_text("HdrTonemap.SdrMaxrgb"),
+				  TRANSFORM_SDR_MAXRGB);
 	obs_property_set_modified_callback(p, transform_changed);
 	obs_property_set_modified_callback(p, transform_changed);
 
 
 	p = obs_properties_add_int(props, "sdr_white_level_nits",
 	p = obs_properties_add_int(props, "sdr_white_level_nits",
@@ -127,6 +145,14 @@ static obs_properties_t *hdr_tonemap_filter_properties(void *data)
 		props, "hdr_output_maximum_nits",
 		props, "hdr_output_maximum_nits",
 		obs_module_text("HdrTonemap.HdrOutputMaximum"), 5, 10000, 1);
 		obs_module_text("HdrTonemap.HdrOutputMaximum"), 5, 10000, 1);
 	obs_property_int_set_suffix(p, " nits");
 	obs_property_int_set_suffix(p, " nits");
+	p = obs_properties_add_int(
+		props, "sdr_input_maximum_nits",
+		obs_module_text("HdrTonemap.SdrInputMaximum"), 5, 10000, 1);
+	obs_property_int_set_suffix(p, " nits");
+	p = obs_properties_add_int(
+		props, "sdr_output_maximum_nits",
+		obs_module_text("HdrTonemap.SdrOutputMaximum"), 5, 10000, 1);
+	obs_property_int_set_suffix(p, " nits");
 
 
 	UNUSED_PARAMETER(data);
 	UNUSED_PARAMETER(data);
 	return props;
 	return props;
@@ -138,6 +164,8 @@ static void hdr_tonemap_filter_defaults(obs_data_t *settings)
 	obs_data_set_default_int(settings, "sdr_white_level_nits", 300);
 	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_input_maximum_nits", 4000);
 	obs_data_set_default_int(settings, "hdr_output_maximum_nits", 1000);
 	obs_data_set_default_int(settings, "hdr_output_maximum_nits", 1000);
+	obs_data_set_default_int(settings, "sdr_input_maximum_nits", 1000);
+	obs_data_set_default_int(settings, "sdr_output_maximum_nits", 300);
 }
 }
 
 
 static void hdr_tonemap_filter_render(void *data, gs_effect_t *effect)
 static void hdr_tonemap_filter_render(void *data, gs_effect_t *effect)
@@ -173,19 +201,26 @@ static void hdr_tonemap_filter_render(void *data, gs_effect_t *effect)
 			gs_effect_set_float(filter->param_multiplier,
 			gs_effect_set_float(filter->param_multiplier,
 					    multiplier);
 					    multiplier);
 			gs_effect_set_float(
 			gs_effect_set_float(
-				filter->param_hdr_input_maximum_nits,
-				filter->hdr_input_maximum_nits);
+				filter->param_input_maximum_nits,
+				(filter->transform == TRANSFORM_SDR_MAXRGB)
+					? filter->sdr_input_maximum_nits
+					: filter->hdr_input_maximum_nits);
 			gs_effect_set_float(
 			gs_effect_set_float(
-				filter->param_hdr_output_maximum_nits,
-				filter->hdr_output_maximum_nits);
+				filter->param_output_maximum_nits,
+				(filter->transform == TRANSFORM_SDR_MAXRGB)
+					? filter->sdr_output_maximum_nits
+					: filter->hdr_output_maximum_nits);
 
 
 			gs_blend_state_push();
 			gs_blend_state_push();
 			gs_blend_function(GS_BLEND_ONE, GS_BLEND_INVSRCALPHA);
 			gs_blend_function(GS_BLEND_ONE, GS_BLEND_INVSRCALPHA);
 
 
 			const char *const tech_name =
 			const char *const tech_name =
-				(filter->transform == TRANSFORM_HDR_MAXRGB)
-					? "MaxRGB"
-					: "Reinhard";
+				(filter->transform == TRANSFORM_SDR_REINHARD)
+					? "Reinhard"
+					: ((filter->transform ==
+					    TRANSFORM_HDR_MAXRGB)
+						   ? "MaxRGB"
+						   : "MaxRGBSDR");
 			obs_source_process_filter_tech_end(filter->context,
 			obs_source_process_filter_tech_end(filter->context,
 							   filter->effect, 0, 0,
 							   filter->effect, 0, 0,
 							   tech_name);
 							   tech_name);
@@ -217,7 +252,8 @@ hdr_tonemap_filter_get_color_space(void *data, size_t count,
 	enum gs_color_space space = source_space;
 	enum gs_color_space space = source_space;
 
 
 	if (source_space == GS_CS_709_EXTENDED || source_space == GS_CS_SRGB) {
 	if (source_space == GS_CS_709_EXTENDED || source_space == GS_CS_SRGB) {
-		if (filter->transform == TRANSFORM_SDR_REINHARD) {
+		if ((filter->transform == TRANSFORM_SDR_REINHARD) ||
+		    filter->transform == TRANSFORM_SDR_MAXRGB) {
 			space = GS_CS_SRGB;
 			space = GS_CS_SRGB;
 			for (size_t i = 0; i < count; ++i) {
 			for (size_t i = 0; i < count; ++i) {
 				if (preferred_spaces[i] != GS_CS_SRGB) {
 				if (preferred_spaces[i] != GS_CS_SRGB) {