Example shaders
Some checks failed
CI / Native Windows Build And Tests (push) Failing after 7s
CI / React UI Build (push) Has been cancelled
CI / Windows Release Package (push) Has been cancelled

This commit is contained in:
2026-05-03 15:44:22 +10:00
parent ee6dbf7510
commit 52bf8c90ea
12 changed files with 447 additions and 0 deletions

View File

@@ -0,0 +1,50 @@
{
"id": "data-mosh",
"name": "Data Mosh",
"description": "Temporal block-smear glitch with scanline tearing and chroma offset.",
"category": "Glitch",
"entryPoint": "shadeVideo",
"temporal": {
"enabled": true,
"historySource": "preLayerInput",
"historyLength": 8
},
"parameters": [
{
"id": "amount",
"label": "Amount",
"type": "float",
"default": 0.45,
"min": 0.0,
"max": 1.0,
"step": 0.01
},
{
"id": "blockCount",
"label": "Block Count",
"type": "vec2",
"default": [32.0, 18.0],
"min": [2.0, 2.0],
"max": [160.0, 120.0],
"step": [1.0, 1.0]
},
{
"id": "tearAmount",
"label": "Tear",
"type": "float",
"default": 0.18,
"min": 0.0,
"max": 1.0,
"step": 0.01
},
{
"id": "chromaShift",
"label": "Chroma Shift",
"type": "float",
"default": 1.8,
"min": 0.0,
"max": 12.0,
"step": 0.1
}
]
}

View File

@@ -0,0 +1,27 @@
float hash21(float2 p)
{
return frac(sin(dot(p, float2(127.1, 311.7))) * 43758.5453123);
}
float4 shadeVideo(ShaderContext context)
{
float2 blocks = max(blockCount, float2(1.0, 1.0));
float2 blockId = floor(context.uv * blocks);
float n = hash21(blockId + floor(context.time * 8.0));
float historyFrame = floor(lerp(1.0, 7.0, n));
float rowNoise = hash21(float2(floor(context.uv.y * blocks.y), floor(context.time * 5.0)));
float tear = (rowNoise * 2.0 - 1.0) * tearAmount * amount * 0.08;
float2 offset = float2(tear, (hash21(blockId + 19.0) * 2.0 - 1.0) * amount * 0.025);
float2 moshedUv = clamp(context.uv + offset, 0.0, 1.0);
float4 previous = sampleTemporalHistory(int(historyFrame), moshedUv);
float2 chromaOffset = float2(chromaShift / max(context.outputResolution.x, 1.0), 0.0) * amount;
float r = sampleVideo(clamp(context.uv + chromaOffset, 0.0, 1.0)).r;
float b = sampleVideo(clamp(context.uv - chromaOffset, 0.0, 1.0)).b;
float3 current = float3(r, context.sourceColor.g, b);
float blockMask = step(0.35, n) * amount;
float3 color = lerp(current, previous.rgb, blockMask);
return float4(saturate(color), context.sourceColor.a);
}

View File

@@ -0,0 +1,42 @@
{
"id": "false-color",
"name": "False Color",
"description": "Maps luminance ranges to exposure-assist colors for camera and shader debugging.",
"category": "Utility",
"entryPoint": "shadeVideo",
"parameters": [
{
"id": "blendAmount",
"label": "Blend",
"type": "float",
"default": 1.0,
"min": 0.0,
"max": 1.0,
"step": 0.01
},
{
"id": "showLuma",
"label": "Show Luma",
"type": "bool",
"default": false
},
{
"id": "lift",
"label": "Lift",
"type": "float",
"default": 0.0,
"min": -0.25,
"max": 0.25,
"step": 0.001
},
{
"id": "gain",
"label": "Gain",
"type": "float",
"default": 1.0,
"min": 0.25,
"max": 2.0,
"step": 0.01
}
]
}

View File

@@ -0,0 +1,26 @@
float3 falseColorRamp(float luma)
{
if (luma < 0.05)
return float3(0.0, 0.0, 0.0);
if (luma < 0.15)
return lerp(float3(0.04, 0.02, 0.24), float3(0.05, 0.18, 0.95), smoothstep(0.05, 0.15, luma));
if (luma < 0.35)
return lerp(float3(0.05, 0.18, 0.95), float3(0.0, 0.85, 1.0), smoothstep(0.15, 0.35, luma));
if (luma < 0.55)
return lerp(float3(0.0, 0.85, 1.0), float3(0.18, 0.95, 0.18), smoothstep(0.35, 0.55, luma));
if (luma < 0.75)
return lerp(float3(0.18, 0.95, 0.18), float3(1.0, 0.92, 0.08), smoothstep(0.55, 0.75, luma));
if (luma < 0.9)
return lerp(float3(1.0, 0.92, 0.08), float3(1.0, 0.35, 0.02), smoothstep(0.75, 0.9, luma));
return lerp(float3(1.0, 0.35, 0.02), float3(1.0, 0.0, 0.0), smoothstep(0.9, 1.0, luma));
}
float4 shadeVideo(ShaderContext context)
{
float3 source = context.sourceColor.rgb;
float luma = saturate((dot(source, float3(0.2126, 0.7152, 0.0722)) + lift) * max(gain, 0.001));
float3 mapped = falseColorRamp(luma);
if (showLuma)
mapped = lerp(mapped, float3(luma, luma, luma), 0.45);
return float4(lerp(source, mapped, saturate(blendAmount)), context.sourceColor.a);
}

View File

@@ -0,0 +1,33 @@
{
"id": "pixelate",
"name": "Pixelate",
"description": "Reduces the effective X and Y pixel count independently to create a low-resolution blocky image.",
"category": "Utility",
"entryPoint": "shadeVideo",
"parameters": [
{
"id": "pixelCount",
"label": "Pixel Count",
"type": "vec2",
"default": [96.0, 54.0],
"min": [2.0, 2.0],
"max": [1920.0, 1080.0],
"step": [1.0, 1.0]
},
{
"id": "gridAmount",
"label": "Grid",
"type": "float",
"default": 0.0,
"min": 0.0,
"max": 1.0,
"step": 0.01
},
{
"id": "gridColor",
"label": "Grid Color",
"type": "color",
"default": [0.0, 0.0, 0.0, 1.0]
}
]
}

View File

@@ -0,0 +1,12 @@
float4 shadeVideo(ShaderContext context)
{
float2 count = max(pixelCount, float2(1.0, 1.0));
float2 cell = floor(context.uv * count);
float2 sampledUv = (cell + 0.5) / count;
float4 color = sampleVideo(clamp(sampledUv, 0.0, 1.0));
float2 local = frac(context.uv * count);
float gridLine = 1.0 - step(0.035, min(min(local.x, 1.0 - local.x), min(local.y, 1.0 - local.y)));
color.rgb = lerp(color.rgb, gridColor.rgb, gridLine * saturate(gridAmount) * gridColor.a);
return color;
}

View File

@@ -0,0 +1,57 @@
{
"id": "safe-area-guides",
"name": "Safe Area Guides",
"description": "Overlays broadcast action/title safe guides plus optional center marks and aspect matte.",
"category": "Utility",
"entryPoint": "shadeVideo",
"parameters": [
{ "id": "showActionSafe", "label": "Action Safe", "type": "bool", "default": true },
{ "id": "showTitleSafe", "label": "Title Safe", "type": "bool", "default": true },
{ "id": "showCenter", "label": "Center Marks", "type": "bool", "default": true },
{
"id": "lineColor",
"label": "Line Color",
"type": "color",
"default": [1.0, 1.0, 1.0, 1.0]
},
{
"id": "lineOpacity",
"label": "Line Opacity",
"type": "float",
"default": 0.65,
"min": 0.0,
"max": 1.0,
"step": 0.01
},
{
"id": "lineThicknessPixels",
"label": "Line Thickness",
"type": "float",
"default": 2.0,
"min": 0.5,
"max": 12.0,
"step": 0.1
},
{
"id": "aspectMode",
"label": "Aspect Matte",
"type": "enum",
"default": "none",
"options": [
{ "value": "none", "label": "None" },
{ "value": "239", "label": "2.39:1" },
{ "value": "185", "label": "1.85:1" },
{ "value": "square", "label": "1:1" }
]
},
{
"id": "matteOpacity",
"label": "Matte Opacity",
"type": "float",
"default": 0.35,
"min": 0.0,
"max": 1.0,
"step": 0.01
}
]
}

