updates
Some checks failed
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 2m22s
CI / Windows Release Package (push) Has been cancelled

This commit is contained in:
2026-05-08 18:07:45 +10:00
parent eede6938cb
commit f322abf79a
24 changed files with 270 additions and 43 deletions

View File

@@ -4,6 +4,29 @@
"description": "Production-style green/blue screen keyer with matte refinement, despill, edge treatment, and debug views.",
"category": "Keying",
"entryPoint": "shadeVideo",
"passes": [
{
"id": "rawMatte",
"source": "shader.slang",
"entryPoint": "buildRawMatte",
"inputs": ["layerInput"],
"output": "rawMatte"
},
{
"id": "refinedMatte",
"source": "shader.slang",
"entryPoint": "refineMatte",
"inputs": ["rawMatte"],
"output": "refinedMatte"
},
{
"id": "final",
"source": "shader.slang",
"entryPoint": "applyKey",
"inputs": ["refinedMatte"],
"output": "layerOutput"
}
],
"parameters": [
{
"id": "screenColor",
@@ -167,6 +190,46 @@
"max": 1.0,
"step": 0.005
},
{
"id": "cropLeft",
"label": "Crop Left",
"description": "Trims the final matte from the left edge as a fraction of frame width.",
"type": "float",
"default": 0.0,
"min": 0.0,
"max": 0.5,
"step": 0.001
},
{
"id": "cropRight",
"label": "Crop Right",
"description": "Trims the final matte from the right edge as a fraction of frame width.",
"type": "float",
"default": 0.0,
"min": 0.0,
"max": 0.5,
"step": 0.001
},
{
"id": "cropTop",
"label": "Crop Top",
"description": "Trims the final matte from the top edge as a fraction of frame height.",
"type": "float",
"default": 0.0,
"min": 0.0,
"max": 0.5,
"step": 0.001
},
{
"id": "cropBottom",
"label": "Crop Bottom",
"description": "Trims the final matte from the bottom edge as a fraction of frame height.",
"type": "float",
"default": 0.0,
"min": 0.0,
"max": 0.5,
"step": 0.001
},
{
"id": "viewMode",
"label": "View",

View File

@@ -50,12 +50,17 @@ float rawAlphaAt(float2 uv, ShaderContext context)
return saturate(alpha);
}
float refinedAlphaAt(float2 uv, ShaderContext context)
float matteAlphaAt(float2 uv)
{
return saturate(sampleVideo(saturate(uv)).a);
}
float refinedAlphaFromMatte(float2 uv, ShaderContext context)
{
float2 pixel = 1.0 / max(context.outputResolution, float2(1.0, 1.0));
float blur = max(matteBlur, 0.0);
float aaRadius = max(blur, 0.65);
float centerAlpha = rawAlphaAt(uv, context);
float centerAlpha = matteAlphaAt(uv);
float alpha = centerAlpha * 0.30;
if (aaRadius > 0.0001)
@@ -64,51 +69,51 @@ float refinedAlphaAt(float2 uv, ShaderContext context)
float2 halfRadius = radius * 0.5;
float alphaMin = centerAlpha;
float alphaMax = centerAlpha;
float sampleAlpha = rawAlphaAt(uv + float2(halfRadius.x, 0.0), context);
float sampleAlpha = matteAlphaAt(uv + float2(halfRadius.x, 0.0));
alpha += sampleAlpha * 0.065;
alphaMin = min(alphaMin, sampleAlpha);
alphaMax = max(alphaMax, sampleAlpha);
sampleAlpha = rawAlphaAt(uv - float2(halfRadius.x, 0.0), context);
sampleAlpha = matteAlphaAt(uv - float2(halfRadius.x, 0.0));
alpha += sampleAlpha * 0.065;
alphaMin = min(alphaMin, sampleAlpha);
alphaMax = max(alphaMax, sampleAlpha);
sampleAlpha = rawAlphaAt(uv + float2(0.0, halfRadius.y), context);
sampleAlpha = matteAlphaAt(uv + float2(0.0, halfRadius.y));
alpha += sampleAlpha * 0.065;
alphaMin = min(alphaMin, sampleAlpha);
alphaMax = max(alphaMax, sampleAlpha);
sampleAlpha = rawAlphaAt(uv - float2(0.0, halfRadius.y), context);
sampleAlpha = matteAlphaAt(uv - float2(0.0, halfRadius.y));
alpha += sampleAlpha * 0.065;
alphaMin = min(alphaMin, sampleAlpha);
alphaMax = max(alphaMax, sampleAlpha);
sampleAlpha = rawAlphaAt(uv + float2(radius.x, 0.0), context);
sampleAlpha = matteAlphaAt(uv + float2(radius.x, 0.0));
alpha += sampleAlpha * 0.06;
alphaMin = min(alphaMin, sampleAlpha);
alphaMax = max(alphaMax, sampleAlpha);
sampleAlpha = rawAlphaAt(uv - float2(radius.x, 0.0), context);
sampleAlpha = matteAlphaAt(uv - float2(radius.x, 0.0));
alpha += sampleAlpha * 0.06;
alphaMin = min(alphaMin, sampleAlpha);
alphaMax = max(alphaMax, sampleAlpha);
sampleAlpha = rawAlphaAt(uv + float2(0.0, radius.y), context);
sampleAlpha = matteAlphaAt(uv + float2(0.0, radius.y));
alpha += sampleAlpha * 0.06;
alphaMin = min(alphaMin, sampleAlpha);
alphaMax = max(alphaMax, sampleAlpha);
sampleAlpha = rawAlphaAt(uv - float2(0.0, radius.y), context);
sampleAlpha = matteAlphaAt(uv - float2(0.0, radius.y));
alpha += sampleAlpha * 0.06;
alphaMin = min(alphaMin, sampleAlpha);
alphaMax = max(alphaMax, sampleAlpha);
sampleAlpha = rawAlphaAt(uv + radius, context);
sampleAlpha = matteAlphaAt(uv + radius);
alpha += sampleAlpha * 0.05;
alphaMin = min(alphaMin, sampleAlpha);
alphaMax = max(alphaMax, sampleAlpha);
sampleAlpha = rawAlphaAt(uv - radius, context);
sampleAlpha = matteAlphaAt(uv - radius);
alpha += sampleAlpha * 0.05;
alphaMin = min(alphaMin, sampleAlpha);
alphaMax = max(alphaMax, sampleAlpha);
sampleAlpha = rawAlphaAt(uv + float2(radius.x, -radius.y), context);
sampleAlpha = matteAlphaAt(uv + float2(radius.x, -radius.y));
alpha += sampleAlpha * 0.05;
alphaMin = min(alphaMin, sampleAlpha);
alphaMax = max(alphaMax, sampleAlpha);
sampleAlpha = rawAlphaAt(uv + float2(-radius.x, radius.y), context);
sampleAlpha = matteAlphaAt(uv + float2(-radius.x, radius.y));
alpha += sampleAlpha * 0.05;
alphaMin = min(alphaMin, sampleAlpha);
alphaMax = max(alphaMax, sampleAlpha);
@@ -118,7 +123,7 @@ float refinedAlphaAt(float2 uv, ShaderContext context)
}
else
{
alpha = rawAlphaAt(uv, context);
alpha = centerAlpha;
}
alpha = saturate((alpha - clipBlack) / max(clipWhite - clipBlack, 0.0001));
@@ -147,17 +152,43 @@ float3 despillColor(float3 color, float alpha)
return saturate(neutralized);
}
float4 shadeVideo(ShaderContext context)
float cropMaskAt(float2 uv, ShaderContext context)
{
float2 feather = 1.0 / max(context.outputResolution, float2(1.0, 1.0));
float left = smoothstep(saturate(cropLeft), saturate(cropLeft) + feather.x, uv.x);
float right = 1.0 - smoothstep(1.0 - saturate(cropRight) - feather.x, 1.0 - saturate(cropRight), uv.x);
float top = smoothstep(saturate(cropTop), saturate(cropTop) + feather.y, uv.y);
float bottom = 1.0 - smoothstep(1.0 - saturate(cropBottom) - feather.y, 1.0 - saturate(cropBottom), uv.y);
return saturate(left * right * top * bottom);
}
float4 buildRawMatte(ShaderContext context)
{
float4 src = context.sourceColor;
float3 color = saturate(src.rgb);
float alpha = refinedAlphaAt(context.uv, context);
float alpha = rawAlphaAt(context.uv, context);
return float4(color, alpha);
}
float4 refineMatte(ShaderContext context)
{
float4 raw = sampleVideo(context.uv);
float alpha = refinedAlphaFromMatte(context.uv, context);
return float4(saturate(raw.rgb), alpha);
}
float4 applyKey(ShaderContext context)
{
float4 keyed = sampleVideo(context.uv);
float3 color = saturate(keyed.rgb);
float alpha = saturate(keyed.a);
float spill = spillAmountForColor(color);
float3 despilled = despillColor(color, alpha);
float cropMask = cropMaskAt(context.uv, context);
alpha *= cropMask;
float edgeAmount = saturate(1.0 - abs(alpha * 2.0 - 1.0));
despilled = lerp(despilled, despilled * saturate(edgeColor.rgb), edgeAmount * saturate(edgeRecover));
alpha = saturate(lerp(alpha, rawAlphaAt(context.uv, context), edgeAmount * saturate(edgeRecover) * 0.35));
if (viewMode == 1)
return float4(alpha, alpha, alpha, 1.0);
@@ -167,10 +198,15 @@ float4 shadeVideo(ShaderContext context)
return float4(despilled, 1.0);
if (viewMode == 4)
{
float rawAlpha = rawAlphaAt(context.uv, context);
float rawAlpha = rawAlphaAt(context.uv, context) * cropMask;
return float4(rawAlpha, alpha, spill, 1.0);
}
float3 premultiplied = saturate(despilled) * alpha;
return float4(premultiplied, alpha);
}
float4 shadeVideo(ShaderContext context)
{
return applyKey(context);
}

