BrushCue now has a Kaleidoscope tool that turns any image into a symmetric, reflective pattern. It is a fast way to create geometric art, abstract textures, or hypnotic backgrounds from photos, illustrations, or gradients.
The three controls that shape the look
The Kaleidoscope tool exposes three parameters so you can dial in the look.
Segments
Segments control how many mirrored wedges are arranged around the center.
Rotation
Rotation spins the effect around the center. Use it to animate in a video or to align details where you want them.
Warp
Warp adds a subtle radial distortion. When it is set above 0, the shader applies a sine-wave ripple to the radius as the pattern is generated, which makes the result feel more organic and glass-like instead of perfectly geometric. The Warp Frequency control sets how many ripples appear across the radius, so higher values mean tighter, more frequent waves. Set Warp to 0 for a clean, static kaleidoscope.
Tips for great results
- Start with a high-contrast image for the most striking effects.
- Simple images tend to work best. You will often see the subject repeated within the effect.
- Play with the segments. It often exposes something interesting.
How the Shader Works
BrushCue aims to avoid keeping secrets about how our effects work. To that end, here is the shader code for this effect if you are interested. You (or the AI assistant) can modify this code within BrushCue to get a custom effect.
@group(0) @binding(0) var input_texture: texture_2d<f32>;
@group(0) @binding(1) var<uniform> rotation: f32;
@group(0) @binding(2) var<uniform> segments: f32;
@group(0) @binding(3) var<uniform> warp: f32;
@group(0) @binding(4) var<uniform> warp_frequency: f32;
fn do_transformation(input: vec4<f32>, position: vec2<u32>) -> vec4<f32> {
// @start_function
// Get normalized coordinates (0.0 to 1.0)
let dims_u = textureDimensions(input_texture);
let dims = vec2<f32>(dims_u);
// Pixel-center UV (reduces half-texel bias vs vec2(position)/dims)
let uv = (vec2<f32>(position) + vec2<f32>(0.5)) / dims;
// Center to [-0.5, 0.5] space
var p = uv - vec2<f32>(0.5);
p = rotate2(p, rotation);
// Polar
let angle_raw = atan2(p.y, p.x);
var r = length(p);
// Optional: subtle radial warp to feel more "glass-like"
// (set warp=0.0 to disable)
r = r + warp * sin(r * warp_frequency);
// Kaleidoscope fold (clean wedge mirror)
let TAU = 6.28318530718;
let seg = TAU / segments;
// Map angle into [0, seg), then mirror fold into [0, seg/2]
var a = fract((angle_raw + TAU) / seg) * seg;
a = abs(a - 0.5 * seg);
// Radius scale (controls zoom/coverage)
let r2 = r * 1.5;
// Back to UV
var k = vec2<f32>(cos(a), sin(a)) * r2 + vec2<f32>(0.5);
// Prefer mirror wrap to avoid "stuck-to-edge" clamp rings
k = vec2<f32>(mirror01(k.x), mirror01(k.y));
return sample(input_texture, k);
// @end_function
}
// @start_helpers
// Helper function for safe texture sampling (nearest-neighbor)
fn sample_at(texture: texture_2d<f32>, uv: vec2<f32>) -> vec4<f32> {
let dims = textureDimensions(texture);
let clamped = clamp(uv, vec2<f32>(0.0), vec2<f32>(0.9999));
let pos = vec2<u32>(clamped * vec2<f32>(dims));
return textureLoad(texture, pos, 0);
}
// Mirror-wrap a scalar into [0, 1] with reflected repeats.
// This tends to look more "kaleidoscope-like" than clamping.
fn mirror01(x: f32) -> f32 {
let t = fract(x);
return 1.0 - abs(2.0 * t - 1.0);
}
// 2D rotation around origin
fn rotate2(v: vec2<f32>, radians: f32) -> vec2<f32> {
let c = cos(radians);
let s = sin(radians);
return vec2<f32>(c * v.x - s * v.y, s * v.x + c * v.y);
}
// @end_helpers
fn sample(tex: texture_2d<f32>, uv: vec2<f32>) -> vec4<f32>; // function implemented for youOne key thing to notice is that we use the "sample" function for this effect. It is provided for you, and using it here keeps transitions smooth and avoids aliasing.