View File

@@ -0,0 +1,58 @@
float edgeLine(float value, float target, float pixels, float resolution)
{
float halfWidth = max(pixels, 0.5) * 0.5 / max(resolution, 1.0);
return 1.0 - smoothstep(halfWidth, halfWidth * 2.0, abs(value - target));
}
float rectGuide(float2 uv, float2 inset, float2 resolution)
{
float mask = 0.0;
float thickness = max(lineThicknessPixels, 0.5);
if (uv.x >= inset.x && uv.x <= 1.0 - inset.x && uv.y >= inset.y && uv.y <= 1.0 - inset.y)
{
mask = max(mask, edgeLine(uv.x, inset.x, thickness, resolution.x));
mask = max(mask, edgeLine(uv.x, 1.0 - inset.x, thickness, resolution.x));
mask = max(mask, edgeLine(uv.y, inset.y, thickness, resolution.y));
mask = max(mask, edgeLine(uv.y, 1.0 - inset.y, thickness, resolution.y));
}
return mask;
}
float aspectMatte(float2 uv, float2 resolution)
{
if (aspectMode == 0)
return 0.0;
float targetAspect = aspectMode == 1 ? 2.39 : (aspectMode == 2 ? 1.85 : 1.0);
float frameAspect = resolution.x / max(resolution.y, 1.0);
if (frameAspect <= targetAspect)
{
float contentHeight = frameAspect / targetAspect;
float inset = (1.0 - contentHeight) * 0.5;
return (uv.y < inset || uv.y > 1.0 - inset) ? 1.0 : 0.0;
}
float contentWidth = targetAspect / frameAspect;
float inset = (1.0 - contentWidth) * 0.5;
return (uv.x < inset || uv.x > 1.0 - inset) ? 1.0 : 0.0;
}
float4 shadeVideo(ShaderContext context)
{
float2 resolution = max(context.outputResolution, float2(1.0, 1.0));
float3 color = lerp(context.sourceColor.rgb, float3(0.0, 0.0, 0.0), aspectMatte(context.uv, resolution) * saturate(matteOpacity));
float mask = 0.0;
if (showActionSafe)
mask = max(mask, rectGuide(context.uv, float2(0.05, 0.05), resolution));
if (showTitleSafe)
mask = max(mask, rectGuide(context.uv, float2(0.10, 0.10), resolution));
if (showCenter)
{
mask = max(mask, edgeLine(context.uv.x, 0.5, lineThicknessPixels, resolution.x) * step(abs(context.uv.y - 0.5), 0.035));
mask = max(mask, edgeLine(context.uv.y, 0.5, lineThicknessPixels, resolution.y) * step(abs(context.uv.x - 0.5), 0.035));
}
float opacity = mask * saturate(lineOpacity) * lineColor.a;
return float4(lerp(color, lineColor.rgb, opacity), context.sourceColor.a);
}

View File

@@ -0,0 +1,47 @@
{
"id": "temporal-echo",
"name": "Temporal Echo",
"description": "Blends multiple older pre-layer frames with decay and tint for configurable motion echoes.",
"category": "Temporal",
"entryPoint": "shadeVideo",
"temporal": {
"enabled": true,
"historySource": "preLayerInput",
"historyLength": 12
},
"parameters": [
{
"id": "echoAmount",
"label": "Echo Amount",
"type": "float",
"default": 0.55,
"min": 0.0,
"max": 1.0,
"step": 0.01
},
{
"id": "decay",
"label": "Decay",
"type": "float",
"default": 0.72,
"min": 0.0,
"max": 1.0,
"step": 0.01
},
{
"id": "frameStride",
"label": "Frame Stride",
"type": "float",
"default": 2.0,
"min": 1.0,
"max": 6.0,
"step": 1.0
},
{
"id": "echoTint",
"label": "Echo Tint",
"type": "color",
"default": [0.65, 0.85, 1.0, 1.0]
}
]
}

