17の平面繰り返しパターンをGLSLで実装してみる - その1

17の平面繰り返しパターンをGLSLで実装してみる - その1

目次

はじめに#

平面の繰り返しパターンは17種に分類することが、数学的に証明されています。それも4つの基本移動(並進・鏡映・回転・映進)の組み合わせで17パターンを作成できます。

詳しくは以下のWikipediaのリンクと参考書籍を参照ください。

文様群

文様群 もしくは壁紙群(かべがみぐん)は、パターンの対称性に基づく、2次元内での繰り返しパターンに関する数学的な分類である。このようなパターンは、建築や美術で頻繁に使用され、そのパターンは17種に大別される。

Wikipedia

このシリーズでは、GLSLで17の平面繰り返しパターンを実装してみます。
紹介するパターンはp1~pmgまでの8パターンになります。残りのパターンは次回に紹介します。

基本的なコードの説明#

このシリーズでは、対称性が分かりやすいように三角形を4つの基本移動をさせることでパターンを作成します。まずは基本的なmainのコードを説明します。

void main() {
  vec2 uv = vUv; // 0~1 の範囲のUV座標
 
  vec2 tile = vec2(6.0); // 分割数を6倍に設定
  float offsetX = 0.5;
 
  vec2 tuv = repeatP1(uv, tile, offsetX);
 
  // 三角形の頂点座標を設定
  vec2 p0 = vec2(0.0, 0.0);
  vec2 p1 = vec2(0.0, 1.0);
  vec2 p2 = vec2(1.0, 1.0);
 
  vec3 color = isUv ?
    vec3(tuv, 0.0) :
    vec3(triangle(tuv, p0, p1, p2));
 
  fragColor = vec4(color, 1.0);
}

vUvには0~1の範囲のUV座標が入っています。tileで分割数を設定し、各パターンの関数に使用します。isUvはuniform変数で、デモのチェックボックスに対応し、チェックを入れるとUV座標を色で表示します。

三角形を描画する関数は次のようになっているので、気になる方は参照ください。
3つの頂点座標を渡し、三角形の内側の場合に1.0を返すので白色になります。

triangle関数のコード
triangle
float triangle(vec2 uv, vec2 p0, vec2 p1, vec2 p2) {
  vec2 v0 = p1 - p0;
  vec2 v1 = p2 - p0;
  vec2 v2 = uv - p0;
 
  float d00 = dot(v0, v0);
  float d01 = dot(v0, v1);
  float d11 = dot(v1, v1);
  float d20 = dot(v2, v0);
  float d21 = dot(v2, v1);
 
  float denom = d00 * d11 - d01 * d01;
 
  float v = (d11 * d20 - d01 * d21) / denom;
  float w = (d00 * d21 - d01 * d20) / denom;
  float u = 1.0 - v - w;
 
  return step(0.0, u) * step(0.0, v) * step(0.0, w);
}

それでは、p1の単純な並進からみていきましょう。

p1(単純な並進)#

p1では基本移動の並進を繰り返して展開します。

p1 単純な並進
p1 単純な並進

デモを見る

このデモでは、偶数行のみX方向にずらせるようにしてます。
コードは以下のようになります。

p1
vec2 repeatP1(vec2 uv, vec2 tile, float offsetX) {
  vec2 p = uv * tile;
 
  float row = floor(p.y);
  p.x += mod(row, 2.0) == 0.0 ? offsetX : 0.0; // 偶数行のみ移動させる
 
  return fract(p);
}

UV座標をtileで拡大し、行番号をfloorで取得します。
mod関数で行番号が偶数のときのみにX方向にoffsetX分移動させています。
最後にfract関数で0~1の範囲に戻すことで繰り返しパターンを作成しています。

pm(一方向の鏡映)#

pmでは左右に鏡映し、並進します。

pn 一方向の鏡映
pn 一方向の鏡映

デモを見る

デモでは、右半分の正三角形を鏡映し繰り返すようにしてます。
コードは以下のようになります。

pm
vec2 repeatPm(vec2 uv, vec2 tile) {
  vec2 p = uv * tile;
  vec2 f = fract(p);
 
  // 右半分を鏡映
  if (f.x > 0.5) {
    f.x = 1.0 - f.x;
  }
 
  return f;
}