View File

@@ -4,6 +4,22 @@
"description": "VHS with wiggle, smear, and YIQ-style color separation inspired by nostalgic analog references.",
"category": "Glitch",
"entryPoint": "shadeVideo",
"passes": [
{
"id": "tapeSmear",
"source": "shader.slang",
"entryPoint": "buildTapeSmear",
"inputs": ["layerInput"],
"output": "tapeSmear"
},
{
"id": "final",
"source": "shader.slang",
"entryPoint": "finishVhs",
"inputs": ["tapeSmear"],
"output": "layerOutput"
}
],
"parameters": [
{
"id": "wiggle",

View File

@@ -158,10 +158,15 @@ float3 blurVhs(float2 uv, float d, int sampleCount)
return sum;
}
float4 shadeVideo(ShaderContext context)
float distortedTapeTime(ShaderContext context)
{
return context.time + context.startupRandom * 113.0;
}
float4 buildTapeSmear(ShaderContext context)
{
float2 uv = context.uv;
float time = context.time + context.startupRandom * 113.0;
float time = distortedTapeTime(context);
float framecount = frac(time * wiggleSpeed / 7.0) * 7.0;
int sampleCount = int(clamp(blurSamples, 3.0, 15.0) + 0.5);
@@ -189,6 +194,13 @@ float4 shadeVideo(ShaderContext context)
float q = rgb2yiq(qBlur).b;
float3 color = yiq2rgb(float3(y, i, q)) - pow(s + e * 2.0, 3.0);
return float4(saturate(color), 1.0);
}
float4 finishVhs(ShaderContext context)
{
float time = distortedTapeTime(context);
float3 color = sampleVideo(context.uv).rgb;
float2 centered = context.uv * 2.0 - 1.0;
centered.x *= context.outputResolution.x / max(context.outputResolution.y, 1.0);
@@ -238,3 +250,8 @@ float4 shadeVideo(ShaderContext context)
return float4(saturate(color), 1.0);
}
float4 shadeVideo(ShaderContext context)
{
return finishVhs(context);
}