perlin noise の生成

http://dame.dyndns.org/misc/misc/perlin1.png

ref: wikipedia:パーリンノイズ
ref: Perlin Noise

CG 業界とかで有名な、なんか雲みたいなノイズのことです。参考サイトの擬似コードをそのまま Ruby に移植してみました。

# http://freespace.virgin.net/hugo.elias/models/m_perlin.htm
class PerlinNoise
  def initialize(seed)
    @seed = seed
  end

  def noise(x, y)
    ([@seed, x, y].hash & 65535) / 65536.0
  end

  def smooth_noise(x, y)
    corners = noise(x-1, y-1) + noise(x-1, y+1) + noise(x+1, y-1) + noise(x+1, y+1)
    sides   = noise(x  , y-1) + noise(x  , y+1) + noise(x-1, y  ) + noise(x+1, y  )
    center  = noise(x  , y  )
    center / 4 + sides / 8 + corners / 16
  end

  def linear_interpolate(a, b, x)
    a * (1 - x) + b * x
  end

  def cosine_interpolate(a, b, x)
    f = (1 - Math.cos(x * Math::PI)) / 2
    a * (1 - f) + b * f
  end

  #alias interpolate linear_interpolate
  alias interpolate cosine_interpolate

  def interpolate_noise(x, y)
    interpolate(
      interpolate(
        smooth_noise(x.floor  , y.floor  ),
        smooth_noise(x.floor+1, y.floor  ),
        x - x.floor),
      interpolate(
        smooth_noise(x.floor  , y.floor+1),
        smooth_noise(x.floor+1, y.floor+1),
        x - x.floor),
      y - y.floor)
  end

  def perlin_noise(x, y)
    (0...$number_of_octaves).map do |i|
      frequency = 2.0 ** i
      amplitude = $persistence ** i
      interpolate_noise(x * frequency, y * frequency) * amplitude
    end.inject(&:+)
  end
end


# generate png
require "zlib"

width = height = 300
depth, color_type = 8, 2

$number_of_octaves = 4
$persistence = 1.0 / 4

pn = PerlinNoise.new(1)

img_data = (0...height).map do |y|
  (0...width).map do |x|
    c = (pn.perlin_noise(x / 30.0, y / 30.0) * 255).floor & 255
    [c, c, c]
  end
end

def chunk(type, data)
  [data.bytesize, type, data, Zlib.crc32(type + data)].pack("NA4A*N")
end

print "\x89PNG\r\n\x1a\n"
print chunk("IHDR", [width, height, 8, 2, 0, 0, 0].pack("NNCCCCC"))
raw_data = img_data.map {|line| ([0] + line.flatten).pack("C*") }.join
print chunk("IDAT", Zlib::Deflate.deflate(raw_data))
print chunk("IEND", "")

座標 (x, y) に 0 から 1 の乱数値を割り当てる必要があるのですが、でっかい配列やハッシュを確保するのはダサいと思ったので、[@seed, x, y].hash を乱数として使ってみました。最近の Ruby 1.9 (trunk) は、Fixnum#hash でもぱっと見は乱数として使えるくらいにわけわからない数字が帰ってくるようになっています (そして起動するたびに変わる) 。

$ ruby19 -e 'p 1.hash'
1345983667

$ ruby19 -e 'p 1.hash'
-2093709963

$ ruby19 -e 'p 1.hash'
1668111793

実行の様子。

$ time ruby19 perlin.rb > perlin.png

real    1m7.940s
user    1m7.936s
sys     0m0.008s

おそっ……。Ruby おそっ……。1.9.f ならどのくらい速いのかな (試してない) 。

RGB ごとに別のパーリンノイズ割り当ててパラメータいじったらなんかサイケデリックなの出来た。
http://dame.dyndns.org/misc/misc/perlin2.png