読者です 読者をやめる 読者になる 読者になる

arXiv探訪

興味の赴くままに数学するだけ

ノイズの生成手法について

自然界の現象を、幾つかのパラメータからそれっぽい感じに再現しようとする試みがある。例えば雲のモヤモヤや地面の起伏などを再現したいとき、物理学に則って分子一つ一つの動きを計算していたらキリがない。学術研究や気象予報などの目的であれば、それもまた一考に値するのだろう。しかし殊ゲームにおいてはそこまでの精密化は現実的ではない、というか必要ない。遊びという枠組みの範疇でそれっぽく見えれば良いのだから、そういった要求に見合った手法が歴史的に様々考えられてきた。

Procedural Coherent Noise

プロシージャル・コヒーレント・ノイズとは、手続きによって生成される干渉縞を持つ乱雑波と直訳できる。乱雑派つまりノイズという言葉は物理学の(もしくは日常的な)用語だが、カオスやフラクタルといった概念と同じように恐らく数学的には未定義だろう。コヒーレントというのは、ある程度の起伏を持つことで干渉が可能であることをいう。完全な乱数値を取るようなノイズ(ホワイトノイズ)とは異なり、部分的には波の形を視認できるようなものを言う。プロシージャルは自動生成と呼ぶこともあるが、有限個のパラメータで全ての値が計算可能なことをいう。もっともコンピュータは全て手続きによって動いているので、この辺の用語は理論的な要求からきている。

ノイズ生成の基本理念は補間にある。まず適当な点の集合を考える。ランダムに取ることもあるが、今回は格子状に取るもの(格子ノイズ)だけを考えよう。次に格子点上の値を無作為に与え、それらを連続的な曲線で滑らかに繋ぐ。格子点上に乗っていない点における値は、必要な時に曲線の式から計算で求めれば良い。その計算に用いるのが補間関数である。何でも良いというわけではなく、そこそこの滑らかさを持ち、そして計算が比較的容易かつ安定したものでなければならない。例えば以前述べたSmoothstep関数は多項式なので足し算と掛け算だけで求められる。他にも三角波を使った補間などがある。このように、値(Value)を単純曲線で結ぶノイズ生成手法をバリュー・ノイズと呼ぶ。バリュー・ノイズは最も基本的なノイズで実装も容易だが、ブロック状に見えやすく人工物っぽさ(Artifact)が残りやすいという欠点がある。

ところで何故格子を考えるかを述べると、それは高次元化の容易さにある。補間関数は1次元の関数だが、まず格子の辺における補間曲線を描き、さらにその間を補間して補間曲面を描き、……といった感じで高次元に拡張できる。さらにその拡張は軸を選ぶ順番に依らない。実際に2次元({ xy }平面)の場合を例に出そう。{ f(t) }を補間関数とする。点{ (0, 0), (1, 0), (0, 1), (1, 1) }の上の値を{ v_{00}, v_{01}, v_{10}, v_{11} }とする。辺{ y=0 }上の補間曲線は{ (v_{10}-v_{00})f(x)+v_{00}=:u_{0}(x) }で表される。同様に辺{ y=1 }では{ (v_{11}-v_{01})f(x)+v_{01}=:u_{1}(x) }となる。これを繋げば{ (u_{1}(x)-u_{0}(x))f(y)+u_{0}(x) }だが、計算すると

{ (v_{11}-v_{01}-v_{10}+v_{00})f(x)f(y)+(v_{10}-v_{00})f(x)+(v_{01}-v_{00})f(y)+v_{00} }

となる。一方、辺{ x=0, x=1 }では{ (v_{01}-v_{00})f(y)+v_{00},\, (v_{11}-v_{10})f(y)+v_{10} }になり、これらを結べば上記の式と一致することを確かめることが出来る。

バリュー・ノイズの欠点はブロック状に見えやすいというものであった。その原因は格子に沿った傾きが、格子点上で0になることにある。この条件は曲線を滑らかに繋ぐ際、与えられた値の範囲を逸脱しないためには必要なのだが、それが逆に不自然さを引き起こしていた。実際にSmoothstep関数は階段(step)を等間隔で作ってしまい、これがブロックの要因である。実は補間関数は上に挙げたもの以外にも沢山あり、連続する4点の値から定めるキュービック補間や多項式補間、スプライン補間などがある。これらは値の逸脱を認めてしまうが、階段を作らないのでより自然な感じの補間になる。(キュービック補間には逸脱しない単調キュービック補間なるものも存在する。)しかし今度は高次元化が難しくなる。

