<前文>
久しぶりのMinimal Pairについて
今週、ちょっと時間が取れて久しぶりにMinimal Pairについての調査をしていたら、結構重大な発見をしたので今週の前文はそれを記録しておく事にします。
RとLの音の違いについてです。
まず前提条件から話しますが、日本人の通訳者やバイリンガルLevelの人でも、一般の日本人が思っている程、RとLの音は区別出来きていません。
彼らはRとLの発音は出来きますが、一般のアメリカ人の会話のスピードでRとLの音を聞くとどっちか全く分からない人がほとんどです。(唯一の例外が小学生以下でアメリカで暮らした事がある人達で、彼らはLとRの絶対音感みたいなのを持っています。)
私も10年、アメリカに住んでいたので英語は日本語と全く変わりないLevelで聞き取れていました。のでそんな事ある訳ないだろう。結構余裕ぶってMinimal PairによるLとRの音の違いが認識出来るかどうかのテストを受けたら、全然区別出来なくてショックを受けました。
ただし全く区別出来ないと言う訳ではなく、単語の最初の音がRやLの場合はそれなりに区別がつきます。
まずRの時はほとんどの場合でウが聞こえます。Lの場合は三割位の場合は、半分くらいムに近い音が聞こえます。日本語のラリルレロと全く区別がつかない場合はLです。
最後の音の場合は、全く違う音になります。これは間違えようがありません。Rの音は丸まります。Lも音はウともオともつかない所謂Dark Lになります。これは全く違う音なので間違えようがないです。
難しいのは子音の次にLやRがある場合です。FryとFlyのような場合です。これは本当に区別がつきません。
FlyでもDark Lで発音する人もいてそういう人の発音なんかファイとしか聞こえません。
そう言う訳で、LとRの音の区別と付けるのは、英語がしゃべれる日本人にとっても難しいんです。
今回、RayとLayのMinimal Pairを整理していたら、Layの音で、三割位が、ムに聞こえて、残り3割位が日本語のラリルレロと全く区別がつかない音に聞こえて、更に残り3割がなんとPlayと聞こえて来たんです。
それだけじゃないんです。
Rayの音を聞いていた時、半分がウの音が最初に聞こえて、残り半分がBrayに聞こえて来たんです。
(P)layと(B)ray。
これ、何か気が付きませんか?
PとBは同じ発音をしますが、Pは無声音でBは有声音です。そこだけが違うんです。日本語で言えば濁音が有声音にあたります。カキクケコとガギグゲゴの違いです。
で思ったんですが、
LとRの違いって、ラリルレロとラ”、リ”、ル”、レ”、ロ”の違いじゃないのかなと。
良くRの音って半母音っていうじゃないですか?
母音は有声音ですから。
後、有声音って声帯を振動させるじゃないですか。
Rの音って舌をどこにもつけないじゃないですか、舌先って結構振動しているんじゃないでしょうか?
勿論、反論もあってDark Lも母音だと言う人もいるからそうなるとオカシクなります。
まあ、全く的外れに終わるかもしれませんが検討する価値は結構あるアイデアだと思っています。流石に忙しいのでこれの調査をする事は出来ませんが一応記録に留めておく事にします。
それでは今週の勉強を始めます。
<本文>
1.今週の予定
今週は以下の内容をやっていきます。
- Niagara: 先週の復習と何かをやる
- Material : Ben Cloward氏のTutorialを勉強する
- NPCのAIを作成するためのAIの復習の続き(Nav Meshの勉強の続き)
- World CompositionによるMap1の作成(動的に生成するActorの追加など)
- UE5におけるWorld Partitionの勉強の続き
2.Niagaraの勉強
2.1 Niagara : Time Distortion Effect - UE4 Tutorial [1]の復習
先週、このTutorialは終わらせる事が出来ましたが、理解していないノードや実装部分が沢山あります。それらを検証していきます。
<Sphere Mask ノードについて>
先週、SphereMaskノードを使用しましたが、AとBの意味が良く分かりません。
これを調べます。
公式のDocumentのSphereMask [2]を見ると
AとBについて述べてそうな箇所はありますが、具体的な意味は分かりません。
これを読むとBがSphereを作成する中心で、AからBにチェックされる点と読めます。
うーん。良く考えたらObject PositionとAbsolute World Positionについても良く分かっていませんでした。
うーん。同じ事を言っているような気もします。
そうだDebugFloatノードで調べて見ましょう。
以下の様に表示されました。
表示されている値はこのActorのLocationの値でした。
今度は
で試してみます。
字が汚くて良く読めません。
でもTransformのLocationとほぼ同じ数字を表しています。
もう一回解説を読み直してみます。
Current Pixelって言っていますね。後、Emissiveに繋げれば可視化出来ると。
やってみます。
おお。
こういう事か。
Absolute World PositionはそれぞれのPixelがさしているObjectのWorld Coordinateにおける値を返しているようです。
今、Planeが
に配置されてます。
RとGの座標が以下の様に対応していると考えると
示してる色と合致します。
左上はR =0、G=0、B=50 で青、右上はR=1以上、G=0、B=50 でPink、右下はR=1以上、G=1以上、B=50で白です。青と緑を混ぜた色が何だったのかは忘れました。
調べたら以下の色でした。
やっぱり同じ色ですね。
分かり易いようにz軸が0になるようにPlaneを移動させました。
でもそれだと実装の意味が分からないと思っていたら、先週やった実装は以下のものでした。
ちょっと間違えていました。
DebugFloatノードで検証したらActor PositionはObject Positionと同じ結果が出ました。
上記の実装の結果は以下の通りです。
うーん。
良く分からん。
これは例が難しすぎます。
もっと簡単な例で考えてみます。
これなら円の中心が原点になるはずです。
なっていますね。
Bに関しては、球の中心で間違いないようですね。
じゃAは何なのかと言うのが分からないんです。
半径はRadiusで決定します。色のGradientはHardnessで決まります。
Aにも原点を入れてみます。
む。真っ白になりました。
あ。
分かりました。先程見たようにAbsolute World PositionはそれぞれのPixelの値を返すのでそれぞれのPixelの位置によって値が微妙に違っているはずです。それがGradientを作っていたんです。
じゃHardnessは何をしているんでしょうか?
Hardness = 0.1の時です。
Hardness = 0.5の時です。
Hardness = 1の時です。
Gradientの割合を調節しているようですね。
もう一回、Absolute World Positionの解説を読んだら今度は意味が分かりました。
つまりそれぞれのpixelのWorld Coordinateにおける位置を返していたんです。その位置がBで指定した球の中心から半径内にあれば、それぞれの距離に対応したGradientの色で表示されます。指定した半径の外にある場合は色は無しになります。
今回やったように、Aに原点を指定したら全てのPixelの計算値は原点と同じになります。
つまりこのMaterialを使用しているStatic Meshに球状の印を示したい時は、必ずそれぞれのpixelのWorld Coordinateにおける位置をSphere MaskノードのAに返す必要があります。それが出来る関数の一つが、Absolute World Positionノードだったわけです。
最初の解説に戻ります。
はい。
今はAの行っている意味が分かります。Bが球の中心だけどAの位置はBに対してどの位離れているの?球の半径には入っているの?球の半径に入っている場合は、中心からどれくらい離れているの。
それを確認します。
ここで大切なのは、Absolute World PositionノードをAに接続するとこのMaterialを使用しているStatic Meshの表面が画面に写っている限り、全てのPixelの位置をそれぞれ調べて返してくる事です。何万個でも返してきてそれぞれの距離をWorld Coordinateで計算します。
最初の文のOne input…of a pointがAにパスするdataの事で、その後の文がBにパスするDataの解説をしています。
やっと理解出来ました。
<波紋を作成する>
この部分は先週のMaterialの勉強のSineの部分で既にやっているのでスキップします。
<MaterialのTessellation>
これはMaterialの勉強する時にやります。これもスキップします。
<Parameter Collectionを使用してMaterial BPと他のBPと値をやり取りする方法>
これもMaterialを勉強する時にやる事にします。
ただ勘違いしている可能性もあるので一応、Parameter Collectionを使用するとMaterial BPと他のBPの間で値をやり取り出来る事だけ確認しておきます。
まずParameter Collectionを作成します。
Playerの操作するキャラの位置をパスするため、vectorを追加します。
Vectorの名前はThird Person Positionとします。
作成したParameter CollectionをDragしてMaterial内に持ち込みます。
Bにつないで円の中心にします。
今度はLevel BPを開いてPlayerが操作するキャラの位置を先程作成したParameter Collectionにパスします。
そのために使用するノードは
です。
テストします。
キャラの位置によって円の中心が移動しています。
はい。
Parameter Collectionを使用するとMaterial BPと他のBPの間で値をやり取り出来るのは確認出来ました。
<Tessellationを使用した時の最適化について>
これもTessellationを勉強する時にやります。
以上です。
2.2 UEにおけるVFXに対しての個人的な感想
今、Blogを見直したら2021-04-26のBlogからほぼ毎週Niagaraの勉強をして来ました。
Niagaraの構造や機能についてはまだかなり勉強する内容はありそうですが、それなりに理解してきました。
ところが、VFXのデザインに関しては全く分かりません。
デザイナーが己のセンスのみに頼って作成しているのか、何かのマニュアルがあってそれに沿って製作しているのかですら今の所不明です。
そう言う訳で、まあ勝手な素人の言い分となりますが、現状のEffectのデザインに対しては個人的には結構な不満があります。
それをまとめておきます。
<光り過ぎ>
Effect全般に言えますが、光り過ぎじゃないでしょうか?
暗がりなら光っているのは目立ちますが、明るい所で光っても何も見えません。ゲームなので背景をいつも暗くする事は出来ないと思います。
普通の色でEffectを作っても良い気がします。
<透明過ぎ>
TransparentなMaterialは高コストです。アニメ調の炎までTransparentなMaterialを使用する必要があるんでしょうか?
<マンガやアニメで多用されているEffectがない>
折角、何十年における漫画やアニメで効果的なEffectの蓄積があるのに、それがEffect化されていないと思いました。
一部はEffect化されているのかもしれませんが、私は見た事ないです。
例えば、集中線。
マンガだと速いパンチを打つと拳の先が消えて集中線だけになります。
こういうEffectは結構基本だと思いますが見た事ないです。
後、だからと言ってそっくりそのまま作成するのもオカシイは思います。
例えばですが、マンガの流血シーンは白黒だから目立つのであって、色の付いているゲーム画面でマンガそっくりの流血シーンを再現しても迫力に欠けます。と言うかゲームの派手な配色からすると流血した事すら気が付かないかもしれません。
流血シーンはキャラの色を一時的に真っ白にするとかの大胆な変更があってもそれでマンガの流血シーンと同じ位のImpactがある方が本質を再現しているのかもしれません。
<カメラに対しての見栄え>
EffectのほとんどはSpriteで制作されている訳で、Cameraに対してどう映るかが重要だと思います。その辺の検討があんまりされてない気がします。
2.3 CGHOW氏の何かをやる
Ribbonを使っているそうなのでTwisted Ribbon in UE4.27 Niagara Tutorial [3] をやる事にします。
Niagara SystemにFountainを追加して新しいNSを作成します。
要らない機能を全部消します。
Render SectionにRibbon Renderer Moduleを追加します。
Ribbon Renderer ModuleのShape変数にTubeをセットします。
更にParticle Spawn SectionにInitialize Ribbon Moduleを追加します。
更にParticleのPosition 変数を追加します。
そのPositionにMake Vectorをセットします。
今度はxにMultiply Floatをセットします。
Aに100、BにReturn Normalized Exec Indexをセットします。
Return Normalized Exec Indexは初めて使用します。
と書かれていました。
Dynamic Input Scriptを開くと以下に示した実装がされています。
短いので今読んでしまいます。
まずMap Getノードです。
Inputは以下の二つですね。
EngineのExecution Count変数は以下の解説がありました。
タイプはInt32のようです。
これを見るとNSではParticle一つ一つに番号が振ってあるようですね。その番号を管理するのがEngineのExecution Count変数みたいです。でもEngineのExecution Count変数は一個しかないから、Particleが一個出来るたびに番号が一個上がっていくようなものですね。
その値をNormalizeしてます。
Normalizeって0から1の間にするって事でしょう。どうやってするんですかね。生成した全部のParticleで割るんでしょうか?
その値にNormalized Index Scaleを掛けています。
以上です。
うーん。Execution Count変数とその正規化の母数が分からないと何をしているのか良く分からないです。これは後で調べる事にします。
Emitter Update SectionにSpawn Burst Instantaneous Moduleを追加します。
Spawn Burst Instantaneous Module のSpawn Count 変数に100をセットします。
Emitter Update Section のEmitter State ModuleのLoop BehaviorにOnceをセットします。
更にParticle Update SectionのParticle State ModuleのKill Particle When Lifetime Has Elapsedのチェックを外します。
この辺の設定は全く何をやっているのか分かりません。
後で調べます。
こんな感じになりました。
Particle Spawn SectionのInitialize Ribbon ModuleのRibbon Twist Mode変数にDirect Setをセットします。
そしてRibbon Twist変数にMultiply FloatをセットしてAに20、BにまたReturn Normalized Exec Index変数をセットします。
すると
こんな捻じれた形になりました。
この形、アメリカのあるお菓子に形がそっくりです。何かお腹が減って来ました。
Ribbon Width変数に20をセットします。
こんな感じに変わりました。
Particle Spawn SectionのSet (PARTICLES) Position ModuleのZにFloat From Curveをセットします。
更に以下の様な設定にします。
すると以下に示した様に
Ribbonの最後の部分がZ軸にそって上がります。
これを利用してRibbonにCurveをつけます。
まず新しいDynamic Inputを作成します。
何をするためのものか全く分かりません。全部終わった後で検討します。
今度はScratch Pad Moduleを使用して以下のModuleを作成しました。
うーん。何をやっているのか良く分かりませんね。これも後で調べます。
作成したScratch ModuleをParticle Spawn SectionのPositionの前にセットします。
一個追加ですが、先程作成したDynamic InputはCurve Index変数にセットしてありました。
それを直しました。
Curveの設定を以下の様にします。
すると以下の様に自由に真ん中で曲げれるようになります。
今度はParticle Update SectionにScale Ribbon Width Moduleを追加します。
設定は以下のようにします。先程のParticleと同じです。
うーん。段々分からなくなって来ました。
こんな形になりました。
今度はMaterialを作成します。
Textureが同じのがないので似たのをセットしました。
PannerノードとかStepノードとか見た事ないノードを多用しています。
内容に関しては後で検討します。
こんな感じです。
Particle Spawn SectionのInitialize Ribbon ModuleのColorの色を変えます。
こんな感じになりました。
今度はParticle Update SectionにDynamic Input Parameters Moduleを追加します。
Erodeの値に0.2をセットします。
以下の様に変化しました。
凄い。
でも何でこうなるのかが分からない。これも後で調べます。
Set Particle Position ModuleをParticle Spawn SectionからParticle Update Sectionに移します。
うーん。こんな事して良いんでしょうか?
たしか先程、Set Particle Position Moduleの後にScratch Moduleをセットしないといけないみたいな話をして他と思ったのですが。
これも後で調べます。
更にSet Particle Position Moduleの後にRibbon Twistを追加します。
Parameterはこんな感じです。
大体の意味は分かりますが一応、後で確認します。
結果は
となりました。
ここからは微調整に入るみたいですが、今週はここまでにします。残りは来週やります。
3.Material の勉強
3.1 Ben Cloward氏のTutorialを勉強する
なんと、PowerについてのTutorialが新しくUploadされていました。Power - Shader Graph Basics - Episode 20 [4] です。これを勉強します。
Powerは頻繁に使用されるノードですが、今一良く分かって無くて、単なる掛け算ノードとどう違うのかと思ったりしています。その辺も含めて勉強したいと思います。
はい。
最初から何でPowerを使用するのかの解説がありました。
Graphを作成するのにDesmos [5] と言うサイトを使用しています。便利そうなので私も使用します。
Ben Cloward氏の解説をまとめるとこんな感じになります。
まずMaterialで使用する数字は大体が、0から1の間の数字です。
それはそうですね。色が0から1の間で表されるから、どうしてもほとんどの数字は0から1の間になります。
ここでPowerのGraphを見てみます。
Y=x^kのGraphです。
K = 1の場合です。
K = 2の場合です。
K=3の場合です。
K=10 の場合です。
はい。Powerの式はkの値がどうなろうが必ず0と1の値を通ります。
そしてKの値が大きくなればなるほど、yの値は中々上昇しないで最後で急に上昇します。
つまり色のContrastを調節するのに使用出来るわけです。
因みにKが0<k<1の時は
K=0.1
k=0.2
K=0.5
K=0.9
逆向きのCurveになります。
MaterialのPowerノードはPowerのこの性質を利用していたんですね。
もう長年の疑問の一つが解決しました。
Tutorialでやっているように実際のIllustrationで試してみます。
イラストはこのサイトのを利用させてもらいました。
ありがとうございます。
以下に実装を示します。
K=1の時です。
K=2の時です。
全然、絵の印象が変わりました。
K=5の時です。
今度は暗すぎますね。
K=0.1 の時です。
全体が白くなってしまいました。
K=0.5 の時です。
K=0.9の時です。
うーん。
Powerの数学的な機能は理解できましたが、芸術的な変化は数字の変化と全然一致してませんね。
星空を選択したのは、星の光が強調されると思ったからですが、これを見るとそんな単純な話ではなかったです。
Powerを組むのはProgrammerの仕事ですが、Powerの数値を弄って美しい色を抽出するのはデザイナーに任すべきですね。
今度はPowerのコストについての話です。
K=2の時のコストです。
TutorialでInstructionの数と実際のコストは必ずしも一致しない事を何度も念を押して確認していますが、今回は一応Instructionの数と実際のコストがLinearの関係にあるとします。
Multiplyノードを使用して同じ計算をします。
すると
以下に示した様にコストが減少しました。
つまり、Powerで計算するよりMultiplyで計算した方がコストは必ず低くなるんです。
もしPowerの値が予め決まっているならば、Multiplyを使用すべきという話でした。
これってもっと噛み砕いて言うと、DesignerにPowerの数値を弄らせる必要があるかどうかと言う事でしょう。
私の意見は、その値を弄った結果の変化が絵として見えるなら、どんな値でもDesignerに決定権を与えるべきでしょう。ので今回のように、絵で表現出来る場合はコストが多少かかってもPowerを使用してDesignerが調節出来るようにすべきですね。
残り2つの例もやっていきます。
<陰影の強調>
これはUnityで解説されています。
UnityのMaterialはこのTutorialシリーズで見てるだけなので一個一個のノードの意味まではわかりませんが、多分下に示した実装と同じでしょう。
これで試してみます。
Powerを追加します。
K=1の時です。
K=2の時です。
黒く部分が多くなりました。
K=5の時です。
もっと黒くなりました。
OpenGLの勉強でOutlineを出す時にOutlineの太さをPowerの値を調節して変える方法が紹介されていたんですが、何でPowerの値を変えるとOutlineの太さが変わるのかその時は全く分かりませんでした。
こうやって黒くしてOutlineに当たる部分の割合を増やしていたんですね。
数年越しの謎が解けました。
<Gamma Correctionについて>
以下に示したsRGBはUE5のTextureをクリックすると表示されるEditorにあるParameterです。
これが普通のRGBにGamma Correctionを掛けて調節しているそうです。
Gamma Correctionの理論的は解説は、このサイト(Gamma Correction and Why It Matters) [6] が分かり易いそうです。
後で読む事にします。
何らかの理由でこのGamma Correctionを外したい時は、そのTextureに0.45乗するとGamma Correctionの効果が外れるそうです。
逆に何らかの理由でGamma Correctionの補正が効いていないTextureにGamma Correctionの補正を効かせたい時はそのtextureに2.2乗するとよいそうです。
ただしこれらは厳密な値とはちょっと違うらしいです。それなりに正しい値が出来ると解釈していた方が安全らしいですが。
以上です。
3.2 TextureのCoordinateについての検証
TextureのCoordinateを以下の様に取っている理由を思い出しました。
TextCoordノードがそうやっていたからです。
左上が0つまり原点になっています。
ただこれ見るとU軸が赤V軸が緑になっていますね。私の図はU軸が緑、V軸が赤になっていてそこは逆でした。
でもこれ見ると合ってますね。
後は回転が軸中心で回れば全部説明出来ます。
先週は回転をTextureを回したと仮定したら上手く説明出来ました。
でも私は軸というかカメラ側を回して全部理屈通りの説明が出来るようにしておきたいんです。ちょっとそれについて検討します。
絵がどっちを向いているのか分かりづらいのでカツラを追加しました。
-0.5ずらしました。
回転させます。
Degree =90 です。
Degree=180です。
Degree=270です。
Degree=360です。
はい。
思い出しました。カメラが別の座標軸の中で移動すると考えると全て上手くいったんです。
それでもう一回やってみます。
青で原点を作成しました。カメラが移している部分は以下になります。
TexCoordを-0.5移動させます。
カメラの写している部分です。
今度はカメラを回転させます。
ここで大切な事はカメラは元のCoordinateを中心に回る事です。
90度回転させました。
写っている画像です。
実際の画像です。
180度回転させました。
写っている画像です。
実際の画像です。
もうこれで十分でしょう。
因みにTexCoordに2を掛けると以下に示したようにTextureのサイズが半分になるのも
カメラのサイズが2倍になったと考えると納得出来ます。
これでTextureの移動、回転、拡大縮小に関しては完璧に説明出来る様に成りました。
3.3 氷のMaterialについての検証
Niagara Systemを勉強していてVFXにおけるMaterialの占める割合の大きさにびっくりしました。
それで氷のEffectを作るのに氷専用のMaterialの作成方法を先に勉強しておくべきとなりそれをやる事にします。
ただし今週は様子を知るのが目的なので軽く見るだけです。
無料で配布されている時に貰ったIce CoolのMaterialを見てみます。
こんな感じです。
IceCoolを開くと以下に示した4つのFolderがあります。
Materialsを開くとなっています。
Effectには以下のMFが入っています。
もう良く分からなくなりました。
この氷の塊に使用されているMaterialを調べます。
MI_IceBergと書かれています。
開くとMI_IceEnvironmentBaseが親です。
親もMaterial Instanceなんですね。
MI_IceEnvironmentBaseを開くと
M_IceCoolが親とありました。やっとMaterialが分かりました。
M_IceCoolを開きます。
こんな感じでした。
このMaterialの分かる部分を見て行こうと思います。
まずResultノードです。
設定は以下に示した様になっています。
さらのMaterialに同じ設定をしてみます。
この時点で全然違います。
まずNormalがないです。そしてCustomized UV0と言う選択肢にすらないOutputがあります。
どうやってNormalが使用出来るようになるのか全然分かりませんが、このNormalから見てみます。
お、最初に見たMFが出て来ました。
これを見ます。
Texture SampleにTexture Sampleが繋がっています。
こんな使用方法も初めて見ました。
この部分は青の色に対して調整出来るようにしているみたいですね。青だからz軸にあたるのか。
VectorのZ成分が変わってしまったのでもう一回Normalizeする必要がありますね。
成程、最初のZ軸を加工しないVersionの使用も選択出来るようになっています。
次のノードです。
ES2とかES3_1とかって携帯電話用の設定でしょう。どのDeviceで使用するかによってどの実装を実行するかをここで指定しているみたいです。
Defaultを遡ります。
このFalseの場合は、先程読んだ実装に繋がっています。Trueの時ですが、
Detail Mapと言う実装と繋がっています。
ここで先程のNormalの青の変更をする時に使用したParameterであるClass Detail UV ScaleがTexCoord[0]に乗算されています。
これでDetail NormalのTextureのサイズと普通のNormalのTextureのサイズを同じにしているのでしょうか?
でもそれなら普通のNormalの計算はなんでBの値だけに乗算しているのでしょうか?
ここでDetail Normalと普通のNormalを混ぜています。
その前のノードがさっき見たこれなんですが、
ここでFalseを選択した場合、普通のNormalではGlass Normal Scaleの値は全く使用しません。にも拘わらずDetail Normal MapではTextureのサイズの調整にGlass Normal Scaleの値が使用されるままです。
これってこの一個前のノードから接続した方が正確な値になったんじゃないでしょうか?
単なる感想ですが。
最後の部分です。
何これ?
Glass Normalを使用しない場合は、単にPixel Normal WSの値をWorld Spaceに返しています。更にGlass Normalを使用しない場合なんか(0,0,1)を返しています。
これでも良いって事ですよね。
成程。
勿論そうじゃない場合は今までの実装で計算した値をTangent Spaceに、今までの実装で計算した値をWorld Coordinateに変換した値をOutput World Spaceに返しています。
結構、読めますね。
最後、Tangent Spaceからの値をResult ノードのNormalに接続しています。
Normalに接続するのですから当然です。
今週はこれ位にしておきます。
結構、Materialの勉強の成果が出ている事が確認出来ました。
4.NPCのAIを作成するためのAIの復習の続き(Nav Meshの勉強の続き)
今週もNav Meshの勉強をしていきます。
今週は以下の二つをやっていく予定です。
4.1 Using Navigation Invokers [7] を勉強する
Navigation Invokersの使い方を勉強するそうです。今回のTutorialは簡単そうです。
<Overview>
Navigation InvokersはComponentでNav MeshをRun timeに生成出来る機能を持っているそうです。
<GoalsとObjectives>
Navigation Invokersの使用方法を勉強します。
<1 - Required Setup>
前に作成したProjectを使用します。
<2 - Creating Your Test Level>
Project SettingのEngine-Navigation SystemのNavigation Enforcing Generate Navigation Only Around Navigation Invokersにチェックを入れます。
Project SettingのEngine-Navigation MeshのRuntime、Runtime GenerationにDynamicをセットします。
こっちは元からDynamic にセットされていました。
Nav Mesh Bounds Volumeを配置します。
FloorのScaleを100倍にしろとか書かれていたのですが、流石に100倍はデカすぎと思い10倍にしました。それに合わせてNav Mesh Bounds Volumeのサイズも変更しています。
<3 - Creating your Agent>
まずいつものNPCを作成します。
Get Random Reachable Point In Radiusノードの半径は1000にしました。この単位は多分cmなので10mです。Floorの元々の大きさが10mで10倍しているので100mなはずです。多分これで大丈夫でしょう。
AI Move to ノードのAcceptance Radiusの値が5.0なんですが、これって5cmって事でしょうか。
ちょっと覚えていません。
分かりました。
Mathew Wadstein のWTF Is? AI Move To in Unreal Engine 4 ( UE4 ) [9] でAcceptance Radiusの簡単な実験が載っていました。Acceptance Radiusの値はcm です。
後、AI Move to ノードのAcceptance RadiusはDestinationつまり目的地からの距離でした。目的地からこれだけ離れていても目的地に着いた事にする。と言う意味です。
それなら5cmでも問題ないですね。
ここからが本番です。
Add ComponentからNavigation Invokerを追加します。
Navigation Radiusの設定は元からTutorialの指定した値になっていました。
これだけだそうです。
試してます。
BP_NPC_Invokerを配置したらNav Meshが生成されました。
Nav Meshのサイズが大きい過ぎるので、先程の値を以下の様に変更しました。
これでテストしてみます。
動的にNav Meshが作成されているのが分かります。
4.2 Optimizing Navigation Mesh Generation Speed [8] を勉強する
これだけ表示がAdvancedになっています。
難しいんでしょうか。
後、
と書かれています。
試しに開いて見たら
読みにくい。
これは後でチェックします。
<Overview>
私が現状の知識で理解した範囲で解釈すると、Nav Meshはタイルを組み合わせて構成されていて、そのタイルの構成方法を編集する方法をここで勉強するみたいです。
ただしこれらの値を正しく設定するにはNav Meshに対しての深い知識と高度な技術が必要で、普通にAIを利用する人達はいじる必要はないみたいです。
今回はこんな機能もあるんだ。ぐらいに理解しておきます。
<Use the Highest Cell Size and Cell Height Possible>
Project SettingにあるNavigation MeshのGeneration、Cell Sizeと同じ個所のCell Heightは
Tutorialの解説によるとCell Size とCell HeightはNavigation Tileを生成するためのVoxelのサイズを決定する時に使用されるそうです。
これらの値が小さい方が、Nav Meshはより最適な距離を導き出しますが、コストは大きくなります。
またRecastNavMesh-Defaultの
DetailにもCell SizeとCell Heightの設定が出来ます。
こっちはそのLevelに関しての設定と言う事でしょうね。
確認する方法は?
この設定の変化を可視化する方法はあると思いますがそれはTutorialには載っていませんでした。
RecastNavMesh-Defaultの
とかのこの辺のParameterを使用したら可視化出来そうですが、
そういうのはないんでしょうか?
試しにCell Sizeを199に変更してみました。
こんな風になりました。
今度は1.9に変更しました。
こんな感じになりました。
Edgeの分割が更に細かくなっていると言えばなっていますが、大半のMeshの形状は同じでした。
後、Tutorialの解説を読むと
と書かれているので、この後にCell Sizeを変えた例の図を貼る予定だったんだけど忘れてしまったんじゃないのかなと読めます。
Reloadしたら図が出て来ました。
やっぱし。しかもこの図、しっかりDraw Poly Edgesを使用しています。
あぶねー。
この節の内容半分Missするところでした。
こんな事あるんですね。びっくりです。これからはTutorialを勉強する時は図やGraphがしっかり表示されているか確認して進める事にします。
<Limit the Tile Size>
読んだんですが、CellとTileの関係を忘れてしまって内容が頭に入って来ません。
先にCellとTileの復習をします。
RecastNavMesh-Defaultの以下のParameterにチェックを入れます。
すると以下の図に示した様にTileだと思われる四角が表示されました。
これがTileなんですね。
今度はCell Sizeを1.9に変更しました。
Tileの大きさは変わってない気がしますね。
もう一回、Draw Poly Edgesをチェックします。
以下の様にTile内にEdgesが表示されました。
これがCellなんでしょうね。
よし。
この解釈でもう一回読み直します。
と書かれています。沢山のCellを組み合わせて一個のTileを作成すると読めます。
この解釈であってそうですね。
「(一般的に言って)沢山のCellによって組み立てられている大きなTileは小さなTileよりも生成するコストが高いです。」
はい。
これは分かります。
正確に言うと、ちょっとandの文法が良く分かりませんが、こんな感じの意味だと理解は出来ます。
しかし次の文の意味が良く分かりません。
「しかしTileを処理する時は、The System(これが何を指しているのか分からない、EditorかEngineの事?)もTileの辺に隣接しているCellの処理を行います。」
要はTileの辺の周りのCellに関してはNav Meshだけじゃなくて、The Systemも計算に使用するのでTileが小っちゃくても計算が常に速くなる訳じゃないと言う事でしょうか。
でもtileの大きさってCellのサイズを弄っても変わらないですよね。
あ、分かりました。
これからTileの大きさを変更する方法を勉強するんです。
<<Recommendation>>
その下に書かれているRecommendationでTileの一辺には32から128個のCellが含まれる位がお勧めである。と書かれています。
重要なのはその次で、Cell Sizeを64に変更したらTile Size UUは64*32から64*128の間の値、つまり2048~8192にセットしろと書かれています。
つまりこのTile Size UUがTileの大きさを決定しているんす。
Cell SizeとTile Sizeを以下のように設定しました。
Tileの大きさが約4分の1になりました。
成程。こうやってむやみにTileの数を増やすと、計算コストが返って高くなる場合もある訳ですね。
納得です。
(後で計算したらCellの数とTileのサイズは一応お勧めの範囲ではありました。)
<Use Simplified Collision for Your Meshes>
当然ですが、Nav Mesh上に配置されているActorのCollisionの状態がNPCの適切なルートの決定に影響を及ぼします。
計算を軽くするためにはNav Mesh上のActorのCollisionはSimpleな方が良いそうです。
Static Meshを開いてCollisionの所をチェックすると
Simple CollisionとComplex Collisionがあります。これのSimple Collisionを常に選択しておくべきと言う事らしいです。
でも具体的にはどうすれば良いのか分かりません。
<Reduce the Number of Objects That Affect the Navigation Mesh>
Nav Meshの計算には、Nav Mesh上に配置されている全てのObjectが使用されます。
小さいObjectでNPCの移動に影響がないモノは、DetailのCollisionにあるCan Ever Affect Navigationのチェックを外してしまいましょう。
これはやり方も分かりました。
<Useful Developer Tools to Manage Navigation Generation>
<<Locking and Unlocking the Navigation Mesh Generation at Strategic Times>>
Navigation Meshの生成を止めたり止めているのを止めたりするための機能があるそうです。
Tutorialでは、Nav Mesh上に配置されるべきObjectが配置される前にNav Meshが生成されると、Objectが配置された後にもう一回Nav Meshの計算をする必要が出来ます。のでObjectが配置されるまでNav Meshの計算を中止させるのに使用するのと便利と書かれていました。
やり方は
bInitialBuildingLockedをTrueにするだけだそうです。
うん。
どうやってそれをするの?
Unreal Engine 4のAPIのbInitialBuildingLocked [10]を見てみるとUNavigationSystemV1クラスが保持する変数のようです。UNavigationSystemV1クラスを呼んでこれればそこからbInitialBuildingLockedの値をセット出来そうではあります。
この辺はUE4C++でガンガンCodeを書かなくなければならなくなった時にもっと調査する事にします。
<<Enable Multi-threaded Navigation Mesh Generation>>
これもUE4C++でガンガンCodeを書かなくなければならなくなった時に勉強し直します。
MaxSimultaneousTileGenerationJobsCountの値をセットする事でmulti-threaded によるNavigation Mesh の生成を管理出来るようになるそうです。
ただしFRecastNavMeshGenerator::Init()にある働いているThreadの数が限界になるそうです。
<<Use Dynamic Obstacles with Full Dynamic Navigation Mesh Generation>>
今度はDynamic Obstaclesを使用する方法です。
Dynamic Obstacles の項目は、
ずっと配置されているActorのDetail内を探していましたが、そこには無くて、Static Meshを開いてDetailのNavigationに行くとありました。
私が理解した範囲ではこのチェックがついていると、このMeshが配置されてた時にこのMeshを配置した回りだけNav Meshを再構築するみたいです。
<<Use Data Chunk Streaming for Static Navigation Meshes Loaded in Sublevels>>
これはSub LevelをLoad する時の場合だそうです。
タイムリーな話題です。
Navigation Meshの構成が変わるのがSub LevelをLoadする時だけならNavigation Mesh Generation MethodをStaticにセットしてNav Mesh Data Chunk Streamingを使用すると良いそうです。
これは分かったですがこの設定ってどこでするんでしょうか?
色々調べたんですが、どうやってこの設定をするのかの情報は見つからなかったです。
<まとめと感想>
Optimizing Navigation Mesh Generation Speed [8]はAdvancedなんで軽く読むだけにしました。
一応、専門用語とその機能は理解したはずです。
Sub LevelをLoadする時の最適化の具体的なやり方が分からなかったのはちょっと残念でした。
5.World CompositionによるMap1の作成(動的に生成するActorの追加など)
5.1 Monsterを動的に生成する
Monsterを動的に生成する方法ですが、先週、もっと良いアイデアが思い付くかもしれないと検証だけして一端中止しました。
それでですね。その事をすっかり忘れていました。
MaterialのTextureの回転については覚えていたんですが、こっちはすっかり忘れていました。
なので、先週言ったやり方で作成する事にします。
<Level 3_6用のMonster Spawn Dataの作成>
Game Instance BP内にStruct Monster Spawn Dataの配列であるMonster Spawn Data 3_6を作成します。
要素に、先週、静的に配置したMonsterのDataを移します。
取りあえずはこれで十分です。
<Monster の動的な生成>
Map 3_6のBP内にMonster発生用の実装を作成しました。
Map1のBP内で実装されているMonster発生用の実装と全く同じやり方です。
Map1のBPではSpawnした後に以下の実装がされていますが、生成したMonsterのDataが必要になる事はないので以下の部分は無駄な気がします。
後で調べます。要らない事が判明したら消します。
テストします。
Monsterが生成されていました。いきなり成功です。
<MonsterをUnload、Loadしてみる>
Unloadされてから戻って来ましたが、普通にLoadされています。
5.2 Monsterを倒したら発生しないようにする
先週決めたやり方でやってみます。
Game Instance BP内にName型の変数、Sub Level Nameを作成します。
ここにSub Levelの名前を保持します。
Level3_6内にTrigger Boxを配置します。
このTrigger Box内にplayerが操作するキャラが侵入したら先程作成したSub Level NameにLevel 3_6 がセットされるようにします。
今度は倒したMonsterが二度と生成されないようにMonster Spawn Dataを変更します。
Game Mode BP内の以下の実装部で、その作業を行っています。
このRemove Defeated Monster from Map 1内の実装を変更します。
以下のようなHelper関数を作成して
実装を以下の様に改造しました。
これで一度倒したMonsterは再生しないはずです。
テストします。
Monsterが消えません。
Helper関数内で参照にしているMonster Spawn Dataが前のままでした。
直しました。
これ前も同じ間違いをしてたのもついでに思い出しました。
後、この間違いを見つけるのに一時間位かかりました。
もうクタクタです。
今度は倒したMonsterが復活して発生する事はなくなりました。
5.3 複数のMonsterを発生させる
取りあえず、一体Monsterを静的に配置してみました。
勿論落ちないように板を下に配置しています。
発生はしますが徘徊していませんね。
理由が分かりました。
Nav Meshが効いていません。
隣のNav Mesh内に配置すると動きだします。
取りあえずの処置で、隣のNav Meshのサイズを大きくしてみました。
今度は普通に徘徊しています。
今度はこのMonsterを動的に発生させます。
先程作成したMonster Spawn Data 3_6にこのMonsterのdataを追加します。
テストしてみます。
おお徘徊しています。
5.4 Monsterの発生するタイミングを遅らせる
Playerの操作するキャラはMapが作成されてから3秒間、動けなくなっています。その間にMonsterに襲われるのはUnfairなので、Monsterが発生する時間を3秒後にします。
テストします。
Screen Shotは上手く取れなかったんですがMonsterの発生が少しだけ遅れるので戦闘から戻った後、ギリギリ逃げれる時間は取れました。
5.5 別のSub LevelにMonsterを発生させる
以下に示したようにMap1にはもう一つ別のNav Meshがあります。
今度はここにMonsterを発生させます。
試しに一体、静的にMonsterを配置させてみます。
テストします。
普通に徘徊して、近づいたら追いかけて来ました。
このMonsterが配置されているSub LevelはLevel 2_5です。
Game Instance BP内にこのSub Level内に発生するMonsterのDataを保持するための配列を作成します。名前はMonsterSpawnData2_5とします。
先程配置したMonsterと同じ位置のMonsterのDataを追加しました。
Level 2_5に以下の実装を追加しました。
テストします。
普通に発生しています。
今度は戦闘で倒した後は復活しない実装を追加します。
Trigger Boxを配置してから気が付きましたが、MonsterはこのSub Level内のみを徘徊する訳ではないです。例えば隣のSub Levelに移動する事もあります。
隣のSub Level内でこのMonsterと戦闘になったらこのMonsterを倒しても消滅しなくなります。
ちょっと考える必要が出て来ました。
以下に示したようにNav Meshを分けてみました。
これで試してみます。
普通にMonsterは動いていますね。
こんな感じです。
以下のようにNav Meshを配置してみました。
試してみます。
4体のMonsterが見えますね。最後の一体はまだ生成されていないのかもしれません。
裏から回って確認してみます。
あれ?一体もいなくなってしまった?
最後の一体だけ見れないのでそれ以外を消して正面から確認しました。
Monster動いていました。
このやり方でMonsterを動的に生成する事にします。
先の動的に生成したMonsterが戦闘で倒されたら二度と復活しないようにします。
前のLevel3_6でやったのと同じやり方でやりました。
それで出来たんですが、
その後からLevel2_5をSaveしてもSave出来てない印が表示されるようになってしまいました。
色々試してみたんですが、Saveは出来るんです。しかしこのSave出来てない印が常に表示されるようになってしまっています。
結論から言うと原因は分からないです。
今週はここまでで一端中止します。
6.UE5におけるWorld Partitionの勉強の続き
今週は自分でWorld Partitionを使用しながらLandscapeの作成でもしようと思ったのですが、さっきのSub LevelがSaveしてもSave出来てない表示のままになるバグが直せないので何か心が折れてしまいました。
それで今回は簡単な内容に変更します。
World PartitionのTutorialを見て感想をまとめる回にします。
6.1 Open Worlds with World Partition | Exploring Unreal Engine 5 [11] を見る
公式のDocumentからOne File Per ActorとHierarchical Level of Detailの解説がありました。
One File Per ActorはLevel上に配置されたActorの情報を別なFileで管理する事で複数の人が同時にLevel Designを行える機能位の理解しかなかったですが、このTutorialを軽く見てる限りでは当たらずとも遠からずだと思いまいした。
Hierarchical Level Of Detailはどんな機能なのか全くわかってなかったですが、このTutorialの解説を聞いた限りでは、山など遠くにあってもUnloadすべきでないものに対して、Leve of Detailを下げて表示する機能みたいです。
これは大変興味深いので後で自分でDocumentを読む事にします。
6.2 World Partition - Hierarchical Level of Detail [12] を読む
最初から遠くにあるActorでもUnloadしてほしくないものがある。と書かれていますね。先程の解釈で間違いないですね。
Static Mesh Actorsに対しての機能みたいですね。Landscapeで作成した山とかは関係ないんでしょうか?
思いっきり書かれていました。LandscapeやWater Componentは今の所対応していません。と
<Creating HLOD Layers>
これ先程のTutorialを見てたらcmdからやらないといけないみたいに言っていたので、今回は見るだけにします。
ぱっと見たらこの後の節でHLOD Layersを実際に使用する時にcmdから指令をしないといけないみたいで、ここではCmdは使用してませんでした。
なのでここは自分でもやってみる事にしました。
中を開くとこんな感じになっていました。
公式のDocumentにそれぞれの項目の解説が載っていますね。
この項目はないですね。
Cell SizeとLoading RangeはAlways Loadedがチェックされている時は効かないそうです。
試しにAlways Loadedにチェックを入れてみました。
Cell SizeとLoading RangeだけじゃなくてParent Layerも消えました。
Cell SizeとLoading RangeはHLOD用のActorのための設定のようです。
と言う事はこのCell SizeとLoading Rangeは必ず元のCell SizeとLoading Rangeより大きくする必要があるって事でしょうか。
後、どんな遠くに行っても消えない訳じゃないって事ですね。あ。絶対消えてほしくないならAlways Loadedにチェックすれば良いと言う事か。
納得しました。
<<Choosing a Layer Type>>
Layer Typeについての解説です。
<<<Instancing>>>
Instanced Static MeshってGraphic Card内で再生するやつじゃなかったじゃないでしょうか。
<<<Merged MeshとSimplified Mesh>>>
この二つはsingle proxy meshに変換すると解説されていますが、single proxy meshが何を指しているのかが分かりません。UE5の新しい機能なんでしょうか。
<<Mesh Merge Settings>>
Mesh Merge Settingsを選択すると以下の項目が表示されます。
これらの項目について解説しています。
<<Proxy Settings>>
Simplified Meshを選択した場合に表示される項目です。
これらの解説をしています。
この辺はまずAlways LoadedでHLODを使いこなせるようになってから勉強する感じでしょうね。
<Using HLOD Layers>
はい。ここではcmdを使用するので今回は読むだけです。
<<Adding Actors>>
Static Meshを選択するとHLODの項目があるのでそこに先程作成したHLODをセットします。
Data Layerでコレコレをしろ。と書かれていますがData Layerって何でしょうか?
Windowを見るとありますね。表示させてみます。
ふむ。
何も表示されませんね。
これってひょっとしてHierarchical Level of Detailの前の節で解説しているData Layerを読まないといけないって奴でしょうか。
ここはスキップしておきます。
次はWorld SettingのHLODにセットしています。
ini fileで指定する事で同様の設定が出来るみたいです。
Ini fileについても勉強せねばと思いつつ何時も後回しにしています。
Configフォルダーを開いたら.iniファイルがこんなにあります。
この時点でどれに書くのか分かりません。
Ini fileについては後でまとめて勉強する事にします。
<<Generating HLODs>>
ここからcmdを使用します。
なのでここは読むだけにします。
ぱらっと読んだですが、どうやらWorldPartitionHLODsBuilderを使用してHLODsを生成するらしいですが、現状ではWorldPartitionHLODsBuilderはcmdからしか実行出来ないみたいです。ので現状ではcmdを使用する必要がある訳ですね。
WorldPartitionHLODsBuilder を使用するためにまずUE4Editor.exeのあるFolderに移動します。
私の場合は以下のFolderにありました。
Cmdに以下のコードを打ち込みます。
YourProject YourMap -run=WorldPartitionBuilderCommandlet -AllowCommandletRendering -Builder=WorldPartitionHLODsBuilder
これってProjectは同じFolder内に無くてもいけるんでしょうか?
勿論、自分で試してる気はありません。
まあ、この辺は後でCmdを使用しなくても出来るようになるはずですのでその時まで待つだけです。
<Visualizing HLODs>
HLODの可視化の仕方についてです。
<<In the Editor>>
EditorではView Modeから選択しろと書かれているので
これの事でしょうか?
色が変わって見えるみたいですが、HLODを作成していないので実際に確認する事は出来ません。
<<At Runtime>>
wp.Runtime.ToggleDrawRuntimeHash2Dで見れるそうです。
以上です。
7.まとめと感想
今週は、結構上手く行っていたんですが、World CompositionでSaveしてもSave出来ていない表示になってしまうバグを一個作ってしましました。
このバグが何をしても直せません。
正直ショックです。
今まで、後から直せないのは理解が足りない証拠と信じて頑なにGit Hubの使用を避けていました。がこれからは結構真剣に検討したいと思います。
だた、このRPGは出来れば今年中に完成させて、来年はUE5で別なGameの作成を開始したいと思っていますのであんまりWorld Compositionだけに拘るのもアレかなと思ったりもしています。
NiagaraにしてもVFXは結局Materialに依存しています。Materialを極める必要があります。そのために今回Materialの勉強でtexcoord[]の機能を完全に理解出来たのは大きかったと思います。
AIはやっとNav Meshの勉強が終わりました。結構知らない事ばかりでした。
World Partitionの勉強をするとUE5についてもっと知りたくなります。Naniteなんかも勉強したくなって来ました。
8.参照(Reference)
[1] UnrealCG. (2021, October 14). Time Distortion Effect - UE4 Tutorial [Video]. YouTube. https://www.youtube.com/watch?v=3ejuQVasl7w
[2] Epic Games. (n.d.-d). Utility Expressions. Unreal Engine Documentation. Retrieved November 7, 2021, from https://docs.unrealengine.com/4.26/en-US/RenderingAndGraphics/Materials/ExpressionReference/Utility/
[3] CGHOW. (2021, October 11). Twisted Ribbon in UE4.27 Niagara Tutorial | Download Files [Video]. YouTube. https://www.youtube.com/watch?v=RvTQup0Jxf4
[4] Cloward, B. [Ben Cloward]. (2021, October 28). Power - Shader Graph Basics - Episode 20 [Video]. YouTube. https://www.youtube.com/watch?v=1EiEjynmgIY&list=PL78XDi0TS4lEBWa2Hpzg2SRC5njCcKydl&index=20
[5] desmos. (n.d.). Desmos | Graphing Calculator. Retrieved November 7, 2021, from https://www.desmos.com/calculator
[6] Davidović, D. (2014, April 1). Gamma Correction and Why It Matters. Game Development Envato Tuts+. Retrieved November 7, 2021, from https://gamedevelopment.tutsplus.com/articles/gamma-correction-and-why-it-matters--gamedev-14466
[7] Epic Games. (n.d.-c). Using Navigation Invokers. Unreal Engine Documentation. Retrieved November 7, 2021, from https://docs.unrealengine.com/4.27/en-US/InteractiveExperiences/ArtificialIntelligence/NavigationSystem/UsingNavigationInvokers/
[8] Epic Games. (n.d.-b). Optimizing Navigation Mesh Generation Speed. Unreal Engine Documentation. Retrieved November 7, 2021, from https://docs.unrealengine.com/4.27/en-US/InteractiveExperiences/ArtificialIntelligence/NavigationSystem/OptimizingNavigationMeshGenerationSpeed/
[9] Wadstein, M. [Mathew Wadstein ]. (2016, August 3). Mathew Wadstein [Video]. YouTube. https://www.youtube.com/channel/UCOVfF7PfLbRdVEm0hONTrNQ
[10] Epic Games. (n.d.-a). bInitialBuildingLocked. Unreal Engine Documentation. Retrieved November 7, 2021, from https://docs.unrealengine.com/4.27/en-US/API/Runtime/NavigationSystem/UNavigationSystemV1/bInitialBuildingLocked/
[11] Polygon Hive. (2021, June 9). Open Worlds with World Partition | Exploring Unreal Engine 5 [Video]. YouTube. https://www.youtube.com/watch?v=4z9Ea7eCxt4
[12] Epic Games. (n.d.-e). World Partition - Hierarchical Level of Detail. Unreal Engine Documentation. Retrieved November 7, 2021, from https://docs.unrealengine.com/5.0/en-US/WorldFeatures/WorldPartition/HLOD/