No.002 乱数について



レイトレーシングでは乱数(擬似乱数)を使用します。 乱数を得る方法はいくつかありますが、ここでは .Net 標準の System.Random を使う方法と、Realistic Ray Tracing (RRT) に 記載されている方法、そして高品質な乱数であるメルセンヌ・ツイスタ法による 乱数の取得方法について説明します。

各種の乱数発生方法を簡単に切り替えて使用する事ができる 乱数発生クラス RNG (Random Number Generator クラス) を定義し、 最後にサンプルプログラムを掲載します。

System.Random
RRT の方法
メルセンヌ・ツイスタ(TinyMT)
RNG クラス
サンプルプログラム

C# における標準的な乱数発生方法がこの System.Random による方法です。
// インスタンスを作成して
Random systemRandom=new System.Random();

// NextDouble() メソッドにて乱数を得る
double randomValue=systemRandom.NextDouble();
非常に簡単ですね。

次に RRT に記載されている乱数発生方法をみてみます。 これは線形合同法と呼ばれる方法で、簡単な計算で乱数を得ることができます。
// 初期設定
ulong seed=7564231;
ulong mult=62089911;

// 乱数計算
unchecked { seed*=mult; }
float r=(float)(seed%uint.MaxValue)/(float)uint.MaxValue;
2 行目と 3 行目で行なっている初期設定は 1 度だけ設定する処理です。 実際の乱数計算は 6 行目と 7 行目で行なっている、mult を掛けて uint.MaxValue で 割った余りを求めて、それを 0 から 1 の範囲に正規化しているだけです。

乱数が必要になった場合毎にこの 6 行目と 7 行目の処理を行うことになります。 実際にはこれらの処理をメソッド化しておき、それを呼び出す形になります。 詳しくは RNG クラスの実装をみて下さい。

最後は高品質な乱数を生成する事ができるメルセンヌ・ツイスタ法を用いた 乱数を得る方法です。 メルセンヌ・ツイスタ法はその 考案者のページ ( http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/mt.html ) で公開されていますが、ToyBox では、その中でもメモリ使用量が 少ない TinyMT を使います。

TinyMT は C で書かれている為、C# で使用する場合には DLL 化する必要があります。 TinyMT.dll を作成する方法に関しては「 補足メモ 2-1 : C# 用 TinyMT の作り方」を 参照して下さい。

TinyMT.dll および TinyMT.cs を以下のリンクよりダウンロードして下さい。

TinyMT.dll
TinyMT.cs


TinyMT クラスを用いた乱数の発生方法は System.Random と同様で、
TinyMT tinyMT=new TinyMT();	// インスタンシエイトして
float randomValue=tinyMT.GenerateFloat();	// 乱数を発生
とするだけです。



使用する乱数を簡単に切り替えられるように RNG クラスを定義しておきます。
[入力用プログラムリスト RNG.pdf は こちら]
// RNG.cs

#define USE_TINY_MT
//#define USE_DOT_NET

using System;

namespace ToyBox {
  public class RNG {
#if USE_TINY_MT
    TinyMT mTinyMT=new TinyMT();
#elif USE_DOT_NET
    Random mRandom=new Random();
#else
    ulong mSeed=7564231;
    ulong mMult=62089911;
#endif

    public float Value {
      get {
#if USE_TINY_MT
        float ret=mTinyMT.GenerateFloat();	// [0,1)
#elif USE_DOT_NET
        float ret=(float)mRandom.NextDouble();
#else
        unchecked { mSeed*=mMult; }
        float ret=(float)(mSeed%uint.MaxValue)/(float)uint.MaxValue;
#endif
        return ret;
      }
    }

    public void Jump(int inStep) {
#if USE_TINY_MT
      mTinyMT.Jump32((ulong)inStep);
#else
      for(int i=0; i<inStep; i++) {
        float t=Value;
      }
#endif
    }
  }
}

最後にそれぞれの乱数を使って、画像にランダムに点を描画する プログラムを示します。 このプログラムで生成された画像を GIMP 等で見ると、 塗り残される場所の存在が分かると思います。

[入力用プログラムリスト Program002.pdf は こちら]
// Program002.cs

using System;
using ToyBox;

namespace No002 {
  class Program002 {
    static void Main(string[] args) {
      const int kWidth=64;
      const int kHeight=64;
      Image imageSystemMath=new Image(kWidth,kHeight,RGB.White);
      Image imageTinyMT=new Image(kWidth,kHeight,RGB.White);
      Image imageRrtRand=new Image(kWidth,kHeight,RGB.White);

      Random systemRandom=new Random();
      RNG rng=new RNG();
			
      ulong seed=7564231;
      ulong mult=62089911;

      const int kNumOfPoints=kWidth*kHeight*4;
      for(int i=0; i<kNumOfPoints; i++) {
        int ix=(int)(systemRandom.NextDouble()*kWidth);
        int iy=(int)(systemRandom.NextDouble()*kHeight);
        imageSystemMath.Set(ix,iy,RGB.Red);

        ix=(int)(rng.Value*kWidth);
        iy=(int)(rng.Value*kHeight);
        imageTinyMT.Set(ix,iy,RGB.Blue);

        unchecked { seed*=mult; }
        float r=(float)(seed%uint.MaxValue)/(float)uint.MaxValue;
        ix=(int)(r*kWidth);

        unchecked { seed*=mult; }
        r=(float)(seed%uint.MaxValue)/(float)uint.MaxValue;
        iy=(int)(r*kHeight);
        imageRrtRand.Set(ix,iy,RGB.Magenta);
      }

      imageSystemMath.WritePPM("testSystemMath.ppm");
      imageTinyMT.WritePPM("testTinyMT.ppm");
      imageRrtRand.WritePPM("testRrtRand.ppm");

      int numOfUntouchedSystemMath=0;
      int numOfUntouchedTinyMT=0;
      int numOfUntouchedRrtRand=0;
      for(int y=0; y<kHeight; y++) {
        for(int x=0; x<kWidth; x++) {
          if(imageSystemMath[x,y]==RGB.White) {
            numOfUntouchedSystemMath++;
          }
          if(imageTinyMT[x,y]==RGB.White) { 
            numOfUntouchedTinyMT++;
          }
          if(imageRrtRand[x,y]==RGB.White) {
            numOfUntouchedRrtRand++;
          }
        }
      }

      Console.WriteLine("num of untouched pixels:");
      Console.WriteLine("System.Math.Random "+numOfUntouchedSystemMath);
      Console.WriteLine("RNG(TinyMT) "+numOfUntouchedTinyMT);
      Console.WriteLine("RRT Random "+numOfUntouchedRrtRand);
    }
  }
}