拡大したUV座標をfractで0~1の範囲にし、右半分(f.x > 0.5)の場合に1.0から引くことで、反転することになるので鏡映の操作になります。

pg(一方向の映進)#

pgでは映進させて左右あるいは上下など一方向に繰り返します。鏡映は含みません。

pg 一方向の映進
pg 一方向の映進

デモを見る

デモでは右半分の三角形を上下に移動できるようにしています。
コードは以下のようになります。

pg
vec2 repeatPg(vec2 uv, vec2 tile, float offsetY) {
  vec2 p = uv * tile;
 
  vec2 cellSize = vec2(0.5, 1.0); // 基本セルサイズ
 
  vec2 q = p;
 
  // X基本並進
  q.x = mod(q.x, cellSize.x);
 
  // 2セル周期で判定
  if (mod(p.x, cellSize.x * 2.0) >= cellSize.x) {
    q.y += cellSize.y * offsetY;
    q.x = cellSize.x - q.x;
  }
 
  // Y基本並進
  q.y = mod(q.y, cellSize.y);
 
  return q;
}

ここでは基本モチーフの領域(cellSize)を横が0.5、縦が1.0の長方形に設定しています。
if文の判定は、右半分の領域かを判定していて、その場合はY方向にoffsetY分移動させ、X方向は反転させています。これでpgの操作が実現できます。

最後にY方向に基本並進させるためにmod関数を使用しています。

cm(鏡映 + 平行映進)#

cmでは、鏡映と平行映進を組み合わせて繰り返します。

cm 鏡映 + 平行映進
cm 鏡映 + 平行映進

デモを見る

デモでは偶数列のみ0.5を掛けることで、半分シフトさせています。

cm
vec2 repeatCm(vec2 uv, vec2 tile) {
  vec2 p = uv * tile;
  vec2 id = floor(p);
  vec2 f  = fract(p);
 
  // 列番号
  float col = mod(id.x, 2.0);
 
  // 偶数列なら半分Yシフト
  f.y += 0.5 * col;
 
  // Y周期
  f.y = mod(f.y, 1.0);
 
  f.x = f.x > 0.5 ? 1.0 - f.x : f.x;
 
  return f;
}

p2(180度回転)#

p2では、180度回転と並進を組み合わせて繰り返します。

p2 180度回転
p2 180度回転

デモを見る

180度回転は、行列を使用して実装しています。
上図のUVのとおり、偶数行を180度回転させるようにしてます。

p2
vec2 repeatP2(vec2 uv, vec2 tile) {
  vec2 p = uv * tile;
  vec2 center = vec2(0.5);
 
  vec2 id = floor(p);
  vec2 f = fract(p) - center;
 
  float parity = mod(id.y, 2.0);
 
  mat2 R = mat2(
    -1.0, 0.0,
    0.0,-1.0
  );
 
  f = mix(R * f, f, parity);
 
  return f + center;
}

偶数行の判定は、mod(id.y, 2.0)で行い、偶数行のときは180度回転させる行列Rを掛けています。

float parity = mod(id.y, 2.0);
 
mat2 R = mat2(
  -1.0, 0.0,
  0.0,-1.0
);
 
f = mix(R * f, f, parity);

回転の中心はUV座標の(0.5, 0.5)に設定しているので、fから中心を引いて回転させた後、中心を足すことで回転させています。

pmm(二方向の鏡映、点を中心にした90度鏡映)#

pmmでは、モチーフが鏡映してから、さらに直行する軸で鏡映します。また、別に考えると点を中心に3回の90度を回転させているともいえます。

pmm 二方向の鏡映
pmm 二方向の鏡映

デモを見る

pmではX軸方向を鏡映していましたが、pmmではさらにY軸方向も鏡映するようにコードを書けばいいでしょう。

pmm
vec2 repeatPmm(vec2 uv, vec2 tile) {
  vec2 p = uv * tile;
  vec2 f = fract(p);
 
  f.x = f.x > 0.5 ? 1.0 - f.x : f.x;
  f.y = f.y > 0.5 ? 1.0 - f.y : f.y;
 
  return f;
}

pgg(二方向の映進)#

pggでは、モチーフが二方向に映進するパターンです。p2(180度回転)してから映進させても同じ結果が得られます。

pgg 二方向の映進
pgg 二方向の映進

