PixelShader で乱数を作る
About
HLSL (シェーダモデル 5.x 時点) では乱数を発生させる関数が用意されていません。ふつう、乱数用のテクスチャを用意し、その値をサンプリングすることで乱数を取得するような手法がとられますが、乱数用のテクスチャを用意して、読み込んで、展開するのは結構なコストです。ここでは HLSL で疑似的な乱数を発生させる1つの方法について紹介します。HLSL (その他 GLSL などのシェーダ) で乱数を発生する方法は様々なところで議論されています。
乱数生成のアルゴリズム等については解説しません。また見られる程度の制度であり、厳密な方法ではない点に注意してください。より精度の高い乱数が必要な時は、テクスチャを利用するしかないでしょう。
ここでは DirectX11 とシェーダモデル(バージョン) 4.0 を利用しています。
PixelShader で乱数を作る
ここでは次のコードで 0.0f ~ 1.0f の小数点付き乱数を発生させます。Web で展開されている議論の中では abs によって絶対値を取るものや、わずかに異なる他の方法もありますが、見かけ上の精度は変化がありませんでした。厳密には異なるかもしれませんが、ここではそこまでの精度を求めていません。
int Seed; float GetRandomNumber(float2 texCoord, int Seed) { return frac(sin(dot(texCoord.xy, float2(12.9898, 78.233)) + Seed) * 43758.5453); } float4 MakeNoiseColor(float2 texCoord) { float value = GetRandomNumber(texCoord, Seed); return float4(value, value, value, 1); }
Seed の値が int である点に注意してください。float にすると値によっては計算結果(乱数)が常に 0 を示すようになります。与える値やキャストを利用すればある程度制御できますが、int にしておけば設定のミスによる事故は防ぐことができます。
Seed を変更しない限り、同じ解像度の画像に対し、乱数は常に同じ結果を返します。同じ乱数の結果が必要であるなら、精度のことも踏まえれば乱数テクスチャを使う方が良いでしょう。あるテクスチャをサンプリングした値を Seed として利用するのも良いかもしれません。
生成結果
テクスチャとしての生成結果は次の通りです。解像度 800x600 の一部を抜粋しています。
0.0f ~ 1.0 の値を 5 段階に分割して、それぞれの値が含まれる数を集計しました。中央の値がやや多めに出てしまっています。精度を優先する場合には、より複雑な他のアルゴリズムに切り替えるしかありません。テクスチャを見る限りでは妥協できる範囲な気がします。
Level | Pt.1 | Pt.2 |
count0-2 | 95362 | 94846 |
count2-4 | 95905 | 95340 |
count4-6 | 97245 | 97496 |
count6-8 | 95621 | 95901 |
count8-10 | 95867 | 96417 |
Seed | 439 | 56 |
ヒストグラムです。実際には丁度 0 や 1 も少ないです。