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

This commit is contained in:
2026-05-06 14:50:00 +10:00
parent 70be7312b8
commit e5221b329f
6 changed files with 264 additions and 13 deletions

View File

@@ -41,15 +41,6 @@
"max": 3.0,
"step": 0.01
},
{
"id": "pixelFilter",
"label": "Pixel Filter",
"type": "float",
"default": 745.0,
"min": 120.0,
"max": 1600.0,
"step": 1.0
},
{
"id": "contrast",
"label": "Contrast",

View File

@@ -1,10 +1,8 @@
float4 balatroSwirl(float2 screenSize, float2 screenCoords, float time)
{
const float pi = 3.14159265359;
float safePixelFilter = max(pixelFilter, 1.0);
float safeScreenLength = max(length(screenSize), 1.0);
float pixelSize = safeScreenLength / safePixelFilter;
float2 uv = (floor(screenCoords * (1.0 / pixelSize)) * pixelSize - 0.5 * screenSize) / safeScreenLength - offset;
float2 uv = (screenCoords - 0.5 * screenSize) / safeScreenLength - offset;
float uvLength = length(uv);
float speed = spinRotation * spinEase * 0.2;

View File

@@ -0,0 +1,122 @@
{
"id": "xyla-exposure-chart",
"name": "XYLA Exposure Chart",
"description": "Procedural grayscale exposure chart inspired by XYLA-style dynamic range charts, with each patch one stop brighter than the previous.",
"category": "Calibration",
"entryPoint": "shadeVideo",
"parameters": [
{
"id": "patchCount",
"label": "Patch Count",
"type": "float",
"default": 15.0,
"min": 2.0,
"max": 21.0,
"step": 1.0
},
{
"id": "baseLevel",
"label": "Base Level",
"type": "float",
"default": 0.00006103515625,
"min": 0.000001,
"max": 0.01,
"step": 0.000001
},
{
"id": "peakLevel",
"label": "Peak Level",
"type": "float",
"default": 1.0,
"min": 0.01,
"max": 1.0,
"step": 0.001
},
{
"id": "gammaEncode",
"label": "Display Gamma",
"type": "float",
"default": 1.0,
"min": 1.0,
"max": 2.6,
"step": 0.01
},
{
"id": "toneCurve",
"label": "Tone Curve",
"type": "enum",
"default": "rec709",
"options": [
{
"value": "linear",
"label": "Linear"
},
{
"value": "gamma",
"label": "Display Gamma"
},
{
"value": "rec709",
"label": "Rec.709"
}
]
},
{
"id": "chartScale",
"label": "Chart Scale",
"type": "float",
"default": 0.86,
"min": 0.25,
"max": 1.0,
"step": 0.01
},
{
"id": "gapSize",
"label": "Gap Size",
"type": "float",
"default": 0.18,
"min": 0.0,
"max": 0.45,
"step": 0.01
},
{
"id": "vertical",
"label": "Vertical",
"type": "bool",
"default": false
},
{
"id": "reverseOrder",
"label": "Reverse Order",
"type": "bool",
"default": false
},
{
"id": "backgroundLevel",
"label": "Background",
"type": "float",
"default": 0.0,
"min": 0.0,
"max": 0.2,
"step": 0.001
},
{
"id": "borderLevel",
"label": "Border",
"type": "float",
"default": 0.08,
"min": 0.0,
"max": 1.0,
"step": 0.001
},
{
"id": "sourceMix",
"label": "Source Mix",
"type": "float",
"default": 0.0,
"min": 0.0,
"max": 1.0,
"step": 0.01
}
]
}

View File

@@ -0,0 +1,78 @@
float boxMask(float2 point, float2 halfSize, float feather)
{
float2 distanceToEdge = abs(point) - halfSize;
float outsideDistance = length(max(distanceToEdge, float2(0.0, 0.0)));
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 rec709Oetf(float linearLevel)
{
float value = saturate(linearLevel);
if (value < 0.018)
return 4.5 * value;
return 1.099 * pow(value, 0.45) - 0.099;
}
float applyToneCurve(float linearLevel)
{
float value = saturate(linearLevel);
if (toneCurve == 1)
{
float safeGamma = max(gammaEncode, 0.001);
return pow(value, 1.0 / safeGamma);
}
if (toneCurve == 2)
return rec709Oetf(value);
return value;
}
float patchBrightness(int patchIndex, int count)
{
int clampedIndex = clamp(patchIndex, 0, max(count - 1, 0));
float linearLevel = baseLevel * exp2(float(clampedIndex));
linearLevel = min(linearLevel, peakLevel);
return applyToneCurve(linearLevel);
}
float4 shadeVideo(ShaderContext context)
{
float2 resolution = max(context.outputResolution, float2(1.0, 1.0));
float2 uv = saturate(context.uv);
float2 centered = uv - 0.5;
float feather = 1.5 / min(resolution.x, resolution.y);
int count = int(clamp(round(patchCount), 2.0, 21.0));
float2 chartHalfSize = vertical
? float2(0.18, 0.46) * chartScale
: float2(0.46, 0.18) * chartScale;
float chartMask = boxMask(centered, chartHalfSize, feather);
float borderMask = chartMask - boxMask(centered, max(chartHalfSize - float2(feather * 3.0, feather * 3.0), float2(0.0, 0.0)), feather);
float axis = vertical ? centered.y : centered.x;
float crossAxis = vertical ? centered.x : centered.y;
float axisHalfSize = vertical ? chartHalfSize.y : chartHalfSize.x;
float crossHalfSize = vertical ? chartHalfSize.x : chartHalfSize.y;
float normalizedAxis = (axis + axisHalfSize) / max(axisHalfSize * 2.0, 0.0001);
float patchPosition = clamp(normalizedAxis, 0.0, 0.999999) * float(count);
int patchIndex = int(floor(patchPosition));
if (reverseOrder)
patchIndex = count - 1 - patchIndex;
float patchSlotCenter = (floor(patchPosition) + 0.5) / float(count);
float localAxis = abs(normalizedAxis - patchSlotCenter) * float(count) * 2.0;
float safeGapSize = saturate(gapSize);
float axisMask = 1.0 - smoothstep(1.0 - safeGapSize, 1.0 - safeGapSize + feather * float(count) * 2.0, localAxis);
float crossMask = 1.0 - smoothstep(crossHalfSize, crossHalfSize + feather, abs(crossAxis));
float insideAxis = step(0.0, normalizedAxis) * step(normalizedAxis, 1.0);
float patchMask = axisMask * crossMask * insideAxis;
float level = patchBrightness(patchIndex, count);
float chartBackground = saturate(backgroundLevel) * chartMask;
float border = saturate(borderLevel) * borderMask;
float grayscale = max(max(chartBackground, border), level * patchMask);
float4 chartColor = float4(grayscale, grayscale, grayscale, 1.0);
return saturate(lerp(chartColor, context.sourceColor, sourceMix));
}