<前文>
前回、やり残した部分をやっていきます。今回は、考察のみです。
<本文>
<考察>
前回、以下の部分の考察を来週しますと言って終了しました。本当は前回終わらしても良かったのですが、ダラダラと続きを書くよりも、一端書くのを止めて考えをまとめた方がいいと判断したからです。
- 再現出来る「Landscape API –パーリンノイズ(Perlin Noise)を使用したランドスケープ(landscape)の生成」の方法について
- 「本当に教科書に載っているレシピでは出来ないのか?」の検証
- 今回のレシピ作成で得た知見は正しいのかの検証。以下の事についての検証が必要です。
- 「エディターの世界(editor world)とゲームの世界(game world)」の違い
- 「パーリンノイズ(Perlin noise)」を作成するためのコードPerlinNoise1234は本当に間違っているのか?」
- UFUNCTION()は必要?
1.の目的は再現できるレシピを作る事ですが、前回のコードは目的通りに動作したのであまり必要ないかもしれないと今は考えています。
2.の検証をするに当たって3.の疑問を先に解決する必要があります。
と言う訳で、3.から検証していきます。
まず、一番簡単なUFUNCTION()について調べてみます。確認しなければならない事はUE4のガーベジコレクション(garbage collection)はUFUNCTION()の有無で決まっているのかどうかです。
UFUNCTION()についての復習
結論から言うとFUNCTION()はガーベジコレクション(garbage collection)とは何の関係もなかったです。
UE4のGarbage Collection Overviewのこのページを読むと以下のように書かれていました。
- 全てのクラスのメンバーはUPROPERTYとして宣言されるべきです。
- もしメンバーが裸で残されていたらUnrealはそれを知る事が出来ません。だからあなたが指しているオブジェクトがあなたの元から消されてしまうかもしれません。
- ただしintやboolなどの値(value)のタイプは、裸で残して置いた方が安全です。それらはセーブもされず、複製もされず、エディターからも見えませんが。
ここでは、メンバーと書かれていますが、UPROPERTYと断言しているのでメンバー変数(member variable)の事ですね。メンバー関数(member function)の場合のUFUNCTION()はガーベジコレクション(garbage collection)と関係ないようです。
と言うか、メモリーを開放しなければならないのは、オブジェクトであって関数じゃないですね。やっぱり焦ってやっても仕方ないですね。
パーリンノイズ(Perlin noise)について
パーリンノイズ(Perlin noise)もそんなに面白そうじゃないし、どこまでやろうかなと思って、取りあえずThe Coding Train のI.5: Perlin Noise - The Nature of Codeを見ていたら、バーリン(Perlin)氏はニューヨーク大学の教授でまだ存命の方だと初めて知りました。
The Coding Train氏が番組で彼のオフィスはこの先の建物と言って世の中狭いなと思いました。以下に示したのがパーリン(Perlin)氏の大学のホームぺ―ジの一部です。
思いっきりCGの人ですね。この人が担当する学部生向けのクラスを見たらWebGLを使っていました。もう大学のCGの授業でOpenGLは使われなくなってしまったのですね。私のいた大学でも3Dを使いたくて必死にC++を勉強してたのは私の代が最後で次の学年からはCGの授業はWebGLでやってました。研究室でも次の学年の人はProcessingを使っていました。私がUE4のC++に拘るのも、その時勉強したC++と3DをUE4上で利用したいと言う気持ちがあるからですが、もうそういう人は出てこないのでしょうね。何か切ないです。
Wikipediaのパーリンノイズ(Perlin noise)を見ていたら、
パーリンノイズ(Perlin noise)はその当時のコンピューターグラフィックの機械のような見た目に対するケンパーリン(Ken Perlin)のフラストレーションの結果、彼によって開発された勾配ノイズ(gradient noise)の一種である。
ちょっとパーリンノイズ(Perlin noise)に興味が出て来ました。遠回りになるかもしれませんが、勉強しますか。
The Coding Train のI.5: Perlin Noise - The Nature of Codeによると、以下に示すような2つのランダムな値を取るグラフがあるとします。左側のグラフは普通のランダムなノイズを表したものです。値は完全に独立していて前の値とも後の値とも関係ありません。それに比べて右のグラフは前の値と今の値が必ず繋がるようになっています。全体としてはランダムですが、なだらかなカーブが形成されています。この右側のグラフがパーリンノイズ(Perlin noise)によって形成されたものだそうです。
オー、分かり易い。では実際はどうやって計算するですか?と思ったらそのための関数を呼び足して終わりでした。
しかしパーリンノイズ(Perlin noise)自体はきちんとしたランドスケープ(landscape)の自動作成のためには、絶対必要な事は分かりました。ランドスケープ(landscape)をパーリンノイズ(Perlin Noise)を使わないで自動作成したら前回のような
トゲトゲになってしまいます。しかし前回のかなり不完全なパーリンノイズ(Perlin noise)でさえ使用した場合、以下に示すように、それなりにスムーズな形になってくれます。
まずコードをサンプルと教科書に限りなく近づけて以下のようにしました。
以下に示すように成りました。
前回、「pnoise( float x, float y, int px, int py )関数が0しか返してこないのが原因でした。どうもxとyの値は0から1の間しか受け付けないらしく、1を超えると返り値が0になってしまうみたいです。」と言っていましたがそんな事はなかったです。Ampの値を使用しない限りは、PerlinNoise2D()のパラメーターの値を上げても問題は起きませんでした。
しかし上図に見られるように、スムーズな値の変化は一方向にしか起きていません。PerlinNoise2D()と言っているのですから2方向で起きるべきです。ウーン?どこが悪いんでしょうか?
「Y軸も余りで座標を求めてる。…」
マジですか?こんなケアレスミスしてたとは。
はい。見事な2Dのランドスケープ(landscape)が出来ました。パーリンノイズ(Perlin noise)がきちんと働いているので大変スムーズな高低差になっています。
PerlinNoise1234に間違いはなかったです。それはそうですね。もし間違いがあればパーリン(Perlin)教授が直接直しますよね。
次に、PerlinNoise2D()関数について調べます。
前回、この関数のコードは全く読まないで進んでしまったので今から読んでいきます。xとyですがランドスケープ(landscape)の盤上の座標です。次のampは何故か、値が0.82…になってしまうので使用してませんが、最後にnoiseと掛けていますので、高さを調節するためのパラメーターです。
ああそうでした。Ampが必ず0.82…になる何て凄くおかしいですよね。これも今直してしまいましょう。
Ampの値は1000.0fです。
あれ。
普通に出来ました。ウーン前回のバグはどこから起きたのでしょう。何か狐につままれるような展開ですが再現出来ない以上先に進むしかないですね。
では、octavesについて考えてみましょう。
Octavesは16ですのでoctaveは1,2,4,8と成ります。ウーン全く分からないですね。
Noise1234::pnoise()関数から見てみます。
以下にコードを示します。
おお、まったく意味が分からない。
パーリンノイズ(Perlin noise)の計算方法を調べた方が速く理解出来そうですね。
YouTubeのPerlin Noise Explained Tutorial 2が簡単に説明していました。ここの説明を基にして更にUnderstanding Perlin Noise、Improved Noise reference implementationを参考にして2Dでのパーリンノイズ(Perlin Noise)の計算方法を以下にまとめます。
----------------------------------------------------------------------------------------------------------------------
注意!パーリンノイズ(Perlin noise)には、2種類あります。単なるパーリンノイズ(Perlin noise)と周期的なパーリンノイズ(Perlin periodic noise)です。ここでは、単なるパーリンノイズ(Perlin noise)について説明しますが実際にレシピで使用されている方は周期的なパーリンノイズ(Perlin periodic noise)です。
<パーリンノイズ(Perlin noise)の計算手順>
- 勾配ベクトル(gradient vector)の値を計算する。
- 距離ベクトル(distance vector)を計算する。
- 勾配ベクトル(gradient vector)と距離ベクトル(distance vector)の内積(dot product)を計算する。
- サンプル値の位置をフェード関数(fade function)を使って調整する。
- 計算した内積(dot product)からフェード関数(fade function)で調節されたサンプル値の位置の内積値を線形補間(Linear interpolation)で求める。
はい。何を言っているか分かりませんね。一つ一つ説明していきます。関係ないですが数学の日本語の専門用語は美しいですね。
<0. 計算の前に…>
2Dパーリンノイズ(Perlin noise)なので平面上の値を計算します。
パーリンノイズ(Perlin noise)でもランダムな値を取る箇所はあります。それを以下の図では黒点で示しました。
では計算を始めます。
<1. 勾配ベクトル(gradient vector)の値を計算する>
勾配ベクトル(gradient vector)の値はランダムに選ばれます。勾配ベクトル(gradient vector)の計算が必要な個所は黒点のみです。
勾配ベクトル(gradient vector)の値を決められた値の中からランダムに選びます。
<2. 距離ベクトル(distance vector)を計算する。>
勾配ベクトル(gradient vector)を表示すると他が見えにくいので消しました。
すべての青い点から無作為に選んだ一点をサンプル点としてオレンジ色でマークしました。オレンジ点と名付けます。
黒点1からの距離ベクトル(distance vector)を計算しました。
黒点2、黒点3、黒点4からの距離ベクトル(distance vector)を計算しました。
これを実際は全ての青い点に対して行います。
<3. 勾配ベクトル(gradient vector)と距離ベクトル(distance vector)の内積(dot product)を計算する。>
計算しました。
内積(dot product)を知らない人はいないと思いますが、一応、計算方法も載せておきます。
ここから引用しました。
<4. サンプル値の位置をフェード関数(fade function)を使って調整する。>
フェード関数(fade function)だけは初めて聞きました。線形補間(linear interpolation)のみだと緩やかなカーブにならないのでこのフェード関数(fade function)で値を補正するのだそうです。
オレンジ点の位置(1/3, 2/3)をフェード関数(fade function)を使って変換します。
オレンジ点の位置(1/3, 2/3)は(0.21, 0.79)になりました。
<5. 計算した内積(dot product)からフェード関数(fade function)で調節されたサンプル値の位置の内積値を線形補間(Linear interpolation)で求める。>
最初に濃いオレンジの内積値を線形補間(linear interpolation)で求めました。
オレンジ点の内積値は-0.25となりました。本来は全ての青い点に対して同じ計算を行います。
はい。以上です。理解するのは20分で済んだのですが、この図を描くのに5時間近くかかってしまいもうボロボロです。
*計算方法の正しさは何回もチェックしましたが、計算そのもののチェックまで手が回りませんでした。もし計算にケアレスミスがあった場合はご了承ください。
----------------------------------------------------------------------------------------------------------------------
何が目的かももう忘れかけてましたが、以下のコードを理解するためでした。
まず、パッと見ですが、
フェード関数(Fade function)の計算。
勾配ベクトル(gradient vector)の計算。
線形補間(Linear Interpolation)の計算。などが推測出来ます。
これが、良く分かりません。パーリンノイズ(Perlin noise)の座標は0から1までのはずで、何故整数値をもっているのでしょう?
以下に示すようなケースならばパーリンノイズ(Perlin noise)の座標は1以上になるのでそのためですかね。
noise1234.cppクラスから以下の定義を見つけました。
フェード関数(fade function)は、6t^5-15t^4+10t^3なのでFADE(t)は同じと確認出来ました。
LEAP()はtを正規化した計算したい位置、a,bをその位置を線上で挟む2点の値と考えれば線形補間(Linear Interpolation)の計算になりますのでこれも同じと確認出来ました。
Grad()関数も見つかりました。そのコメントには
とあり、更に最後に0.507を掛けている理由も分かりました。
しかし他が分かりません。Grad()関数にパスしているHashとはなんでしょうか?
Permとは何でしょうか?
配置表:ただの0から255までのランダムなごちゃごちゃの数字
0から255までのランダムな数字の羅列でした。
それでは、grad()関数のコードをみてみましょう。
&7とする事で0から7までの数字からランダムに一個選んでいると考えられます。
8種類のタイプから1つ選ぶ。まず思い浮かぶのは以下に示す勾配ベクトル(gradient vector)のグルーブです。このgrad関数内ではこのグループ内から一つ選んでいるのでしょうか?
その次のアギュメント(argument)であるfx0, fy0は何でしょうか?
以下の部分を見ると,
黒点1からの距離ベクトル(distance vector)と考えられます。更に、fx0, fy1は黒点2からの距離ベクトル(distance vector)。Fx1, fy0は黒点3からの距離ベクトル(distance vector)。Fx1, fy1は黒点4からの距離ベクトル(distance vector)と考えられます。
Grad()関数のコメントには、
と書かれているので、ここで、
- 勾配ベクトル(gradient vector)の値を計算する。
- 勾配ベクトル(gradient vector)と距離ベクトル(distance vector)の内積(dot product)を計算する。
までが行われていると考えられます。それでは確認してみましょう。
最初のコメントが「8つの簡単な勾配の方向に入れます。」とありますので、最初の行を見てみましょう。
ウーン。これはhの値が4より小さければ、uの値がxそれ以外の場合はyになるといっているだけだと思います。次の行はその逆でこれはhの値が4より小さければvの値がyそれ以外の場合はxになるといっています。
決してコメントに書かれている内容を計算しているようには見えません。ウーン正直分かりません。
なので、grad()関数だけ抜き出してテストしてみました。
その結果と、理論通りの計算結果を比較した所、もし勾配ベクトル(gradient vector)が以下に示す通りなら全く同じになると分かりました。
計算結果
それでは、grad()関数についてまとめます。
Grad()関数は、距離ベクトル(distance vector)とランダムな一つの整数をパラメーターとしてパスして
- 勾配ベクトル(gradient vector)の値を計算する。
- 勾配ベクトル(gradient vector)と距離ベクトル(distance vector)の内積(dot product)を計算する。
を行います。
勾配ベクトルに使用されている値は、
と考えられます。正規化されていないために、最後に0.507を掛けている必要があります。
LEAPの計算をみます。これは値の計算には全く影響はないですがPerlin Noise Explained Tutorial 2で横の線形補間(Linear Interpolation)だったのが縦の線形補間(Linear Interpolation)になっています。
FADE()関数?(マクロ?)の使用方法に関しては全く同じです。サンプルの位置に対してFADE()関数で補正してその値で線形補間(Linear Interpolation)してます。
<オクターブ(Octave)>
すこし逸脱しますが一か所判明した部分を先に解説します。オクターブ(Octave)についてです。
教科書のHow it works…内のPerlin Noise2D()関数の解説で
もっと細かいデーティルのランドスケープを得るために、いくつかのパーリンノイズ(Perlin noise)のオクターブ(octaves)の合計を使用したいはずです。
Understanding Perlin Noiseにオクターブ(octaves)についての記事がありました。
計算方法は全く同じでした。それぞれのオクターブ(octaves)で計算して最後に加算しています。
こういう計算をすると更に本当の地形のようになるそうです。
<周期的なパーリンノイズ(Perlin periodic noise)>
もうほとんど理解しましたね。後は、周期的なパーリンノイズ(Perlin periodic noise)が普通のパーリンノイズ(Perlin noise)とどう違うのかとそれに関してpxとpyがどう関係するかです。
もうここまで来ると周期的に同じ値を繰り返すのでしょうと、大体想像つくのですが、その考えをサポートする資料が見つかりませんでした。のでここではコードからのみの解説をします。
以下に普通のパーリンノイズ(Perlin periodic noise)のix0, iy0, ix1, iy1の計算方法を示します。
今度は、周期的なパーリンノイズ(Perlin periodic noise)のix0, iy0, ix1, iy1の計算方法を示します。
はい。周期的なパーリンノイズ(Perlin periodic noise)のix0, iy0, ix1, iy1は、pxとpyの間の値を繰り返している事が分かります。
でも、そもそもix0, iy0, ix1, iy1が元々何を指しているのか知りませんでした。
はい。分かりました。
ix0, iy0, ix1, iy1は、この黒点の位置を表しています。
つまり、この黒点の数がpx、pyを超えるとまた、同じ値を繰り返すと言う事です。後、書いていなかったのですが、置換表(Permutation table)は同じ条件なら必ず同じ値を返すそうです。単なるランダム数とはそこが違うそうです。なのでpx、pyを超えた時、全く同じ事を繰り返すようになるはずです。
では、最後に最初の質問に戻りましょう。もう質問が何だったかも忘れてしまいましたのでもう一度ここに書きます。
質問:「パーリンノイズ(Perlin noise)」を作成するためのコードPerlinNoise1234は本当に間違っているのか?」
解答: 間違っていませんでしたが、正しい使い方を理解するのためには、パーリンノイズ(Perlin noise)そのものをかなり理解する必要があり、間違えて使用しても仕方ないかなと思います。
今週はここまでです。残りは来週やります。
<まとめ>
来週まとめます。
<おまけ>
これも来週にします。