🔳
GLSLでピタゴラスタイリング
2つの大きさの正方形で平面を敷き詰めるピタゴラスタイリング(Pythagorean Tiling)というタイリング手法があります。この記事ではGLSLでピタゴラスタイリングを実装します。
ピタゴラスタイリングは以下の図のように切り取ると正方形格子状の繰り返しパターンになっています。左右2つとも切り取り方は異なりますが、どちらも繰り返しパターンになっています。
2つの大きさの正方形を塗り分けて境界線を描画する方法について考えます。GLSLで実装すると以下のようになります。コードはGLSL Sandbox互換になっています。
precision highp float;
uniform vec2 resolution;
#define PI 3.14159265359
mat2 rotate(float r) {
float c = cos(r);
float s = sin(r);
return mat2(c, s, -s, c);
}
vec2 polarMod(vec2 p, float n) {
float s = 2.0 * PI / n;
float a = atan(p.y, p.x);
a = floor(a / s) * s;
p *= rotate(a);
return p;
}
void main(void) {
vec2 p = gl_FragCoord.xy / min(resolution.x, resolution.y);
p *= 8.0;
float a = PI / 6.0; // 0 < a <= PI / 4
p *= rotate(a);
p = mod(p, 1.0) - 0.5;
p *= rotate(-a);
p = polarMod(p, 4.0);
float s = 0.5 * sin(a);
float v = p.x < s && p.y < s ? 0.2 : 0.4;
float w = 0.02;
v = mix(v, 1.0, step(abs(p.y - s), w));
v = mix(v, 1.0, step(abs(p.x - s), w) * step(p.y, s));
gl_FragColor = vec4(vec3(v), 1.0);
}
描画結果は以下のようになります。
上述した繰り返しパターンのうち右側の切り取り方法で実装しています。以下の図のように繰り返しパターンの一単位の中で、さらに90度回転の回転対称になっていることを利用しています。
次に各正方形で0~1の座標値を取得する方法について考えます。GLSLで実装すると以下のようになります。
precision highp float;
uniform vec2 resolution;
#define PI 3.14159265359
mat2 rotate(float r) {
float c = cos(r);
float s = sin(r);
return mat2(c, s, -s, c);
}
void main(void) {
vec2 p = gl_FragCoord.xy / min(resolution.x, resolution.y);
p *= 8.0;
float a = PI / 6.0; // 0 < a <= PI / 4
p *= rotate(a);
p = mod(p, 1.0);
p *= rotate(-a);
float c = cos(a);
float s = sin(a);
vec2 q = p;
bool l = true; // large square or not
if (p.y < c) {
if (p.x < 0.0) {
if (p.y < c - s) {
q += vec2(c, s);
} else {
q += vec2(s, s - c);
l = false;
}
}
} else {
q.y -= c;
if (p.x < c - s) {
q.x += s;
} else {
q.x -= c - s;
l = false;
}
}
q /= l ? c : s;
gl_FragColor = vec4(q, 0.0, 1.0);
}
描画結果は以下のようになります。
上述した繰り返しパターンのうち左側の切り取り方法で、以下の図のように愚直に座標値を修正しています。
関数に切り出すと以下のようになります。
vec2 pythagoreanTiling(vec2 p, float a, bool normalized, out bool isLarge) {
p *= rotate(a);
p = mod(p, 1.0);
p *= rotate(-a);
float c = cos(a);
float s = sin(a);
vec2 q = p;
isLarge = true;
if (p.y < c) {
if (p.x < 0.0) {
if (p.y < c - s) {
q += vec2(c, s);
} else {
q += vec2(s, s - c);
isLarge = false;
}
}
} else {
q.y -= c;
if (p.x < c - s) {
q.x += s;
} else {
q.x -= c - s;
isLarge = false;
}
}
if (normalized) {
q /= isLarge ? c : s;
}
return q;
}
Discussion