增加新的处理脚本
This commit is contained in:
parent
5cb2875ba6
commit
12764a1a22
@ -1,6 +1,9 @@
|
|||||||
-- HUD Overlay Color Script for OBS (separate Create / Update buttons)
|
-- HUD Overlay Color Script for OBS (separate Create / Update buttons)
|
||||||
-- Green-screen style keying: keeps only the green symbols and removes all other colors (including black) + custom symbol color
|
-- Black-to-transparent keying: the black background becomes fully transparent and
|
||||||
-- Principle: greenness = G - max(R, B); green symbols are positive, black/gray/white backgrounds are ~0
|
-- symbol pixels are drawn with a pure preset color (default pure green).
|
||||||
|
-- Principle: subtract a black level (kills the codec noise floor), use max(R, G, B) as
|
||||||
|
-- alpha (x gain) so anti-aliased edges survive as partial alpha instead of being eroded,
|
||||||
|
-- then output the preset color x alpha (premultiplied).
|
||||||
-- Requires: obs-shaderfilter plugin (installed)
|
-- Requires: obs-shaderfilter plugin (installed)
|
||||||
-- Install: OBS -> Tools -> Scripts -> + -> select this file -> pick a color
|
-- Install: OBS -> Tools -> Scripts -> + -> select this file -> pick a color
|
||||||
-- "Create Scene" : builds the scene from scratch (deletes and recreates both media sources)
|
-- "Create Scene" : builds the scene from scratch (deletes and recreates both media sources)
|
||||||
@ -10,9 +13,9 @@ obs = obslua
|
|||||||
|
|
||||||
local SETTING_FRONT_PATH = "front_path"
|
local SETTING_FRONT_PATH = "front_path"
|
||||||
local SETTING_BACK_PATH = "back_path"
|
local SETTING_BACK_PATH = "back_path"
|
||||||
local SETTING_COLOR = "hud_color" -- preset color index
|
local SETTING_COLOR = "hud_color" -- preset color index (1 = keep original green)
|
||||||
local SETTING_KEY_LOW = "key_low"
|
local SETTING_BLACK_LEVEL = "black_level"
|
||||||
local SETTING_KEY_HIGH = "key_high"
|
local SETTING_GAIN = "alpha_gain"
|
||||||
local SETTING_OPACITY = "opacity"
|
local SETTING_OPACITY = "opacity"
|
||||||
|
|
||||||
local SCENE_NAME = "HUD Overlay Color"
|
local SCENE_NAME = "HUD Overlay Color"
|
||||||
@ -20,14 +23,14 @@ local FRONT_NAME = "HUD - Front Color"
|
|||||||
local BACK_NAME = "Center IG - Back Color"
|
local BACK_NAME = "Center IG - Back Color"
|
||||||
local FILTER_NAME = "HUD Colorize"
|
local FILTER_NAME = "HUD Colorize"
|
||||||
|
|
||||||
-- Preset colors (ordered from strongest to weakest contrast against a teal background)
|
-- Preset colors; index 1 is pure green (the original HUD color)
|
||||||
local COLOR_PRESETS = {
|
local COLOR_PRESETS = {
|
||||||
|
{ name = "Green (original HUD color)", r = 0.0, g = 1.0, b = 0.0 },
|
||||||
{ name = "Red (recommended, complementary to teal)", r = 1.0, g = 0.0, b = 0.0 },
|
{ name = "Red (recommended, complementary to teal)", r = 1.0, g = 0.0, b = 0.0 },
|
||||||
{ name = "Magenta", r = 1.0, g = 0.0, b = 1.0 },
|
{ name = "Magenta", r = 1.0, g = 0.0, b = 1.0 },
|
||||||
{ name = "Orange", r = 1.0, g = 0.5, b = 0.0 },
|
{ name = "Orange", r = 1.0, g = 0.5, b = 0.0 },
|
||||||
{ name = "Yellow", r = 1.0, g = 1.0, b = 0.0 },
|
{ name = "Yellow", r = 1.0, g = 1.0, b = 0.0 },
|
||||||
{ name = "White", r = 1.0, g = 1.0, b = 1.0 },
|
{ name = "White", r = 1.0, g = 1.0, b = 1.0 },
|
||||||
{ name = "Green (original color)", r = 0.0, g = 1.0, b = 0.0 },
|
|
||||||
{ name = "Bright green (neon green)", r = 0.25, g = 1.0, b = 0.1 },
|
{ name = "Bright green (neon green)", r = 0.25, g = 1.0, b = 0.1 },
|
||||||
{ name = "Black (strong contrast on light backgrounds)", r = 0.0, g = 0.0, b = 0.0 },
|
{ name = "Black (strong contrast on light backgrounds)", r = 0.0, g = 0.0, b = 0.0 },
|
||||||
}
|
}
|
||||||
@ -35,32 +38,33 @@ local COLOR_PRESETS = {
|
|||||||
local front_path = ""
|
local front_path = ""
|
||||||
local back_path = ""
|
local back_path = ""
|
||||||
local color_index = 1
|
local color_index = 1
|
||||||
local key_low = 0.02
|
local black_level = 0.055 -- 14/255, measured noise floor of the HUD capture
|
||||||
local key_high = 0.20
|
local alpha_gain = 1.35
|
||||||
local opacity = 1.0
|
local opacity = 1.0
|
||||||
|
|
||||||
local fit_retries = 0
|
local fit_retries = 0
|
||||||
|
|
||||||
-- Green-preserving keying + recolor shader:
|
-- Black-key shader:
|
||||||
-- greenness = G - max(R, B); only greenish pixels are positive
|
-- h = max(px - black_level, 0) -> drop the noise floor, pure black becomes 0
|
||||||
-- alpha = smoothstep(key_low, key_high, greenness); soft edges preserve the anti-aliased transition
|
-- k = max(h.r, h.g, h.b) -> brightness of the symbol at this pixel
|
||||||
-- Color is forced to the specified HUD color (premultiplied output)
|
-- a = clamp(k * alpha_gain) -> soft alpha; anti-aliased edges keep partial alpha
|
||||||
|
-- Output is premultiplied: preset color x alpha (symbol pixels are pure HUD color)
|
||||||
local SHADER_TEXT = [==[
|
local SHADER_TEXT = [==[
|
||||||
uniform float key_low <
|
uniform float black_level <
|
||||||
string label = "Key low";
|
string label = "Black level";
|
||||||
string widget_type = "slider";
|
string widget_type = "slider";
|
||||||
float minimum = 0.0;
|
float minimum = 0.0;
|
||||||
float maximum = 0.5;
|
float maximum = 0.3;
|
||||||
float step = 0.001;
|
float step = 0.001;
|
||||||
> = 0.02;
|
> = 0.055;
|
||||||
|
|
||||||
uniform float key_high <
|
uniform float alpha_gain <
|
||||||
string label = "Key high";
|
string label = "Alpha gain";
|
||||||
string widget_type = "slider";
|
string widget_type = "slider";
|
||||||
float minimum = 0.01;
|
float minimum = 0.5;
|
||||||
float maximum = 1.0;
|
float maximum = 4.0;
|
||||||
float step = 0.005;
|
float step = 0.01;
|
||||||
> = 0.20;
|
> = 1.35;
|
||||||
|
|
||||||
uniform float hud_r <
|
uniform float hud_r <
|
||||||
string label = "HUD color R";
|
string label = "HUD color R";
|
||||||
@ -68,7 +72,7 @@ uniform float hud_r <
|
|||||||
float minimum = 0.0;
|
float minimum = 0.0;
|
||||||
float maximum = 1.0;
|
float maximum = 1.0;
|
||||||
float step = 0.01;
|
float step = 0.01;
|
||||||
> = 1.0;
|
> = 0.0;
|
||||||
|
|
||||||
uniform float hud_g <
|
uniform float hud_g <
|
||||||
string label = "HUD color G";
|
string label = "HUD color G";
|
||||||
@ -76,7 +80,7 @@ uniform float hud_g <
|
|||||||
float minimum = 0.0;
|
float minimum = 0.0;
|
||||||
float maximum = 1.0;
|
float maximum = 1.0;
|
||||||
float step = 0.01;
|
float step = 0.01;
|
||||||
> = 0.0;
|
> = 1.0;
|
||||||
|
|
||||||
uniform float hud_b <
|
uniform float hud_b <
|
||||||
string label = "HUD color B";
|
string label = "HUD color B";
|
||||||
@ -101,13 +105,16 @@ float4 mainImage(VertData v_in) : TARGET
|
|||||||
// The OBS filter chain input is premultiplied; un-premultiply first (media sources have alpha=1, so usually no effect)
|
// The OBS filter chain input is premultiplied; un-premultiply first (media sources have alpha=1, so usually no effect)
|
||||||
px.rgb *= (px.a > 0.0) ? (1.0 / px.a) : 0.0;
|
px.rgb *= (px.a > 0.0) ? (1.0 / px.a) : 0.0;
|
||||||
|
|
||||||
// Greenness: only green symbols are positive; black/gray/white backgrounds are ~0
|
// Subtract the black level: pure black (and the codec noise floor) becomes exactly 0
|
||||||
float greenness = px.g - max(px.r, px.b);
|
float3 h = max(px.rgb - black_level, 0.0);
|
||||||
|
|
||||||
// Soft-threshold keying: fully transparent below key_low, fully opaque above key_high
|
// Brightness key: 0 for black, ~1.0 for pure green symbols
|
||||||
float a = smoothstep(key_low, max(key_high, key_low + 0.001), greenness) * opacity;
|
float k = max(h.r, max(h.g, h.b));
|
||||||
|
|
||||||
// Premultiplied output: specified color x alpha
|
// Soft alpha with gain: thin strokes become more solid without eroding their edges
|
||||||
|
float a = clamp(k * alpha_gain, 0.0, 1.0) * opacity;
|
||||||
|
|
||||||
|
// Premultiplied output: pure HUD color x alpha
|
||||||
return float4(float3(hud_r, hud_g, hud_b) * a, a);
|
return float4(float3(hud_r, hud_g, hud_b) * a, a);
|
||||||
}
|
}
|
||||||
]==]
|
]==]
|
||||||
@ -156,8 +163,8 @@ local function apply_key_filter(src)
|
|||||||
local fs = obs.obs_data_create()
|
local fs = obs.obs_data_create()
|
||||||
obs.obs_data_set_bool(fs, "from_file", false)
|
obs.obs_data_set_bool(fs, "from_file", false)
|
||||||
obs.obs_data_set_string(fs, "shader_text", SHADER_TEXT)
|
obs.obs_data_set_string(fs, "shader_text", SHADER_TEXT)
|
||||||
obs.obs_data_set_double(fs, "key_low", key_low)
|
obs.obs_data_set_double(fs, "black_level", black_level)
|
||||||
obs.obs_data_set_double(fs, "key_high", key_high)
|
obs.obs_data_set_double(fs, "alpha_gain", alpha_gain)
|
||||||
obs.obs_data_set_double(fs, "hud_r", preset.r)
|
obs.obs_data_set_double(fs, "hud_r", preset.r)
|
||||||
obs.obs_data_set_double(fs, "hud_g", preset.g)
|
obs.obs_data_set_double(fs, "hud_g", preset.g)
|
||||||
obs.obs_data_set_double(fs, "hud_b", preset.b)
|
obs.obs_data_set_double(fs, "hud_b", preset.b)
|
||||||
@ -339,18 +346,19 @@ end
|
|||||||
------------------------------------------------------------
|
------------------------------------------------------------
|
||||||
|
|
||||||
function script_description()
|
function script_description()
|
||||||
return "Green-screen style keying: keeps only the green symbols and removes all other colors (including black).\n" ..
|
return "Black-to-transparent keying: the black background becomes fully transparent and symbol pixels are drawn with a pure preset color (default pure green).\n" ..
|
||||||
"Principle: greenness = G - max(R, B), which is less likely to pick up background noise than the old 'use the G channel as alpha' approach.\n" ..
|
"alpha = max(R, G, B) after subtracting a black level, so anti-aliased edges survive as partial alpha instead of being eroded.\n" ..
|
||||||
|
"Compared with the old greenness keying, thin lines and small text no longer fade or break apart.\n" ..
|
||||||
"\"Create Scene\": builds the scene from scratch (deletes and recreates both media sources).\n" ..
|
"\"Create Scene\": builds the scene from scratch (deletes and recreates both media sources).\n" ..
|
||||||
"\"Update Scene\": updates the existing scene in place (video paths, symbol color, keying parameters).\n" ..
|
"\"Update Scene\": updates the existing scene in place (video paths, symbol color, keying parameters).\n" ..
|
||||||
"The filter panel also has HUD color R/G/B sliders for fine-tuning to any color."
|
"The filter panel also has Black level / Alpha gain / HUD color R/G/B sliders for fine-tuning."
|
||||||
end
|
end
|
||||||
|
|
||||||
function script_properties()
|
function script_properties()
|
||||||
local props = obs.obs_properties_create()
|
local props = obs.obs_properties_create()
|
||||||
|
|
||||||
obs.obs_properties_add_path(props, SETTING_FRONT_PATH,
|
obs.obs_properties_add_path(props, SETTING_FRONT_PATH,
|
||||||
"Foreground video (HUD, green symbols)", obs.OBS_PATH_FILE,
|
"Foreground video (HUD, green symbols on black)", obs.OBS_PATH_FILE,
|
||||||
"Video files (*.avi *.mp4 *.mov *.mkv *.webm);;All files (*.*)", "")
|
"Video files (*.avi *.mp4 *.mov *.mkv *.webm);;All files (*.*)", "")
|
||||||
|
|
||||||
obs.obs_properties_add_path(props, SETTING_BACK_PATH,
|
obs.obs_properties_add_path(props, SETTING_BACK_PATH,
|
||||||
@ -363,10 +371,10 @@ function script_properties()
|
|||||||
obs.obs_property_list_add_int(clist, c.name, i)
|
obs.obs_property_list_add_int(clist, c.name, i)
|
||||||
end
|
end
|
||||||
|
|
||||||
obs.obs_properties_add_float_slider(props, SETTING_KEY_LOW,
|
obs.obs_properties_add_float_slider(props, SETTING_BLACK_LEVEL,
|
||||||
"Green lower bound (increase if noise/color artifacts remain)", 0.0, 0.5, 0.001)
|
"Black level (increase if a dark halo/noise remains around the symbols)", 0.0, 0.3, 0.001)
|
||||||
obs.obs_properties_add_float_slider(props, SETTING_KEY_HIGH,
|
obs.obs_properties_add_float_slider(props, SETTING_GAIN,
|
||||||
"Green upper bound (decrease if thin lines fade or edges get too soft)", 0.01, 1.0, 0.005)
|
"Alpha gain (increase if the symbols look too transparent over bright sky)", 0.5, 4.0, 0.01)
|
||||||
obs.obs_properties_add_float_slider(props, SETTING_OPACITY,
|
obs.obs_properties_add_float_slider(props, SETTING_OPACITY,
|
||||||
"Overall opacity", 0.0, 1.0, 0.01)
|
"Overall opacity", 0.0, 1.0, 0.01)
|
||||||
|
|
||||||
@ -382,8 +390,8 @@ function script_defaults(settings)
|
|||||||
obs.obs_data_set_default_string(settings, SETTING_BACK_PATH,
|
obs.obs_data_set_default_string(settings, SETTING_BACK_PATH,
|
||||||
"D:\\Test Pictures + Video\\Center IG - Back Videos.avi")
|
"D:\\Test Pictures + Video\\Center IG - Back Videos.avi")
|
||||||
obs.obs_data_set_default_int(settings, SETTING_COLOR, 1)
|
obs.obs_data_set_default_int(settings, SETTING_COLOR, 1)
|
||||||
obs.obs_data_set_default_double(settings, SETTING_KEY_LOW, 0.02)
|
obs.obs_data_set_default_double(settings, SETTING_BLACK_LEVEL, 0.055)
|
||||||
obs.obs_data_set_default_double(settings, SETTING_KEY_HIGH, 0.20)
|
obs.obs_data_set_default_double(settings, SETTING_GAIN, 1.35)
|
||||||
obs.obs_data_set_default_double(settings, SETTING_OPACITY, 1.0)
|
obs.obs_data_set_default_double(settings, SETTING_OPACITY, 1.0)
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -391,7 +399,7 @@ function script_update(settings)
|
|||||||
front_path = obs.obs_data_get_string(settings, SETTING_FRONT_PATH)
|
front_path = obs.obs_data_get_string(settings, SETTING_FRONT_PATH)
|
||||||
back_path = obs.obs_data_get_string(settings, SETTING_BACK_PATH)
|
back_path = obs.obs_data_get_string(settings, SETTING_BACK_PATH)
|
||||||
color_index = obs.obs_data_get_int(settings, SETTING_COLOR)
|
color_index = obs.obs_data_get_int(settings, SETTING_COLOR)
|
||||||
key_low = obs.obs_data_get_double(settings, SETTING_KEY_LOW)
|
black_level = obs.obs_data_get_double(settings, SETTING_BLACK_LEVEL)
|
||||||
key_high = obs.obs_data_get_double(settings, SETTING_KEY_HIGH)
|
alpha_gain = obs.obs_data_get_double(settings, SETTING_GAIN)
|
||||||
opacity = obs.obs_data_get_double(settings, SETTING_OPACITY)
|
opacity = obs.obs_data_get_double(settings, SETTING_OPACITY)
|
||||||
end
|
end
|
||||||
|
|||||||
458
deploy/HUD_Overlay_Outline.lua
Normal file
458
deploy/HUD_Overlay_Outline.lua
Normal file
@ -0,0 +1,458 @@
|
|||||||
|
-- HUD Overlay Outline Script for OBS (separate Create / Update buttons)
|
||||||
|
-- New approach: detect the green symbols first, then draw them in pure green with a
|
||||||
|
-- dark green outline, so they stay readable over ANY background (bright sky, green terrain, clouds).
|
||||||
|
-- Principle:
|
||||||
|
-- 1. symbol detection: subtract a black level, alpha = max(R, G, B) x gain,
|
||||||
|
-- multiplied by a greenness factor (G - max(R, B)) to reject gray/white noise
|
||||||
|
-- 2. outline: sample the symbol alpha in a ring around each pixel; pixels that are
|
||||||
|
-- near a symbol but not part of it get a dark outline underneath the green fill
|
||||||
|
-- 3. premultiplied output: pure green x symbol alpha + outline color x outline alpha
|
||||||
|
-- Requires: obs-shaderfilter plugin (installed)
|
||||||
|
-- Install: OBS -> Tools -> Scripts -> + -> select this file
|
||||||
|
-- "Create Scene" : builds the scene from scratch (deletes and recreates both media sources)
|
||||||
|
-- "Update Scene" : updates the existing scene in place (video paths, color, keying parameters)
|
||||||
|
|
||||||
|
obs = obslua
|
||||||
|
|
||||||
|
local SETTING_FRONT_PATH = "front_path"
|
||||||
|
local SETTING_BACK_PATH = "back_path"
|
||||||
|
local SETTING_BLACK_LEVEL = "black_level"
|
||||||
|
local SETTING_GAIN = "alpha_gain"
|
||||||
|
local SETTING_GREEN_THRESH = "green_thresh"
|
||||||
|
local SETTING_OUT_WIDTH = "outline_width"
|
||||||
|
local SETTING_OUT_ALPHA = "outline_alpha"
|
||||||
|
local SETTING_OPACITY = "opacity"
|
||||||
|
|
||||||
|
local SCENE_NAME = "HUD Overlay Outline"
|
||||||
|
local FRONT_NAME = "HUD - Front Outline"
|
||||||
|
local BACK_NAME = "Center IG - Back Outline"
|
||||||
|
local FILTER_NAME = "HUD Outline"
|
||||||
|
|
||||||
|
local front_path = ""
|
||||||
|
local back_path = ""
|
||||||
|
local black_level = 0.055 -- 14/255, measured noise floor of the HUD capture
|
||||||
|
local alpha_gain = 1.35
|
||||||
|
local green_thresh = 0.10 -- greenness (G - max(R,B)) needed to count as a green symbol
|
||||||
|
local outline_width = 2.0 -- outline thickness in source pixels
|
||||||
|
local outline_alpha = 0.9
|
||||||
|
local opacity = 1.0
|
||||||
|
|
||||||
|
local fit_retries = 0
|
||||||
|
|
||||||
|
-- Symbol-detect + outline shader:
|
||||||
|
-- sym_alpha(uv): black level subtraction -> brightness key x gain -> greenness gate
|
||||||
|
-- main: symbol = pure green x sym_alpha; ring of neighbor sym_alpha forms the outline
|
||||||
|
local SHADER_TEXT = [==[
|
||||||
|
uniform float black_level <
|
||||||
|
string label = "Black level";
|
||||||
|
string widget_type = "slider";
|
||||||
|
float minimum = 0.0;
|
||||||
|
float maximum = 0.3;
|
||||||
|
float step = 0.001;
|
||||||
|
> = 0.055;
|
||||||
|
|
||||||
|
uniform float alpha_gain <
|
||||||
|
string label = "Alpha gain";
|
||||||
|
string widget_type = "slider";
|
||||||
|
float minimum = 0.5;
|
||||||
|
float maximum = 4.0;
|
||||||
|
float step = 0.01;
|
||||||
|
> = 1.35;
|
||||||
|
|
||||||
|
uniform float green_thresh <
|
||||||
|
string label = "Green threshold";
|
||||||
|
string widget_type = "slider";
|
||||||
|
float minimum = 0.01;
|
||||||
|
float maximum = 0.5;
|
||||||
|
float step = 0.005;
|
||||||
|
> = 0.10;
|
||||||
|
|
||||||
|
uniform float outline_width <
|
||||||
|
string label = "Outline width (px)";
|
||||||
|
string widget_type = "slider";
|
||||||
|
float minimum = 0.0;
|
||||||
|
float maximum = 8.0;
|
||||||
|
float step = 0.5;
|
||||||
|
> = 2.0;
|
||||||
|
|
||||||
|
uniform float outline_alpha <
|
||||||
|
string label = "Outline strength";
|
||||||
|
string widget_type = "slider";
|
||||||
|
float minimum = 0.0;
|
||||||
|
float maximum = 1.0;
|
||||||
|
float step = 0.01;
|
||||||
|
> = 0.9;
|
||||||
|
|
||||||
|
uniform float hud_r <
|
||||||
|
string label = "Symbol color R";
|
||||||
|
string widget_type = "slider";
|
||||||
|
float minimum = 0.0;
|
||||||
|
float maximum = 1.0;
|
||||||
|
float step = 0.01;
|
||||||
|
> = 0.0;
|
||||||
|
|
||||||
|
uniform float hud_g <
|
||||||
|
string label = "Symbol color G";
|
||||||
|
string widget_type = "slider";
|
||||||
|
float minimum = 0.0;
|
||||||
|
float maximum = 1.0;
|
||||||
|
float step = 0.01;
|
||||||
|
> = 1.0;
|
||||||
|
|
||||||
|
uniform float hud_b <
|
||||||
|
string label = "Symbol color B";
|
||||||
|
string widget_type = "slider";
|
||||||
|
float minimum = 0.0;
|
||||||
|
float maximum = 1.0;
|
||||||
|
float step = 0.01;
|
||||||
|
> = 0.0;
|
||||||
|
|
||||||
|
uniform float outline_brightness <
|
||||||
|
string label = "Outline brightness";
|
||||||
|
string widget_type = "slider";
|
||||||
|
float minimum = 0.0;
|
||||||
|
float maximum = 1.0;
|
||||||
|
float step = 0.01;
|
||||||
|
> = 0.35;
|
||||||
|
|
||||||
|
uniform float opacity <
|
||||||
|
string label = "Opacity";
|
||||||
|
string widget_type = "slider";
|
||||||
|
float minimum = 0.0;
|
||||||
|
float maximum = 1.0;
|
||||||
|
float step = 0.01;
|
||||||
|
> = 1.0;
|
||||||
|
|
||||||
|
// Symbol alpha at a given UV: 1 = solid green symbol, 0 = background/noise
|
||||||
|
float sym_alpha(float2 uv)
|
||||||
|
{
|
||||||
|
float4 px = image.Sample(textureSampler, uv);
|
||||||
|
px.rgb *= (px.a > 0.0) ? (1.0 / px.a) : 0.0;
|
||||||
|
|
||||||
|
// drop the codec noise floor; pure black becomes exactly 0
|
||||||
|
float3 h = max(px.rgb - black_level, 0.0);
|
||||||
|
|
||||||
|
// brightness key
|
||||||
|
float k = max(h.r, max(h.g, h.b));
|
||||||
|
|
||||||
|
// greenness gate: green symbols pass, gray/white noise is rejected
|
||||||
|
float gn = h.g - max(h.r, h.b);
|
||||||
|
float gf = clamp(gn / green_thresh, 0.0, 1.0);
|
||||||
|
|
||||||
|
return clamp(k * alpha_gain, 0.0, 1.0) * gf;
|
||||||
|
}
|
||||||
|
|
||||||
|
float4 mainImage(VertData v_in) : TARGET
|
||||||
|
{
|
||||||
|
float a_sym = sym_alpha(v_in.uv);
|
||||||
|
|
||||||
|
// Ring of 8 neighbor taps at outline_width distance -> dilated symbol alpha
|
||||||
|
float2 d = uv_pixel_interval * outline_width;
|
||||||
|
float2 dg = d * 0.7071;
|
||||||
|
float ring = 0.0;
|
||||||
|
ring = max(ring, sym_alpha(v_in.uv + float2( d.x, 0.0)));
|
||||||
|
ring = max(ring, sym_alpha(v_in.uv + float2(-d.x, 0.0)));
|
||||||
|
ring = max(ring, sym_alpha(v_in.uv + float2( 0.0, d.y)));
|
||||||
|
ring = max(ring, sym_alpha(v_in.uv + float2( 0.0, -d.y)));
|
||||||
|
ring = max(ring, sym_alpha(v_in.uv + float2( dg.x, dg.y)));
|
||||||
|
ring = max(ring, sym_alpha(v_in.uv + float2(-dg.x, dg.y)));
|
||||||
|
ring = max(ring, sym_alpha(v_in.uv + float2( dg.x, -dg.y)));
|
||||||
|
ring = max(ring, sym_alpha(v_in.uv + float2(-dg.x, -dg.y)));
|
||||||
|
|
||||||
|
// Outline only where the symbol itself is not already opaque
|
||||||
|
float a_out = min(ring * outline_alpha, 1.0 - a_sym);
|
||||||
|
|
||||||
|
float a_sym_o = a_sym * opacity;
|
||||||
|
float a_out_o = a_out * opacity;
|
||||||
|
|
||||||
|
// Premultiplied: pure symbol color over a dimmer pure-green outline
|
||||||
|
float3 rgb = float3(hud_r, hud_g, hud_b) * a_sym_o
|
||||||
|
+ float3(hud_r, hud_g, hud_b) * outline_brightness * a_out_o;
|
||||||
|
return float4(rgb, a_sym_o + a_out_o);
|
||||||
|
}
|
||||||
|
]==]
|
||||||
|
|
||||||
|
------------------------------------------------------------
|
||||||
|
-- Utility functions
|
||||||
|
------------------------------------------------------------
|
||||||
|
|
||||||
|
local function remove_source_by_name(name)
|
||||||
|
local s = obs.obs_get_source_by_name(name)
|
||||||
|
if s ~= nil then
|
||||||
|
obs.obs_source_remove(s)
|
||||||
|
obs.obs_source_release(s)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function create_media_source(name, path)
|
||||||
|
local s = obs.obs_data_create()
|
||||||
|
obs.obs_data_set_string(s, "local_file", path)
|
||||||
|
obs.obs_data_set_bool(s, "is_local_file", true)
|
||||||
|
obs.obs_data_set_bool(s, "looping", true)
|
||||||
|
obs.obs_data_set_bool(s, "restart_on_activate", true)
|
||||||
|
obs.obs_data_set_bool(s, "close_when_inactive", false)
|
||||||
|
local src = obs.obs_source_create("ffmpeg_source", name, s, nil)
|
||||||
|
obs.obs_data_release(s)
|
||||||
|
return src
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Point an existing media source at a new file without recreating it
|
||||||
|
local function update_media_source_path(src, path)
|
||||||
|
local s = obs.obs_source_get_settings(src)
|
||||||
|
obs.obs_data_set_string(s, "local_file", path)
|
||||||
|
obs.obs_source_update(src, s)
|
||||||
|
obs.obs_data_release(s)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function apply_key_filter(src)
|
||||||
|
local old = obs.obs_source_get_filter_by_name(src, FILTER_NAME)
|
||||||
|
if old ~= nil then
|
||||||
|
obs.obs_source_filter_remove(src, old)
|
||||||
|
obs.obs_source_release(old)
|
||||||
|
end
|
||||||
|
|
||||||
|
local fs = obs.obs_data_create()
|
||||||
|
obs.obs_data_set_bool(fs, "from_file", false)
|
||||||
|
obs.obs_data_set_string(fs, "shader_text", SHADER_TEXT)
|
||||||
|
obs.obs_data_set_double(fs, "black_level", black_level)
|
||||||
|
obs.obs_data_set_double(fs, "alpha_gain", alpha_gain)
|
||||||
|
obs.obs_data_set_double(fs, "green_thresh", green_thresh)
|
||||||
|
obs.obs_data_set_double(fs, "outline_width", outline_width)
|
||||||
|
obs.obs_data_set_double(fs, "outline_alpha", outline_alpha)
|
||||||
|
obs.obs_data_set_double(fs, "opacity", opacity)
|
||||||
|
|
||||||
|
-- First check whether the obs-shaderfilter plugin is loaded
|
||||||
|
if obs.obs_source_get_display_name("shader_filter") == nil then
|
||||||
|
obs.obs_data_release(fs)
|
||||||
|
obs.script_log(obs.LOG_WARNING,
|
||||||
|
"HUD Overlay Outline: obs-shaderfilter plugin not detected; keying was not applied. Please fully quit OBS (including the system tray) and restart it.")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local f = obs.obs_source_create("shader_filter", FILTER_NAME, fs, nil)
|
||||||
|
obs.obs_data_release(fs)
|
||||||
|
|
||||||
|
if f ~= nil then
|
||||||
|
obs.obs_source_filter_add(src, f)
|
||||||
|
obs.obs_source_release(f)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function fit_items_to_canvas()
|
||||||
|
local vi = obs.obs_video_info()
|
||||||
|
if obs.obs_get_video_info(vi) == false then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
local cw, ch = vi.base_width, vi.base_height
|
||||||
|
|
||||||
|
local scene_src = obs.obs_get_source_by_name(SCENE_NAME)
|
||||||
|
if scene_src == nil then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
local scene = obs.obs_scene_from_source(scene_src)
|
||||||
|
local items = obs.obs_scene_enum_items(scene)
|
||||||
|
local all_ready = true
|
||||||
|
|
||||||
|
for _, item in ipairs(items) do
|
||||||
|
local s = obs.obs_sceneitem_get_source(item)
|
||||||
|
local w = obs.obs_source_get_width(s)
|
||||||
|
local h = obs.obs_source_get_height(s)
|
||||||
|
if w > 0 and h > 0 then
|
||||||
|
local scale = math.min(cw / w, ch / h)
|
||||||
|
local sc = obs.vec2()
|
||||||
|
sc.x = scale
|
||||||
|
sc.y = scale
|
||||||
|
obs.obs_sceneitem_set_scale(item, sc)
|
||||||
|
local pos = obs.vec2()
|
||||||
|
pos.x = (cw - w * scale) / 2
|
||||||
|
pos.y = (ch - h * scale) / 2
|
||||||
|
obs.obs_sceneitem_set_pos(item, pos)
|
||||||
|
-- Use Lanczos when upscaling line-art footage; sharper than the default bilinear
|
||||||
|
if obs.obs_sceneitem_set_scale_filter ~= nil then
|
||||||
|
obs.obs_sceneitem_set_scale_filter(item, obs.OBS_SCALE_LANCZOS or 4)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
all_ready = false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
obs.sceneitem_list_release(items)
|
||||||
|
obs.obs_source_release(scene_src)
|
||||||
|
return all_ready
|
||||||
|
end
|
||||||
|
|
||||||
|
local function fit_retry()
|
||||||
|
fit_retries = fit_retries - 1
|
||||||
|
if fit_items_to_canvas() or fit_retries <= 0 then
|
||||||
|
obs.timer_remove(fit_retry)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function schedule_fit()
|
||||||
|
fit_retries = 10
|
||||||
|
obs.timer_remove(fit_retry)
|
||||||
|
obs.timer_add(fit_retry, 500)
|
||||||
|
end
|
||||||
|
|
||||||
|
------------------------------------------------------------
|
||||||
|
-- Create scene (from scratch)
|
||||||
|
------------------------------------------------------------
|
||||||
|
|
||||||
|
local function create_scene()
|
||||||
|
if front_path == "" or back_path == "" then
|
||||||
|
obs.script_log(obs.LOG_WARNING, "HUD Overlay Outline: please set the foreground and background video paths first")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local scene = nil
|
||||||
|
local scene_src = obs.obs_get_source_by_name(SCENE_NAME)
|
||||||
|
if scene_src ~= nil then
|
||||||
|
scene = obs.obs_scene_from_source(scene_src)
|
||||||
|
else
|
||||||
|
scene = obs.obs_scene_create(SCENE_NAME)
|
||||||
|
scene_src = obs.obs_scene_get_source(scene)
|
||||||
|
end
|
||||||
|
|
||||||
|
remove_source_by_name(FRONT_NAME)
|
||||||
|
remove_source_by_name(BACK_NAME)
|
||||||
|
|
||||||
|
local back = create_media_source(BACK_NAME, back_path)
|
||||||
|
obs.obs_scene_add(scene, back)
|
||||||
|
|
||||||
|
local front = create_media_source(FRONT_NAME, front_path)
|
||||||
|
apply_key_filter(front)
|
||||||
|
obs.obs_scene_add(scene, front)
|
||||||
|
|
||||||
|
obs.obs_source_release(back)
|
||||||
|
obs.obs_source_release(front)
|
||||||
|
|
||||||
|
obs.obs_frontend_set_current_scene(scene_src)
|
||||||
|
obs.obs_source_release(scene_src)
|
||||||
|
|
||||||
|
schedule_fit()
|
||||||
|
|
||||||
|
obs.script_log(obs.LOG_INFO, "HUD Overlay Outline: scene created")
|
||||||
|
end
|
||||||
|
|
||||||
|
------------------------------------------------------------
|
||||||
|
-- Update scene (in place, without recreating sources)
|
||||||
|
------------------------------------------------------------
|
||||||
|
|
||||||
|
local function update_scene()
|
||||||
|
if front_path == "" or back_path == "" then
|
||||||
|
obs.script_log(obs.LOG_WARNING, "HUD Overlay Outline: please set the foreground and background video paths first")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local scene_src = obs.obs_get_source_by_name(SCENE_NAME)
|
||||||
|
if scene_src == nil then
|
||||||
|
obs.script_log(obs.LOG_WARNING,
|
||||||
|
"HUD Overlay Outline: scene \"" .. SCENE_NAME .. "\" does not exist yet; please use \"Create Scene\" first")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local back = obs.obs_get_source_by_name(BACK_NAME)
|
||||||
|
local front = obs.obs_get_source_by_name(FRONT_NAME)
|
||||||
|
if back == nil or front == nil then
|
||||||
|
if back ~= nil then obs.obs_source_release(back) end
|
||||||
|
if front ~= nil then obs.obs_source_release(front) end
|
||||||
|
obs.obs_source_release(scene_src)
|
||||||
|
obs.script_log(obs.LOG_WARNING,
|
||||||
|
"HUD Overlay Outline: media sources are missing; please use \"Create Scene\" to rebuild the scene")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Update video files in place
|
||||||
|
update_media_source_path(back, back_path)
|
||||||
|
update_media_source_path(front, front_path)
|
||||||
|
|
||||||
|
-- Re-apply the keying filter with the current keying parameters
|
||||||
|
apply_key_filter(front)
|
||||||
|
|
||||||
|
obs.obs_source_release(back)
|
||||||
|
obs.obs_source_release(front)
|
||||||
|
|
||||||
|
obs.obs_frontend_set_current_scene(scene_src)
|
||||||
|
obs.obs_source_release(scene_src)
|
||||||
|
|
||||||
|
schedule_fit()
|
||||||
|
|
||||||
|
obs.script_log(obs.LOG_INFO, "HUD Overlay Outline: scene updated")
|
||||||
|
end
|
||||||
|
|
||||||
|
local function on_create_clicked(props, p)
|
||||||
|
create_scene()
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
local function on_update_clicked(props, p)
|
||||||
|
update_scene()
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
------------------------------------------------------------
|
||||||
|
-- Script UI and settings
|
||||||
|
------------------------------------------------------------
|
||||||
|
|
||||||
|
function script_description()
|
||||||
|
return "Detects the green HUD symbols, then draws them in pure green with a dark green outline,\n" ..
|
||||||
|
"so they stay readable over any background (bright sky, green terrain, clouds).\n" ..
|
||||||
|
"Symbol detection: alpha = max(R, G, B) x gain after black-level subtraction, gated by greenness G - max(R, B) to reject noise.\n" ..
|
||||||
|
"\"Create Scene\": builds the scene from scratch (deletes and recreates both media sources).\n" ..
|
||||||
|
"\"Update Scene\": updates the existing scene in place (video paths, keying parameters).\n" ..
|
||||||
|
"The filter panel has sliders for symbol color, outline width, strength and brightness."
|
||||||
|
end
|
||||||
|
|
||||||
|
function script_properties()
|
||||||
|
local props = obs.obs_properties_create()
|
||||||
|
|
||||||
|
obs.obs_properties_add_path(props, SETTING_FRONT_PATH,
|
||||||
|
"Foreground video (HUD, green symbols on black)", obs.OBS_PATH_FILE,
|
||||||
|
"Video files (*.avi *.mp4 *.mov *.mkv *.webm);;All files (*.*)", "")
|
||||||
|
|
||||||
|
obs.obs_properties_add_path(props, SETTING_BACK_PATH,
|
||||||
|
"Background video (bottom layer)", obs.OBS_PATH_FILE,
|
||||||
|
"Video files (*.avi *.mp4 *.mov *.mkv *.webm);;All files (*.*)", "")
|
||||||
|
|
||||||
|
obs.obs_properties_add_float_slider(props, SETTING_BLACK_LEVEL,
|
||||||
|
"Black level (increase if a dark halo/noise remains)", 0.0, 0.3, 0.001)
|
||||||
|
obs.obs_properties_add_float_slider(props, SETTING_GAIN,
|
||||||
|
"Alpha gain (increase if symbols look too transparent)", 0.5, 4.0, 0.01)
|
||||||
|
obs.obs_properties_add_float_slider(props, SETTING_GREEN_THRESH,
|
||||||
|
"Green threshold (increase if non-green noise appears)", 0.01, 0.5, 0.005)
|
||||||
|
obs.obs_properties_add_float_slider(props, SETTING_OUT_WIDTH,
|
||||||
|
"Outline width (px)", 0.0, 8.0, 0.5)
|
||||||
|
obs.obs_properties_add_float_slider(props, SETTING_OUT_ALPHA,
|
||||||
|
"Outline strength", 0.0, 1.0, 0.01)
|
||||||
|
obs.obs_properties_add_float_slider(props, SETTING_OPACITY,
|
||||||
|
"Overall opacity", 0.0, 1.0, 0.01)
|
||||||
|
|
||||||
|
obs.obs_properties_add_button(props, "create_btn", "Create Scene", on_create_clicked)
|
||||||
|
obs.obs_properties_add_button(props, "update_btn", "Update Scene", on_update_clicked)
|
||||||
|
|
||||||
|
return props
|
||||||
|
end
|
||||||
|
|
||||||
|
function script_defaults(settings)
|
||||||
|
obs.obs_data_set_default_string(settings, SETTING_FRONT_PATH,
|
||||||
|
"D:\\Test Pictures + Video\\HUD - Front Video.avi")
|
||||||
|
obs.obs_data_set_default_string(settings, SETTING_BACK_PATH,
|
||||||
|
"D:\\Test Pictures + Video\\Center IG - Back Videos.avi")
|
||||||
|
obs.obs_data_set_default_double(settings, SETTING_BLACK_LEVEL, 0.055)
|
||||||
|
obs.obs_data_set_default_double(settings, SETTING_GAIN, 1.35)
|
||||||
|
obs.obs_data_set_default_double(settings, SETTING_GREEN_THRESH, 0.10)
|
||||||
|
obs.obs_data_set_default_double(settings, SETTING_OUT_WIDTH, 2.0)
|
||||||
|
obs.obs_data_set_default_double(settings, SETTING_OUT_ALPHA, 0.9)
|
||||||
|
obs.obs_data_set_default_double(settings, SETTING_OPACITY, 1.0)
|
||||||
|
end
|
||||||
|
|
||||||
|
function script_update(settings)
|
||||||
|
front_path = obs.obs_data_get_string(settings, SETTING_FRONT_PATH)
|
||||||
|
back_path = obs.obs_data_get_string(settings, SETTING_BACK_PATH)
|
||||||
|
black_level = obs.obs_data_get_double(settings, SETTING_BLACK_LEVEL)
|
||||||
|
alpha_gain = obs.obs_data_get_double(settings, SETTING_GAIN)
|
||||||
|
green_thresh = obs.obs_data_get_double(settings, SETTING_GREEN_THRESH)
|
||||||
|
outline_width = obs.obs_data_get_double(settings, SETTING_OUT_WIDTH)
|
||||||
|
outline_alpha = obs.obs_data_get_double(settings, SETTING_OUT_ALPHA)
|
||||||
|
opacity = obs.obs_data_get_double(settings, SETTING_OPACITY)
|
||||||
|
end
|
||||||
Loading…
x
Reference in New Issue
Block a user