Annotations
This commit is contained in:
@@ -6,6 +6,8 @@ float4 balatroSwirl(float2 screenSize, float2 screenCoords, float time, float se
|
||||
float2 uv = (screenCoords - 0.5 * screenSize) / safeScreenLength - offset - seedOffset;
|
||||
float uvLength = length(uv);
|
||||
|
||||
// First warp: convert to polar space and twist the angle more near the
|
||||
// center, creating the large spiral motion.
|
||||
float speed = spinRotation * spinEase * 0.2;
|
||||
if (isRotate)
|
||||
speed = time * speed;
|
||||
@@ -19,6 +21,8 @@ float4 balatroSwirl(float2 screenSize, float2 screenCoords, float time, float se
|
||||
speed = (time + seed * 17.0) * spinSpeed;
|
||||
float2 uv2 = float2(uv.x + uv.y, uv.x + uv.y);
|
||||
|
||||
// Second warp: a short iterative feedback loop turns the spiral into
|
||||
// painterly bands while preserving a fixed compile-time loop bound.
|
||||
for (int i = 0; i < 5; ++i)
|
||||
{
|
||||
uv2 += float2(sin(max(uv.x, uv.y)), sin(max(uv.x, uv.y))) + uv;
|
||||
@@ -32,6 +36,8 @@ float4 balatroSwirl(float2 screenSize, float2 screenCoords, float time, float se
|
||||
float c1p = max(0.0, 1.0 - contrastMod * abs(1.0 - paintRes));
|
||||
float c2p = max(0.0, 1.0 - contrastMod * abs(paintRes));
|
||||
float c3p = 1.0 - min(1.0, c1p + c2p);
|
||||
// Three soft band weights drive the palette; lighting rides on the brightest
|
||||
// bands so the swirl keeps dimensional highlights.
|
||||
float light = (lighting - 0.2) * max(c1p * 5.0 - 4.0, 0.0) + lighting * max(c2p * 5.0 - 4.0, 0.0);
|
||||
|
||||
float safeContrast = max(contrast, 0.001);
|
||||
|
||||
@@ -31,6 +31,8 @@ float normalizedFisheyeRadius(float theta, float halfFov)
|
||||
{
|
||||
float safeHalfFov = max(halfFov, 0.0001);
|
||||
|
||||
// Match common fisheye projection families while keeping the selected FOV
|
||||
// normalized to the same source-image radius.
|
||||
if (fisheyeModel == 1)
|
||||
{
|
||||
return sin(theta * 0.5) / max(sin(safeHalfFov * 0.5), 0.0001);
|
||||
@@ -49,6 +51,7 @@ float normalizedFisheyeRadius(float theta, float halfFov)
|
||||
|
||||
float3 equirectangularRay(float2 uv)
|
||||
{
|
||||
// Convert equirectangular UVs into longitude/latitude on the unit sphere.
|
||||
float longitude = (uv.x - 0.5) * TWO_PI;
|
||||
float latitude = (0.5 - uv.y) * PI;
|
||||
float latitudeCos = cos(latitude);
|
||||
@@ -82,6 +85,8 @@ float4 sampleEdgeFilledVideo(float2 sourceUv, ShaderContext context)
|
||||
float inwardLength = max(length(inward), 0.000001);
|
||||
inward /= inwardLength;
|
||||
|
||||
// Outside the fisheye image, sample back inward from the nearest edge so the
|
||||
// fill looks like stretched lens content instead of a hard color plate.
|
||||
float blurDistance = max(edgeBlur, 0.0);
|
||||
float4 color = sampleVideo(clampedUv) * 0.32;
|
||||
color += sampleVideo(saturate(clampedUv + inward * blurDistance * 0.35)) * 0.26;
|
||||
@@ -114,6 +119,7 @@ float4 shadeVideo(ShaderContext context)
|
||||
float phi = atan2(ray.y, ray.x);
|
||||
float fisheyeRadius = normalizedFisheyeRadius(theta, halfFov);
|
||||
|
||||
// Project the mirrored sphere ray back into the circular fisheye source.
|
||||
float2 sourceUv = float2(
|
||||
center.x + cos(phi) * fisheyeRadius * radius.x,
|
||||
center.y - sin(phi) * fisheyeRadius * radius.y
|
||||
|
||||
@@ -43,6 +43,8 @@ float normalizedFisheyeRadius(float theta, float halfFov)
|
||||
{
|
||||
float safeHalfFov = max(halfFov, 0.0001);
|
||||
|
||||
// Different fisheye lenses map angle to image radius differently. Normalize
|
||||
// each model by the selected half-FOV so the outer lens edge stays at 1.0.
|
||||
if (fisheyeModel == 1)
|
||||
{
|
||||
return sin(theta * 0.5) / max(sin(safeHalfFov * 0.5), 0.0001);
|
||||
@@ -67,6 +69,8 @@ float4 shadeVideo(ShaderContext context)
|
||||
float virtualFov = radiansFromDegrees(clamp(virtualFovDegrees, 1.0, 175.0));
|
||||
float tanHalfFov = tan(virtualFov * 0.5);
|
||||
|
||||
// Build a virtual output-camera ray, then rotate it into the fisheye lens
|
||||
// coordinate system before asking where that ray lands on the source image.
|
||||
float3 ray = outputProjection == 1
|
||||
? buildCylindricalRay(screen, outputAspect, tanHalfFov)
|
||||
: buildRectilinearRay(screen, outputAspect, tanHalfFov);
|
||||
@@ -86,6 +90,7 @@ float4 shadeVideo(ShaderContext context)
|
||||
float phi = atan2(ray.y, ray.x);
|
||||
float fisheyeRadius = normalizedFisheyeRadius(theta, halfFov);
|
||||
|
||||
// Polar lens coordinates become UVs inside the circular fisheye image.
|
||||
float2 sourceUv = float2(
|
||||
center.x + cos(phi) * fisheyeRadius * radius.x,
|
||||
center.y - sin(phi) * fisheyeRadius * radius.y
|
||||
|
||||
@@ -23,6 +23,8 @@ float3 matteSampleColor(float2 uv, ShaderContext context)
|
||||
if (blur <= 0.0001)
|
||||
return center;
|
||||
|
||||
// Pre-blur only the color used for screen comparison; the final image keeps
|
||||
// its original detail and alpha is refined in a later pass.
|
||||
float2 radius = pixel * blur;
|
||||
float3 color = center * 0.36;
|
||||
color += saturate(sampleVideo(saturate(uv + float2(radius.x, 0.0))).rgb) * 0.16;
|
||||
@@ -37,6 +39,8 @@ 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;
|
||||
// Direction distance is less sensitive to brightness, while chroma distance
|
||||
// follows broadcast-style color difference; screenBalance blends the two.
|
||||
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));
|
||||
}
|
||||
@@ -65,6 +69,8 @@ float refinedAlphaFromMatte(float2 uv, ShaderContext context)
|
||||
|
||||
if (aaRadius > 0.0001)
|
||||
{
|
||||
// A small fixed kernel smooths edges and collects min/max alpha for
|
||||
// black/white cleanup without needing dynamic loops or arrays.
|
||||
float2 radius = pixel * aaRadius;
|
||||
float2 halfRadius = radius * 0.5;
|
||||
float alphaMin = centerAlpha;
|
||||
@@ -126,6 +132,8 @@ float refinedAlphaFromMatte(float2 uv, ShaderContext context)
|
||||
alpha = centerAlpha;
|
||||
}
|
||||
|
||||
// Final matte shaping happens after blur/cleanup so clip and contrast affect
|
||||
// the refined edge rather than the raw screen-distance estimate.
|
||||
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));
|
||||
@@ -135,6 +143,8 @@ float refinedAlphaFromMatte(float2 uv, ShaderContext context)
|
||||
float spillAmountForColor(float3 color)
|
||||
{
|
||||
float3 keyColor = saturate(screenColor.rgb);
|
||||
// Measure spill as color energy aligned with the screen color minus the
|
||||
// strongest opposing channel, leaving neutral highlights mostly intact.
|
||||
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);
|
||||
@@ -187,6 +197,8 @@ float4 applyKey(ShaderContext context)
|
||||
float cropMask = cropMaskAt(context.uv, context);
|
||||
alpha *= cropMask;
|
||||
|
||||
// Edge recovery is strongest around 50% alpha, where fringing usually lives,
|
||||
// and fades away for solid foreground/background pixels.
|
||||
float edgeAmount = saturate(1.0 - abs(alpha * 2.0 - 1.0));
|
||||
despilled = lerp(despilled, despilled * saturate(edgeColor.rgb), edgeAmount * saturate(edgeRecover));
|
||||
|
||||
|
||||
@@ -36,6 +36,8 @@ float4 shadeVideo(ShaderContext context)
|
||||
float4 accumulated = float4(0.0, 0.0, 0.0, 0.0);
|
||||
float clampedSteps = clamp(raySteps, 1.0, 77.0);
|
||||
|
||||
// Ray-march a folded procedural field. distanceToSurface advances the ray,
|
||||
// while inverse-distance accumulation creates the glowing filaments.
|
||||
for (int i = 0; i < 77; ++i)
|
||||
{
|
||||
if (float(i) >= clampedSteps)
|
||||
@@ -49,11 +51,14 @@ float4 shadeVideo(ShaderContext context)
|
||||
position.xy = mul(rotateAroundZ(2.0 + originalPosition.z), position.xy);
|
||||
position.xy = mul(happyAccidentMatrix(originalPosition, timeCos), position.xy);
|
||||
|
||||
// Color comes from pre-fold space so the palette varies smoothly even as
|
||||
// the geometry folds into repeated cells.
|
||||
float colorSeed = 0.5 * originalPosition.z + length(position - originalPosition);
|
||||
float4 palette = 1.0 + sin(colorSeed + float4(0.0, 4.0, 3.0, 6.0));
|
||||
palette /= 0.55 + 1.55 * dot(originalPosition.xy, originalPosition.xy);
|
||||
|
||||
position = abs(frac(position) - 0.5);
|
||||
// Distance to a tiny box/cross primitive inside each repeated cell.
|
||||
distanceToSurface = abs(min(length(position.xy) - 0.125, min(position.x, position.y) + 0.001)) + 0.001;
|
||||
accumulated += palette.w * palette / distanceToSurface;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@ float3 sampleLutCell(float3 index)
|
||||
float g = floor(index.g + 0.5);
|
||||
float b = floor(index.b + 0.5);
|
||||
|
||||
// The 33^3 cube is packed as blue slices laid horizontally, with red across
|
||||
// each slice and green down the atlas.
|
||||
float atlasWidth = LUT_SIZE * LUT_SIZE;
|
||||
float2 lutUv;
|
||||
lutUv.x = (r + b * LUT_SIZE + 0.5) / atlasWidth;
|
||||
@@ -30,6 +32,9 @@ float3 applyLut33(float3 color)
|
||||
float3 c011 = sampleLutCell(float3(baseIndex.r, nextIndex.g, nextIndex.b));
|
||||
float3 c111 = sampleLutCell(float3(nextIndex.r, nextIndex.g, nextIndex.b));
|
||||
|
||||
// Tetrahedral interpolation chooses one of six paths through the cube.
|
||||
// This avoids the muddy diagonals that simple trilinear LUT sampling can
|
||||
// introduce for strong grades.
|
||||
if (blend.r > blend.g)
|
||||
{
|
||||
if (blend.g > blend.b)
|
||||
@@ -55,6 +60,8 @@ float hash12(float2 value)
|
||||
|
||||
float3 outputDither(float2 pixel)
|
||||
{
|
||||
// Subtract paired hashes to center the dither around zero, then scale to
|
||||
// roughly one 8-bit code value.
|
||||
float r = hash12(pixel + float2(17.0, 31.0)) - hash12(pixel + float2(83.0, 47.0));
|
||||
float g = hash12(pixel + float2(29.0, 71.0)) - hash12(pixel + float2(53.0, 19.0));
|
||||
float b = hash12(pixel + float2(61.0, 11.0)) - hash12(pixel + float2(7.0, 97.0));
|
||||
|
||||
@@ -20,6 +20,8 @@ float4 shadeVideo(ShaderContext context)
|
||||
float2 p = (fragCoord + fragCoord - resolution) / resolution.y / safeScale;
|
||||
p -= center + float2(sin(seed * 6.2831853), cos(seed * 6.2831853)) * 0.035;
|
||||
|
||||
// Build a skewed coordinate system around an offset "black hole" so the
|
||||
// waves pinch and stretch instead of staying radially symmetric.
|
||||
float iterator = 0.2;
|
||||
float2 diagonal = normalize(float2(-1.0 + seed * 0.5, 1.0 - seed * 0.35));
|
||||
float2 blackholeCenter = p - iterator * diagonal;
|
||||
@@ -30,6 +32,8 @@ float4 shadeVideo(ShaderContext context)
|
||||
float2 v = singularitySpiral(c, time, iterator);
|
||||
float2 waves = float2(0.0001, 0.0001);
|
||||
|
||||
// Iterative sine feedback creates the accretion texture; the iterator value
|
||||
// also damps later steps to keep the pattern stable.
|
||||
for (; iterator < 9.0; iterator += 1.0)
|
||||
{
|
||||
waves += 1.0 + sin(v);
|
||||
@@ -40,6 +44,8 @@ float4 shadeVideo(ShaderContext context)
|
||||
float disk = 2.0 + diskRadius * diskRadius * (0.25 * safeTightness) - diskRadius;
|
||||
float centerDarkness = 0.5 + 1.0 / max(dot(c, c), 0.0001);
|
||||
float rim = 0.025 + abs(length(p) - safeRingRadius) * safeTightness;
|
||||
// Exponential falloff turns the accumulated wave field into bright rims and
|
||||
// a darker center without hard thresholds.
|
||||
float4 redBlueGradient = exp(c.x * float4(0.6, -0.4, -1.0, 0.0) * colorShift);
|
||||
float4 waveColor = waves.xyyx;
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@ float2 jumpy(float2 uv, float framecount)
|
||||
float2 look = uv;
|
||||
float m = frac(framecount / 4.0);
|
||||
float dy = look.y - m;
|
||||
// Localize the horizontal tear to a moving scanline window instead of
|
||||
// bending the whole frame equally.
|
||||
float window = 1.0 / (1.0 + 80.0 * dy * dy);
|
||||
look.x += 0.05 * sin(look.y * 10.0 + framecount) / 20.0 * onOff(4.0, 4.0, 0.3, framecount) * (0.5 + cos(framecount * 20.0)) * window;
|
||||
float vShift = (0.1 * wiggle) * 0.4 * onOff(2.0, 3.0, 0.9, framecount) * (sin(framecount) * sin(framecount * 20.0) + (0.5 + 0.1 * sin(framecount * 200.0) * cos(framecount)));
|
||||
@@ -59,6 +61,8 @@ float grainScalar(float2 uv)
|
||||
float3 animatedChromaGrain(float2 uv, float time, float2 outputResolution, float grainSize)
|
||||
{
|
||||
float safeGrainSize = max(grainSize, 0.001);
|
||||
// Quantize the coordinates first so larger grain sizes become visible
|
||||
// chroma blocks rather than simply lower-frequency smooth noise.
|
||||
float2 baseUv = uv * outputResolution * float2(0.85, 0.95) / safeGrainSize;
|
||||
float2 grainUv = floor(baseUv) + 0.5;
|
||||
float2 drift = float2(time * 19.7, time * 23.3);
|
||||
@@ -87,6 +91,8 @@ float valueNoise2(float2 p)
|
||||
float tapeLineNoise(float2 uv, float time, float2 outputResolution)
|
||||
{
|
||||
float y = floor(uv.y * outputResolution.y);
|
||||
// Combine stable per-line noise with frame-rate noise so bands have both
|
||||
// slow tape wander and fast electronic shimmer.
|
||||
float slowLine = valueNoise2(float2(y * 0.021, floor(time * 10.0)));
|
||||
float fastLine = noiseHash(float2(y * 1.73, floor(time * 59.94)));
|
||||
float line = (slowLine * 0.7 + fastLine * 0.3) * 2.0 - 1.0;
|
||||
@@ -102,6 +108,8 @@ float3 analogStatic(float2 uv, float time, float2 outputResolution)
|
||||
float frame = floor(time * 59.94);
|
||||
float seed = frac(time);
|
||||
|
||||
// Several differently skewed hashes keep the snow from forming obvious
|
||||
// diagonal or grid patterns at broadcast frame cadence.
|
||||
float2 goldPixel = pixel + float2(0.37, 0.61) + frame;
|
||||
float snowA = goldNoise(goldPixel, seed + 0.1);
|
||||
float snowB = goldNoise(goldPixel * float2(0.37, 2.11) + float2(19.0, 41.0), seed + 0.2);
|
||||
@@ -146,6 +154,8 @@ float3 blurVhs(float2 uv, float d, int sampleCount)
|
||||
float2 pixelOffset = float2(d, 0.0);
|
||||
float2 scale = 0.66 * 8.0 * pixelOffset;
|
||||
|
||||
// The circular tap pattern approximates soft tape smear while keeping the
|
||||
// maximum loop bound fixed for shader compilation.
|
||||
for (int i = 0; i < 15; ++i)
|
||||
{
|
||||
if (i >= sampleCount)
|
||||
@@ -170,6 +180,8 @@ float4 buildTapeSmear(ShaderContext context)
|
||||
float framecount = frac(time * wiggleSpeed / 7.0) * 7.0;
|
||||
int sampleCount = int(clamp(blurSamples, 3.0, 15.0) + 0.5);
|
||||
|
||||
// Split the source into YIQ, smear each component by a different amount,
|
||||
// then recombine to mimic luma/chroma bandwidth mismatch on tape.
|
||||
float d = 0.1 - round(frac(time / 3.0)) * 0.1;
|
||||
uv = jumpy(uv, framecount);
|
||||
float s = 0.0001 * -d + 0.0001 * wiggle * sin(time * wiggleSpeed);
|
||||
@@ -202,6 +214,8 @@ float4 finishVhs(ShaderContext context)
|
||||
float time = distortedTapeTime(context);
|
||||
float3 color = sampleVideo(context.uv).rgb;
|
||||
|
||||
// Radial red/blue offsets create lens and deck misregistration before the
|
||||
// wider tape effects are layered in.
|
||||
float2 centered = context.uv * 2.0 - 1.0;
|
||||
centered.x *= context.outputResolution.x / max(context.outputResolution.y, 1.0);
|
||||
float2 aberrationOffset = centered * (aberrationAmount * 0.0015);
|
||||
@@ -219,6 +233,8 @@ float4 finishVhs(ShaderContext context)
|
||||
float halationMask = smoothstep(0.45, 1.0, halationLuma) * halationAmount;
|
||||
color += halationSource * float3(1.0, 0.38, 0.24) * halationMask * 0.35;
|
||||
|
||||
// Bloom and fade are applied as separate layers so highlights glow without
|
||||
// flattening the full picture into the faded black level.
|
||||
float3 bloomSource = softBloom(context.uv, context.outputResolution, 2.0 + smear * 2.5);
|
||||
float bloomLuma = dot(bloomSource, float3(0.299, 0.587, 0.114));
|
||||
float bloomMask = smoothstep(0.32, 1.0, bloomLuma) * bloomAmount;
|
||||
@@ -229,6 +245,8 @@ float4 finishVhs(ShaderContext context)
|
||||
float luma = dot(color, float3(0.299, 0.587, 0.114));
|
||||
float noiseMask = lerp(0.65, 1.0, 1.0 - saturate(luma));
|
||||
float chunkiness = lerp(1.0, 2.4, saturate((noiseSize - 1.0) / 5.0));
|
||||
// Push darker regions harder: analog noise reads most naturally in shadows
|
||||
// and avoids washing out bright highlights.
|
||||
float3 chromaNoise = float3(speckle.x * 1.2, speckle.y * 0.28, speckle.z * 1.35);
|
||||
color += chromaNoise * noiseAmount * noiseMask * chunkiness;
|
||||
color.rg = lerp(color.rg, float2(color.r, color.g) + speckle.xy * noiseAmount * 0.2 * chunkiness, 0.35);
|
||||
|
||||
@@ -17,6 +17,8 @@ bool intersectCube(float3 rayOrigin, float3 rayDirection, float halfExtent, out
|
||||
float3 boxMin = float3(-halfExtent, -halfExtent, -halfExtent);
|
||||
float3 boxMax = float3(halfExtent, halfExtent, halfExtent);
|
||||
|
||||
// Slab intersection: find the ray interval that overlaps all three box
|
||||
// axes, then keep the nearest positive hit.
|
||||
float3 invDir = 1.0 / rayDirection;
|
||||
float3 t0 = (boxMin - rayOrigin) * invDir;
|
||||
float3 t1 = (boxMax - rayOrigin) * invDir;
|
||||
@@ -43,6 +45,8 @@ float2 cubeFaceUv(float3 hitPoint, float halfExtent, float zoom)
|
||||
float2 uv = float2(0.5, 0.5);
|
||||
float safeZoom = max(zoom, 0.001);
|
||||
|
||||
// The dominant coordinate tells which face was hit; the other two axes
|
||||
// become that face's local UVs.
|
||||
if (face.x >= face.y && face.x >= face.z)
|
||||
{
|
||||
uv = hitPoint.x > 0.0
|
||||
@@ -79,6 +83,8 @@ float4 shadeVideo(ShaderContext context)
|
||||
float yaw = spin;
|
||||
float pitch = spin * 0.61 + 0.35;
|
||||
|
||||
// Rotate the camera ray into cube-local space instead of rotating the cube
|
||||
// geometry, which keeps the intersection math axis-aligned.
|
||||
float3 localOrigin = rotateY(rotateX(rayOrigin, -pitch), -yaw);
|
||||
float3 localDirection = rotateY(rotateX(rayDirection, -pitch), -yaw);
|
||||
|
||||
@@ -96,6 +102,8 @@ float4 shadeVideo(ShaderContext context)
|
||||
|
||||
float3 normal;
|
||||
float3 face = abs(localHit);
|
||||
// Reconstruct the face normal from the hit point so lighting follows the
|
||||
// same face choice used for UV lookup.
|
||||
if (face.x >= face.y && face.x >= face.z)
|
||||
normal = float3(sign(localHit.x), 0.0, 0.0);
|
||||
else if (face.y >= face.x && face.y >= face.z)
|
||||
|
||||
@@ -18,6 +18,8 @@ float4 shadeVideo(ShaderContext context)
|
||||
float resolutionAspect = max(context.outputResolution.x, 1.0) / max(context.outputResolution.y, 1.0);
|
||||
float width = saturate(overlayScale);
|
||||
float height = width * resolutionAspect / targetAspect;
|
||||
// Keep the scope in a 16:9 frame, then shrink it if the requested scale
|
||||
// would push the overlay beyond the screen bounds.
|
||||
float fitScale = min(1.0 / max(width, 0.001), 1.0 / max(height, 0.001));
|
||||
width *= min(fitScale, 1.0);
|
||||
height *= min(fitScale, 1.0);
|
||||
@@ -36,6 +38,8 @@ float4 shadeVideo(ShaderContext context)
|
||||
|
||||
float3 bg = lerp(color.rgb, float3(0.0, 0.0, 0.0), saturate(backgroundOpacity));
|
||||
float labelHeight = min(max(pad.x * 0.95, 0.048), 0.12);
|
||||
// Label textures are authored in UV space, so compensate for the overlay
|
||||
// and output aspect ratios to keep the glyphs from stretching.
|
||||
float labelWidth = labelHeight * height * max(context.outputResolution.y, 1.0) / max(width * max(context.outputResolution.x, 1.0), 0.001);
|
||||
float labelX = max(pad.x * 0.5, labelWidth * 0.55);
|
||||
float y0 = pad.y;
|
||||
@@ -63,6 +67,8 @@ float4 shadeVideo(ShaderContext context)
|
||||
float requestedSamples = clamp(waveformSamples, 1.0, 96.0);
|
||||
float density = 0.0;
|
||||
|
||||
// For each output pixel, march through source rows at the same X coordinate
|
||||
// and accumulate hits where sampled luma lands near this pixel's Y level.
|
||||
for (int sampleIndex = 0; sampleIndex < 96; sampleIndex++)
|
||||
{
|
||||
float samplePosition = float(sampleIndex);
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
float boxMask(float2 point, float2 halfSize, float feather)
|
||||
{
|
||||
// Signed-distance box mask gives the chart and border pixel-sized feathered
|
||||
// edges without branching per side.
|
||||
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);
|
||||
@@ -31,6 +33,8 @@ float applyToneCurve(float linearLevel)
|
||||
float patchBrightness(int patchIndex, int count)
|
||||
{
|
||||
int clampedIndex = clamp(patchIndex, 0, max(count - 1, 0));
|
||||
// Each patch is one stop brighter than the previous patch until it clips at
|
||||
// the requested peak level, matching the Xyla-style exposure ramp.
|
||||
float linearLevel = baseLevel * exp2(float(clampedIndex));
|
||||
linearLevel = min(linearLevel, peakLevel);
|
||||
return applyToneCurve(linearLevel);
|
||||
@@ -60,6 +64,8 @@ float4 shadeVideo(ShaderContext context)
|
||||
if (reverseOrder)
|
||||
patchIndex = count - 1 - patchIndex;
|
||||
|
||||
// Build each patch as a slot along the main axis, then mask the cross-axis
|
||||
// extents so vertical and horizontal charts share the same logic.
|
||||
float patchSlotCenter = (floor(patchPosition) + 0.5) / float(count);
|
||||
float localAxis = abs(normalizedAxis - patchSlotCenter) * float(count) * 2.0;
|
||||
float safeGapSize = saturate(gapSize);
|
||||
|
||||
Reference in New Issue
Block a user