このGLSL rand()ワンライナーの起源は何ですか?


92

私は、ウェブのあちこちで参照されているシェーダーで使用するための、この疑似乱数ジェネレータを見てきました。

float rand(vec2 co){
  return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453);
}

「カノニカル」または「どこかでウェブで見つけたワンライナー」と呼ばれています。

この関数の起源は何ですか?定数値は見た目と同じくらい恣意的ですか、それともそれらの選択にいくつかの芸術がありますか?この機能のメリットについての議論はありますか?

編集:私が遭遇したこの関数への最も古い参照は、20082月のこのアーカイブで、元のページはWebから削除されています。しかし、それについて他のどこよりも議論はありません。


これはノイズ関数であり、手続き的に生成された地形を作成するために使用されます。このようなものに似ていますen.wikipedia.org/wiki/Perlin_noise
foreyez

回答:


42

非常に興味深い質問です。

私は答えを入力しながらこれを理解しようとしています:)まずそれをいじる簡単な方法:http : //www.wolframalpha.com/input/? i=plot%28+mod%28+sin%28x*12.9898 +%2B + y * 78.233%29 + * + 43758.5453%2C1%29x%3D0..2%2C + y%3D0..2%29

次に、ここで何をしようとしているのかを考えてみましょう。2つの入力座標x、yに対して、「乱数」を返します。しかし、これは乱数ではありません。同じx、yを入力するたびに同じです。ハッシュ関数です!

関数が最初に行うことは、2dから1dに移行することです。それ自体は興味深いことではありませんが、通常は繰り返されないように番号が選択されています。また、浮動小数点数も追加されています。yまたはxからさらに数ビットありますが、数値が適切に選択されているため、ミックスされます。

次に、ブラックボックスのsin()関数をサンプリングします。これは実装に大きく依存します!

最後に、分数を乗算して取得することにより、sin()実装のエラーを増幅します。

これは一般的なケースでは良いハッシュ関数ではないと思います。sin()は、GPUでは数値的にブラックボックスです。ほぼすべてのハッシュ関数を取得して変換することで、より優れたものを構築できるはずです。難しいのは、CPUハッシュで使用される一般的な整数演算を浮動小数点演算(半分または32ビット)または固定小数点演算に変換することですが、それは可能であるはずです。

繰り返しになりますが、ハッシュ関数としてのこれの実際の問題は、sin()がブラックボックスであることです。


1
これは起源についての質問には答えませんが、私はそれが本当に答えられるとは思いません。例示的なグラフのため、私はこの答えを受け入れます。
Grumdrig 2014年

19

起源はおそらく論文です:「乱数の生成について、y = [(a + x)sin(bx)] mod 1の助けを借りて」、WJJレイ、第22回統計家会議、および第7回確率論に関するビリニュス会議と数学統計、1998年8月

編集:このペーパーのコピーが見つからず、 "TestU01"の参照が明確でない可能性があるため、疑似CのTestU01で説明されているスキームを次に示します。

#define A1 ???
#define A2 ???
#define B1 pi*(sqrt(5.0)-1)/2
#define B2 ???

uint32_t n;   // position in the stream

double next() {
  double t = fract(A1     * sin(B1*n));
  double u = fract((A2+t) * sin(B2*t));
  n++;
  return u;
} 

ここで推奨される定数値はB1のみです。

これはストリーム用であることに注意してください。1Dハッシュ 'n'に変換すると、整数グリッドになります。だから私の推測では、誰かがこれを見て「t」を単純な関数f(x、y)に変換したと思います。上記の元の定数を使用すると、次のようになります。

float hash(vec2 co){
  float t = 12.9898*co.x + 78.233*co.y; 
  return fract((A2+t) * sin(t));  // any B2 is folded into 't' computation
}

3
確かに非常に興味深いです!Googleブックスでジャーナル自体と同様にそれを参照する論文を見つけましが、講演または論文自体はジャーナルに含まれていなかったようです。
Grumdrig