デモを見る
pgg
vec2 repeatPgg(vec2 uv, vec2 tile) {
  vec2 p = uv * tile;
 
  vec2 id = floor(p);
  vec2 f = fract(p);
 
  if (f.x > 0.5) {
    f.x = f.x - 0.5;
    f.y = 1.0 - f.y;
  }
 
  if (mod(id.y, 2.0) < 1.0) {
    f.x = 0.5 - f.x;
    f.y = 1.0 - f.y;
  }
 
  return f;
}

pmg(一方向の鏡映、他方向の映進)#

pmgでは、鏡映したモチーフが、さらに映進して繰り返すパターンです。方向が互い違いになるパターンができあがります。

pmg 一方向の鏡映、他方向の映進
pmg 一方向の鏡映、他方向の映進

デモを見る
pmg
vec2 repeatPmg(vec2 uv, vec2 tile, float offsetX) {
 
  vec2 p  = uv * tile;
  vec2 id = floor(p);
  vec2 f  = fract(p);
 
  if(mod(id.y, 2.0) < 1.0){
    f.y = 1.0 - f.y;
    f.x = fract(f.x - offsetX);
  }
 
  if(f.x > 0.5){
    f.x = 1.0 - f.x;
  }
 
  return f;
}

まとめ#

今回は、p1~pmgまでの8パターンを紹介しました。次回は残りの9パターンを紹介します。

参考書籍#

Related Post
「リアルタイムグラフィックスの数学」勉強ログ - 第8章 3Dレンダリング
image

はじめに

「リアルタイムグラフィックスの数学」の第 8 章の 3D レンダリングについての勉強ログです。

<AmazonLink imageId="61CP8Asy52L.SY522" linkId="3Iy0agT" title="リアルタイムグラフィックスの数学 ― GLSLではじめるシェーダプログラミング" author="巴山竜来" />

テクスチャマッピング

地面とレイの交点を計算し、地面に市松模様をテクスチャとしてマッピングします。市松模様のテクスチャは次のようになります。

float text(vec2 st) {
  return mod(floor(st.s) + floor(st.t), 2.0);
}

ここで 3D 空間のカメラの設定をします。カメラの向きを$\bm{x} = (0, 0, -1)$とし、カメラの上方向を$\bm{y} = (0, 1, 0)$とします。この値に対して外積をとることで、撮影する向きに対する水平方向$(1, 0, 0)$を得ることができます。実際の計算は次のようになります。

Meta Data
公開日:2025-10-11
Tags:
#GLSL
#数学
#勉強ログ
「リアルタイムグラフィックスの数学」勉強ログ - 第7章 距離とSDF
image

はじめに

「リアルタイムグラフィックスの数学」の第 7 章の距離と SDF についての勉強ログです。

<AmazonLink imageId="61CP8Asy52L.SY522" linkId="3Iy0agT" title="リアルタイムグラフィックスの数学 ― GLSLではじめるシェーダプログラミング" author="巴山竜来" />

2 次元 SDF

胞体ノイズでは近傍点との距離を返す関数でしたが、近くの「点」ではなく「図形」との距離を返す関数を考えます。ここでは図形との負の値もありうる距離を返す関数を導入します。これがSDF(Signed Distance Function, 符号付き距離関数)となります。

円の SDF

円の SDF について考えてみます。円は中心点 C と半径 r から決まりますが、C からの距離が r より小さければ円の「内部」、r より大きければ円の「外部」となり、r と等しい場合は円の「境界」です。平面上の点 P に対して、円の外部では値は正、内部では値が負とします。これが円の SDF を定めます。

円のSDF

Meta Data
公開日:2025-10-05
Tags:
#GLSL
#数学
#勉強ログ
「リアルタイムグラフィックスの数学」勉強ログ - 第6章 胞体ノイズ
image

はじめに

「リアルタイムグラフィックスの数学」の第 6 章の胞体ノイズについての勉強ログです。

<AmazonLink imageId="61CP8Asy52L.SY522" linkId="3Iy0agT" title="リアルタイムグラフィックスの数学 ― GLSLではじめるシェーダプログラミング" author="巴山竜来" />

胞体ノイズとは?

値ノイズと勾配ノイズは格子点でのランダムな値を使ったノイズ関数でした。一方この章で学ぶ胞体ノイズは距離を使ってつくられます。胞体(セル)とは生物の細胞などのことで、胞体ノイズは「近さ」によって空間をバラバラに分割します。