Perlin Noise

Ken Perlinは1983年に新しいノイズ生成手法を考案し、それを1985年のSIGGRAPHで発表した({ \lbrack 1 \rbrack })。現在はパーリン・ノイズと呼ばれており*1、彼はそれで賞を貰っている。彼の発想は、格子点の上に与える情報を、値(Value)ではなく傾き(Gradient)にするというものだった。勾配ノイズと呼ばれる、新しいタイプの生成手法である。

格子の一つを{ I^{n}=\lbrack 0, 1\rbrack^{n} }と見なして、{ x\in I^{n} }における値{ v(x) }を求めよう。まず格子の{ 2^{n} }個の頂点{ p }に対し、勾配ベクトル{ g(p) }を無作為に与える。これらのベクトルは理論的には何でも良くて、例えば単位球面上に取る。実装の際は高速化と方向性アーティファクト(Directional Artifact)を取り除くために、立方体の中心から各辺の中心へと延びる12本のベクトルから選ぶらしい({ \lbrack 2 \rbrack })。それで{ x\in I^{n} }に対し、内積{ (x-p)\cdot g(p) }を各頂点の重みとして考える。これらを改めて補間することで{ x }における値を計算する。これが{ v(x) }を定める。

頂点における勾配が与えられたものと一致していることを、{ n=2 }を例に見てみよう。頂点{ (0, 0) }での重みは{ w_{00}(x)=x_{1}g_{1}(0, 0)+x_{2}g_{2}(0, 0) }である。同様に{ w_{10}(x), w_{01}(x), w_{11}(x) }を定めれば、{ f }を補間関数として

{ \begin{align*} v(x) =& (w_{11}-w_{01}-w_{10}+w_{00})(x_{1}, x_{2})f(x_{1})f(x_{2}) \\ &+(w_{10}-w_{00})(x_{1}, x_{2})f(x_{1})+(w_{01}-w_{00})(x_{1}, x_{2})f(x_{2}) \\ &+w_{00}(x_{1}, x_{2}) \end{align*} }

と表される。{ \partial_{j}v(0, 0)=\partial_{j}w_{00}(0, 0)=g_{j}(0, 0) }より、確かに{ \mathrm{grad}(v)(0, 0)=g(0, 0) }になっている。

あと理論面で大事なことは、値がどの程度の範囲内に収まるかを評価することだろう。重みが一番強く出るのは位置ベクトルと勾配ベクトルの向きが揃ったときで、勾配ベクトルの長さの{ \sqrt{n+1} }倍({ I^{n} }の対辺長)で絶対値が抑えられる。補間はその間の値を取るので、厳密解ではないが十分な評価を得る。

Simplex Noise

パーリン・ノイズは質の良いノイズを生成するが、次元が高いときに計算量が多いという欠点がある。ただでさえ格子の数が指数的に増えるのに、使う勾配ベクトルも同じように増えていき、演算回数も多いので誤差が出やすい。パーリン氏は格子を三角にすることで、勾配ベクトルの数を{ n+1 }本に減らし、問題の解決を試みた。

まず次のようにして座標系{ x }を斜交座標系{ x^{\prime} }へと変換する。

{ \displaystyle \left(\begin{matrix} 1+\frac{\sqrt{n+1}-1}{n} & \frac{\sqrt{n+1}-1}{n} & \dots & \\ \frac{\sqrt{n+1}-1}{n} & 1+\frac{\sqrt{n+1}-1}{n} & \dots & \\ \vdots & \vdots & & & \\ & & & 1+\frac{\sqrt{n+1}-1}{n} \end{matrix}\right) \left( \begin{matrix} x_{1} \\ x_{2} \\ \vdots \\ x_{n} \end{matrix}\right) = \left( \begin{matrix} x^{\prime}_{1} \\ x^{\prime}_{2} \\ \vdots \\ x^{\prime}_{n} \end{matrix} \right) }

