【UE4】Sobolの森 (Random Sobol Cell 2D/3Dについて)


random forest と呼ばれる決定木の話ではありません。ご注意を。

前回に引き続き、株式会社ヒストリアさん主催の第13回UE4ぷちコン応募後のあれこれなど。
やったこと、やれなかったこと、誤魔化したところなどの整理と備忘録として。

(もしよろしければ、私の作品の動画も見てくださいませ)

Random Sobol Cell 関数

Random Sobol Cell は、UE4.17で追加された機能で、リリースノートの「新規:Sobol ブループリントとマテリアルノード」の画像を見ると何やら良さげな匂いがするのに、使い方が良く分からない。

使用例は Epic Games Japan のおかずさんの Slide Share の49~52ページ目、くらいしか見当たらない。皆さん、あまり使っていないのかしら?

というわけで、私が分かった範囲でメモを残しておく。

どうやらこういうものらしい

※3Dでやると混乱しそうだったので、2Dでやってます。

【左側】普通の乱数でボックス内にCylinderを16本生成する。
【右側】ボックス内を2×4マス(=Cell)に分けて、Sobol Random Cell 2D でCylinderをそれぞれのマスに2本ずつ生成する。つまり、2 × 4 × 2 = 16 本のCylinderが生成されている。

つまり、普通の乱数ではどこかに偏ってしまいがち(左側)だが、あらかじめマス(=Cell)に切って Sobol を使えば、それなりに均一に、それなりにランダムにばら撒ける(右側)、ということのようだ。

UE4.17のリリースノートに掲載されている画像のように、森などを作るときに便利だとわかる。
実際、UE4ぷちコン#13に応募した私の作品でも、Sobol Random Cell 2D で森を作っている。

これも上記のボックス内にCylinderを生やす例と同様、ボックスの中に Sobol Random Cell 2D で木を生やす仕組みを持たせたアクタを、レベル中にたくさん配置してある。
ちなみに、なぜ Foliage を使っていないのかというと、Foliage だとレベルエディットが面倒くさいからである。この道狭いな、とか思ったときに、木のカタマリをザバっと動かしたいからである。

私の使い方:

  • アクタで作る森: コリジョンとセットのものや、キャラクタがインタラクションしそうなもの。
  • Foliageで作る森: キャラクタがインタラクションしないようなもの。遠景など。大地に生えている草なら近景でも Foliage で良かろう。

使い方(備忘録)

何回使ってもややこしくて、試行錯誤の時間が長くなってしまうので、備忘録として。

  • Sobol Random Cell 2D を使いたい範囲を NumCellsX * NumCellsY 個のセルに分けたとする。
  • 一つのセルの中には NumTreeInCell 個の木を生やしたいとする。
Blueprintで書くと大きくなってしまうので、C++ダミーコード。
void DoSobol(int NumCellsX, int NumCellsY, int NumTreeInCell)
{
    const int NumCellsTotal = NumCellsX * NumCellsY;
    const FVector2D RandomSeed(RandomFloat(), RandomFloat()); // (0, 0)~(1, 1)の範囲

    // Y方向のセルの個数分のループ
    for (int IndexY = 0; IndexY < NumCellsY; ++IndexY)
    {
        // X方向のセルの個数分のループ
        for (int IndexX = 0; IndexX < NumCellsX; ++IndexX)
        {
            const FVector2D Cell((float)IndexX, (float)IndexY);

            // 1つのセルに生やす木の数のループ
            for (int IndexTreeInCell = 0; IndexTreeInCell < NumTreeInCell; ++IndexTreeInCell)
            {
                const FVector2D SobolValue = RandomSobolCell2D( // Sobol 本体
                    IndexTreeInCell,    // int Index
                    NumCellsTotal,      // int NumCells
                    Cell,               // FVector2D Cell
                    RandomSeed);        // FVector2D Seed

                AddInstancedStaticMeshTree(SobolValue); // 木を生やす何らかの関数
            }
        }
    }
}

コメントで Sobol 本体と書いたところは、Random Sobol Cell 2D の Blueprint ノードと見比べてみていただきたい。

    const FVector2D SobolValue = RandomSobolCell2D( // Sobol 本体
        IndexTreeInCell,    // int Index
        NumCellsTotal,      // int NumCells
        Cell,               // FVector2D Cell
        RandomSeed);        // FVector2D Seed

さて、ここで一安心したいところだが、Random Sobol Cell 2D/3D の返り値も多少癖がある。
気分的には、0~1の範囲で返してもらいたいところだが、実際は平気で1.0を超えてくる。
初めて使ったときには、使い方が間違ったのかと思った。だが大丈夫。

  • (0, 0)番目のセルに入っている木の場合: (0.0, 0.0)~(1.0, 1.0) の返り値
  • (0, 1)番目のセルに入っている木の場合: (0.0, 1.0)~(1.0, 2.0) の返り値
  •  中略
  • (2, 4)番目のセルに入っている木の場合: (2.0, 4.0)~(3.0, 5.0) の返り値

という値が返ってくる。

なので、実際の座標に直したいときは、

    const FVector2D CellSize(BoxSize.X / NumCellsX, BoxSize.Y / NumCellsY);
    const FVector2D TreeLocation2D = SobolValue * CellSize;

と、Sobol の返り値にセルのサイズを掛ければよい。