more shaders and updates/changes
All checks were successful
CI / React UI Build (push) Successful in 10s
CI / Native Windows Build And Tests (push) Successful in 2m22s
CI / Windows Release Package (push) Successful in 2m35s

This commit is contained in:
2026-05-08 20:32:19 +10:00
parent 163d70e9bd
commit 6ea6971dd6
9 changed files with 589 additions and 14 deletions

View File

@@ -0,0 +1,102 @@
{
"id": "crt-bulge",
"name": "CRT Bulge",
"description": "Warps the image like convex CRT glass, with optional rounded screen edges and vignette darkening.",
"category": "Distortion",
"entryPoint": "shadeVideo",
"parameters": [
{
"id": "bulgeAmount",
"label": "Bulge",
"type": "float",
"default": -0.04,
"min": -0.5,
"max": 0.8,
"step": 0.01,
"description": "Positive values swell the center outward; negative values pinch it inward."
},
{
"id": "zoom",
"label": "Zoom",
"type": "float",
"default": 1.04,
"min": 0.5,
"max": 2,
"step": 0.01,
"description": "Scales the source before distortion, useful for hiding warped edges."
},
{
"id": "edgeRoundness",
"label": "Edge Roundness",
"type": "float",
"default": 0.08,
"min": 0,
"max": 0.35,
"step": 0.01,
"description": "Rounds the visible screen corners like older CRT glass."
},
{
"id": "edgeFeather",
"label": "Edge Feather",
"type": "float",
"default": 2,
"min": 0,
"max": 24,
"step": 0.1,
"description": "Softens the rounded screen edge in pixels."
},
{
"id": "sourceEdgeFeather",
"label": "Source Edge Feather",
"type": "float",
"default": 1.5,
"min": 0,
"max": 16,
"step": 0.1,
"description": "Antialiases warped source edges when the distortion reveals outside-frame pixels."
},
{
"id": "vignetteAmount",
"label": "Vignette",
"type": "float",
"default": 0.18,
"min": 0,
"max": 1,
"step": 0.01,
"description": "Darkens the glass toward the screen edges."
},
{
"id": "edgeMode",
"label": "Edge Mode",
"type": "enum",
"default": "black",
"options": [
{
"value": "black",
"label": "Black"
},
{
"value": "clamp",
"label": "Clamp"
},
{
"value": "mirror",
"label": "Mirror"
}
],
"description": "Chooses how warped samples outside the source frame are filled."
},
{
"id": "outsideColor",
"label": "Outside Color",
"type": "color",
"default": [
0,
0,
0,
1
],
"description": "Color used outside the curved screen or source frame."
}
]
}

View File

@@ -0,0 +1,71 @@
float mirroredCoordinate(float coordinate)
{
float wrapped = frac(coordinate * 0.5) * 2.0;
return wrapped <= 1.0 ? wrapped : 2.0 - wrapped;
}
float roundedBoxMask(float2 point, float2 halfSize, float radius, float feather)
{
float2 distanceToEdge = abs(point) - (halfSize - radius);
float outsideDistance = length(max(distanceToEdge, float2(0.0, 0.0))) - radius;
float insideDistance = min(max(distanceToEdge.x, distanceToEdge.y), 0.0);
float signedDistance = outsideDistance + insideDistance;
return 1.0 - smoothstep(0.0, max(feather, 0.00001), signedDistance);
}
float sourceBoundsMask(float2 uv, float2 resolution)
{
float2 pixel = 1.0 / max(resolution, float2(1.0, 1.0));
float2 feather = pixel * max(sourceEdgeFeather, 0.0);
float left = smoothstep(0.0, max(feather.x, 0.00001), uv.x);
float right = 1.0 - smoothstep(1.0 - max(feather.x, 0.00001), 1.0, uv.x);
float top = smoothstep(0.0, max(feather.y, 0.00001), uv.y);
float bottom = 1.0 - smoothstep(1.0 - max(feather.y, 0.00001), 1.0, uv.y);
return saturate(left * right * top * bottom);
}
float2 applyBulge(float2 uv, float2 resolution)
{
float2 centered = uv * 2.0 - 1.0;
float aspect = resolution.x / max(resolution.y, 1.0);
float2 aspectCentered = float2(centered.x * aspect, centered.y);
float radiusSq = dot(aspectCentered, aspectCentered);
float amount = clamp(bulgeAmount, -0.95, 0.95);
float scale = 1.0 / max(1.0 + amount * radiusSq, 0.05);
return centered * scale / max(zoom, 0.001) * 0.5 + 0.5;
}
float4 sampleWarped(float2 uv, float2 resolution, out bool insideSource)
{
insideSource = uv.x >= 0.0 && uv.x <= 1.0 && uv.y >= 0.0 && uv.y <= 1.0;
if (edgeMode == 1)
return sampleVideo(clamp(uv, 0.0, 1.0));
if (edgeMode == 2)
return sampleVideo(float2(mirroredCoordinate(uv.x), mirroredCoordinate(uv.y)));
float edgeMask = sourceBoundsMask(uv, resolution);
float4 color = sampleVideo(clamp(uv, 0.0, 1.0));
return lerp(outsideColor, color, edgeMask);
}
float4 shadeVideo(ShaderContext context)
{
float2 resolution = max(context.outputResolution, float2(1.0, 1.0));
float2 sourceUv = applyBulge(context.uv, resolution);
bool insideSource = false;
float4 color = sampleWarped(sourceUv, resolution, insideSource);
float2 centered = context.uv * 2.0 - 1.0;
float feather = max(edgeFeather, 0.0) / min(resolution.x, resolution.y);
float screenMask = roundedBoxMask(centered, float2(1.0, 1.0), saturate(edgeRoundness), feather);
color = lerp(outsideColor, color, screenMask);
float2 aspectCentered = float2(centered.x * resolution.x / max(resolution.y, 1.0), centered.y);
float edgeDistance = saturate(length(aspectCentered) * 0.72);
float vignette = lerp(1.0, 1.0 - saturate(vignetteAmount), smoothstep(0.35, 1.05, edgeDistance));
color.rgb *= vignette;
return saturate(color);
}