第 1 近傍距離とボロノイ分割

胞体ノイズは空間内に点をバラまいて、各点への距離を測ることで得られますが、バラまかれたこれらの点は特徴点と呼ばれます。ここで特徴点を$A_1,...,A_n$とします。空間内の点 P を定めたとき、P から特徴点$A_i$への距離を$d(P, A_i)$で表すと、すべての特徴点までの距離の最小値は次のように求まります。

$$ \mathrm{min}(d(P, A_1), ... , d(P, A_n)) $$

Meta Data
公開日:2025-09-30
Tags:
#GLSL
#数学
#勉強ログ
「リアルタイムグラフィックスの数学」勉強ログ - 第5章ノイズの調理法
image

はじめに

「リアルタイムグラフィックスの数学」の第 5 章のノイズの調理法についての勉強ログです。

<AmazonLink imageId="61CP8Asy52L.SY522" linkId="3Iy0agT" title="リアルタイムグラフィックスの数学 ― GLSLではじめるシェーダプログラミング" author="巴山竜来" />

再帰

再帰関数とは、関数の中で自分自身の関数を呼び出すような関数のことです。GLSL では再帰関数は使えませんが、for 文を使って再帰的な処理を行うことができます。

非整数ブラウン運動(fBM)

1 以下の定数 G に対し、1 変数ノイズ関数 noise(x)の周波数を 2 倍するごとに値を G 倍して、それを足し合わせてみましょう。式にすると次のようになります。

$$ noise(x) + Gnoise(2x) + G^2noise(4x) + ... + G^knoise(2^kx) $$

ここで素材となるノイズ関数 noise(x)はどのような関数でもいいですが、値の範囲を$[-0.5, 0.5]$区画にずらしておきます。このように加工されたノイズ関数は非整数ブラウン運動(fractional Brownian motion, fBM)と呼ばれます。

Meta Data
公開日:2025-09-24
Tags:
#GLSL
#数学
#勉強ログ
「リアルタイムグラフィックスの数学」勉強ログ - 第4章勾配ノイズ
image

はじめに

「リアルタイムグラフィックスの数学」の第 4 章の勾配ノイズについての勉強ログです。

<AmazonLink imageId="61CP8Asy52L.SY522" linkId="3Iy0agT" title="リアルタイムグラフィックスの数学 ― GLSLではじめるシェーダプログラミング" author="巴山竜来" />

勾配ノイズとは?

前回は値ノイズについて見てきました。値ノイズは計算量も少なく、簡単につくれるノイズですが、格子で区切っているのでブロック状に見えてしまいますし、ムラが現れやすいです。値ノイズを改善したノイズが勾配ノイズになります。

値ノイズの場合は、格子点での乱数の「値」を使うのに対し、勾配ノイズは乱数のベクトル値を「勾配」として使用します。ノイズ関数でよく使用されるパーリンノイズは、この勾配ノイズの一種であります。この章では、勾配ノイズと 2002 年の Perlin による改良版勾配ノイズ(パーリンノイズ)について見ていきます。

勾配ノイズの構成法

1 変数

値ノイズは乱数値をエルミート補間してつくりました。ここで別の見方として、$h(0) = 0$,$h(1) = 1$,$h'(0) = h'(1) = 0$ を満たすエルミート補間関数$h(x)$に対して、$w(x)$を次のように定義します。

Meta Data
公開日:2025-09-20
Tags:
#GLSL
#数学
#勉強ログ
「リアルタイムグラフィックスの数学」勉強ログ - 第3章値ノイズ
image

はじめに

「リアルタイムグラフィックスの数学」の第 3 章の値ノイズについての勉強ログです。

<AmazonLink imageId="61CP8Asy52L.SY522" linkId="3Iy0agT" title="リアルタイムグラフィックスの数学 ― GLSLではじめるシェーダプログラミング" author="巴山竜来" />

値ノイズの構成法

まずは 2 次元の値ノイズの構成法について見てみましょう。

平面上の正方形の4つの頂点(格子点)