1
また、タイトルから、私が尋ねfract(sin(dot(co.xy ,vec2(12.9898,78.233))) * (co.xy + vec2(43758.5453, SOMENUMBER))ている機能は、論文の機能に合わせて戻る必要があるように見えます。
Grumdrig

そしてもう1つ、これが実際に関数の使用の起源である場合、繰り返し使用されるマジックナンバー(aおよびの選択b)の起源の問題は残りますが、引用する論文で使用された可能性があります。
Grumdrig

私もその紙を見つけることができません。(編集:上記のリンクと同じ論文)
MBレイノルズ

回答を詳細に更新してください。
MBレイノルズ

8

定数値は任意であり、特にそれらが非常に大きく、小数点以下2桁が素数から離れていることが特に重要です。

高振幅正弦の1を超える係数に4000を掛けたものが周期関数です。それは窓のブラインドのようであるか、4000倍されて、ドット積によってある角度で回転するため、非常に小さく作られた波形の金属です。

関数が2次元であるため、内積は周期関数をX軸とY軸に対して斜めに回転させる効果があります。およそ13/79の比率で。これは非効率的です。実際に(13x + 79y)の副鼻腔を使用することで同じことを達成できます。これにより、数学が少なくても同じことを実現できます。

関数の周期がXとYの両方で見つかった場合は、それをサンプリングして、単純な正弦波のように見えるようにすることができます。

これがグラフで拡大した写真です

私は起源を知りませんが、他の多くの人と同じです。定期的にグラフィックスで使用すると、モアレパターンが発生する傾向があり、最終的には再び発生することがわかります。


しかし、GPUでは、XとYの範囲は0..1であり、グラフを変更すると、はるかにランダムに見えます。これは言葉のように聞こえるかもしれませんが、私の数学の教育は18歳で終わったので、実際には問題です。
文字列

ランダム関数がその形をしていることを確認できるようにズームインしました。リッジが非常に速く変化している点を除いて、変化を確認するには小さくズームする必要があります...そのポイントを想像することができます尾根では、1から1のxとyの値に対して、0から1の高さのかなりランダムな数値が得られます。
2013

ああ、私は理解しており、そのコアでsin関数を使用する乱数生成には非常に論理的であるように思われます
文字列

2
それは本質的に線形のジグザグであり、罪はほんの少しの変化を加えることになっています、それは誰かがあなたの前で1から10のカードのパックを非常に高速にぐるぐるとフリックして、あなたが試してみることになっているのと同じですカードから数字のパターンを拾うと、それらはランダムな数字になります。なぜなら、カードがどれだけ速く回転しているかに関連する正確な同期でカードを選択することによってのみパターンを取得できるからです。
2013

ただ、ノート、それは何をするより高速ではありません(13x + 79y)ので、dot(XY, AB)その点の製品、として、あなたが記述を正確に何をしますx,y dot 13, 79 = (13x + 79y)
WHN

1

多分それはいくつかの非再帰的カオスマッピングです、それからそれは多くのことを説明するかもしれませんが、大きな数での単なる恣意的な操作であるかもしれません。

編集:基本的に、関数fract(sin(x)* 43758.5453)は単純なハッシュのような関数であり、sin(x)は-1から1までのスムーズなsin補間を提供するため、sin(x)* 43758.5453は-からの補間になります- 43758.5453〜43758.5453。これは非常に大きな範囲であるため、xの小さなステップでも結果として大きなステップが提供され、小数部で実際に大きな変動が生じます。"fract"は-0.99 ...から0.999 ...の範囲の値を取得するために必要です。さて、ハッシュ関数のようなものがあれば、ベクトルから生成ハッシュ用の関数を作成する必要があります。最も簡単な方法は、入力ベクトルの任意のxコンポーネント、yコンポーネントに対して「ハッシュ」を個別に呼び出すことです。ただし、対称的な値がいくつかあります。したがって、ベクトルから何らかの値を取得する必要があります。アプローチは、ランダムなベクトルを見つけ、そのベクトルの「ドット」積を見つけることです。ここでは、fract(sin(dot(co.xy、vec2(12.9898,78.233)))* 43758.5453); また、選択されたベクトルによれば、「ドット」積が計算された後、その長さは「sin」関数のいくつかのペリロイドを持つのに十分長くなければなりません。


4e5も同様に機能するはずですが、なぜマジック番号43758.5453なのかわかりません。(また、私は避けランド(0)= 0にいくつかの分数によってXオフセットになる。
ファブリNEYRET

1
4e5を使用すると、小数ビットの変動がそれほど大きくならず、常に同じ値が得られると思います。したがって、2つの条件が満たされる必要があります。十分に大きく、小数部分の十分なバリエーションがあります。
ローマ、

「常に同じ価値を与える」とはどういう意味ですか?(もしそれが常に同じ数字を取ることを意味するなら、最初に、それらはまだ混沌としている、2番目に、floatは10 ^ pではなくm * 2 ^ pとして格納されるので、* 4e5はビットをスクランブルする)。
Fabrice NEYRET 2016年

4 * 10 ^ 5の指数表現を書いたと思ったので、sin(x)* 4e5は無秩序な数を与えます。正弦波からの小数ビットは、あなたにも良いチャトイックを与えることに同意します。
ローマ

しかし、それはxの範囲に依存します。つまり、関数が小さい値(-0.001、0.001)と大きい値(-1、1)に対してロバストである必要があるかどうかを意味します。fract(sin(x /1000.0)* 43758.5453);で違いを確認することができます。そしてfract(sin(x /1000.0)* 4e5);、ここでxは[-1。、1.]の範囲です。2番目のバリアントでは、イメージはより単調になります(少なくとも、シェーダーの違いがわかります)。しかし、一般的には、4e5を使用しても十分な結果が得られることに同意します。
ローマ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.