@razorpay/blade
Version:
The Design System that powers Razorpay
924 lines (770 loc) • 39.3 kB
JavaScript
var rzpGlassVertexShader=`
precision mediump float;
attribute vec2 position;
attribute vec2 uv;
// Zoom & Pan uniforms (computed in vertex shader for efficiency)
uniform float uZoom;
uniform vec2 uPan; // vec2(uPanX, uPanY)
// Output varyings
varying vec2 vUv; // Raw screen UV (for screen-space effects like feathering)
varying vec2 vContentUv; // Transformed UV for video/content sampling (zoom + pan applied)
void main() {
// Raw screen UV for screen-space effects
vUv = uv;
// Compute zoomed/panned UV for content sampling
// Zoom: scale around center (0.5, 0.5)
// Pan: offset the view
vContentUv = (uv - 0.5) / uZoom + 0.5;
vContentUv += uPan;
gl_Position = vec4(position, 0, 1);
}
`;var rzpGlassFragmentShader=`
precision mediump float;
uniform float uTime;
uniform vec2 iResolution;
uniform float uDpr;
uniform sampler2D uVideoTexture;
uniform sampler2D uGradientMap;
uniform sampler2D uGradientMap2; // Second gradient map for cross-fade blending
uniform sampler2D uCenterGradientMap; // Separate gradient map for center ellipse
// Layer toggles (enable/disable actual effects)
uniform float uEnableDisplacement;
uniform float uEnableColorama;
uniform float uEnableBloom;
uniform float uEnableLightSweep;
// ============================================
// COLORAMA UNIFORMS (Adobe AE v5 Pipeline)
// Pipeline: Scalar → Remap → Warp → Wrap → Lookup → Blend
// ============================================
// --- 1. INPUT PHASE (Scalar Index Generation) ---
uniform float uInputMin; // Input range min (default 0.0)
uniform float uInputMax; // Input range max (default 1.0)
// --- 2. MODIFY PHASE (Index Space Warping) ---
uniform float uModifyGamma; // Gamma curve: <1 = brights, >1 = darks (default 1.0)
uniform float uPosterizeLevels; // 0 = off, >0 = number of discrete steps
uniform float uCycleRepetitions; // Stretch/compress the index (default 1.0)
uniform float uPhaseShift; // Static offset (default 0.0)
uniform float uCycleSpeed; // Cycling animation speed (default 0.0)
// --- 3. OUTPUT CYCLE (Wrap & Lookup) ---
uniform float uWrapMode; // 0 = clamp, 1 = wrap/fract (default 1.0)
uniform float uReverse; // 0 = normal, 1 = reverse gradient (default 0.0)
// --- 4. COMPOSITE ---
uniform float uBlendWithOriginal; // 0 = full effect, 1 = original (default 0.0)
uniform float uGradientMapBlend; // 0 = uGradientMap, 1 = uGradientMap2 (default 0.0)
// --- 5. LIGHT EFFECT ---
uniform float uLightIntensity; // Strength of light sweep effect
uniform float uFrameCount; // Current frame number
uniform float uLightStartFrame; // Frame when light effect starts
// --- 6. DISPLACEMENT ---
uniform float uNumSegments; // Number of glass slits (default 45.0)
uniform float uSlitAngle; // Angle of slits in radians (default 0.13)
uniform float uDisplacementX; // X displacement amount (default -12.0)
uniform float uDisplacementY; // Y displacement amount (default -20.0)
// --- 7. CENTER ELEMENT ---
uniform float uEnableCenterElement; // Toggle center element (0 = off, 1 = on)
uniform float uCenterAnimDuration; // Duration of one animation cycle in seconds
uniform float uCenterAnimTime; // Current animation time in seconds (resets with video loop)
// --- 8. COLOR CORRECTION ---
uniform float uCCBlackPoint; // Levels black point (default 0.0)
uniform float uCCWhitePoint; // Levels white point (default 1.0)
uniform float uCCMidtoneGamma; // Midtone gamma (default 1.2)
uniform float uCCGamma; // Output gamma (default 1.2)
uniform float uCCContrast; // Contrast boost (default 0.0)
// --- 9. ZOOM & PAN ---
uniform float uZoom; // Zoom level (1.0 = normal, 2.0 = 2x zoom) - still needed for edge feather check
uniform vec4 uEdgeFeather; // Per-side feathering: vec4(top, right, bottom, left) clockwise, 0 = none, 1 = max
uniform vec2 uRefResolution; // Reference resolution for zoom-independent displacement
uniform vec4 uVisibleUvBounds; // vec4(minX, minY, maxX, maxY) - visible portion of canvas in container
// --- 10. BACKGROUND COLOR ---
uniform vec3 uBackgroundColor; // Background color to blend with (RGB 0-1)
// UV coordinates from the vertex shader
varying vec2 vUv; // Raw screen UV (for screen-space effects)
varying vec2 vContentUv; // Transformed UV with zoom/pan applied (for content sampling)
// ============================================
// UTILITY FUNCTIONS
// ============================================
// Rec. 709 luminance calculation
float luminance(vec3 color) {
return dot(color, vec3(0.2126, 0.7152, 0.0722));
}
// ============================================
// COLORAMA EFFECT (Adobe After Effects v5 Pipeline)
// ============================================
//
// Pipeline:
// Image → Scalar Field (0-1) → Warp/Animate → Gradient Lookup → Composite
//
// This matches AE's indexed gradient remapping with time-domain cycling.
vec3 applyColoramaWithGradient(
sampler2D gradientMap, // Gradient map texture to sample from
float rawIntensity, // Raw luminance from pixel
float inputMin, // Input range min
float inputMax, // Input range max
float gamma, // Gamma curve (pow)
float posterizeLevels, // 0 = off, else discrete steps
float cycleReps, // Cycle repetitions (stretch)
float phaseShift, // Static offset
float cycleSpeed, // Time-based cycling
float wrapMode, // 0 = clamp, 1 = wrap
float reverse // 0 = normal, 1 = flip
) {
// ─────────────────────────────────────────────
// STEP 1: INPUT PHASE - Scalar Index Generation
// ─────────────────────────────────────────────
// Normalize intensity to input range
float t = clamp((rawIntensity - inputMin) / (inputMax - inputMin), 0.0, 1.0);
// ─────────────────────────────────────────────
// STEP 2: MODIFY PHASE - Index Space Warping
// ─────────────────────────────────────────────
// a) Gamma / Curves - reshape the intensity distribution
t = pow(t, gamma);
// b) Posterize - quantize to discrete levels (branchless)
// When posterizeLevels <= 0, keeps t unchanged
float posterized = floor(t * posterizeLevels + 0.0001) / max(posterizeLevels, 0.0001);
t = mix(t, posterized, step(0.001, posterizeLevels));
// c) Cycle Repetitions - stretch/compress across gradient
t = t * cycleReps;
// d) Phase Shift - static offset
t = t + phaseShift;
// e) Cycling Animation - time-based offset
t = t + cycleSpeed * uTime;
// ─────────────────────────────────────────────
// STEP 3: OUTPUT CYCLE - Wrap & Lookup (branchless)
// ─────────────────────────────────────────────
// Wrap (fract) vs Clamp - branchless selection
t = mix(clamp(t, 0.0, 1.0), fract(t), step(0.5, wrapMode));
// Reverse direction - branchless
t = mix(t, 1.0 - t, step(0.5, reverse));
// Gradient lookup (1D texture sample)
return texture2D(gradientMap, vec2(t, 0.5)).rgb;
}
// ============================================
// DISPLACEMENT FUNCTIONS
// ============================================
// Create striped displacement map for glass refraction effect
// Returns: x = signed displacement (-1 to 1), y = local UV x position within segment
// gradientStart: gradient value at left edge (typically 1.0 for white)
// gradientEnd: gradient value at right edge (typically 0.0 for black)
// gradientPower: power curve for falloff (1.0 = linear, <1.0 = steeper, >1.0 = gentler)
// centerPoint: center value for signed conversion (typically 0.5)
// aspect: screen aspect ratio (width/height) for consistent slit angle
vec2 createStripedDisplacement(
vec2 uv,
float numSegments,
float angle,
float gradientStart,
float gradientEnd,
float gradientPower,
float centerPoint,
float aspect
) {
// Work in aspect-corrected UV space where x and y have equal visual scale
// This ensures consistent slit angle regardless of viewport dimensions
vec2 aspectUV = uv * vec2(aspect, 1.0);
// Apply slant in aspect-corrected space
float slantedX = aspectUV.x - aspectUV.y * tan(angle);
// Calculate segment properties (account for aspect-scaled x range)
float segmentWidth = aspect / numSegments;
float localUVx = fract(slantedX / segmentWidth); // 0-1 within each segment
// Create the displacement map gradient
// Use smoothstep for smoother interpolation
float smoothUVx = smoothstep(0.0, 1.0, localUVx);
// Interpolate from gradientEnd (left) to gradientStart (right)
float rawGradient = mix(gradientEnd, gradientStart, smoothUVx);
// Apply power curve for falloff control
rawGradient = pow(rawGradient, gradientPower);
// Convert to signed displacement (-1 to 1) using centerPoint
// centerPoint is the neutral value (typically 0.5)
float signedDisplacement = (rawGradient - centerPoint) / centerPoint;
return vec2(signedDisplacement, localUVx);
}
// Apply displacement offset to UV coordinates
vec2 applyDisplacement(vec2 uv, float signedDisplacement, vec2 maxDisplacement, vec2 resolution) {
vec2 displaceOffset = vec2(
signedDisplacement * maxDisplacement.x / resolution.x,
signedDisplacement * maxDisplacement.y / resolution.y
);
return uv + displaceOffset;
}
// Create thin slanted stripes with multi-stop gradient color
// Returns RGB color: gradient stripes with 3 color stops
// Stop 1 (0%): colorStart, Stop 2 (stopPosition): colorMid, Stop 3 (100%): transparent
vec4 createStripes(
vec2 uv,
float numSegments,
float angle,
float stopPosition, // Position of middle stop (0.0 to 1.0)
vec4 colorStart, // Color at 0% (left edge)
vec4 colorMid, // Color at stopPosition
float aspect // Screen aspect ratio for consistent angle
) {
// Work in aspect-corrected UV space where x and y have equal visual scale
vec2 aspectUV = uv * vec2(aspect, 1.0);
// Apply slant in aspect-corrected space
float slantedX = aspectUV.x - aspectUV.y * tan(angle);
// Calculate segment properties (account for aspect-scaled x range)
float segmentWidth = aspect / numSegments;
float localUVx = 1.0 - fract(slantedX / segmentWidth); // 0-1 within each segment, reversed
// Multi-stop gradient:
// 0% -> colorStart (green)
// stopPosition -> colorMid (white)
// 100% -> transparent (black with 0 opacity)
vec4 gradientColor;
float opacity;
if (localUVx < stopPosition) {
float t = localUVx / stopPosition;
gradientColor = mix(colorMid, colorStart, t);
opacity = 0.5;
} else {
float t = (localUVx - stopPosition) / (1.0 - stopPosition);
gradientColor = mix(vec4(0.0), colorMid, t);
opacity = 0.5 - t * 0.5;
}
return gradientColor * opacity;
}
// Sample texture with displacement applied
vec4 sampleWithDisplacement(
sampler2D tex,
vec2 uv,
float signedDisplacement,
vec2 maxDisplacement,
vec2 resolution
) {
vec2 displacedUV = applyDisplacement(uv, signedDisplacement, maxDisplacement, resolution);
return texture2D(tex, displacedUV);
}
// ============================================
// POST-PROCESSING EFFECTS
// ============================================
vec3 applyBloom(vec3 color, float intensity, float innerMask) {
const float whiteCoreThresholdMin = 0.5; // Start of white core mask
const float whiteCoreThresholdMax = 0.85; // End of white core mask
const float whiteCoreBlendStrength = 0.85; // How much to blend towards pure white
const float bloomThresholdMin = 0.3; // Start of bloom glow
const float bloomThresholdMax = 0.7; // End of bloom glow
const vec3 bloomColor = vec3(1.0, 0.99, 0.97); // Warm white bloom tint
const float bloomStrength = 0.10; // Intensity of bloom glow
// -------------------------------
// Calculate how much we're in the "center" bright area
// Use a tighter threshold for the white core
float whiteCoreMask = smoothstep(whiteCoreThresholdMin, whiteCoreThresholdMax, intensity) * innerMask;
// Pure white target
vec3 pureWhite = vec3(1.0);
// Blend the center towards pure white (not additive, but replacement blend)
// This ensures the very center goes to white, not gray
color = mix(color, pureWhite, whiteCoreMask * whiteCoreBlendStrength);
// Additional soft bloom glow around the white core
float bloomBase = smoothstep(bloomThresholdMin, bloomThresholdMax, intensity);
float bloomAmount = bloomBase * innerMask;
color += bloomColor * bloomAmount * bloomStrength;
return color;
}
// ============================================
// ANIMATED POLYGON SHAPE
// ============================================
// Struct to return shape data
struct ShapeData {
float shape; // The shape value (0 = outside, 1 = center)
float gradient; // Gradient from gray (edge) to white (center)
};
// Signed distance function for a regular polygon
// p: point to test, r: radius, n: number of sides
float sdPolygon(vec2 p, float r, float n) {
// Angle and radius
float an = 3.141593 / n;
vec2 acs = vec2(cos(an), sin(an));
// Reduce to first sector
float bn = mod(atan(p.x, p.y), 2.0 * an) - an;
p = length(p) * vec2(cos(bn), abs(sin(bn)));
// Line sdf
p -= r * acs;
p.y += clamp(-p.y, 0.0, r * acs.y);
return length(p) * sign(p.x);
}
// Fast signed distance function for an ellipse
// Approximation that avoids expensive acos(), cube roots, and branching
// Accurate enough for visual effects, ~5x faster than exact version
float sdEllipse(vec2 p, vec2 ab) {
// Normalize point by ellipse radii
vec2 q = p / ab;
float k1 = length(q);
// Early out for points very close to center (avoid division issues)
if (k1 < 0.0001) return -min(ab.x, ab.y);
// Gradient-based distance approximation
vec2 q2 = q / ab; // Second normalization for gradient
float k2 = length(q2);
return k1 * (k1 - 1.0) / k2;
}
// Helper: Calculate a single shape instance at a given animation phase
ShapeData calculateSingleShape(
vec2 uv,
float linearT, // Animation phase 0-1
float solidCoreMask,
vec2 resolution,
// Shape parameters
float shapeType,
float shapeWidth,
float shapeHeight,
float centerY,
float animRange,
float edgeSoftness,
float grayLevel,
float shapeAngleStart,
float shapeAngleEnd,
float shapeSize
) {
// Map 0-1 to position range (left to right)
float posOffset = (linearT * 2.0 - 1.0) * animRange;
float centerX = 0.5 + posOffset;
// Animate rotation
float animatedAngle = mix(shapeAngleStart, shapeAngleEnd, linearT);
// Correct for aspect ratio
float aspect = resolution.x / resolution.y;
// Calculate position relative to center
vec2 shapeCenter = vec2(centerX, centerY);
vec2 delta = uv - shapeCenter;
// Rotate delta by animated angle FIRST
float cosA = cos(animatedAngle);
float sinA = sin(animatedAngle);
vec2 rotatedDelta = vec2(
delta.x * cosA - delta.y * sinA,
delta.x * sinA + delta.y * cosA
);
// Apply aspect correction AFTER rotation
rotatedDelta.x *= aspect;
// Calculate distance based on shape type
float dist;
if (shapeType < 0.5) {
dist = sdEllipse(rotatedDelta, vec2(shapeWidth, shapeHeight));
} else {
vec2 scaledDelta = rotatedDelta / vec2(shapeWidth, shapeHeight);
dist = sdPolygon(scaledDelta, shapeSize / min(shapeWidth, shapeHeight), shapeType);
dist *= min(shapeWidth, shapeHeight);
}
// Normalize distance for gradient
float normalizedDist;
if (shapeType < 0.5) {
normalizedDist = dist / max(shapeWidth, shapeHeight) + 1.0;
} else {
normalizedDist = (dist / shapeSize) + 1.0;
}
normalizedDist = clamp(normalizedDist, 0.0, 1.0);
// Create soft shape mask
float shapeMask = 1.0 - smoothstep(-edgeSoftness * 0.1, edgeSoftness * 0.05, dist);
// Create gradient
float gradient = mix(1.0, grayLevel, smoothstep(0.0, 1.0, normalizedDist));
// Apply solid core mask
shapeMask *= solidCoreMask;
ShapeData result;
result.shape = shapeMask;
result.gradient = gradient;
return result;
}
// Creates two staggered animated shapes that move left-to-right
// When one reaches the far edge, another appears from the left
ShapeData calculateAnimatedShape(
vec2 uv,
float time, // Current animation time in seconds
float solidCoreMask,
vec2 resolution
) {
// --- Configurable parameters ---
const float shapeType = 3.0; // 0 = ellipse, 3 = triangle, 4 = square, 5 = pentagon, etc.
const float shapeWidth = 0.1; // Width/horizontal scale (wide)
const float shapeHeight = 0.8; // Height/vertical scale (short)
const float centerY = 0.4; // Vertical center position
float cycleDuration = uCenterAnimDuration; // Seconds per animation cycle (from uniform)
const float animRange = 0.7; // How far left/right it travels
const float edgeSoftness = 0.5; // Softness of shape edge
const float grayLevel = 0.5; // Gray color at shape edge
const float shapeAngleStart = 0.6; // Rotation angle at start (slight tilt)
const float shapeAngleEnd = 0.1; // Rotation angle at end (opposite tilt)
const float shapeSize = 0.4; // Overall size for polygon mode
const float staggerOffset = 0.4; // Offset between the two shapes (0.5 = half cycle apart)
const float shape2Scale = 1.3; // Scale multiplier for 2nd shape (1.0 = same size)
// -------------------------------
// Time-based animation: time / cycleDuration gives progress through cycle
float linearT1 = fract(time / cycleDuration); // Shape 1: 0->1 repeating
float linearT2 = fract(time / cycleDuration + staggerOffset); // Shape 2: offset by staggerOffset
// Calculate both shapes (shape 2 is slightly larger)
ShapeData shape1 = calculateSingleShape(
uv, linearT1, solidCoreMask, resolution,
shapeType, shapeWidth, shapeHeight, centerY, animRange,
edgeSoftness, grayLevel, shapeAngleStart, shapeAngleEnd, shapeSize
);
ShapeData shape2 = calculateSingleShape(
uv, linearT2, solidCoreMask, resolution,
shapeType, shapeWidth * shape2Scale, shapeHeight * shape2Scale, centerY, animRange,
edgeSoftness, grayLevel, shapeAngleStart, shapeAngleEnd, shapeSize * shape2Scale
);
// Combine both shapes (take max of masks, blend gradients)
ShapeData result;
result.shape = max(shape1.shape, shape2.shape);
// Weighted average of gradients based on shape masks
float totalMask = shape1.shape + shape2.shape;
if (totalMask > 0.001) {
result.gradient = (shape1.gradient * shape1.shape + shape2.gradient * shape2.shape) / totalMask;
} else {
result.gradient = 0.5;
}
return result;
}
// Apply shape effect to intensity (for colorama/displacement pipeline)
float applyShapeToIntensity(
float baseIntensity,
ShapeData shapeData,
float effectStrength
) {
// Blend the shape gradient into the base intensity
// This makes the shape area brighter/differently colored through colorama
float shapeContribution = shapeData.gradient * shapeData.shape;
return mix(baseIntensity, shapeContribution, shapeData.shape * effectStrength);
}
// AE-style color processing (levels, gamma, contrast). Used in both ripple and normal mode.
vec3 applyColorCorrection(vec3 color) {
color = (color - uCCBlackPoint) / (uCCWhitePoint - uCCBlackPoint);
color = pow(max(color, vec3(0.0)), vec3(1.0 / (uCCMidtoneGamma * uCCGamma)));
color = color * (1.0 + uCCContrast) - uCCContrast * 0.5;
return clamp(color, 0.0, 1.0);
}
// Feather at container edges (visible bounds). Used in both ripple and normal mode.
vec3 applyEdgeFeathering(vec3 color, vec3 bgColor) {
// Apply edge feathering when zoomed in
// Feathering is applied at the container edges (visible bounds), not canvas edges
// uEdgeFeather = vec4(top, right, bottom, left) — clockwise like CSS
if (any(greaterThan(uEdgeFeather, vec4(0.0)))) {
vec2 screenUV = vUv;
// Get visible UV bounds (where container clips the canvas)
float visMinX = uVisibleUvBounds.x;
float visMinY = uVisibleUvBounds.y;
float visMaxX = uVisibleUvBounds.z;
float visMaxY = uVisibleUvBounds.w;
// Calculate visible dimensions in UV space for aspect-correct feathering on X axis
float visibleWidth = visMaxX - visMinX;
float visibleHeight = visMaxY - visMinY;
float visibleAspect = visibleWidth / visibleHeight;
// Per-side feather amounts (X sides get aspect correction for pixel-equal width)
float featherTop = uEdgeFeather.x * 0.15 / visibleAspect;
float featherRight = uEdgeFeather.y * 0.15 / visibleAspect;
float featherBottom = uEdgeFeather.z * 0.15 / visibleAspect;
float featherLeft = uEdgeFeather.w * 0.15 / visibleAspect;
// Apply feathering at container edges (visible bounds)
float left = smoothstep(visMinX, visMinX + featherLeft, screenUV.x);
float right = smoothstep(visMaxX, visMaxX - featherRight, screenUV.x);
float bottom = smoothstep(visMinY, visMinY + featherBottom, screenUV.y);
float top = smoothstep(visMaxY, visMaxY - featherTop, screenUV.y);
float edgeMask = left * right * bottom * top;
// Blend towards background color instead of white to avoid visible edges
color = mix(bgColor, color, edgeMask);
}
return color;
}
void main() {
// ============================================
// LAYER 1: Base glass effect (fine slits, whole image)
// ============================================
float numSegments = uNumSegments;
float angle = uSlitAngle;
const float blurRadius = 9.0;
const float sigma = 4.0;
// Calculate aspect ratio for consistent slit angles
float aspect = iResolution.x / iResolution.y;
// Displacement parameters (in pixels, scaled by DPR)
// Scale displacement by reference resolution ratio to maintain consistency across browser zoom levels
float resolutionScale = iResolution.x / uRefResolution.x;
vec2 maxDisplacement = vec2(uDisplacementX, uDisplacementY) * uDpr * resolutionScale;
// Use pre-computed UV from vertex shader (zoom + pan already applied)
vec2 uv = vContentUv;
// Base displacement (fine slits)
// Use raw screen UV (vUv) so slit positions stay fixed on screen regardless of pan/zoom
float gradientStart = 1.0;
float gradientEnd = 0.0;
float gradientPower = 1.0;
float centerPoint = 0.5;
vec2 displacementData = createStripedDisplacement(
vUv,
numSegments,
angle,
gradientStart,
gradientEnd,
gradientPower,
centerPoint,
aspect
);
float signedDisplacement = displacementData.x;
float localUVx = displacementData.y;
// ============================================
// LAYER 2: Inner glass effect (larger slits, center only, STATIC)
// ============================================
// Inner segments are a divisor of outer segments for perfect alignment
// 45 / 5 = 9 segments (5x larger slits that align with outer grid)
float innerNumSegments = numSegments;
float innerAngle = angle; // Same angle as outer
// Inner slits are STATIC - use raw screen UV so slits stay fixed on screen
vec2 innerDisplacementData = createStripedDisplacement(
vUv,
innerNumSegments,
innerAngle,
gradientStart,
gradientEnd,
gradientPower,
centerPoint,
aspect
);
float innerSignedDisplacement = innerDisplacementData.x;
float innerLocalUVx = innerDisplacementData.y;
// ============================================
// COMPUTE SOLID CORE MASK EARLY (needed for light & displacement masking)
// ============================================
vec4 videoFrameSample = texture2D(uVideoTexture, uv);
float maskIntensity = luminance(videoFrameSample.rgb);
float solidCoreMask = smoothstep(0.4, 0.7, maskIntensity);
// Store original displacement for shape refraction (before masking)
float originalInnerDisplacement = innerSignedDisplacement;
// Mask inner displacement by solidCoreMask - no inner displacement in center
innerSignedDisplacement *= (1.0 - solidCoreMask);
// Inner layer displacement uses same values (scaled by resolutionScale for zoom independence)
vec2 innerMaxDisplacement = vec2(30.0, 0.0) * uDpr * resolutionScale;
// ============================================
// CENTER ELEMENT (Static Ellipse + Animated Shapes)
// ============================================
float staticEllipseMask = 0.0;
float staticEllipseGradient = 0.0;
ShapeData shapeData;
shapeData.shape = 0.0;
shapeData.gradient = 0.0;
float centerFadeInDuration = 10.0;
float centerFramesSinceStart = uFrameCount - uLightStartFrame;
float centerEffectActivation = clamp(centerFramesSinceStart / centerFadeInDuration, 0.0, 1.0);
if (uEnableCenterElement > 0.5) {
// Calculate static ellipse mask
{
const float ellipseCenterX = 0.3;
const float ellipseCenterY = -0.15;
const float ellipseWidth = 0.5;
const float ellipseHeight = 0.8;
const float ellipseAngle = 0.3;
const float ellipseSoftness = 1.0;
const float ellipseGrayLevel = 0.5;
float aspect = iResolution.x / iResolution.y;
// Use RAW UV (no displacement) for the mask calculation
vec2 delta = uv - vec2(ellipseCenterX, ellipseCenterY);
// Rotate
float cosA = cos(ellipseAngle);
float sinA = sin(ellipseAngle);
vec2 rotatedDelta = vec2(
delta.x * cosA - delta.y * sinA,
delta.x * sinA + delta.y * cosA
);
rotatedDelta.x *= aspect;
// Calculate ellipse distance
float ellipseDist = sdEllipse(rotatedDelta, vec2(ellipseWidth, ellipseHeight));
// Soft shape mask
staticEllipseMask = 1.0 - smoothstep(-ellipseSoftness * 0.1, ellipseSoftness * 0.05, ellipseDist);
// Gradient
float normalizedDist = ellipseDist / max(ellipseWidth, ellipseHeight) + 1.0;
normalizedDist = clamp(normalizedDist, 0.0, 1.0);
staticEllipseGradient = mix(1.0, ellipseGrayLevel, smoothstep(0.0, 1.0, normalizedDist));
// Apply solid core mask
staticEllipseMask *= solidCoreMask;
}
// Block displacement inside the static ellipse
originalInnerDisplacement *= (1.0 - staticEllipseMask);
innerSignedDisplacement *= (1.0 - staticEllipseMask);
signedDisplacement *= (1.0 - staticEllipseMask);
// Calculate displaced UV for shape - use inner displacement for the shape refraction
vec2 shapeDisplacedUV = applyDisplacement(uv, originalInnerDisplacement, innerMaxDisplacement, iResolution);
// Calculate animated shape using DISPLACED UVs so glass slits refract through it
shapeData = calculateAnimatedShape(
shapeDisplacedUV, // Use displaced UVs!
uCenterAnimTime, // Time-based animation in seconds (resets with video loop)
solidCoreMask,
iResolution
);
// Combine static ellipse with animated shapes
{
float combinedShape = max(shapeData.shape, staticEllipseMask) * centerEffectActivation;
float totalMask = shapeData.shape + staticEllipseMask;
float combinedGradient = shapeData.gradient;
if (totalMask > 0.001) {
combinedGradient = (shapeData.gradient * shapeData.shape + staticEllipseGradient * staticEllipseMask) / totalMask;
}
shapeData.shape = combinedShape;
shapeData.gradient = combinedGradient;
}
}
// ============================================
// INNER EFFECT ACTIVATION - starts after uLightStartFrame
// ============================================
float innerFadeInDuration = 10.0; // Slower fade-in for inner effect
float innerFramesSinceStart = uFrameCount - uLightStartFrame;
float innerEffectActivation = clamp(innerFramesSinceStart / innerFadeInDuration, 0.0, 1.0);
// Create edge mask (solidCoreMask already computed earlier for light masking)
float edgeMask = smoothstep(0.3, 0.6, maskIntensity); // Where shape starts
// Blend outer and inner slits on the edges
// Inner contribution is multiplied by activation (fades in after frame 200)
float outerContribution = 1.0 - edgeMask * 0.5 * innerEffectActivation;
float innerContribution = edgeMask * innerEffectActivation;
// Combined displacement: inner effect fades in over time
float combinedDisplacement = signedDisplacement * outerContribution +
innerSignedDisplacement * innerContribution;
// Blend max displacement smoothly
vec2 combinedMaxDisplacement = maxDisplacement * outerContribution +
innerMaxDisplacement * innerContribution;
// For the CENTER area (solidCoreMask): use 100% inner (larger) slits displacement
// For the EDGES: use the combined displacement (blended outer + inner)
// This ensures the logo center always has the larger slits
combinedDisplacement = mix(combinedDisplacement, originalInnerDisplacement, solidCoreMask);
combinedMaxDisplacement = mix(combinedMaxDisplacement, innerMaxDisplacement, solidCoreMask);
// ============================================
// LAYER 1: DISPLACEMENT (toggleable)
// ============================================
vec4 textureSample;
if (uEnableDisplacement > 0.5) {
textureSample = sampleWithDisplacement(
uVideoTexture, uv,
combinedDisplacement, combinedMaxDisplacement,
iResolution
);
} else {
// No displacement - sample directly
textureSample = texture2D(uVideoTexture, uv);
}
// Blend localUVx for highlights - also fades in with activation
float combinedLocalUVx = localUVx * outerContribution + innerLocalUVx * innerContribution;
localUVx = combinedLocalUVx;
// Store innerMask for later use (bloom, etc.) - also tied to activation
float innerMask = edgeMask * innerEffectActivation;
// Get Phase From: Intensity (Rec. 709 luminance)
float baseIntensity = luminance(textureSample.rgb);
// Boost intensity in center areas to push them more towards white
float centerWhiteBoost = 0.1;
baseIntensity = baseIntensity + innerMask * centerWhiteBoost;
baseIntensity = clamp(baseIntensity, 0.0, 1.0);
// Keep base intensity for outer colorama (using uGradientMap)
float outerIntensity = baseIntensity;
// ============================================
// CENTER GREEN STRIPES OVERLAY (before colorama)
// ============================================
float stripeFadeOutDuration = 10.0;
float stripeFramesSinceStart = uFrameCount - uLightStartFrame;
// Effect is active UNTIL uLightStartFrame, then fades out
float stripeEffectActivation = 1.0 - clamp(stripeFramesSinceStart / stripeFadeOutDuration, 0.0, 1.0);
// Animated vertical height mask - grows from 0 to full height
// Height animation: starts at center (0.5) and expands outward
float stripeHeightDelay = 15.0; // frames to wait before starting
float stripeHeightGrowDuration = 80.0; // frames to reach full height
float stripeHeightProgress = clamp((uFrameCount - stripeHeightDelay) / stripeHeightGrowDuration, 0.0, 1.0);
// Ease the progress for smoother animation
float easedHeightProgress = 1.0 - pow(1.0 - stripeHeightProgress, 2.0);
// Calculate animated bounds - expand from center (0.5) outward
float heightHalfSpan = 0.2 * easedHeightProgress; // 0.06 = half of the 0.12 total height (0.44 to 0.56)
float softEdge = 0.1 * easedHeightProgress; // Soft edge also scales with progress
float bottomEdge = 0.5 - heightHalfSpan - softEdge;
float bottomFull = 0.5 - heightHalfSpan;
float topFull = 0.5 + heightHalfSpan;
float topEdge = 0.5 + heightHalfSpan + softEdge;
// When progress is 0, force mask to 0 to avoid glitchy edge at y=0.5
float stripeHeightMask = easedHeightProgress > 0.001
? smoothstep(bottomEdge, bottomFull, uv.y) * smoothstep(topEdge, topFull, uv.y)
: 0.0;
vec4 centerGreenSlantedLines = createStripes(
vUv + vec2(-0.0035, 0.0), // slight offset to avoid moiré
numSegments,
uSlitAngle,
0.15, // stopPosition (20%)
vec4(20.0/255.0, 200.0/255.0, 20.0/255.0, 0.2), // colorStart (green at 0%)
vec4(0.0, 0.0, 0.0, 0.0), // colorMid (white at 20%)
aspect // aspect ratio for consistent angle
) * solidCoreMask * stripeHeightMask;
// Overlay green stripes on center element area
float stripeOverlayMask = solidCoreMask * stripeEffectActivation;
// Modify baseIntensity/outerIntensity with stripes before colorama
vec4 stripedTexture = vec4(textureSample.rgb, 1.0) - centerGreenSlantedLines*0.7 * stripeOverlayMask;
stripedTexture = clamp(stripedTexture, 0.0, 1.0);
// Update intensity for colorama input
float stripedIntensity = luminance(stripedTexture.rgb);
outerIntensity = mix(baseIntensity, stripedIntensity, stripeOverlayMask);
// ============================================
// LAYER 2: COLORAMA (toggleable)
// ============================================
vec3 color;
if (uEnableColorama > 0.5) {
// OUTER colorama: uses base intensity, cross-fades between uGradientMap and uGradientMap2
vec3 outerColoramaResult1 = applyColoramaWithGradient(
uGradientMap, outerIntensity,
uInputMin, uInputMax, uModifyGamma, uPosterizeLevels,
uCycleRepetitions, uPhaseShift, uCycleSpeed,
uWrapMode, uReverse
);
vec3 outerColoramaResult2 = applyColoramaWithGradient(
uGradientMap2, outerIntensity,
uInputMin, uInputMax, uModifyGamma, uPosterizeLevels,
uCycleRepetitions, uPhaseShift, uCycleSpeed,
uWrapMode, uReverse
);
vec3 outerColoramaResult = mix(outerColoramaResult1, outerColoramaResult2, uGradientMapBlend);
// Start with outer colorama as base
vec3 blendedColorama = outerColoramaResult;
// CENTER colorama: only compute when inside shape (skip texture sample otherwise)
// This saves a texture lookup + specular pow() for ~90% of pixels
if (shapeData.shape > 0.001) {
vec3 centerColoramaResult = applyColoramaWithGradient(
uCenterGradientMap, shapeData.gradient,
uInputMin, uInputMax, uModifyGamma, uPosterizeLevels,
uCycleRepetitions, uPhaseShift, uCycleSpeed,
uWrapMode, uReverse
);
// Add specular highlight to center (shiny reflection effect)
float specularPower = 8.0; // Higher = tighter/smaller highlight
float specularIntensity = 1.9; // Brightness of the specular
float specular = pow(shapeData.gradient, specularPower) * specularIntensity;
centerColoramaResult += vec3(specular); // Add bright highlight
// Blend between outer and center colorama based on shape
blendedColorama = mix(outerColoramaResult, centerColoramaResult, shapeData.shape);
}
color = mix(blendedColorama, textureSample.rgb, uBlendWithOriginal);
} else {
color = textureSample.rgb;
}
// Store intensity for bloom (use the blended value)
float intensity = mix(outerIntensity, shapeData.gradient, shapeData.shape);
// ============================================
// LAYER 3: BLOOM (toggleable)
// ============================================
if (uEnableBloom > 0.5) {
color = applyBloom(color, intensity, innerMask);
}
// ============================================
// LAYER 4: SHAPE HIGHLIGHT (toggleable via light sweep toggle)
// ============================================
if (uEnableLightSweep > 0.5) {
float lightFadeInDuration = 30.0;
float framesSinceStart = uFrameCount - uLightStartFrame;
float lightActivation = clamp(framesSinceStart / lightFadeInDuration, 0.0, 1.0);
// Add subtle brightness boost at shape center
float shapeHighlight = pow(shapeData.gradient, 2.0) * shapeData.shape;
color += vec3(1.0) * shapeHighlight * 0.15 * uLightIntensity * lightActivation;
}
// Color correction and edge feathering (shared with ripple mode)
color = applyColorCorrection(color);
// Blend with background color if provided (uBackgroundColor.r < 0 means not set)
if (uBackgroundColor.r >= 0.0) {
// Calculate luminance to determine how bright the pixel is
float brightness = luminance(color);
// Blend white/bright areas with background color
// Bright areas (close to white) become the background color
float blendStart = 0.94; // Start blending at this brightness
float blendEnd = 1.0; // Fully background color at this brightness
float blendAmount = smoothstep(blendStart, blendEnd, brightness);
// Mix the shader color with background color
color = mix(color, uBackgroundColor, blendAmount);
}
// Use background color for feathering if set, otherwise default to white
vec3 featherColor = uBackgroundColor.r >= 0.0 ? uBackgroundColor : vec3(1.0);
color = applyEdgeFeathering(color, featherColor);
gl_FragColor = vec4(color, 1.0);
}
`;
export { rzpGlassFragmentShader, rzpGlassVertexShader };
//# sourceMappingURL=rzpGlassShader.js.map