|
#include <metal_stdlib> |
|
#include <SwiftUI/SwiftUI.h> |
|
using namespace metal; |
|
|
|
float hash21(float2 p) { |
|
float3 p3 = fract(float3(p.xyx) * 0.1031); |
|
p3 += dot(p3, p3.yzx + 33.33); |
|
return fract((p3.x + p3.y) * p3.z); |
|
} |
|
|
|
float vnoise(float2 p) { |
|
float2 i = floor(p); |
|
float2 f = fract(p); |
|
float2 u = f * f * (3.0 - 2.0 * f); // smoothstep hermite |
|
float a = hash21(i); |
|
float b = hash21(i + float2(1.0, 0.0)); |
|
float c = hash21(i + float2(0.0, 1.0)); |
|
float d = hash21(i + float2(1.0, 1.0)); |
|
return mix(mix(a, b, u.x), mix(c, d, u.x), u.y); |
|
} |
|
|
|
float fbm(float2 p, int octaves) { |
|
float val = 0.0; |
|
float amp = 0.5; |
|
float freq = 1.0; |
|
for (int i = 0; i < octaves; i++) { |
|
val += amp * vnoise(p * freq); |
|
freq *= 2.0; |
|
amp *= 0.5; |
|
} |
|
return val; |
|
} |
|
|
|
|
|
|
|
[[ stitchable ]] half4 rippleDissolve(float2 pos, SwiftUI::Layer l, float4 boundingRect, float progress) { |
|
float2 size = boundingRect.zw; |
|
float2 uv = pos / size; |
|
float2 center = float2(0.5,0.5); |
|
|
|
float2 p = uv - center; |
|
float aspect = size.x / size.y; |
|
p.x *= aspect; |
|
|
|
float dist = length(p); |
|
float maxDist = length(float2(0.5 * aspect, 0.5)); |
|
float normDist = clamp(dist / maxDist, 0.0, 1.0); |
|
float angle = atan2(p.y, p.x); |
|
|
|
// Noise to break up the wavefront |
|
float noiseWarp = fbm(float2(angle * 2.5 + 10.0, normDist * 6.0 + progress * 2.0), 4); |
|
float noiseCart = fbm(p * 12.0 + float2(progress * 2.0, -progress * 1.5), 3); |
|
float warpedDist = normDist + (noiseWarp - 0.5) * 0.45 + (noiseCart - 0.5) * 0.4; |
|
|
|
// Wavefront expanding outward |
|
float waveFront = progress * 1.2; |
|
|
|
// Envelope for multiple waves |
|
float sigma = 0.2; // Wider envelope to fit multiple waves |
|
float delta = warpedDist - waveFront; |
|
float baseEnvelope = exp(-delta * delta / (2.0 * sigma * sigma)); |
|
|
|
float waveFreq = 40.0; // Controls number of ripples |
|
float ripples = max(0.0, cos(delta * waveFreq)); |
|
float envelope = baseEnvelope * ripples; |
|
|
|
// How far behind the wave this pixel is (0 = at/ahead of wave, 1 = fully behind) |
|
float feather = 0.2 + 0.03 * noiseWarp; // widened boundary |
|
float behindWave = smoothstep(waveFront + feather, waveFront - feather, warpedDist); |
|
|
|
// Zero effect at progress 0 |
|
float gate = smoothstep(0.0, 0.05, progress); |
|
envelope *= gate; |
|
behindWave *= gate; |
|
|
|
// Displacement only at the wavefront — the burn edge |
|
float2 dir = (dist > 0.001) ? normalize(p) : float2(0.0); |
|
float pushAmt = envelope * 0.15; |
|
float2 uvOffset = dir * pushAmt; |
|
uvOffset.x /= aspect; |
|
|
|
// Chromatic aberration at the wavefront |
|
float caStrength = envelope * 0.005; |
|
float2 caOffset = dir * caStrength; |
|
caOffset.x /= aspect; |
|
|
|
half4 colorR = l.sample((uv - uvOffset - caOffset) * size); |
|
half4 colorG = l.sample((uv - uvOffset) * size); |
|
half4 colorB = l.sample((uv - uvOffset + caOffset) * size); |
|
half4 color = half4(colorR.r, colorG.g, colorB.b, colorG.a); |
|
|
|
// Color dodge glow at the wavefront |
|
half glow = half(envelope * 0.95); |
|
color.rgb = clamp(color.rgb / max(1.0h - glow, 0.01h), 0.0h, 1.0h); |
|
|
|
// Scatter: pixels behind the wave get flung in random directions |
|
float2 scatter = float2(hash21(p * 50.0) - 0.5, hash21(p * 50.0 + 7.0) - 0.5); |
|
float2 scatteredUV = uv + scatter; |
|
scatteredUV = mix(uv - uvOffset, scatteredUV, behindWave); |
|
|
|
// Re-sample with scattered UVs (replaces the wavefront-only sample) |
|
half4 scatterR = l.sample((scatteredUV - caOffset) * size); |
|
half4 scatterG = l.sample(scatteredUV * size); |
|
half4 scatterB = l.sample((scatteredUV + caOffset) * size); |
|
half4 scattered = half4(scatterR.r, scatterG.g, scatterB.b, scatterG.a); |
|
|
|
// Blend from wavefront sample to scattered sample |
|
color = mix(color, scattered, half(behindWave)); |
|
|
|
// Scattered pixels fade out as they disperse |
|
float scatterFade = 1.0 - smoothstep(0.3, 1.0, behindWave); |
|
color *= half(scatterFade); |
|
// |
|
return color; |
|
} |