平面上の正方形の 4 つの頂点(格子点)を用いて、値ノイズを構成します。各格子点での乱数値、または乱数ベクトルを使って生成されるノイズを格子ノイズと呼びます。格子点の成分は整数になるようにします。点$(x, y)$に対して床値$[]$を使って、$(x, y)$を取り囲むマスの格子点は次の 4 つのベクトルで表します。

Meta Data
公開日:2025-09-16
Tags:
#GLSL
#数学
#勉強ログ
「リアルタイムグラフィックスの数学」勉強ログ - 第2章疑似乱数
image

はじめに

「リアルタイムグラフィックスの数学」の第 2 章の疑似乱数についての勉強ログです。

<AmazonLink imageId="61CP8Asy52L.SY522" linkId="3Iy0agT" title="リアルタイムグラフィックスの数学 ― GLSLではじめるシェーダプログラミング" author="巴山竜来" />

レガシー乱数

GLSL ES 1.0 ではビット演算が使えないため、ハッシュ関数ではなくサイン関数を使用した乱数生成が行われていました。The Book of Shaders ではサイン関数を使用した乱数生成の方法が紹介されています。

https://thebookofshaders.com/10/?lan=jp

サイン関数自体には乱数性はないですが、サイン関数の値に大きい値をかけて桁を上げ、fractで小数部分を取り出すことで乱数のように見えることができます。

レガシー乱数の 1 変数と 2 変数のコードは次のようになります。

// 1変数のレガシー乱数
float fractSin11(float x) {
  return fract(1000.0 * sin(x));
}

// 2変数のレガシー乱数
float fractSin21(vec2 xy) {
  return fract(sin(dot(xy, vec2(12.9898, 78.233))) * 43758.5453123);
}
Meta Data
公開日:2025-09-10
Tags:
#GLSL
#数学
#勉強ログ
「リアルタイムグラフィックスの数学」勉強ログ - 第1章補間
image

はじめに

「リアルタイムグラフィックスの数学」の第 1 章の補間についての勉強ログです。

<AmazonLink imageId="61CP8Asy52L.SY522" linkId="3Iy0agT" title="リアルタイムグラフィックスの数学 ― GLSLではじめるシェーダプログラミング" author="巴山竜来" />

線形補間とグラデーション

mix 関数について

GLSL における mix 関数は以下のような数式になります。

$$ \bm{a} + x\overrightarrow{AB} = \bm{a} + x(\bm{b} - \bm{a}) = (1 - x)\bm{a} + x\bm{b} $$

これは、線分 AB があり、その線分上を動く 0 以上 1 以下の x に対して、線分 AB を x: (1 - x)に内分する点の位置ベクトルを表しています。上記の式のベクトルをmix(a, b, x)として定義しています。このように 2 点間をつなぐことを補間と呼び、上記の式は線形補間(Linear Interpolation)の公式です。

コード 1.1 のように、赤(1, 0, 0)と青(0, 0, 1)を mix 関数でつなぐことで 2 色のグラデーションを作ることができます。

vec2 RED = vec3(1.0, 0.0, 0.0);
vec3 BLUE = vec3(0.0, 0.0, 1.0);
vec3 col = mix(RED, BLUE, pos.x);

赤と青のグラデーション

Meta Data
公開日:2025-09-07
Tags:
#GLSL
#数学
#勉強ログ
勉強のために「リアルタイムグラフィックスの数学」のGLSLコードを確認できるサイトを作った

はじめに

シェーダに再挑戦したいと思い、「リアルタイムグラフィックスの数学」の本をまた読み直してます。

<AmazonLink imageId="61CP8Asy52L.SY522" linkId="3Iy0agT" title="リアルタイムグラフィックスの数学 ― GLSLではじめるシェーダプログラミング" author="巴山竜来" />

勉強する際に、本に書いているサンプルコードを Web サイトですぐに確認できるように、Astro と Three.js を使用して GLSL コードを表示するサイトを作成しました。サイトはこちらになります。

リポジトリはこちらになります!

https://github.com/nono-k/book-of-realtime-graphics-math

勉強方法の方針

いったん、本を読みながらローカルの VSCode 上で GLSL コードを書いて確認して、Web サイトに GLSL のコードと実行結果のサムネをアップしていきたいと思います。

また、このブログで勉強した内容を章ごとに残していきたいと思います。以下はリンクになります。

Meta Data
公開日:2025-09-06
Tags:
#GLSL
#数学
Info