GLSLで17の平面繰り返しパターン
二次元平面上に展開する繰り返しパターンは17のタイプに分類できると言われています。この記事ではGLSLを用いてシェーダーで17の繰り返しパターンをそれぞれ実装をしていきます。
17の平面繰り返しパターン
p1 (単純な並進)
vec2 patternP1(vec2 p, vec2 motifSize, float offset) {
vec2 q = vec2(
mod(p.x - motifSize.x * floor(p.y / motifSize.y) * offset, motifSize.x),
mod(p.y, motifSize.y)
);
return q;
}
引数motifSize
は繰り返しパターンのモチーフに使用する四角形の大きさ、引数offset
はX方向のずれの大きさで[0, 1]
の範囲の値を指定します。
vec2 q = patternP1(p, vec2(0.5, 1.0), 0.5);
gl_FragColor = vec4(q, 0.0, 1.0);
float v = drawTriangle(q, vec2(0.1, 0.1), vec2(0.4, 0.1), vec2(0.1, 0.9));
gl_FragColor = vec4(vec3(v), 1.0);
pm (一方向の鏡映)
vec2 patternPm(vec2 p, vec2 motifSize) {
vec2 q = mod(p, motifSize);
q.x = mod(p.x, motifSize.x * 2.0) < motifSize.x ? q.x : motifSize.x - q.x;
return q;
}
引数motifSize
は繰り返しパターンのモチーフに使用する四角形の大きさを指定します。
vec2 q = patternPm(p, vec2(0.5, 1.0));
gl_FragColor = vec4(q, 0.0, 1.0);
float v = drawTriangle(q, vec2(0.1, 0.1), vec2(0.4, 0.1), vec2(0.1, 0.9));
gl_FragColor = vec4(vec3(v), 1.0);
pg (一方向のすべり鏡映)
vec2 patternPg(vec2 p, vec2 motifSize, float glind) {
vec2 q = p;
q.x = mod(q.x, motifSize.x);
if (mod(p.x, motifSize.x * 2.0) >= motifSize.x) {
q.y += motifSize.y * glind;
q.x = motifSize.x - q.x;
}
q.y = mod(q.y, motifSize.y);
return q;
}
引数motifSize
は繰り返しパターンのモチーフに使用する四角形の大きさ、引数glind
はすべり鏡映の移動量の大きさで[0, 1]
の範囲の値を指定します。
vec2 q = patternPg(p, vec2(0.5, 1.0), 0.5);
gl_FragColor = vec4(q, 0.0, 1.0);
float v = drawTriangle(q, vec2(0.1, 0.1), vec2(0.4, 0.1), vec2(0.1, 0.9));
gl_FragColor = vec4(vec3(v), 1.0);
cm (一方向の対角、他方向に鏡映)
vec2 patternCm(vec2 p, vec2 cellSize) {
vec2 q = diamondTiling(p, cellSize.x, cellSize.y);
q.x = q.x < 0.5 * cellSize.x ? 0.5 * cellSize.x - q.x : q.x - 0.5 * cellSize.x;
return q;
}
引数cellSize
は繰り返しパターンのベースになるひし形格子の大きさを指定します。
vec2 q = patternCm(p, vec2(2.0, 1.0));
gl_FragColor = vec4(q, 0.0, 1.0);
float v = drawTriangle(q, vec2(0.05, 0.2), vec2(0.8, 0.5), vec2(0.05, 0.8));
gl_FragColor = vec4(vec3(v), 1.0);
p2 (2回割の回転)
vec2 patternP2(vec2 p, vec2 motifSize, float center) {
vec2 q = p;
q.x = mod(q.x, motifSize.x);
if (mod(p.x, motifSize.x * 2.0) >= motifSize.x) {
q.y += motifSize.y * center;
q.x = motifSize.x - q.x;
q.y *= -1;
}
q.y = mod(q.y, motifSize.y);
return q;
}
引数motifSize
は繰り返しパターンのモチーフに使用する四角形の大きさ、引数center
は回転の中心位置で[0, 1]
の範囲の値を指定します。
vec2 q = patternP2(p, vec2(0.5, 1.0), 0.5);
gl_FragColor = vec4(q, 0.0, 1.0);
float v = drawTriangle(q, vec2(0.1, 0.1), vec2(0.4, 0.1), vec2(0.1, 0.9));
gl_FragColor = vec4(vec3(v), 1.0);
pmm (二方向の鏡映)
vec2 patternPmm(vec2 p, vec2 motifSize) {
vec2 q = mod(p, motifSize);
q.x = mod(p.x, motifSize.x * 2.0) < motifSize.x ? q.x : motifSize.x - q.x;
q.y = mod(p.y, motifSize.y * 2.0) < motifSize.y ? q.y : motifSize.y - q.y;
return q;
}
引数motifSize
は繰り返しパターンのモチーフに使用する四角形の大きさを指定します。
vec2 q = patternPmm(p, vec2(0.5, 1.0));
gl_FragColor = vec4(q, 0.0, 1.0);
float v = drawTriangle(q, vec2(0.1, 0.1), vec2(0.4, 0.1), vec2(0.1, 0.9));
gl_FragColor = vec4(vec3(v), 1.0);
cmm (2本の対角線の両方に鏡映)
vec2 patternCmm(vec2 p, vec2 cellSize) {
vec2 q = diamondTiling(p, cellSize.x, cellSize.y);
q.x = q.x < 0.5 * cellSize.x ? 0.5 * cellSize.x - q.x : q.x - 0.5 * cellSize.x;
q.y = q.y < 0.5 * cellSize.y ? 0.5 * cellSize.y - q.y : q.y - 0.5 * cellSize.y;
return q;
}
引数cellSize
は繰り返しパターンのベースになるひし形格子の大きさを指定します。
vec2 q = patternCmm(p, vec2(2.0, 1.0));
gl_FragColor = vec4(q, 0.0, 1.0);
float v = drawTriangle(q, vec2(0.05, 0.05), vec2(0.8, 0.05), vec2(0.05, 0.3));
gl_FragColor = vec4(vec3(v), 1.0);
pgg (二方向のすべり鏡映)
vec2 patternPgg(vec2 p, vec2 motifSize, float glind) {
vec2 q = p;
if (mod(p.y, motifSize.y * 2.0) >= motifSize.y) {
q.x -= motifSize.x * glind * 2.0;
q.y = motifSize.y - q.y;
}
vec2 r = q;
q.x = mod(q.x, motifSize.x);
if (mod(r.x, motifSize.x * 2.0) >= motifSize.x) {
q.x = motifSize.x - q.x;
q.y *= -1;
}
q.y = mod(q.y, motifSize.y);
return q;
}
引数motifSize
は繰り返しパターンのモチーフに使用する四角形の大きさ、引数glind
はすべり鏡映の移動量の大きさで[0, 1]
の範囲の値を指定します。
vec2 q = patternPgg(p, vec2(0.5, 1.0), 0.5);
gl_FragColor = vec4(q, 0.0, 1.0);
float v = drawTriangle(q, vec2(0.1, 0.1), vec2(0.4, 0.1), vec2(0.1, 0.9));
gl_FragColor = vec4(vec3(v), 1.0);
pmg (一方向の鏡映、他方向にすべり鏡映)
vec2 patternPmg(vec2 p, vec2 motifSize, float glind) {
vec2 q = p;
q.y = mod(p.y, motifSize.y);
if (mod(p.y, motifSize.y * 2.0) >= motifSize.y) {
q.y = motifSize.y - q.y;
q.x -= motifSize.x * glind * 2.0;
}
vec2 r = q;
r.x = mod(q.x, motifSize.x);
if (mod(q.x, motifSize.x * 2.0) >= motifSize.x) {
r.x = motifSize.x - r.x;
}
return r;
}
引数motifSize
は繰り返しパターンのモチーフに使用する四角形の大きさ、引数glind
はすべり鏡映の移動量の大きさで[0, 1]
の範囲の値を指定します。
vec2 q = patternPmg(p, vec2(0.5, 1.0), 0.5);
gl_FragColor = vec4(q, 0.0, 1.0);
float v = drawTriangle(q, vec2(0.1, 0.1), vec2(0.4, 0.1), vec2(0.1, 0.9));
gl_FragColor = vec4(vec3(v), 1.0);
p3 (3回割の回転)
vec2 patternP3(vec2 p, float cellSize) {
const float angleStep = 2.0 * PI / 3.0;
const vec2 centerHexagon = vec2(sqrt(3) / 2, 1.0);
vec2 q = equilateralHexagonTiling(p / cellSize);
q -= centerHexagon;
float a = atan(q.x, q.y);
a = floor(a / angleStep) * angleStep;
q *= rotate(-a);
q *= cellSize;
return q;
}
引数cellSize
は繰り返しパターンのベースになる六角形格子の一辺の大きさを指定します。
vec2 q = patternP3(p, 1.0);
gl_FragColor = vec4(q, 0.0, 1.0);
float v = drawTriangle(q, vec2(0.05, 0.0), vec2(0.8, -0), vec2(0.1, 0.8));
gl_FragColor = vec4(vec3(v), 1.0);
p31m (3回割の回転と鏡映)
vec2 patternP31m(vec2 p, float cellSize) {
const float angleStep = 2.0 * PI / 3.0;
const vec2 centerHexagon = vec2(sqrt(3) / 2, 1.0);
vec2 q = equilateralHexagonTiling(p.yx / cellSize);
q -= centerHexagon;
float a = atan(q.x, q.y) + PI / 6;
q = length(q) * vec2(sin(a), cos(a));
a = floor(a / angleStep) * angleStep;
q *= rotate(-a);
q *= cellSize;
float b = atan(q.x, q.y);
float r = length(q);
b = b < 0.5 * angleStep ? b : angleStep - b;
q = r * vec2(sin(b), cos(b));
return q;
}
引数cellSize
は繰り返しパターンのベースになる六角形格子の一辺の大きさを指定します。
vec2 q = patternP31m(p, 1.0);
gl_FragColor = vec4(q, 0.0, 1.0);
float v = drawTriangle(q, vec2(0.05, 0.05), vec2(0.5, 0.4), vec2(0.1, 0.8));
gl_FragColor = vec4(vec3(v), 1.0);
p3m1 (3回割の回転と鏡映)
vec2 patternP3m1(vec2 p, float cellSize) {
const float angleStep = 2.0 * PI / 3.0;
const vec2 centerHexagon = vec2(sqrt(3) / 2, 1.0);
vec2 q = equilateralHexagonTiling(p / cellSize);
q -= centerHexagon;
float a = atan(q.x, q.y);
a = floor(a / angleStep) * angleStep;
q *= rotate(-a);
q *= cellSize;
float b = atan(q.x, q.y);
float r = length(q);
b = b < 0.5 * angleStep ? b : angleStep - b;
q = r * vec2(sin(b), cos(b));
return q;
}
引数cellSize
は繰り返しパターンのベースになる六角形格子の一辺の大きさを指定します。
vec2 q = patternP3m1(p, 1.0);
gl_FragColor = vec4(q, 0.0, 1.0);
float v = drawTriangle(q, vec2(0.05, 0.05), vec2(0.4, 0.4), vec2(0.1, 0.8));
gl_FragColor = vec4(vec3(v), 1.0);
p4 (4回割の回転)
vec2 patternP4(vec2 p, float cellSize) {
const float angleStep = PI / 2.0;
vec2 q = mod(p, cellSize);
q -= 0.5 * cellSize;
float a = atan(q.y, q.x);
a = floor(a / angleStep) * angleStep;
q *= rotate(a);
return q;
}
引数cellSize
は繰り返しパターンのベースになる四角形格子の一辺の大きさを指定します。
vec2 q = patternP4(p, 1.0);
gl_FragColor = vec4(q, 0.0, 1.0);
float v = drawTriangle(q, vec2(0.05, 0.05), vec2(0.5, 0.05), vec2(0.05, 0.3));
gl_FragColor = vec4(vec3(v), 1.0);
p4m (4回割の回転と鏡映)
vec2 patternP4m(vec2 p, float cellSize) {
const float angleStep = PI / 2.0;
vec2 q = mod(p, cellSize);
q -= 0.5 * cellSize;
float a = atan(q.y, q.x);
a = floor(a / (2.0 * PI / 4)) * (2.0 * PI / 4);
q *= rotate(a);
float b = atan(q.x, q.y);
float r = length(q);
b = b < 0.5 * angleStep ? b : angleStep - b;
q = r * vec2(sin(b), cos(b));
return q;
}
引数cellSize
は繰り返しパターンのベースになる四角形格子の一辺の大きさを指定します。
vec2 q = patternP4m(p, 1.0);
gl_FragColor = vec4(q, 0.0, 1.0);
float v = drawTriangle(q, vec2(0.0, 0.0), vec2(0.4, 0.45), vec2(0.1, 0.35));
gl_FragColor = vec4(vec3(v), 1.0);
p4g (4回割の回転とすべり鏡映)
vec2 patternP4g(vec2 p, float cellSize) {
const float angleStep = PI / 2.0;
vec2 q = mod(p, cellSize);
q.x = mod(p.x, cellSize * 2.0) < cellSize ? q.x : cellSize - q.x;
q.y = mod(p.y, cellSize * 2.0) < cellSize ? q.y : cellSize - q.y;
q -= 0.5 * cellSize;
float a = atan(q.y, q.x);
a = floor(a / angleStep) * angleStep;
q *= rotate(a);
return q;
}
引数cellSize
は繰り返しパターンのベースになる四角形格子の一辺の大きさを指定します。
vec2 q = patternP4g(p, 1.0);
gl_FragColor = vec4(q, 0.0, 1.0);
float v = drawTriangle(q, vec2(0.05, 0.05), vec2(0.5, 0.05), vec2(0.05, 0.3));
gl_FragColor = vec4(vec3(v), 1.0);
p6 (6回割の回転)
vec2 patternP6(vec2 p, float cellSize) {
const float angleStep = PI / 3.0;
const vec2 centerHexagon = vec2(sqrt(3) / 2, 1.0);
vec2 q = equilateralHexagonTiling(p / cellSize);
q -= centerHexagon;
float a = atan(q.x, q.y);
a = floor(a / angleStep) * angleStep;
q *= rotate(-a);
q *= cellSize;
return q;
}
引数cellSize
は繰り返しパターンのベースになる六角形格子の一辺の大きさを指定します。
vec2 q = patternP6(p, 1.0);
gl_FragColor = vec4(q, 0.0, 1.0);
float v = drawTriangle(q, vec2(0.1, 0.1), vec2(0.4, 0.4), vec2(0.1, 0.9));
gl_FragColor = vec4(vec3(v), 1.0);
p6m (6回割の回転と鏡映方向の対角、他方向に鏡映)
vec2 patternP6m(vec2 p, float cellSize) {
const float angleStep = PI / 3.0;
const vec2 centerHexagon = vec2(sqrt(3) / 2, 1.0);
vec2 q = equilateralHexagonTiling(p / cellSize);
q -= centerHexagon;
float a = atan(q.x, q.y);
a = floor(a / angleStep) * angleStep;
q *= rotate(-a);
q *= cellSize;
float b = atan(q.x, q.y);
float r = length(q);
b = b < 0.5 * angleStep ? b : angleStep - b;
q = r * vec2(sin(b), cos(b));
return q;
}
引数cellSize
は繰り返しパターンのベースになる六角形格子の一辺の大きさを指定します。
vec2 q = patternP6m(p, 1.0);
gl_FragColor = vec4(q, 0.0, 1.0);
float v = drawTriangle(q, vec2(0.02, 0.1), vec2(0.2, 0.45), vec2(0.05, 0.9));
gl_FragColor = vec4(vec3(v), 1.0);
補足
繰り返しパターンの実装で使用しているマクロ変数や独自定義した関数です。
#define PI 3.14159265359
mat2 rotate(float r) {
float c = cos(r);
float s = sin(r);
return mat2(c, s, -s, c);
}
// https://iquilezles.org/articles/distfunctions2d/
float sdTriangle( in vec2 p, in vec2 p0, in vec2 p1, in vec2 p2 )
{
vec2 e0 = p1-p0, e1 = p2-p1, e2 = p0-p2;
vec2 v0 = p -p0, v1 = p -p1, v2 = p -p2;
vec2 pq0 = v0 - e0*clamp( dot(v0,e0)/dot(e0,e0), 0.0, 1.0 );
vec2 pq1 = v1 - e1*clamp( dot(v1,e1)/dot(e1,e1), 0.0, 1.0 );
vec2 pq2 = v2 - e2*clamp( dot(v2,e2)/dot(e2,e2), 0.0, 1.0 );
float s = sign( e0.x*e2.y - e0.y*e2.x );
vec2 d = min(min(vec2(dot(pq0,pq0), s*(v0.x*e0.y-v0.y*e0.x)),
vec2(dot(pq1,pq1), s*(v1.x*e1.y-v1.y*e1.x))),
vec2(dot(pq2,pq2), s*(v2.x*e2.y-v2.y*e2.x)));
return -sqrt(d.x)*sign(d.y);
}
float drawTriangle(vec2 p, vec2 p0, vec2 p1, vec2 p2) {
float d = sdTriangle(p, p0, p1, p2);
return step(0.0, d);
}
vec2 diamondTiling(vec2 p, float width, float height) {
float h = 0.5 * height;
vec2 q = p;
float skew = 0.5 * width / h;
q.x -= q.y * skew;
q = vec2(mod(q.x, width), mod(q.y, h));
if (q.x / width + q.y / h >= 1.0) {
q.x -= 0.5 * width;
} else {
q.y += h;
}
q.x += mod(q.y, h) * skew;
return q;
}
vec2 equilateralHexagonTiling(vec2 p) {
const float WIDTH = sqrt(3);
const float HALF_WIDTH = 0.5 * WIDTH;
const float TRIANGLE_HEIGHT = 0.5;
const float SIDE_LENGTH = 1.0;
const float ROW_HEIGHT = SIDE_LENGTH + TRIANGLE_HEIGHT;
const float TRIANGLE_SKEW = sqrt(3);
vec2 q = p;
bool isEvenRow = int(mod(floor(q.y / ROW_HEIGHT), 2.0)) == 0;
q.y = mod(q.y, ROW_HEIGHT);
if (q.y < TRIANGLE_HEIGHT) { // top and bottom triangles
q.x += isEvenRow ? HALF_WIDTH : 0;
q.x -= TRIANGLE_SKEW * q.y;
q.x = mod(q.x, WIDTH);
if (q.x / WIDTH + q.y / TRIANGLE_HEIGHT < 1.0) { // top triangle
q.y += ROW_HEIGHT;
} else { // bottom triangle
q.x -= HALF_WIDTH;
}
q.x += TRIANGLE_SKEW * mod(q.y, ROW_HEIGHT);
} else { // center rectangle
q.x -= !isEvenRow ? HALF_WIDTH : 0;
q.x = mod(q.x, WIDTH);
}
return q;
}
diamondTiling
関数、equilateralHexagonTiling
関数はそれぞれ別記事にしてますので、そちらを参照ください。
Discussion