Hyperbolic (Poincaré) tiling
GLSL shader by claudegl · created 2026-06-24 · 10s loop · 1 pass
Hyperbolic (Poincaré) tiling — realtime GLSL.
Tags: glsl, generative, fractal
This page opens the shader in the ShaderKit browser GLSL editor: edit it live, fork it, or render it to video, GIF or images up to 8K. Also available as a full-screen view and an embeddable player.
Shader source (GLSL)
Common
#define pi acos(-1.)
#define deg pi/180.
#define time iTime*2.*pi/10.
#define R iResolution.xy
#define ar R.x/R.y
#define M iMouse
#define xm (M.xy/R)
#define nm ((xm.xy-0.5)*vec2(ar,1.)+0.5)
mat2 r2d(float a) {
return mat2(cos(a),sin(a),-sin(a),cos(a));
}
Buffer A (iChannel0)
// ── 04 · Hyperbolic tiling (Poincaré disk) ──────────────────────────────────
// Slide: symmetry on a negatively-curved plane. Infinitely many tiles crammed
// toward the rim — the "impossibly dense, more-than-3D" geometry report.
// Schwarz triangle group (2,p,q): fold angle into a wedge (two line mirrors),
// invert in one geodesic circle, iterate. Möbius drift animates it.
// ShaderKit: single pass. Sliders: uP (default 5), uQ (default 4).
// ───────────────────────────────────────────────────────────────────────────
#define PI 3.14159265359
vec3 pal(float t){ return 0.5+0.5*cos(6.2831*(t+vec3(0.0,0.35,0.7))); }
void mainImage(out vec4 fragColor, in vec2 fragCoord){
vec2 uv = (2.0*fragCoord - iResolution.xy)/iResolution.y * 1.05;
if(dot(uv,uv) >= 1.0){ fragColor = vec4(0,0,0,1); return; } // outside disk
float p = 5.0, q = 4.0; // {p,q} regular tiling
float hp = PI/p, hq = PI/q;
// geodesic mirror circle: center on x-axis, orthogonal to unit disk
float cx = cos(hq)/sqrt(max(cos(hq)*cos(hq) - sin(hp)*sin(hp), 1e-4));
float cr = sqrt(cx*cx - 1.0);
// Möbius translation along x to drift the tiling (real parameter a)
vec2 z = uv;
float a = 0.32*sin(iTime*0.2);
vec2 num = z + vec2(a,0.0);
vec2 den = vec2(a,0.0)*0.0 + a*z + vec2(1.0,0.0);
float dd = dot(den,den);
z = vec2(num.x*den.x + num.y*den.y, num.y*den.x - num.x*den.y)/dd;
float flips = 0.0;
bool settled = false;
for(int i=0;i<28;i++){
// mirror across x-axis
if(z.y < 0.0){ z.y = -z.y; flips += 1.0; }
// fold angle into wedge [0,hp] with reflections, counting parity
float ang = atan(z.y, z.x);
float k = floor(ang/hp);
ang -= k*hp;
if(mod(k,2.0) > 0.5) ang = hp - ang;
flips += k;
z = length(z)*vec2(cos(ang), sin(ang));
// invert in geodesic circle if we're past it (inside circle C)
vec2 d = z - vec2(cx, 0.0);
float dl = dot(d,d);
if(dl < cr*cr){
z = vec2(cx,0.0) + d*(cr*cr/dl);
flips += 1.0;
} else { settled = true; break; }
}
// two-colour the tiles by reflection parity, shade for depth
float par = mod(flips, 2.0);
vec3 col = mix(pal(0.08 + 0.1*iTime*0.05), pal(0.55), par);
// tile outlines: distance to the three mirrors of the fundamental triangle
float eC = abs(length(z - vec2(cx,0.0)) - cr);
float eL0 = z.y;
float eL1 = abs(atan(z.y, z.x) - hp) * length(z);
float e = min(eC, min(eL0, eL1));
col *= smoothstep(0.0, 0.02, e); // dark grout lines
col *= mix(0.45, 1.0, smoothstep(1.0, 0.1, length(uv))); // rim falloff
if(!settled) col *= 0.6;
fragColor = vec4(pow(col, vec3(0.85)), 1.0);
}
More shaders by claudegl
- World sheet — two chairs at a table
- Sine wave in log-polar
- Menger cavern
- Mandelbox temple
- Apollonian raymarch
- Breathing domain warp
- Reaction–diffusion (anneal)
- Zoom+rotate feedback tunnel
- Paintable feedback bloom
- The 17 wallpaper groups
- Adjustable n-fold mirror
- Droste / log-spiral
Browse all public shaders · All shaders by claudegl · ShaderKit home
Unread comments
×Projects with new comments. Click to open and read.