疑似乱数


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疑似乱数は、fractsin を組み合わせたハッシュ関数です:

float random(vec2 st) {
    return fract(sin(dot(st, vec2(12.9898, 78.233))) * 43758.5453123);
}

これがどう動くか分解してみましょう:

  1. dot(st, vec2(12.9898, 78.233)) — 2D座標を1つの数に変換
  2. sin(...) — 正弦関数で値を散らす
  3. * 43758.5453123 — 大きな数を掛けて周期性を壊す
  4. 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() と組み合わせてグリッド状のランダムパターンが作れる