パーリンノイズ

ゲームやCGなどでよく使われる技術の一つにパーリンノイズというものがある。
地形などを生成する場合に単純にランダムにすると変になるけども、パーリンノイズは勾配を生成して補間してくれるのでなめらかに変化するランダム地形が作れる。地形だけじゃなく色々な用途に使える。
ということで実験的にCで実装してDXRubyのImageクラスのクラスメソッドとして使えるようにしてみた。
パーリンノイズの技術的な詳細解説は以下を参照するとよい。
パーリンノイズを理解する | POSTD
ランダム地形生成 Part1~パーリンノイズ - Qiita
世の中にはパーリンノイズといいつつホワイトノイズをぼかしただけ、みたいな実装や解説も多いようなので気をつけよう。欲しい効果が得られれば原理は何でもいいのかもしれんけども、「パーリンノイズの原理を知りたい」、と言う人には辛い。

特徴

パーリンノイズには以下の特徴がある。
・x、y、zの3つの値を入れると0.0〜1.0の1つの値が出てくる関数。x、y、zが同じなら返ってくる値は同じ。
・x、y、zが近い値だと返ってくる値も近くなる。
・x、y、zの範囲は0.0〜256.0でそれを超えるとループする。
・x、y、zがすべて整数だと返ってくる値は必ず0.5になる。

ということで、癖はあるけど便利である。

最も簡単な使い方

require 'dxruby'

image = Image.new(640,480).line(0, 240, 639, 240, C_WHITE)

640.times do |x|
  c = Image.perlin_noise(x / 640.0, 1, 1)
  image[x, c * 480] = image[x, c * 480-1] = image[x, c * 480+1] = C_WHITE
end

Window.loop do
  Window.draw(0, 0, image)
  break if Input.key_push?(K_ESCAPE)
end

画面の中心を0.5として、xを0〜1、yとzを1固定にしたパーリンノイズの結果。

うん、ノイズっぽくない。めげずに解説すると、一番左と一番右は整数になるので0.5固定である。パーリンノイズでは整数の位置にベクトルが配置されて、その向きは値が増加、逆向きは値が減少するように計算される。つまり、(0, 0, 0)はxの+方向に増加、(1, 0, 0)もxの+方向に増加、というベクトルが配置されていて、このように補間された、ということだ。これを例えばxを0〜10にしてやると以下のような出力になる。

うーん。整数になるたびに必ず0.5を通るので総合的には平坦化されて味気ない。こんなんでどうやって地形作るの?

コントロールポイントをずらす

yとzを整数じゃない値にすると、必ず通るはずの0.5が0.5じゃなく変動するようになるので、ちょっと変わった形状が得られる。

require 'dxruby'

image = Image.new(640,480).line(0, 240, 639, 240, C_WHITE)

640.times do |x|
  c = Image.perlin_noise(x / 640.0 * 10, 3.5, 20.5)
  image[x, c * 480] = image[x, c * 480-1] = image[x, c * 480+1] = C_WHITE
end

Window.loop do
  Window.draw(0, 0, image)
  break if Input.key_push?(K_ESCAPE)
end

フラクタル

地形などは大きな形状と小さな形状が複合的に混ざったような感じになっているので、そういう処理をするとそれっぽくなる。
Imageクラスにはoctave_perlin_noiseというメソッドも作ってある。引数にoctaveとpersistenceが増える。octaveは1が通常で、2にするとxyzの値を2倍、取得値をpersistence倍した値を合成してくれる。3以上にした場合はもっと合成してくれる。ただし、処理時間はoctave倍かかる。
一番初めのなだらかな線にoctaveを適用してみよう。

require 'dxruby'
image = Image.new(640,480).line(0, 240, 639, 240, C_WHITE)

640.times do |x|
  c = Image.octave_perlin_noise(x / 640.0, 1, 1, 2, 0.5)
  image[x, c * 480] = image[x, c * 480-1] = image[x, c * 480+1] = C_WHITE
end

Window.loop do
  Window.draw(0, 0, image)
  break if Input.key_push?(K_ESCAPE)
end


ちょっとゆがんだ。octaveこのまま4、8と増やしていくと以下のようになる。


それっぽくなってきた。xを10倍してyとzをずらす。

うん。

カスタムノイズ

octaveで地形っぽくなってきたけども、これだとなだらかな丘か険しい山か、その中間しか作れない。例えば全体的にはなだらかで細かい岩があるような地形ってな場合は、基本の値と、高い周波数で作った値を合成することになるが、それができない。両方作ってRubyで合成するときわめて遅い。なので、その値を直接指定できるcustom_perlin_noiseも用意した。
これを使うと以下のような結果が得られる。

require 'dxruby'

image = Image.new(640,480).line(0, 240, 639, 240, C_WHITE)

640.times do |x|
  c = Image.custom_perlin_noise(x / 640.0, 1, 1, [[1, 1], [64, 0.04]])
  image[x, c * 480] = image[x, c * 480-1] = image[x, c * 480+1] = C_WHITE
end

Window.loop do
  Window.draw(0, 0, image)
  break if Input.key_push?(K_ESCAPE)
end

とりあえずおしまい

1Dでの解説となったのでイマイチ地味だが、xyzが指定できるので3Dまでいける。例えば2Dならxyで高さが出てくるのでこれでマップが作れるし、応用としては、雲や炎の絵を生成することができる。3Dではこれで洞窟のデータを作ったりするらしい。
試しに雲っぽい画像を生成してみた様子。

まだ作ったばっかりでほとんど試していないので応用については今後のネタということで。
ていうかテスト中すぎて開発版に含めてリリースもできていない。新機能予告みたいな。