Skip to content

Instantly share code, notes, and snippets.

@andrewgleave
Created February 8, 2025 18:51
Show Gist options
  • Save andrewgleave/c773f370f8eae7d98d65dbb178456638 to your computer and use it in GitHub Desktop.
Save andrewgleave/c773f370f8eae7d98d65dbb178456638 to your computer and use it in GitHub Desktop.
Complex Gradient Animation in SwiftUI - based on https://hturan.com/writing/complex-numbers-glsl
import SwiftUI
extension View {
func complexGradientEffect(angle: Double, offset: Double) -> some View {
let function = ShaderFunction(
library: .default,
name: "complexGradient"
)
let shader = Shader(function: function, arguments: [
.boundingRect,
.float(angle),
.float(offset)
])
return self.colorEffect(shader, isEnabled: true)
}
}
struct ExampleComplexGradientView: View {
let start = Date()
var body: some View {
GeometryReader { geometry in
ZStack {
TimelineView(.animation) { context in
let elapsed = Date().timeIntervalSince(start)
let angle = sin(elapsed * 0.10) * .pi
Rectangle()
.complexGradientEffect(angle: angle, offset: 0.0)
.ignoresSafeArea()
.frame(width: geometry.size.width, height: geometry.size.height)
}
}
}
}
}
#Preview {
ExampleComplexGradientView()
}
#include <metal_stdlib>
#include <metal_math>
using namespace metal;
#define CONST static constant constexpr const
CONST half PI = 3.14159265h;
// Complex helpers using half precision
typedef half2 Complex;
Complex divideComplex(Complex a, Complex b) {
half denominator = b.x * b.x + b.y * b.y;
return half2((a.x * b.x + a.y * b.y) / denominator,
(a.y * b.x - a.x * b.y) / denominator);
}
Complex logComplex(Complex z) {
return half2(metal::fast::log(length(z)),
metal::fast::atan2(z.y, z.x));
}
// Palette
CONST half3 A = half3(0.50h, 0.52h, 0.53h);
CONST half3 B = half3(0.46h, 0.32h, 0.35h);
CONST half3 C_p = half3(0.82h, 0.84h, 0.65h);
CONST half3 D = half3(0.53h, 0.23h, 0.22h);
[[stitchable]] half4 complexGradient(float2 position, half4 color, float4 box, float angle, float offset) {
half lineLength = 0.5h;
half c = metal::fast::cos(half(angle));
half s = metal::fast::sin(half(angle));
half2 p = half2(s * lineLength, c * lineLength);
half2 q = -p;
half minDimension = min(half(box.z), half(box.w));
half2 uv = (half2(position) - 0.5h * half2(half(box.z), half(box.w))) / minDimension;
half2 z_minus_p = uv - p;
half2 z_minus_q = uv - q;
half2 division = divideComplex(z_minus_p, z_minus_q);
half2 log_p_over_q = logComplex(division);
half imaginary = log_p_over_q.y / PI;
imaginary = 2.0h * fract((imaginary + 1.0h) / 2.0h) - 1.0h;
half3 angleVal = (0.38h + half(offset)) * 2.0h * PI * (C_p * imaginary + D);
half3 cosVal = half3(metal::fast::cos(angleVal.x),
metal::fast::cos(angleVal.y),
metal::fast::cos(angleVal.z));
half3 col = A + B * cosVal;
return half4(col, 1);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment