Unity:PerlinNoise で自然で良い感じにレイアウトして遊ぶ

PerlinNoise を使って咲かせた花

3DCG の世界では PerlinNoise は切っても切り離せない存在で、一般的な乱数と異なり、 例えば様々な自然現象を表現するのに都合の良い値を取得(サンプリング)することができます。

例えば、パラメータを調整するなりすれば、リアルタイムに、適当な位置に草木を生やしたりできるかと思います(上の絵は演出の都合上過剰に生やしています、回転方向もバラバラです)。 他に良い感じに汚れたり輝いたり曇ったりするテクスチャの生成・表現、粒子などを表現するためにも利用されています。

Unity3D には PerlinNoise (の値)を取得するための関数が用意されているので、容易に PerlinNoise を使うことができます。 ここでは PerlinNoise を使って良い感じにオブジェクトを配置する方法について紹介します。 (別にオブジェクトを配置することだけにとどまらせる必要はないと思います。)

乱数と PerlinNoise の比較

サンプルシーンを開くと先の画像(左)のような実行結果が得られます。PerlinNoise を使ってオブジェクトを配置しています。 一方で後の画像(右)のように乱数でオブジェクトを配置するようなソースコードも書いてあります。

画像を比較してもわかる通り、オブジェクトの分布が全く異なります。ある種のまとまりを表現したいものの、 まったく固定された値(位置)にはならない、といったものを表現したいとき、PerlinNoise などの "ノイズ" が乱数よりも役に立ちます。

PerlinNoise のテクスチャを生成する

サンプルの主要なシーンは実行すると画像のような感じになってます。 まずはよく見る PerlinNoise を可視化したテクスチャを生成します。サンプルシーンの左上に表示されるものです。

PerlinNoiseTextureSetter.cs のコアなコードは下記の通りです。 Unity のリファレンスに掲載されているサンプルソースコードとほぼ同等です。

(特に Unity3D) の PerlinNoise からは、ある座標 (x, y) の値を取得することができます。 PerlinNoise の値は一般的な乱数と同じように 0 ~ 1 で与えられます。


#region Field

public int pixelWidth = 100;
public int pixelHeight = 100;

public float perlinNoiseOriginX = 0;
public float perlinNoiseOriginY = 0;
public float perlinNoiseScale = 1.0F;

private Texture2D noiseTexture;
private Color[] pixelColors;

…

for (float y = 0; y < this.noiseTexture.height; y++)
{
	for (float x = 0; x < this.noiseTexture.width; x++)
	{
		float xCoord = this.perlinNoiseOriginX 
                             + x / noiseTexture.width * this.perlinNoiseScale;
		float yCoord = this.perlinNoiseOriginY
                             + y / noiseTexture.height * this.perlinNoiseScale;
		float value  = Mathf.PerlinNoise(xCoord, yCoord);

		this.pixelColors[(int)(y * this.noiseTexture.width + x)]
                    = new Color(value, value, value);
	}
}

this.noiseTexture.SetPixels(this.pixelColors);
this.noiseTexture.Apply();

perlinNoiseOriginX, Y は、PerlinNoise の基準となる位置で、一般的な乱数における Seed に相当すると考えても良いでしょう(厳密にはそうではないですが)。 また perlinNoiseScale は、PerlinNoise によって生み出される乱数のディティールを制御します。これについてはパラメータを変更して遊ぶとどのように変化するか分かります。

PerlinNoise を使って良い感じにオブジェクトを配置する

今度は PerlinNoise を使って、良い感じに、自然な感じにオブジェクトを生成してみます。 サンプルを開けばわかりますが、ここでは説明の都合上(そして私の開発の都合上)メインカメラを平行投影にしています。もちろん透視投影でも良いと思います。

PerlinNoiseTextureSetter.cs のコアなコードは下記の通りです。 0 ~ 1 の乱数をそれぞれ X, Y 座標用に取得し、後はテクスチャのときと同じように、その値を元に、PerlinNoise の値を取得します。

public float objectScale = 0.2f;
public float perlinNoiseOriginX = 0;
public float perlinNoiseOriginY = 0;
public float perlinNoiseScale = 10;
public float generateObjectThreshold = 0.7f;

…

float randomValueX = Random.value;
float randomValueY = Random.value;
float noiseValue   = Mathf.PerlinNoise
                 (this.perlinNoiseOriginX + randomValueX * this.perlinNoiseScale,
                  this.perlinNoiseOriginY + randomValueY * this.perlinNoiseScale);

if (noiseValue < this.generateObjectThreshold)
{
	return;
}

Camera camera = Camera.main;

Vector3 randomPoint  = new Vector3(randomValueX * camera.pixelWidth,
                                   randomValueY * camera.pixelHeight,
				   0);
Vector3 worldPoint   = Camera.main.ScreenToWorldPoint(randomPoint);
worldPoint.z = camera.transform.position.z 
             + (camera.farClipPlane - camera.nearClipPlane) / 2;

GameObject gameObject = GameObject.CreatePrimitive(PrimitiveType.Sphere);

gameObject.transform.position = worldPoint;
gameObject.transform.localScale *= this.objectScale;
gameObject.transform.SetParent(base.gameObject.transform);

this.objectList.Add(gameObject);

取得した PerlinNoise の値が閾値以上のとき(つまりテクスチャ上では白く見えるとき)、オブジェクトを生成します。 PerlinNoise の値を取得するにあたって、0 ~ 1 の x, y 座標を使いましたから、そのままこれをオブジェクトの位置にします。

スクリーン座標系は、0 ~ 1 で定義される座標系です。左下を (0, 0) 右上を (1, 1) とします。 これをワールド座標系に直すための関数は Unity では ScreenToWorldPoint として用意されています。

スクリーン座標系をワールド座標系に直し、その位置にオブジェクトを生成します。 こうすることで、スクリーンの任意の位置にオブジェクトを表示する、という機能が実現できます。

PerlinNoise を使ってオブジェクトを配置している様子(正方形)。

実際に期待した位置にオブジェクトが配置されているかどうかは、テクスチャのアスペクト比と、スクリーンのアスペクト比を一致させて、 テクスチャに設定する PerlinNoise の原点と拡大率および、オブジェクトを生成する PerlinNoise の原点と拡大率を合わせれば確認できます。 テクスチャの白い位置にオブジェクトが配置されていくのが確認できます。

逆に言えば、乱数とは異なり、原点(Seed)と拡大率が変わらなければ、(特に Unity の) PerlinNoise の値は変わらないということです。

他の異なる表現も試してみる

閾値とパラメータを変更すればオブジェクトの配置される位置を変更し、雰囲気を変えることができます。 先の例では流れがある様な雰囲気を持った配置ですが、あとの例では一か所にまとまる様な雰囲気です。これはパラメータ変更のみで実現しています。

テクスチャを参考にしながら、閾値や PerlinNoise の Scale を調整すると、期待するようにオブジェクトを配置することができるかと思います。

※必ずしも PerlinNoise だけであらゆるパターンが実現できるわけではありません。ここでは割愛しましたが、PerlinNoise の性質を理解することで、 ある表現が PerlinNoise で実現できるか、PerlinNoise を使うべきか、判断できるかと思います。