View File

@@ -0,0 +1,20 @@
float4 shadeVideo(ShaderContext context)
{
int stride = int(clamp(frameStride, 1.0, 6.0) + 0.5);
float3 echo = float3(0.0, 0.0, 0.0);
float total = 0.0;
float weight = 1.0;
for (int i = 1; i <= 6; ++i)
{
int frame = i * stride;
float3 sampleColor = sampleTemporalHistory(frame, context.uv).rgb * echoTint.rgb;
echo += sampleColor * weight;
total += weight;
weight *= saturate(decay);
}
float3 echoColor = echo / max(total, 0.0001);
float amount = saturate(echoAmount) * echoTint.a;
return float4(lerp(context.sourceColor.rgb, echoColor, amount), context.sourceColor.a);
}

View File

@@ -0,0 +1,51 @@
{
"id": "waveform-overlay",
"name": "Waveform Overlay",
"description": "Draws a lightweight luma waveform overlay along the bottom of the video.",
"category": "Utility",
"entryPoint": "shadeVideo",
"parameters": [
{
"id": "overlayHeight",
"label": "Overlay Height",
"type": "float",
"default": 0.32,
"min": 0.1,
"max": 1.0,
"step": 0.01
},
{
"id": "waveformOpacity",
"label": "Waveform Opacity",
"type": "float",
"default": 0.8,
"min": 0.0,
"max": 1.0,
"step": 0.01
},
{
"id": "backgroundOpacity",
"label": "Background",
"type": "float",
"default": 0.35,
"min": 0.0,
"max": 1.0,
"step": 0.01
},
{
"id": "lineThickness",
"label": "Line Thickness",
"type": "float",
"default": 2.0,
"min": 0.5,
"max": 10.0,
"step": 0.1
},
{
"id": "waveformColor",
"label": "Waveform Color",
"type": "color",
"default": [0.2, 1.0, 0.65, 1.0]
}
]
}

View File

@@ -0,0 +1,24 @@
float4 shadeVideo(ShaderContext context)
{
float4 color = context.sourceColor;
float height = saturate(overlayHeight);
float overlayStart = 1.0 - height;
if (context.uv.y < overlayStart)
return color;
float2 scopeUv = float2(context.uv.x, (context.uv.y - overlayStart) / max(height, 0.001));
float luma = dot(sampleVideo(float2(context.uv.x, 0.5)).rgb, float3(0.2126, 0.7152, 0.0722));
float targetY = 1.0 - saturate(luma);
float pixelThickness = max(lineThickness, 0.5) / max(context.outputResolution.y * height, 1.0);
float wave = 1.0 - smoothstep(pixelThickness, pixelThickness * 2.5, abs(scopeUv.y - targetY));
float grid = 0.0;
grid = max(grid, 1.0 - smoothstep(0.002, 0.006, abs(scopeUv.y - 0.25)));
grid = max(grid, 1.0 - smoothstep(0.002, 0.006, abs(scopeUv.y - 0.50)));
grid = max(grid, 1.0 - smoothstep(0.002, 0.006, abs(scopeUv.y - 0.75)));
float3 bg = lerp(color.rgb, float3(0.0, 0.0, 0.0), saturate(backgroundOpacity));
float3 withGrid = lerp(bg, float3(0.16, 0.22, 0.28), grid * 0.5);
float3 scoped = lerp(withGrid, waveformColor.rgb, wave * saturate(waveformOpacity) * waveformColor.a);
return float4(scoped, color.a);
}