adjustments to control and stack saving
This commit is contained in:
49
shaders/white-balance-correction/shader.json
Normal file
49
shaders/white-balance-correction/shader.json
Normal file
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"id": "white-balance-correction",
|
||||
"name": "White Balance Correction",
|
||||
"description": "Operator-friendly tint, color balance, and exposure correction intended to pair with the white match probe.",
|
||||
"category": "Color",
|
||||
"entryPoint": "shadeVideo",
|
||||
"parameters": [
|
||||
{
|
||||
"id": "warmCool",
|
||||
"label": "Warm / Cool",
|
||||
"type": "float",
|
||||
"default": 0.0,
|
||||
"min": -1.0,
|
||||
"max": 1.0,
|
||||
"step": 0.001,
|
||||
"description": "Moves the image cooler at negative values and warmer at positive values."
|
||||
},
|
||||
{
|
||||
"id": "greenMagenta",
|
||||
"label": "Green / Magenta",
|
||||
"type": "float",
|
||||
"default": 0.0,
|
||||
"min": -1.0,
|
||||
"max": 1.0,
|
||||
"step": 0.001,
|
||||
"description": "Moves the image toward magenta at negative values and toward green at positive values."
|
||||
},
|
||||
{
|
||||
"id": "exposure",
|
||||
"label": "Exposure",
|
||||
"type": "float",
|
||||
"default": 0.0,
|
||||
"min": -4.0,
|
||||
"max": 4.0,
|
||||
"step": 0.01,
|
||||
"description": "Exposure offset in stop units, using a Blender-style 2^exposure brightness scale."
|
||||
},
|
||||
{
|
||||
"id": "strength",
|
||||
"label": "Strength",
|
||||
"type": "float",
|
||||
"default": 1.0,
|
||||
"min": 0.0,
|
||||
"max": 1.0,
|
||||
"step": 0.01,
|
||||
"description": "Blends the correction with the original image."
|
||||
}
|
||||
]
|
||||
}
|
||||
35
shaders/white-balance-correction/shader.slang
Normal file
35
shaders/white-balance-correction/shader.slang
Normal file
@@ -0,0 +1,35 @@
|
||||
float3 applyWhiteBalanceOnly(float3 color)
|
||||
{
|
||||
float warmAmount = clamp(warmCool, -1.0, 1.0);
|
||||
float tintAmount = clamp(greenMagenta, -1.0, 1.0);
|
||||
|
||||
// Warm/cool pivots red against blue while keeping green more stable.
|
||||
float3 warmCoolGain = float3(
|
||||
exp2(warmAmount * 0.35),
|
||||
exp2(-abs(warmAmount) * 0.08),
|
||||
exp2(-warmAmount * 0.35));
|
||||
|
||||
// Green/magenta pivots green against the average of red and blue.
|
||||
float3 tintGain = float3(
|
||||
exp2(-tintAmount * 0.22),
|
||||
exp2(tintAmount * 0.35),
|
||||
exp2(-tintAmount * 0.22));
|
||||
|
||||
return color * warmCoolGain * tintGain;
|
||||
}
|
||||
|
||||
float3 applyExposureLikeBlender(float3 color)
|
||||
{
|
||||
// Match the compositor-style exposure model: every +1.0 stop doubles the
|
||||
// image and every -1.0 stop halves it.
|
||||
return color * exp2(exposure);
|
||||
}
|
||||
|
||||
float4 shadeVideo(ShaderContext context)
|
||||
{
|
||||
float4 source = context.sourceColor;
|
||||
float3 balanced = applyWhiteBalanceOnly(source.rgb);
|
||||
float3 corrected = applyExposureLikeBlender(balanced);
|
||||
source.rgb = lerp(source.rgb, corrected, saturate(strength));
|
||||
return source;
|
||||
}
|
||||
147
shaders/white-match-probe/shader.json
Normal file
147
shaders/white-match-probe/shader.json
Normal file
@@ -0,0 +1,147 @@
|
||||
{
|
||||
"id": "white-match-probe",
|
||||
"name": "White Match Probe",
|
||||
"description": "Samples a movable box, stores a reference color on trigger using shader-local feedback, and compares the current sample against the held reference for camera matching.",
|
||||
"category": "Utility",
|
||||
"entryPoint": "storeReferenceState",
|
||||
"passes": [
|
||||
{
|
||||
"id": "store",
|
||||
"source": "shader.slang",
|
||||
"entryPoint": "storeReferenceState",
|
||||
"output": "referenceState"
|
||||
},
|
||||
{
|
||||
"id": "display",
|
||||
"source": "shader.slang",
|
||||
"entryPoint": "displayReferenceCompare",
|
||||
"inputs": [
|
||||
"referenceState"
|
||||
],
|
||||
"output": "layerOutput"
|
||||
}
|
||||
],
|
||||
"feedback": {
|
||||
"enabled": true,
|
||||
"writePass": "store"
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"id": "referenceSource",
|
||||
"label": "Reference Source",
|
||||
"type": "enum",
|
||||
"default": "captured",
|
||||
"options": [
|
||||
{
|
||||
"value": "captured",
|
||||
"label": "Captured Sample"
|
||||
},
|
||||
{
|
||||
"value": "manual",
|
||||
"label": "Manual Color"
|
||||
}
|
||||
],
|
||||
"description": "Choose whether the probe compares against a captured screen sample or a manually selected reference color."
|
||||
},
|
||||
{
|
||||
"id": "captureReference",
|
||||
"label": "Capture Reference",
|
||||
"type": "trigger",
|
||||
"description": "Stores the current sample box average as the held reference."
|
||||
},
|
||||
{
|
||||
"id": "sampleCenter",
|
||||
"label": "Sample Center",
|
||||
"type": "vec2",
|
||||
"default": [
|
||||
0.5,
|
||||
0.5
|
||||
],
|
||||
"min": [
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"max": [
|
||||
1.0,
|
||||
1.0
|
||||
],
|
||||
"step": [
|
||||
0.001,
|
||||
0.001
|
||||
],
|
||||
"description": "Center of the sample box in normalized coordinates."
|
||||
},
|
||||
{
|
||||
"id": "sampleSize",
|
||||
"label": "Sample Size",
|
||||
"type": "vec2",
|
||||
"default": [
|
||||
0.14,
|
||||
0.14
|
||||
],
|
||||
"min": [
|
||||
0.02,
|
||||
0.02
|
||||
],
|
||||
"max": [
|
||||
0.5,
|
||||
0.5
|
||||
],
|
||||
"step": [
|
||||
0.001,
|
||||
0.001
|
||||
],
|
||||
"description": "Width and height of the sample box."
|
||||
},
|
||||
{
|
||||
"id": "manualReference",
|
||||
"label": "Manual Reference",
|
||||
"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
|
||||
],
|
||||
"description": "Manual reference color used when Reference Source is set to Manual Color."
|
||||
},
|
||||
{
|
||||
"id": "overlayOpacity",
|
||||
"label": "Overlay Opacity",
|
||||
"type": "float",
|
||||
"default": 0.9,
|
||||
"min": 0.0,
|
||||
"max": 1.0,
|
||||
"step": 0.01,
|
||||
"description": "Strength of the swatch and box overlay."
|
||||
},
|
||||
{
|
||||
"id": "differenceGain",
|
||||
"label": "Difference Gain",
|
||||
"type": "float",
|
||||
"default": 2.0,
|
||||
"min": 0.0,
|
||||
"max": 8.0,
|
||||
"step": 0.01,
|
||||
"description": "Scales the displayed reference-vs-current difference."
|
||||
}
|
||||
]
|
||||
}
|
||||
235
shaders/white-match-probe/shader.slang
Normal file
235
shaders/white-match-probe/shader.slang
Normal file
@@ -0,0 +1,235 @@
|
||||
static const int kReferenceCellIndex = 0;
|
||||
static const int kMetadataCellIndex = 1;
|
||||
|
||||
float2 cellCenterPixelForIndex(int index)
|
||||
{
|
||||
return float2(1.0 + float(index) * 3.0, 1.0);
|
||||
}
|
||||
|
||||
float2 cellCenterUvForIndex(ShaderContext context, int index)
|
||||
{
|
||||
return (cellCenterPixelForIndex(index) + 0.5) / context.outputResolution;
|
||||
}
|
||||
|
||||
bool pixelIsInsideCell(float2 pixelCoord, int index)
|
||||
{
|
||||
float minX = float(index) * 3.0;
|
||||
float maxX = minX + 3.0;
|
||||
return pixelCoord.x >= minX && pixelCoord.x < maxX && pixelCoord.y >= 0.0 && pixelCoord.y < 3.0;
|
||||
}
|
||||
|
||||
float4 readStoredCell(ShaderContext context, int index)
|
||||
{
|
||||
if (context.feedbackAvailable <= 0)
|
||||
return float4(0.0, 0.0, 0.0, 0.0);
|
||||
return sampleFeedback(cellCenterUvForIndex(context, index));
|
||||
}
|
||||
|
||||
float3 sampleProbeAverage(ShaderContext context)
|
||||
{
|
||||
float2 clampedSize = clamp(sampleSize, float2(0.001, 0.001), float2(1.0, 1.0));
|
||||
float2 halfSize = clampedSize * 0.5;
|
||||
float2 minUv = clamp(sampleCenter - halfSize, float2(0.0, 0.0), float2(1.0, 1.0));
|
||||
float2 maxUv = clamp(sampleCenter + halfSize, float2(0.0, 0.0), float2(1.0, 1.0));
|
||||
|
||||
float3 total = float3(0.0, 0.0, 0.0);
|
||||
float weight = 0.0;
|
||||
for (int y = 0; y < 3; ++y)
|
||||
{
|
||||
for (int x = 0; x < 3; ++x)
|
||||
{
|
||||
float2 t = float2((float(x) + 0.5) / 3.0, (float(y) + 0.5) / 3.0);
|
||||
float2 uv = lerp(minUv, maxUv, t);
|
||||
total += sampleLayerInput(uv).rgb;
|
||||
weight += 1.0;
|
||||
}
|
||||
}
|
||||
|
||||
return total / max(weight, 0.0001);
|
||||
}
|
||||
|
||||
float4 storeReferenceState(ShaderContext context)
|
||||
{
|
||||
float2 pixelCoord = floor(context.uv * context.outputResolution);
|
||||
float3 currentSample = sampleProbeAverage(context);
|
||||
|
||||
float previousTriggerCount = context.feedbackAvailable > 0
|
||||
? readStoredCell(context, kMetadataCellIndex).r
|
||||
: -1.0;
|
||||
float currentTriggerCount = float(captureReference);
|
||||
bool captureNow = context.feedbackAvailable <= 0 || currentTriggerCount > previousTriggerCount + 0.5;
|
||||
|
||||
float3 storedReference = context.feedbackAvailable > 0
|
||||
? readStoredCell(context, kReferenceCellIndex).rgb
|
||||
: currentSample;
|
||||
if (captureNow)
|
||||
storedReference = currentSample;
|
||||
|
||||
float4 metadata = float4(currentTriggerCount, captureReferenceTime, 0.0, 1.0);
|
||||
if (!captureNow && context.feedbackAvailable > 0)
|
||||
metadata = readStoredCell(context, kMetadataCellIndex);
|
||||
|
||||
if (pixelIsInsideCell(pixelCoord, kReferenceCellIndex))
|
||||
return float4(storedReference, 1.0);
|
||||
if (pixelIsInsideCell(pixelCoord, kMetadataCellIndex))
|
||||
return metadata;
|
||||
|
||||
return float4(0.0, 0.0, 0.0, 1.0);
|
||||
}
|
||||
|
||||
float rectMask(float2 uv, float2 minUv, float2 maxUv)
|
||||
{
|
||||
if (uv.x < minUv.x || uv.x > maxUv.x)
|
||||
return 0.0;
|
||||
if (uv.y < minUv.y || uv.y > maxUv.y)
|
||||
return 0.0;
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
float borderMask(float2 uv, float2 minUv, float2 maxUv, float thickness)
|
||||
{
|
||||
float outer = rectMask(uv, minUv, maxUv);
|
||||
float inner = rectMask(uv, minUv + thickness, maxUv - thickness);
|
||||
return saturate(outer - inner);
|
||||
}
|
||||
|
||||
float luminance(float3 color)
|
||||
{
|
||||
return dot(color, float3(0.2126, 0.7152, 0.0722));
|
||||
}
|
||||
|
||||
float3 activeReferenceColor(float3 capturedReference)
|
||||
{
|
||||
// Enum parameters are exposed as their zero-based option index.
|
||||
// 0 = captured sample, 1 = manual color.
|
||||
return referenceSource == 1 ? manualReference.rgb : capturedReference;
|
||||
}
|
||||
|
||||
float4 displayReferenceCompare(ShaderContext context)
|
||||
{
|
||||
float3 liveColor = sampleLayerInput(context.uv).rgb;
|
||||
float3 currentSample = sampleProbeAverage(context);
|
||||
float3 capturedReference = sampleVideo(cellCenterUvForIndex(context, kReferenceCellIndex)).rgb;
|
||||
float3 storedReference = activeReferenceColor(capturedReference);
|
||||
float3 delta = currentSample - storedReference;
|
||||
float3 absoluteDelta = abs(delta);
|
||||
float differenceMagnitude = max(absoluteDelta.r, max(absoluteDelta.g, absoluteDelta.b));
|
||||
|
||||
float3 displayColor = liveColor;
|
||||
float opacity = saturate(overlayOpacity);
|
||||
|
||||
float2 halfSize = clamp(sampleSize, float2(0.001, 0.001), float2(1.0, 1.0)) * 0.5;
|
||||
float2 boxMin = clamp(sampleCenter - halfSize, float2(0.0, 0.0), float2(1.0, 1.0));
|
||||
float2 boxMax = clamp(sampleCenter + halfSize, float2(0.0, 0.0), float2(1.0, 1.0));
|
||||
float pixelThickness = 2.0 / max(min(context.outputResolution.x, context.outputResolution.y), 1.0);
|
||||
float outerOutline = borderMask(context.uv, boxMin - pixelThickness, boxMax + pixelThickness, pixelThickness);
|
||||
float innerOutline = borderMask(context.uv, boxMin, boxMax, pixelThickness);
|
||||
if (outerOutline > 0.5)
|
||||
displayColor = float3(0.0, 0.0, 0.0);
|
||||
if (innerOutline > 0.5)
|
||||
displayColor = lerp(displayColor, float3(1.0, 0.0, 0.0), opacity);
|
||||
|
||||
float2 swatchSize = float2(0.06, 0.07);
|
||||
float2 panelOrigin = float2(0.03, 0.04);
|
||||
float2 gap = float2(0.075, 0.0);
|
||||
float2 refMin = panelOrigin;
|
||||
float2 curMin = panelOrigin + gap;
|
||||
float2 diffMin = panelOrigin + gap * 2.0;
|
||||
float2 refMax = refMin + swatchSize;
|
||||
float2 curMax = curMin + swatchSize;
|
||||
float2 diffMax = diffMin + swatchSize;
|
||||
float swatchBorder = min(swatchSize.x, swatchSize.y) * 0.08;
|
||||
|
||||
float refFill = rectMask(context.uv, refMin, refMax);
|
||||
float curFill = rectMask(context.uv, curMin, curMax);
|
||||
float diffFill = rectMask(context.uv, diffMin, diffMax);
|
||||
float refOutline = borderMask(context.uv, refMin, refMax, swatchBorder);
|
||||
float curOutline = borderMask(context.uv, curMin, curMax, swatchBorder);
|
||||
float diffOutline = borderMask(context.uv, diffMin, diffMax, swatchBorder);
|
||||
|
||||
if (refFill > 0.5)
|
||||
displayColor = storedReference;
|
||||
if (curFill > 0.5)
|
||||
displayColor = currentSample;
|
||||
if (diffFill > 0.5)
|
||||
{
|
||||
float3 neutralBase = float3(0.5, 0.5, 0.5);
|
||||
float3 signedDeltaDisplay = saturate(neutralBase + delta * max(differenceGain, 0.0) * 0.5);
|
||||
displayColor = signedDeltaDisplay;
|
||||
}
|
||||
if (refOutline > 0.5 || curOutline > 0.5 || diffOutline > 0.5)
|
||||
displayColor = float3(0.0, 0.0, 0.0);
|
||||
|
||||
// Approximate the difference in two operator-friendly axes:
|
||||
// warm/cool leans red versus blue, and green/magenta leans green versus
|
||||
// the average of red and blue. Centered bars make "match" obvious.
|
||||
float warmCool = clamp((delta.r - delta.b) * max(differenceGain, 0.0), -1.0, 1.0);
|
||||
float greenMagenta = clamp((delta.g - (delta.r + delta.b) * 0.5) * max(differenceGain, 0.0), -1.0, 1.0);
|
||||
float brightnessDelta = clamp((luminance(currentSample) - luminance(storedReference)) * max(differenceGain, 0.0) * 1.5, -1.0, 1.0);
|
||||
|
||||
float barWidth = 0.18;
|
||||
float barHeight = 0.018;
|
||||
float halfBarWidth = barWidth * 0.5;
|
||||
float2 warmCoolMin = float2(0.03, 0.13);
|
||||
float2 warmCoolMax = warmCoolMin + float2(barWidth, barHeight);
|
||||
float2 tintMin = float2(0.03, 0.157);
|
||||
float2 tintMax = tintMin + float2(barWidth, barHeight);
|
||||
float2 brightnessMin = float2(0.03, 0.184);
|
||||
float2 brightnessMax = brightnessMin + float2(barWidth, barHeight);
|
||||
float centerX = warmCoolMin.x + halfBarWidth;
|
||||
|
||||
float warmCoolFill = rectMask(context.uv, warmCoolMin, warmCoolMax);
|
||||
float tintFill = rectMask(context.uv, tintMin, tintMax);
|
||||
float brightnessFill = rectMask(context.uv, brightnessMin, brightnessMax);
|
||||
float warmCoolOutline = borderMask(context.uv, warmCoolMin, warmCoolMax, barHeight * 0.12);
|
||||
float tintOutline = borderMask(context.uv, tintMin, tintMax, barHeight * 0.12);
|
||||
float brightnessOutline = borderMask(context.uv, brightnessMin, brightnessMax, barHeight * 0.12);
|
||||
float targetHalfWidth = 0.0015;
|
||||
float warmCoolCenter = rectMask(context.uv, float2(centerX - targetHalfWidth, warmCoolMin.y), float2(centerX + targetHalfWidth, warmCoolMax.y));
|
||||
float tintCenter = rectMask(context.uv, float2(centerX - targetHalfWidth, tintMin.y), float2(centerX + targetHalfWidth, tintMax.y));
|
||||
float brightnessCenter = rectMask(context.uv, float2(centerX - targetHalfWidth, brightnessMin.y), float2(centerX + targetHalfWidth, brightnessMax.y));
|
||||
|
||||
float warmCoolPosition = centerX + warmCool * halfBarWidth;
|
||||
float tintPosition = centerX + greenMagenta * halfBarWidth;
|
||||
float brightnessPosition = centerX + brightnessDelta * halfBarWidth;
|
||||
float indicatorHalfWidth = 0.0018;
|
||||
float warmCoolIndicator = rectMask(context.uv, float2(warmCoolPosition - indicatorHalfWidth, warmCoolMin.y), float2(warmCoolPosition + indicatorHalfWidth, warmCoolMax.y));
|
||||
float tintIndicator = rectMask(context.uv, float2(tintPosition - indicatorHalfWidth, tintMin.y), float2(tintPosition + indicatorHalfWidth, tintMax.y));
|
||||
float brightnessIndicator = rectMask(context.uv, float2(brightnessPosition - indicatorHalfWidth, brightnessMin.y), float2(brightnessPosition + indicatorHalfWidth, brightnessMax.y));
|
||||
|
||||
if (warmCoolFill > 0.5)
|
||||
{
|
||||
float gradientT = saturate((context.uv.x - warmCoolMin.x) / max(barWidth, 0.0001));
|
||||
float3 coolColor = float3(0.18, 0.52, 1.0);
|
||||
float3 warmColor = float3(1.0, 0.48, 0.10);
|
||||
float3 gradientColor = gradientT <= 0.5
|
||||
? lerp(coolColor, float3(1.0, 1.0, 1.0), gradientT * 2.0)
|
||||
: lerp(float3(1.0, 1.0, 1.0), warmColor, (gradientT - 0.5) * 2.0);
|
||||
displayColor = gradientColor;
|
||||
}
|
||||
if (tintFill > 0.5)
|
||||
{
|
||||
float gradientT = saturate((context.uv.x - tintMin.x) / max(barWidth, 0.0001));
|
||||
float3 magentaColor = float3(1.0, 0.25, 0.75);
|
||||
float3 greenColor = float3(0.18, 0.92, 0.32);
|
||||
float3 gradientColor = gradientT <= 0.5
|
||||
? lerp(magentaColor, float3(1.0, 1.0, 1.0), gradientT * 2.0)
|
||||
: lerp(float3(1.0, 1.0, 1.0), greenColor, (gradientT - 0.5) * 2.0);
|
||||
displayColor = gradientColor;
|
||||
}
|
||||
if (brightnessFill > 0.5)
|
||||
{
|
||||
float gradientT = saturate((context.uv.x - brightnessMin.x) / max(barWidth, 0.0001));
|
||||
float3 darkColor = float3(0.18, 0.18, 0.18);
|
||||
float3 gradientColor = lerp(darkColor, float3(1.0, 1.0, 1.0), gradientT);
|
||||
displayColor = gradientColor;
|
||||
}
|
||||
if (warmCoolCenter > 0.5 || tintCenter > 0.5 || brightnessCenter > 0.5)
|
||||
displayColor = float3(0.12, 0.45, 1.0);
|
||||
if (warmCoolIndicator > 0.5 || tintIndicator > 0.5 || brightnessIndicator > 0.5)
|
||||
displayColor = float3(1.0, 0.0, 0.0);
|
||||
if (warmCoolOutline > 0.5 || tintOutline > 0.5 || brightnessOutline > 0.5)
|
||||
displayColor = float3(0.0, 0.0, 0.0);
|
||||
|
||||
return float4(saturate(displayColor), 1.0);
|
||||
}
|
||||
Reference in New Issue
Block a user