title: "疑似乱数" description: "GLSLでの疑似乱数生成を学びます。fract(sin(dot(...)))パターンを使ったハッシュ関数で、ランダムなパターンを生成します。" chapter: "07-noise" order: 1 challenge: description: "画面をグリッド状に分割し、各セルをランダムな色で塗ってみましょう。floor()でUV座標をグリッド化し、ランダム関数で色を生成します。" hints:
- "floor(uv * 10.0) でUV座標を10x10のグリッドに量子化できます。"
- "R, G, B それぞれに異なるランダム値を割り当てるには、random関数にオフセットを加えます。例: random(grid + vec2(1.0, 0.0))"
- "vec3 color = vec3(random(grid), random(grid + vec2(1.0, 0.0)), random(grid + vec2(0.0, 1.0))); で各セルにランダムな色が割り当てられます。"
疑似乱数
GLSLにはrandom()がない
多くのプログラミング言語には random() や Math.random() といった乱数生成関数が用意されていますが、GLSLには組み込みのランダム関数がありません。
これはGPUの設計に関係しています。GPUは何百万ものピクセルを並列に処理します。各ピクセルは他のピクセルとは独立に計算されるため、共有の乱数生成器を持つことが難しいのです。
そこで、GLSLでは疑似乱数を自分で作ります。
ハッシュ関数
最も有名なGLSL疑似乱数は、fract と sin を組み合わせたハッシュ関数です:
float random(vec2 st) {
return fract(sin(dot(st, vec2(12.9898, 78.233))) * 43758.5453123);
}
これがどう動くか分解してみましょう:
dot(st, vec2(12.9898, 78.233))— 2D座標を1つの数に変換sin(...)— 正弦関数で値を散らす* 43758.5453123— 大きな数を掛けて周期性を壊すfract(...)— 小数部分だけ取り出して 0.0〜1.0 に収める
結果は決定的です。同じ入力には常に同じ出力が返ります。しかし出力は十分に「でたらめ」に見えるため、実用的なランダム値として使えます。
1Dの疑似乱数
まずは1D版を見てみましょう:
float random(float x) {
return fract(sin(x * 12.9898) * 43758.5453123);
}
入力値が少し変わるだけで、出力は大きく変わります。これがハッシュ関数の特徴です。
2Dの疑似乱数
2D版では、vec2 を入力に取り、dot 積で1Dに変換します:
float random(vec2 st) {
return fract(sin(dot(st, vec2(12.9898, 78.233))) * 43758.5453123);
}
右のエディタでは、各ピクセルのUV座標をこの関数に渡して、ランダムな白黒パターンを生成しています。
グリッドとの組み合わせ
ピクセルごとではなく、セルごとにランダムな値が欲しい場合は、floor() でUV座標を量子化します:
vec2 grid = floor(uv * 10.0); // 10x10のグリッド
float r = random(grid); // セルごとに同じ値
floor() で同じセル内のピクセルは同じ座標を持つようになるので、同じランダム値が返ります。
注意点
- この関数は暗号学的に安全ではありません。見た目のランダムさのみに使います
sinのマジックナンバーは長年の経験則で選ばれたものです- GPU によって
sinの精度が異なるため、環境によって微妙に結果が変わることがあります
まとめ
- GLSLには組み込みの乱数関数がない
fract(sin(dot(...)))パターンで疑似乱数を生成- 同じ入力には常に同じ出力(決定的)
floor()と組み合わせてグリッド状のランダムパターンが作れる