From 52bf8c90ea4df3221467267bd17c96828cac9c76 Mon Sep 17 00:00:00 2001 From: Aiden Date: Sun, 3 May 2026 15:44:22 +1000 Subject: [PATCH] Example shaders --- shaders/data-mosh/shader.json | 50 +++++++++++++++++++++++ shaders/data-mosh/shader.slang | 27 +++++++++++++ shaders/false-color/shader.json | 42 +++++++++++++++++++ shaders/false-color/shader.slang | 26 ++++++++++++ shaders/pixelate/shader.json | 33 +++++++++++++++ shaders/pixelate/shader.slang | 12 ++++++ shaders/safe-area-guides/shader.json | 57 ++++++++++++++++++++++++++ shaders/safe-area-guides/shader.slang | 58 +++++++++++++++++++++++++++ shaders/temporal-echo/shader.json | 47 ++++++++++++++++++++++ shaders/temporal-echo/shader.slang | 20 +++++++++ shaders/waveform-overlay/shader.json | 51 +++++++++++++++++++++++ shaders/waveform-overlay/shader.slang | 24 +++++++++++ 12 files changed, 447 insertions(+) create mode 100644 shaders/data-mosh/shader.json create mode 100644 shaders/data-mosh/shader.slang create mode 100644 shaders/false-color/shader.json create mode 100644 shaders/false-color/shader.slang create mode 100644 shaders/pixelate/shader.json create mode 100644 shaders/pixelate/shader.slang create mode 100644 shaders/safe-area-guides/shader.json create mode 100644 shaders/safe-area-guides/shader.slang create mode 100644 shaders/temporal-echo/shader.json create mode 100644 shaders/temporal-echo/shader.slang create mode 100644 shaders/waveform-overlay/shader.json create mode 100644 shaders/waveform-overlay/shader.slang diff --git a/shaders/data-mosh/shader.json b/shaders/data-mosh/shader.json new file mode 100644 index 0000000..4ec19f9 --- /dev/null +++ b/shaders/data-mosh/shader.json @@ -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 + } + ] +} diff --git a/shaders/data-mosh/shader.slang b/shaders/data-mosh/shader.slang new file mode 100644 index 0000000..c56bc3a --- /dev/null +++ b/shaders/data-mosh/shader.slang @@ -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); +} diff --git a/shaders/false-color/shader.json b/shaders/false-color/shader.json new file mode 100644 index 0000000..3d3acf6 --- /dev/null +++ b/shaders/false-color/shader.json @@ -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 + } + ] +} diff --git a/shaders/false-color/shader.slang b/shaders/false-color/shader.slang new file mode 100644 index 0000000..9db5be2 --- /dev/null +++ b/shaders/false-color/shader.slang @@ -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); +} diff --git a/shaders/pixelate/shader.json b/shaders/pixelate/shader.json new file mode 100644 index 0000000..27ccc57 --- /dev/null +++ b/shaders/pixelate/shader.json @@ -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] + } + ] +} diff --git a/shaders/pixelate/shader.slang b/shaders/pixelate/shader.slang new file mode 100644 index 0000000..3924aec --- /dev/null +++ b/shaders/pixelate/shader.slang @@ -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; +} diff --git a/shaders/safe-area-guides/shader.json b/shaders/safe-area-guides/shader.json new file mode 100644 index 0000000..a8bb6fe --- /dev/null +++ b/shaders/safe-area-guides/shader.json @@ -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 + } + ] +} diff --git a/shaders/safe-area-guides/shader.slang b/shaders/safe-area-guides/shader.slang new file mode 100644 index 0000000..3aba354 --- /dev/null +++ b/shaders/safe-area-guides/shader.slang @@ -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); +} diff --git a/shaders/temporal-echo/shader.json b/shaders/temporal-echo/shader.json new file mode 100644 index 0000000..165c244 --- /dev/null +++ b/shaders/temporal-echo/shader.json @@ -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] + } + ] +} diff --git a/shaders/temporal-echo/shader.slang b/shaders/temporal-echo/shader.slang new file mode 100644 index 0000000..908bdb2 --- /dev/null +++ b/shaders/temporal-echo/shader.slang @@ -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); +} diff --git a/shaders/waveform-overlay/shader.json b/shaders/waveform-overlay/shader.json new file mode 100644 index 0000000..877d70f --- /dev/null +++ b/shaders/waveform-overlay/shader.json @@ -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] + } + ] +} diff --git a/shaders/waveform-overlay/shader.slang b/shaders/waveform-overlay/shader.slang new file mode 100644 index 0000000..fa913e5 --- /dev/null +++ b/shaders/waveform-overlay/shader.slang @@ -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); +}