左の変換行列を{ M }と置く。何故この行列かというと、原点とその対点{ (1, \dotsc, 1) }を結ぶ線を軸として、他の点も回転させることなく均等に広げ、その上で最長辺が元の格子の最長辺と同じ長さを持つようにするためである(スケールを合わせた)。逆行列

{ \displaystyle M^{-1}=\left( \begin{matrix} 1+\frac{\frac{1}{\sqrt{n+1}}-1}{n} & \frac{\frac{1}{\sqrt{n+1}}-1}{n} & \dots & \\ \frac{\frac{1}{\sqrt{n+1}}-1}{n} & 1+\frac{\frac{1}{\sqrt{n+1}}-1}{n} & \dots & \\ \vdots & \vdots & & & \\ & & & 1+\frac{\frac{1}{\sqrt{n+1}}-1}{n} \end{matrix}\right) }

と求めやすい。

斜交座標を求めたら、その整数部{ \lfloor x^{\prime} \rfloor }と内部座標{ x^{\prime}-\lfloor x^{\prime} \rfloor }を考える。(元の座標系に射影された)斜交座標の格子点の上に勾配ベクトルが与えられているので、{ x^{\prime} }を囲む単体の頂点が計算に必要なベクトルとなる。単体はシュレーフリに従い、内部座標を大きい順に並べ、その番号の辺を順に選ぶことで求められる。例えば{ x^{\prime}-\lfloor x^{\prime} \rfloor =(0.4, 0.5, 0.3) }なら{ 2\rightarrow 1\rightarrow 3 }の順に選ぶので、{ (0, 0, 0), (0, 1, 0), (1, 1, 0), (1, 1, 1) }に対応する頂点が{ x^{\prime}-\lfloor x^{\prime} \rfloor }を囲む単体になる。あとは整数部を足して、{ x }を囲む単体(の斜交座標)が得られる。

単体の頂点に勾配ベクトルが与えられているが、位置ベクトルを求めるには元の座標系に戻す必要がある。{ x }を囲む単体の頂点座標は、その斜交座標を{ M^{-1} }で引き戻すことで得られる。これと{ x }との差を考え、更に対応する勾配ベクトルとを内積して重みを計算する。最後に{ n+1 }個の重みを補間することで値{ v(x) }が得られる。値の範囲はパーリン・ノイズの半分程度になる。シンプレックス・ノイズの計算は複雑だが計算量はかなり減る。平方根があるのでちょっと心配になるけど、今時は回路そのものに開平機構があるんじゃないかな。というか定数で良い。

よりリアルなノイズを目指して

ノイズは3DCG等におけるテクスチャ生成によく用いられるらしい。すると{ I^{2} }上のノイズを考えることになるが、これでは粗すぎる。そこでより細かいノイズを考えることにする。一般に各辺を{ 2^{N} }(例えば{ 2^{8}=256 }で細分し、その上のノイズを考える。このとき生成されたノイズはオクターブOctave)が{ N }であるという。そして{ I^{2} }における最大値と最小値の差(振幅(Amplitude)という)の{ -N }乗を残存性(Persistence)という。一般に残存性が高いほど、波が潰れない。

ところで勾配ノイズは、格子点上で値が0になるという特徴を持っている。従って、単一のノイズでは不自然さが残ってしまう。そこで異なるオクターブのノイズを適当に足し合わせることで、よりリアルなノイズを作ることがよく行われる。(足し合わせの際は係数に補正を掛けることが多い。)あるいは絶対値を取ることで乱流(Turbulence)を加えることもある。三角波で捻ったり、敢えて3次元で作って斜めに切断したり、この辺はデザイナーの腕の見せ所なのだろう。

参考文献

[1] Ken Perlin. An image synthesizer. ACM SIGGRAPH Comput. Graph. 19(3), 1985, pp. 287-296.
[2] Ken Perlin. Improving noise. ACM Trans. Graph. 21(3), 2002, pp. 681-682.
[3] Ken Perlin. Noise hardware. In Real-Time Shading SIGGRAPH Course Notes, Olano M., (Ed.), 2001.

パーリンさん何者だよ天才かよ。

参考にしたサイト

*1:バリュー・ノイズと混同されることもある