Commands/Shaders/Get-OBSClockDigitalNixieShader.ps1

function Get-OBSClockDigitalNixieShader {

[Alias('Set-OBSClockDigitalNixieShader','Add-OBSClockDigitalNixieShader')]
param(
# Set the current_time_ms of OBSClockDigitalNixieShader
[Alias('current_time_ms')]
[ComponentModel.DefaultBindingProperty('current_time_ms')]
[Int32]
$CurrentTimeMs,
# Set the current_time_sec of OBSClockDigitalNixieShader
[Alias('current_time_sec')]
[ComponentModel.DefaultBindingProperty('current_time_sec')]
[Int32]
$CurrentTimeSec,
# Set the current_time_min of OBSClockDigitalNixieShader
[Alias('current_time_min')]
[ComponentModel.DefaultBindingProperty('current_time_min')]
[Int32]
$CurrentTimeMin,
# Set the current_time_hour of OBSClockDigitalNixieShader
[Alias('current_time_hour')]
[ComponentModel.DefaultBindingProperty('current_time_hour')]
[Int32]
$CurrentTimeHour,
# Set the timeMode of OBSClockDigitalNixieShader
[ComponentModel.DefaultBindingProperty('timeMode')]
[Int32]
$TimeMode,
# Set the offsetHours of OBSClockDigitalNixieShader
[ComponentModel.DefaultBindingProperty('offsetHours')]
[Int32]
$OffsetHours,
# Set the offsetSeconds of OBSClockDigitalNixieShader
[ComponentModel.DefaultBindingProperty('offsetSeconds')]
[Int32]
$OffsetSeconds,
# Set the corecolor of OBSClockDigitalNixieShader
[ComponentModel.DefaultBindingProperty('corecolor')]
[Single[]]
$Corecolor,
# Set the halocolor of OBSClockDigitalNixieShader
[ComponentModel.DefaultBindingProperty('halocolor')]
[Single[]]
$Halocolor,
# Set the flarecolor of OBSClockDigitalNixieShader
[ComponentModel.DefaultBindingProperty('flarecolor')]
[Single[]]
$Flarecolor,
# Set the anodecolor of OBSClockDigitalNixieShader
[ComponentModel.DefaultBindingProperty('anodecolor')]
[Single[]]
$Anodecolor,
# Set the anodehighlightscolor of OBSClockDigitalNixieShader
[ComponentModel.DefaultBindingProperty('anodehighlightscolor')]
[Single[]]
$Anodehighlightscolor,
# The name of the source. This must be provided when adding an item for the first time
[Parameter(ValueFromPipelineByPropertyName)]
[Alias('SceneItemName')]
[String]
$SourceName,
# The name of the filter. If this is not provided, this will default to the shader name.
[Parameter(ValueFromPipelineByPropertyName)]
[String]
$FilterName,
# The inline value of the shader. This will normally be provided as a default parameter, based off of the name.
[Alias('ShaderContent')]
[String]
$ShaderText,
# If set, will force the recreation of a shader that already exists
[Management.Automation.SwitchParameter]
$Force,
# If set, will pass thru the commands that would be sent to OBS (these can be sent at any time with Send-OBS)
[Management.Automation.SwitchParameter]
$PassThru,
# If set, will not wait for a response from OBS (this will be faster, but will not return anything)
[Management.Automation.SwitchParameter]
$NoResponse,
# If set, use the shader elapsed time, instead of the OBS system elapsed time
[ComponentModel.DefaultBindingProperty('use_shader_elapsed_time')]
[Management.Automation.SwitchParameter]
$UseShaderTime
)


process {
$shaderName = 'clock_digital_nixie'
$ShaderNoun = 'OBSClockDigitalNixieShader'
if (-not $psBoundParameters['ShaderText']) {    
    $psBoundParameters['ShaderText'] = $ShaderText = '
//based on https://www.shadertoy.com/view/fsBcRm
uniform int current_time_ms;
uniform int current_time_sec;
uniform int current_time_min;
uniform int current_time_hour;
uniform int timeMode<
  string label = "Time mode";
  string widget_type = "select";
  int option_0_value = 0;
  string option_0_label = "Time";
  int option_1_value = 1;
  string option_1_label = "Enable duration";
  int option_2_value = 2;
  string option_2_label = "Active duration";
  int option_3_value = 3;
  string option_3_label = "Show duration";
  int option_4_value = 4;
  string option_4_label = "Load duration";
> = 0;

uniform int offsetHours = 0;
uniform int offsetSeconds = 0;

// Colors as named variables, if you want to tweak them
uniform float3 corecolor = {1.0,0.7,0.0};
uniform float3 halocolor = {1.0,0.5,0.0};
uniform float3 flarecolor = {1.0,0.3,0.0};
uniform float3 anodecolor = {0.2,0.1,0.1};
uniform float3 anodehighlightscolor = {1.0,0.5,0.0};

#ifndef OPENGL
#define mod(x,y) (x - y * floor(x / y))
#define lessThan(a,b) (a < b)
#define greaterThan(a,b) (a > b)
#endif

// psrdnoise (c) Stefan Gustavson and Ian McEwan,
// ver. 2021-12-02, published under the MIT license:
// https://github.com/stegu/psrdnoise/
float psrdnoise(float2 x, float2 period, float alpha, out float2 gradient)
{
    float2 uv = float2(x.x+x.y*0.5, x.y);
    float2 i0 = floor(uv), f0 = frac(uv);
    float cmp = step(f0.y, f0.x);
    float2 o1 = float2(cmp, 1.0-cmp);
    float2 i1 = i0 + o1, i2 = i0 + 1.0;
    float2 v0 = float2(i0.x - i0.y*0.5, i0.y);
    float2 v1 = float2(v0.x + o1.x - o1.y*0.5, v0.y + o1.y);
    float2 v2 = float2(v0.x + 0.5, v0.y + 1.0);
    float2 x0 = x - v0, x1 = x - v1, x2 = x - v2;
    float3 iu, iv, xw, yw;
    if(any(greaterThan(period, float2(0.0,0.0)))) {
        xw = float3(v0.x, v1.x, v2.x);
        yw = float3(v0.y, v1.y, v2.y);
        if(period.x > 0.0)
            xw = mod(float3(v0.x, v1.x, v2.x), period.x);
        if(period.y > 0.0)
            yw = mod(float3(v0.y, v1.y, v2.y), period.y);
        iu = floor(xw + 0.5*yw + 0.5); iv = floor(yw + 0.5);
    } else {
        iu = float3(i0.x, i1.x, i2.x); iv = float3(i0.y, i1.y, i2.y);
    }
    float3 hash = mod(iu, 289.0);
    hash = mod((hash*51.0 + 2.0)*hash + iv, 289.0);
    hash = mod((hash*34.0 + 10.0)*hash, 289.0);
    float3 psi = hash*0.07482 + alpha;
    float3 gx = cos(psi); float3 gy = sin(psi);
    float2 g0 = float2(gx.x, gy.x);
    float2 g1 = float2(gx.y, gy.y);
    float2 g2 = float2(gx.z, gy.z);
    float3 w = 0.8 - float3(dot(x0, x0), dot(x1, x1), dot(x2, x2));
    w = max(w, 0.0); float3 w2 = w*w; float3 w4 = w2*w2;
    float3 gdotx = float3(dot(g0, x0), dot(g1, x1), dot(g2, x2));
    float n = dot(w4, gdotx);
    float3 w3 = w2*w; float3 dw = -8.0*w3*gdotx;
    float2 dn0 = w4.x*g0 + dw.x*x0;
    float2 dn1 = w4.y*g1 + dw.y*x1;
    float2 dn2 = w4.z*g2 + dw.z*x2;
    gradient = 10.9*(dn0 + dn1 + dn2);
    return 10.9*n;
}

// Compute the shortest distance from p
// to a line segment from p1 to p2.
float lined(float2 p1, float2 p2, float2 p) {
    float2 p1p2 = p2 - p1;
    float2 v = normalize(p1p2);
    float2 s = p - p1;
    float t = dot(v, s);
    if (t<0.0) return length(s);
    if (t>length(p1p2)) return length(p - p2);
    return length(s - t*v);
}

// Compute the shortest distance from p to a circle
// with center at c and radius r. (Extremely simple.)
float circled(float2 c, float r, float2 p) {
    return abs(length(p - c) - r);
}

// Compute the shortest distance from p to a
// circular arc with center c from p1 to p2.
// p1, p2 are in the +angle direction (ccw),
// to resolve the major/minor arc ambiguity, so
// specifying p1, p2 in the wrong order will
// yield the complement to the arc you wanted.
// If p1 = p2, the entire circle is drawn, but
// you don''t want to use this function to draw
// a circle. Use the simple circled() instead.
// If p1 and p2 have different distances to c,
// the end of the arc will not look right. If
// this is inconvenient, uncomment the 3rd line.
float arcd(float2 c, float2 p1, float2 p2, float2 p) {

    float2 v1 = p1 - c;
    float2 v2 = p2 - c;
    // Optional: make sure p1, p2 are both on the circle
    // v2 = normalize(v2)*length(v1);
    float2 v = p - c;

    float2 w = float2(dot(v, -float2(-v1.y, v1.x)), dot(v, float2(-v2.y, v2.x)));

    if(dot(v1, float2(-v2.y, v2.x)) >= 0.0) { // Arc angle <= pi
        if(all(lessThan(float2(0.0,0.0), w))) {
            return min(length(p1-p), length(p2-p)); // nearest end
        } else {
            return abs(length(v) - length(v1)); // dist to arc
        }
    } else { // Arc angle > pi
        if(any(lessThan(float2(0.0,0.0), w))) {
            return min(length(p1-p), length(p2-p));
        } else {
            return abs(length(v) - length(v1));
        }
    }
}

// A convenient anti-aliased step() using auto derivatives
float aastep(float threshold, float value) {
    float afwidth = 0.7 * length(float2(ddx(value), ddy(value)));
    return smoothstep(threshold-afwidth, threshold+afwidth, value);
}

// A smoothstep() that blends to an aastep() under minification
float aasmoothstep(float t1, float t2, float v) {
    float aw = 0.7 * length(float2(ddx(v), ddy(v)));
    float sw = max(0.5*(t2-t1), aw);
    float st = 0.5*(t1+t2);
    return smoothstep(st-sw, st+sw, v);
}

// Distance field of a hexagonal (simplex) grid
// The return vector contains the distances to the
// three closest points, sorted by magnitude.
float3 hexgrid(float2 p) {

    const float stretch = 1.0/0.8660;
    
    // v.y = v.y + 0.0001; // needed if no stretching (rounding errors)
    p.y = p.y * stretch;
    // Transform to grid space (axis-aligned "simplex" grid)
    float2 uv = float2(p.x + p.y*0.5, p.y);
    // Determine which simplex we''re in, with i0 being the "base"
    float2 i0 = floor(uv);
    float2 f0 = frac(uv);
    // o1 is the offset in simplex space to the second corner
    float cmp = step(f0.y, f0.x);
    float2 o1 = float2(cmp, 1.0-cmp);
    // Enumerate the remaining simplex corners
    float2 i1 = i0 + o1;
    float2 i2 = i0 + float2(1.0, 1.0);
    // Transform corners back to texture space
    float2 p0 = float2(i0.x - i0.y * 0.5, i0.y);
    float2 p1 = float2(p0.x + o1.x - o1.y * 0.5, p0.y + o1.y);
    float2 p2 = float2(p0.x + 0.5, p0.y + 1.0);
    float3 d = float3(length(p-p0), length(p-p1), length(p-p2));
    // Only three values - bubble sort is just fine.
    d.yz = (d.y < d.z) ? d.yz : d.zy;
    d.xy = (d.x < d.y) ? d.xy : d.yx;
    d.yz = (d.y < d.z) ? d.yz : d.zy;
    return d;
}

// The digits. Simple functions, only a lot of them.

// These glyphs and their implementation as distance fields
// are the original work of me (stefan.gustavson@gmail.com),
// and the code below is released under the MIT license:
// https://opensource.org/licenses/MIT
// (If that is inconvenient for you, let me know. I''m reasonable.)
//
// Experts say mortals should not attempt to design character shapes.
// "It''s just ten simple digits", I thought, "How hard can it be?"
// A week later, after countless little tweaks to proportions and
// curvature, and with a notepad full of sketches and pen-and-paper
// math, some of it horribly wrong because it was decades since I
// solved this kind of equations by hand, I know the answer:
// It can be *really* hard. But also loads of fun!
//
float nixie0(float2 p) {
    // Special hack instead of pasting together arcs and lines
    float d = lined(float2(2.0,2.0), float2(2.0, 6.0), p);
    return abs(d - 2.0);
}

float nixie1(float2 p) {
    float d1 = lined(float2(2.0, 0.0), float2(2.0, 8.0), p);
    float d2 = lined(float2(2.0, 8.0), float2(1.0, 6.0), p);
    return min(d1, d2);
}

float nixie1alt(float2 p) { // Straight line
    return lined(float2(2.0, 0.0), float2(2.0, 8.0), p);
}

float nixie2(float2 p) {
    const float x = 3.2368345; // Icky coordinates,
    const float y = 4.4283002; // used twice below
    float d1 = lined(float2(4.25, 0.0), float2(-0.25, 0.0), p);
    float d2 = arcd(float2(10.657842, -5.001899), // Also icky
                    float2(x, y), float2(-0.25, 0.0), p);
    float d3 = arcd(float2(2.0, 6.0), float2(x, y), float2(0.0, 6.0), p);
    return min(min(d1, d2), d3);
}

float nixie2alt(float2 p) { // Straight neck
    float d1 = lined(float2(4.0, 0.0), float2(0.0,0.0), p);
    float d2 = lined(float2(0.0,0.0), float2(3.6, 4.8), p);
    float d3 = arcd(float2(2.0, 6.0), float2(3.6, 4.8), float2(0.0, 6.0), p);
    return min(min(d1, d2), d3);
}

float nixie3(float2 p) {
    // Two round parts:
    // float d1 = arcd(float2(2.0, 2.1), float2(-0.1, 2.1), float2(2.0, 4.2), p);
    // float d2 = arcd(float2(2.0, 6.1), float2(2.0, 4.2), float2(0.1, 6.1), p);
    // Angled top, more like classic Nixie tube digits:
    float d1 = arcd(float2(2.0, 2.25), float2(-0.25, 2.25), float2(2.0, 4.5), p);
    float d2 = lined(float2(2.0, 4.5), float2(4.0, 7.75), p);
    float d3 = lined(float2(4.0, 7.75), float2(0.0, 7.75), p);
    return min(min(d1, d2), d3);
}

float nixie3alt(float2 p) { // Two round parts of the same size
    float d1 = arcd(float2(2.0,2.0), float2(0.0, 2.0), float2(2.0, 4.0), p);
    float d2 = arcd(float2(2.0, 6.0), float2(2.0, 4.0), float2(0.0, 6.0), p);
    return min(d1, d2);
}

float nixie4(float2 p) {
    // This digit is 5.0 units wide, most others are 4.0 or 4.5
    float d1 = lined(float2(4.0, 0.0), float2(4.0, 8.0), p);
    float d2 = lined(float2(4.0, 8.0), float2(0.0, 2.0), p);
    float d3 = lined(float2(0.0, 2.0), float2(5.0, 2.0), p);
    return min(min(d1, d2), d3);
}

float nixie4alt(float2 p) {
    // This digit is 4.0 units wide, but looks cropped
    float d1 = lined(float2(4.0, 0.0), float2(4.0, 8.0), p);
    float d2 = lined(float2(4.0, 8.0), float2(0.0, 2.0), p);
    float d3 = lined(float2(0.0, 2.0), float2(4.0, 2.0), p);
    return min(min(d1, d2), d3);
}

float nixie5(float2 p) {
    float d1 = lined(float2(4.0, 7.75), float2(0.5, 7.75), p);
    float d2 = lined(float2(0.5, 7.75), float2(0.0, 4.5), p);
    float d3 = lined(float2(0.0, 4.5), float2(2.0, 4.5), p);
    float d4 = arcd(float2(2.0, 2.25), float2(-0.25, 2.25), float2(2.0, 4.5), p);
    return min(min(d1, d2), min(d3, d4));
}

float nixie5alt(float2 p) {
    float d1 = lined(float2(4.0, 8.0), float2(0.0, 8.0), p);
    float d2 = lined(float2(0.0, 8.0), float2(0.0, 5.0), p);
    float d3 = lined(float2(0.0, 5.0), float2(2.0, 5.0), p);
    float d4 = arcd(float2(2.0, 3.0), float2(4.0, 3.0), float2(2.0, 5.0), p);
    float d5 = lined(float2(4.0, 3.0), float2(4.0, 2.0), p);
    float d6 = arcd(float2(2.0,2.0), float2(0.0, 2.0), float2(4.0, 2.0), p);
    return min(min(min(d1, d2), min(d3, d4)), min(d5, d6));
}

float nixie6(float2 p) {
    float d1 = arcd(float2(84.0/13.0, 2.25), float2(3.0, 8.0), float2(-0.25, 2.25), p);
    float d2 = circled(float2(2.0, 2.25), 2.25, p);
    return min(d1, d2);
}

float nixie6alt(float2 p) { // Straight neck
    float d1 = lined(float2(0.4, 3.2), float2(3.0, 8.0), p);
    float d2 = circled(float2(2.0,2.0), 2.0, p);
    return min(d1, d2);
}

float nixie7(float2 p) { // Ugly coordinates, but these expressions are exact
    float d1 = lined(float2(0.0, 7.75), float2(0.25*sqrt(2259.0)-8.0, 7.75), p);
    float d2 = arcd(float2(-8.0, 12.0), float2(2.5, 5.0), float2(0.25*sqrt(2259.0)-8.0, 7.75), p);
    float d3 = arcd(float2(10.0, 0.0), float2(2.5, 5.0), float2(10.0-2.5*sqrt(13.0), 0.0), p);
    return min(min(d1, d2), d3);
}

float nixie7alt(float2 p) { // Straight neck
    float d1 = lined(float2(0.0, 8.0), float2(4.0, 8.0), p);
    float d2 = lined(float2(4.0, 8.0), float2(1.0, 0.0), p);
    return min(d1, d2);
}

float nixie8(float2 p) {
    float d1 = circled(float2(2.0, 2.2), 2.2, p);
    float d2 = circled(float2(2.0, 6.2), 1.8, p);
    return min(d1, d2);
}

float nixie8alt(float2 p) { // Same size loops
    float d1 = circled(float2(2.0,2.0), 2.0, p);
    float d2 = circled(float2(2.0, 6.0), 2.0, p);
    return min(d1, d2);
}

float nixie9(float2 p) {
    float d1 = arcd(float2(-32.0/13.0, 5.75), float2(1.0, 0.0), float2(4.25, 5.75), p);
    float d2 = circled(float2(2.0, 5.75), 2.25, p);
    return min(d1, d2);
}

float nixie9alt(float2 p) { // Straight neck
    float d1 = lined(float2(3.6, 4.8), float2(1.0, 0.0), p);
    float d2 = circled(float2(2.0, 6.0), 2.0, p);
    return min(d1, d2);
}

float nixieminus(float2 p) {
    return lined(float2(0.5, 4.0), float2(3.5, 4.0), p);
}

float nixieequals(float2 p) {
    float d1 = lined(float2(0.5, 3.0), float2(3.5, 3.0), p);
    float d2 = lined(float2(0.5, 5.0), float2(3.5, 5.0), p);
    return min(d1, d2);
}

float nixieplus(float2 p) {
    float d1 = lined(float2(0.0, 4.0), float2(4.0, 4.0), p);
    float d2 = lined(float2(2.0, 2.0), float2(2.0, 6.0), p);
    return min(d1, d2);
}

float nixiedot(float2 p) {
    // circled with r=0 yields a point, but with more work
    return length(p - float2(2.0, 0.0));
}

float nixiecolon(float2 p) {
    float d1 = length(p - float2(2.0,2.0));
    float d2 = length(p - float2(2.0, 5.0));
    return min(d1, d2);
}

// End of MIT-licensed code

float number(int n, float2 p) {
    switch(n) {
        case 0: return nixie0(p);
        case 1: return nixie1(p);
        case 2: return nixie2(p);
        case 3: return nixie3(p);
        case 4: return nixie4(p);
        case 5: return nixie5(p);
        case 6: return nixie6(p);
        case 7: return nixie7(p);
        case 8: return nixie8(p);
        case 9: return nixie9(p);
        default: return 1e10;
    }
}
// Display the current time with a retro Nixie-tube look
// Stefan Gustavson (stegu on shadertoy.com) 2022-01-26
// All code in the "Image" tab is public domain.
// Functions in the "Common" tab are also public domain,
// except where a separate license is specified.
float4 mainImage(VertData v_in) : TARGET
{
    float2 uv = ((float2(v_in.uv.x,1.0-v_in.uv.y) * uv_size)/uv_size.x);
    float time = 0.0;
    if(timeMode == 0){
        time = float(current_time_hour*3600+current_time_min*60+current_time_sec) + float(current_time_ms)/1000.0;
    }else if(timeMode == 1){
        time = elapsed_time_enable;
    }else if(timeMode == 2){
        time = elapsed_time_active;
    }else if(timeMode == 3){
        time = elapsed_time_show;
    }else if(timeMode == 4){
        time = elapsed_time_start;
    }
    time += offsetSeconds + offsetHours * 3600;
    if(time < 0)
        time = 0.9999-time;
    float2 p = -3.0+uv*50.0 - float2(0.0,9.0);

    float bbox = 1.0-max(max(1.0-aastep(-3.0, p.x), aastep(47.0, p.x)),
                           max(1.0-aastep(-3.0, p.y), aastep(11.0, p.y)));

    // Some relief for the GPU: exit early if we''re in the black margins
    if(bbox == 0.0) {
        return float4(float3(0.0,0.0,0.0),1.0);
    }
    
    // If not, well, let''s put that GPU to good use!
    float secs = floor(mod(time, 60.0));
    float mins = floor(mod(time, 3600.0)/60.0);
    float hrs = floor(time/3600.0);
    int h10 = int(floor(hrs/10.0));
    int h1 = int(floor(mod(hrs, 10.0)));
    int m10 = int(floor(mins/10.0));
    int m1 = int(floor(mod(mins, 10.0)));
    int s10 = int(floor(secs/10.0));
    int s1 = int(floor(mod(secs, 10.0)));

    float2 wspace = float2(6.5, 0.0);
    float2 nspace = float2(3.5, 0.0);
    float d = 1e10;
    d = min(d, number(h10, p));
    d = min(d, number(h1, p-wspace));
    d = min(d, nixiecolon(p-wspace-1.45*nspace)-0.2);
    d = min(d, number(m10, p-2.0*wspace-nspace));
    d = min(d, number(m1, p-3.0*wspace-nspace));
    d = min(d, nixiecolon(p-3.0*wspace-2.4*nspace)-0.2);
    d = min(d, number(s10, p-4.0*wspace-2.0*nspace));
    d = min(d, number(s1, p-5.0*wspace-2.0*nspace));

    float2 g; // For gradients returned from psrdnoise()

    // Digit outlines
    float core = 1.0-aastep(0.2, d);
    // "flare" is a wide blurry region around the characters, and
    // "flarenoise" is a spatio-temporal modulation of its extents
    // (slight flickering, but not all characters at the same time)
    float flarenoise = psrdnoise(float2(p.x*0.1,5.0*elapsed_time), float2(0.0,0.0), 0.0, g);
    float flare = 1.0-smoothstep(0.0, 2.5, d + 0.05*flarenoise);
    flare *= flare; // A more rapid decline towards the edge
    // "glow" is a variation in the intensity of the glowy cathode (core)
    float glow = 0.8+0.2*psrdnoise(p - float2(0.0, 2.0*elapsed_time), float2(0.0,0.0), 4.0*time, g);
    // Now we mess up the distance field a little for the "halo" effect
    d += 0.1*psrdnoise(p - float2(0.0, 2.0*elapsed_time), float2(0.0,0.0), 8.0*time, g);
    d += 0.05*psrdnoise(2.0*p - float2(0.0, 4.0*elapsed_time) + 0.15*g, float2(0.0,0.0), -16.0*time, g);
    // "halo" is a kind of flame/plasma cloud near the core. A real Nixie tube
    // doesn''t have this, but it adds some appealing visual detail.
    // Looks more like hot filaments than "cold cathodes", but... oh, well.
    float halo = 1.0-smoothstep(-0.3, 0.3, d);

    // Brittle parameters! This scale/shift of p has a strong impact
    // on the pattern at the edges of the grid through "anodefade".
    float3 anodedists = hexgrid(1.7*p+float2(0.1,0.23));
    float anodedist = anodedists.y - anodedists.x; // Voronoi cell borders
    // Fade the hexagonal holes in the anode towards the edges
    float anodefade = max(max(1.0-aasmoothstep(-2.2, -1.5, p.x), aasmoothstep(45.5, 46.2, p.x)),
                          max(1.0-aasmoothstep(-2.0, -1.6, p.y), aasmoothstep(9.4, 10.0, p.y)));
    float anode = 1.0 - aastep(0.1, anodedist - anodefade);

    float anodecolornoise = 0.02*psrdnoise(p*float2(0.2,2.0), float2(0.0,0.0), 0.0, g);
    float3 anodecolorresult = anodecolor+ anodecolornoise*anodehighlightscolor; // Long variable names, I know

    float3 mixcolor = float3(0.0,0.0,0.0); // Mix additively from black
    mixcolor = lerp(mixcolor, flarecolor, 0.5*flare);
    mixcolor = lerp(mixcolor, halocolor, 0.9*halo);
    mixcolor = lerp(mixcolor, corecolor, core*glow);
    mixcolor = lerp(mixcolor, anodecolorresult, anode);
    mixcolor *= bbox; // AA-mask to black at the very edge of the bounding box
    return float4(mixcolor,1.0);
}

'

}
$MyVerb, $myNoun = $MyInvocation.InvocationName -split '-',2
if (-not $myNoun) {
    $myNoun = $myVerb
    $myVerb = 'Get'    
}
switch -regex ($myVerb) {
    Get {
        $FilterNamePattern = "(?>$(
            if ($FilterName) {
                [Regex]::Escape($FilterName)
            }
            else {
                [Regex]::Escape($ShaderNoun -replace '^OBS' -replace 'Shader$'),[Regex]::Escape($shaderName) -join '|'
            }
        ))"

        if ($SourceName) {
            Get-OBSInput | 
                Where-Object InputName -eq $SourceName |
                Get-OBSSourceFilterList |
                Where-Object FilterName -Match $FilterNamePattern
        } else {
            $obs.Inputs |
                Get-OBSSourceFilterList |
                Where-Object FilterName -Match $FilterNamePattern
        }        
    }
    'Remove' {
        if ($SourceName) {
            Get-OBSInput | 
                Where-Object InputName -eq $SourceName |
                Get-OBSSourceFilterList |
                Where-Object FilterName -Match $FilterNamePattern |
                Remove-OBSSourceFilter
        }
    }
    '(?>Add|Set)' {
        $ShaderSettings = [Ordered]@{}
        :nextParameter foreach ($parameterMetadata in $MyInvocation.MyCommand.Parameters[@($psBoundParameters.Keys)]) {
            foreach ($parameterAttribute in $parameterMetadata.Attributes) {
                if ($parameterAttribute -isnot [ComponentModel.DefaultBindingPropertyAttribute]) { continue }
                $ShaderSettings[$parameterAttribute.Name] = $PSBoundParameters[$parameterMetadata.Name]
                if ($ShaderSettings[$parameterAttribute.Name] -is [switch]) {
                    $ShaderSettings[$parameterAttribute.Name] = $ShaderSettings[$parameterAttribute.Name] -as [bool]
                }
                continue nextParameter
            }            
        }

        if (-not $PSBoundParameters['FilterName']) {
            $filterName = $PSBoundParameters['FilterName'] = $shaderName
        }

        $ShaderFilterSplat = [Ordered]@{
            ShaderSetting = $ShaderSettings
            FilterName = $FilterName
            SourceName = $SourceName
        }        

        foreach ($CarryOnParameter in "PassThru", "NoResponse","Force") {
            if ($PSBoundParameters.ContainsKey($CarryOnParameter)) {
                $ShaderFilterSplat[$CarryOnParameter] = $PSBoundParameters[$CarryOnParameter]
            }
        }

        if (-not $script:CachedShaderFilesFromCommand) {
            $script:CachedShaderFilesFromCommand = @{}
        }

        if ($Home -and -not $script:CachedShaderFilesFromCommand[$shaderName]) {
            $MyObsPowerShellPath = Join-Path $home ".obs-powershell"
            $ThisShaderPath = Join-Path $MyObsPowerShellPath "$shaderName.shader"
            $shaderText | Set-Content -LiteralPath $ThisShaderPath
            $script:CachedShaderFilesFromCommand[$shaderName] = Get-Item -LiteralPath $ThisShaderPath
        }
        if ($script:CachedShaderFilesFromCommand[$shaderName]) {
            $ShaderFilterSplat.ShaderFile = $script:CachedShaderFilesFromCommand[$shaderName].FullName
        } else {
            $ShaderFilterSplat.ShaderText = $shaderText
        }        

        if ($myVerb -eq 'Add') {                        
            Add-OBSShaderFilter @ShaderFilterSplat
        } else {
            Set-OBSShaderFilter @ShaderFilterSplat
        }
    }
}

}


}