diff --git a/README.md b/README.md index 510f6a2..3efe5d1 100644 --- a/README.md +++ b/README.md @@ -248,4 +248,4 @@ If `SLANG_ROOT` is not set, the workflow falls back to the repo-local default un - compute shaders or a small 1x1 or nx1 RGBA16f render target for abritary data store - allow shaders to read other shaders data store based on name? or output over OSC - Mipmappong for shader declared textures -- unwrap a fish eyelens and mirror it and map it to equirectangulr for environmnet map purposes +- Multipass for shaders at request diff --git a/shaders/greenscreen-key/shader.json b/shaders/greenscreen-key/shader.json index e9c3c98..992fdf5 100644 --- a/shaders/greenscreen-key/shader.json +++ b/shaders/greenscreen-key/shader.json @@ -1,7 +1,7 @@ { "id": "greenscreen-key", "name": "Greenscreen Key", - "description": "Keys out a green screen background and outputs transparent alpha for compositing.", + "description": "Production-style green/blue screen keyer with matte refinement, despill, edge treatment, and debug views.", "category": "Keying", "entryPoint": "shadeVideo", "parameters": [ @@ -16,7 +16,7 @@ }, { "id": "threshold", - "label": "Threshold", + "label": "Screen Gain", "type": "float", "default": 0.24, "min": 0.01, @@ -27,20 +27,29 @@ "id": "softness", "label": "Softness", "type": "float", - "default": 0.12, + "default": 0.16, "min": 0.001, "max": 0.5, "step": 0.005 }, { - "id": "edgeSoftness", - "label": "Edge Softness", + "id": "screenBalance", + "label": "Screen Balance", "type": "float", - "default": 0.08, + "default": 0.5, "min": 0.0, - "max": 0.4, + "max": 1.0, "step": 0.005 }, + { + "id": "screenPreBlur", + "label": "Screen PreBlur", + "type": "float", + "default": 1.0, + "min": 0.0, + "max": 8.0, + "step": 0.1 + }, { "id": "erodeDilate", "label": "Erode/Dilate", @@ -50,6 +59,51 @@ "max": 0.3, "step": 0.005 }, + { + "id": "matteBlur", + "label": "Matte Blur", + "type": "float", + "default": 1.25, + "min": 0.0, + "max": 6.0, + "step": 0.1 + }, + { + "id": "matteGamma", + "label": "Matte Gamma", + "type": "float", + "default": 1.0, + "min": 0.25, + "max": 4.0, + "step": 0.01 + }, + { + "id": "matteContrast", + "label": "Matte Contrast", + "type": "float", + "default": 1.0, + "min": 0.25, + "max": 4.0, + "step": 0.01 + }, + { + "id": "blackCleanup", + "label": "Black Cleanup", + "type": "float", + "default": 0.0, + "min": 0.0, + "max": 1.0, + "step": 0.005 + }, + { + "id": "whiteCleanup", + "label": "White Cleanup", + "type": "float", + "default": 0.0, + "min": 0.0, + "max": 1.0, + "step": 0.005 + }, { "id": "despill", "label": "Despill", @@ -60,14 +114,41 @@ "step": 0.01 }, { - "id": "edgeBoost", - "label": "Edge Boost", + "id": "despillBias", + "label": "Despill Bias", "type": "float", - "default": 0.08, - "min": -0.2, - "max": 0.3, + "default": 0.0, + "min": -0.5, + "max": 0.5, "step": 0.005 }, + { + "id": "spillTint", + "label": "Spill Tint", + "type": "color", + "default": [1.0, 1.0, 1.0, 1.0], + "min": [0.0, 0.0, 0.0, 0.0], + "max": [1.0, 1.0, 1.0, 1.0], + "step": [0.01, 0.01, 0.01, 0.01] + }, + { + "id": "edgeRecover", + "label": "Edge Recover", + "type": "float", + "default": 0.18, + "min": 0.0, + "max": 1.0, + "step": 0.005 + }, + { + "id": "edgeColor", + "label": "Edge Color", + "type": "color", + "default": [1.0, 1.0, 1.0, 1.0], + "min": [0.0, 0.0, 0.0, 0.0], + "max": [1.0, 1.0, 1.0, 1.0], + "step": [0.01, 0.01, 0.01, 0.01] + }, { "id": "clipBlack", "label": "Clip Black", @@ -85,6 +166,19 @@ "min": 0.5, "max": 1.0, "step": 0.005 + }, + { + "id": "viewMode", + "label": "View", + "type": "enum", + "default": "composite", + "options": [ + { "value": "composite", "label": "Composite" }, + { "value": "matte", "label": "Matte" }, + { "value": "spill", "label": "Spill" }, + { "value": "despill", "label": "Despill" }, + { "value": "status", "label": "Status" } + ] } ] } diff --git a/shaders/greenscreen-key/shader.slang b/shaders/greenscreen-key/shader.slang index 646ae5b..bf0de37 100644 --- a/shaders/greenscreen-key/shader.slang +++ b/shaders/greenscreen-key/shader.slang @@ -9,31 +9,167 @@ float luma709(float3 color) return dot(color, float3(0.2126, 0.7152, 0.0722)); } +float2 chroma709(float3 color) +{ + float y = luma709(color); + return float2((color.b - y) * 0.5647, (color.r - y) * 0.7132); +} + +float3 matteSampleColor(float2 uv, ShaderContext context) +{ + float2 pixel = 1.0 / max(context.outputResolution, float2(1.0, 1.0)); + float blur = max(screenPreBlur, 0.0); + float3 center = saturate(sampleVideo(saturate(uv)).rgb); + if (blur <= 0.0001) + return center; + + float2 radius = pixel * blur; + float3 color = center * 0.36; + color += saturate(sampleVideo(saturate(uv + float2(radius.x, 0.0))).rgb) * 0.16; + color += saturate(sampleVideo(saturate(uv - float2(radius.x, 0.0))).rgb) * 0.16; + color += saturate(sampleVideo(saturate(uv + float2(0.0, radius.y))).rgb) * 0.16; + color += saturate(sampleVideo(saturate(uv - float2(0.0, radius.y))).rgb) * 0.16; + return color; +} + +float keyDistanceAt(float2 uv, ShaderContext context) +{ + float3 color = matteSampleColor(uv, context); + float3 keyColor = saturate(screenColor.rgb); + float chromaDistance = distance(chroma709(color), chroma709(keyColor)) * 2.65; + float directionDistance = length(safeNormalize(max(color, float3(0.0001, 0.0001, 0.0001))) - safeNormalize(max(keyColor, float3(0.0001, 0.0001, 0.0001)))) * 0.55; + return lerp(directionDistance, chromaDistance, saturate(screenBalance)); +} + +float rawAlphaAt(float2 uv, ShaderContext context) +{ + float keyDistance = keyDistanceAt(uv, context); + float matteCenter = threshold + erodeDilate; + float matteFeather = max(softness, 0.0005); + float alpha = smoothstep(matteCenter - matteFeather, matteCenter + matteFeather, keyDistance); + return saturate(alpha); +} + +float refinedAlphaAt(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 alpha = centerAlpha * 0.30; + + if (aaRadius > 0.0001) + { + float2 radius = pixel * aaRadius; + float2 halfRadius = radius * 0.5; + float alphaMin = centerAlpha; + float alphaMax = centerAlpha; + float sampleAlpha = rawAlphaAt(uv + float2(halfRadius.x, 0.0), context); + alpha += sampleAlpha * 0.065; + alphaMin = min(alphaMin, sampleAlpha); + alphaMax = max(alphaMax, sampleAlpha); + sampleAlpha = rawAlphaAt(uv - float2(halfRadius.x, 0.0), context); + alpha += sampleAlpha * 0.065; + alphaMin = min(alphaMin, sampleAlpha); + alphaMax = max(alphaMax, sampleAlpha); + sampleAlpha = rawAlphaAt(uv + float2(0.0, halfRadius.y), context); + alpha += sampleAlpha * 0.065; + alphaMin = min(alphaMin, sampleAlpha); + alphaMax = max(alphaMax, sampleAlpha); + sampleAlpha = rawAlphaAt(uv - float2(0.0, halfRadius.y), context); + alpha += sampleAlpha * 0.065; + alphaMin = min(alphaMin, sampleAlpha); + alphaMax = max(alphaMax, sampleAlpha); + sampleAlpha = rawAlphaAt(uv + float2(radius.x, 0.0), context); + alpha += sampleAlpha * 0.06; + alphaMin = min(alphaMin, sampleAlpha); + alphaMax = max(alphaMax, sampleAlpha); + sampleAlpha = rawAlphaAt(uv - float2(radius.x, 0.0), context); + alpha += sampleAlpha * 0.06; + alphaMin = min(alphaMin, sampleAlpha); + alphaMax = max(alphaMax, sampleAlpha); + sampleAlpha = rawAlphaAt(uv + float2(0.0, radius.y), context); + alpha += sampleAlpha * 0.06; + alphaMin = min(alphaMin, sampleAlpha); + alphaMax = max(alphaMax, sampleAlpha); + sampleAlpha = rawAlphaAt(uv - float2(0.0, radius.y), context); + alpha += sampleAlpha * 0.06; + alphaMin = min(alphaMin, sampleAlpha); + alphaMax = max(alphaMax, sampleAlpha); + sampleAlpha = rawAlphaAt(uv + radius, context); + alpha += sampleAlpha * 0.05; + alphaMin = min(alphaMin, sampleAlpha); + alphaMax = max(alphaMax, sampleAlpha); + sampleAlpha = rawAlphaAt(uv - radius, context); + alpha += sampleAlpha * 0.05; + alphaMin = min(alphaMin, sampleAlpha); + alphaMax = max(alphaMax, sampleAlpha); + sampleAlpha = rawAlphaAt(uv + float2(radius.x, -radius.y), context); + alpha += sampleAlpha * 0.05; + alphaMin = min(alphaMin, sampleAlpha); + alphaMax = max(alphaMax, sampleAlpha); + sampleAlpha = rawAlphaAt(uv + float2(-radius.x, radius.y), context); + alpha += sampleAlpha * 0.05; + alphaMin = min(alphaMin, sampleAlpha); + alphaMax = max(alphaMax, sampleAlpha); + + alpha = lerp(alpha, alphaMin, saturate(blackCleanup)); + alpha = lerp(alpha, alphaMax, saturate(whiteCleanup)); + } + else + { + alpha = rawAlphaAt(uv, context); + } + + alpha = saturate((alpha - clipBlack) / max(clipWhite - clipBlack, 0.0001)); + alpha = saturate((alpha - 0.5) * max(matteContrast, 0.0001) + 0.5); + alpha = pow(max(alpha, 0.0), max(matteGamma, 0.0001)); + return saturate(alpha); +} + +float spillAmountForColor(float3 color) +{ + float3 keyColor = saturate(screenColor.rgb); + float keyComponent = dot(color, safeNormalize(max(keyColor, float3(0.0001, 0.0001, 0.0001)))); + float opposingComponent = max(max(color.r * (1.0 - keyColor.r), color.g * (1.0 - keyColor.g)), color.b * (1.0 - keyColor.b)); + return saturate(keyComponent - opposingComponent + despillBias); +} + +float3 despillColor(float3 color, float alpha) +{ + float3 keyColor = safeNormalize(max(screenColor.rgb, float3(0.0001, 0.0001, 0.0001))); + float spill = spillAmountForColor(color) * despill * (1.0 - alpha * 0.35); + float neutral = luma709(color); + float3 neutralized = color - keyColor * spill; + neutralized = max(neutralized, float3(0.0, 0.0, 0.0)); + neutralized = lerp(neutralized, float3(neutral, neutral, neutral), spill * 0.18); + neutralized = lerp(neutralized, neutralized * saturate(spillTint.rgb), saturate(spill)); + return saturate(neutralized); +} + float4 shadeVideo(ShaderContext context) { float4 src = context.sourceColor; float3 color = saturate(src.rgb); + float alpha = refinedAlphaAt(context.uv, context); + float spill = spillAmountForColor(color); + float3 despilled = despillColor(color, alpha); - float3 keyColor = safeNormalize(max(screenColor.rgb, float3(0.0001, 0.0001, 0.0001))); - float3 sampleColor = safeNormalize(max(color, float3(0.0001, 0.0001, 0.0001))); + 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)); - float chromaDistance = length(sampleColor - keyColor); - float matteCenter = threshold - erodeDilate; - float matteFeather = max(softness + edgeSoftness, 0.0005); - float alpha = smoothstep(matteCenter - matteFeather, matteCenter + matteFeather, chromaDistance); - - alpha = saturate((alpha - clipBlack) / max(clipWhite - clipBlack, 0.0001)); - alpha = saturate(alpha + edgeBoost); - - float greenExcess = max(0.0, color.g - max(color.r, color.b)); - float spillReduction = greenExcess * despill; - - float3 despilled = color; - despilled.g = max(0.0, despilled.g - spillReduction); - - float neutral = luma709(despilled); - despilled.rb += spillReduction * 0.25; - despilled = lerp(float3(neutral, neutral, neutral), despilled, 0.92); + if (viewMode == 1) + return float4(alpha, alpha, alpha, 1.0); + if (viewMode == 2) + return float4(spill, spill * 0.55, 0.0, 1.0); + if (viewMode == 3) + return float4(despilled, 1.0); + if (viewMode == 4) + { + float rawAlpha = rawAlphaAt(context.uv, context); + return float4(rawAlpha, alpha, spill, 1.0); + } float3 premultiplied = saturate(despilled) * alpha; return float4(premultiplied, alpha);