女子高生になりたい

はるみちゃんのブログだよ。えへへ。

【その他いろいろな設定】HDRPで絵作りに挑戦していく【Part 3】

前回の続きです。

sakataharumi.hatenablog.jp

4 Ambient Occlusion

Ambient Occlusion(環境遮蔽)を利用して、細かい部分の陰影をつけていきます。

f:id:sakata_harumi:20210214155916p:plain
AO適用前

AO適用前の状態では、例えば、手前の三段重ねの発泡スチロールの横の部分など本来光が遮られていそうな場所でも光があたってしまっている事がわかります。

docs.unity3d.com

ボリュームの設定から、AOを追加し、Intensityなどを調整しました。 f:id:sakata_harumi:20210214155451p:plain

f:id:sakata_harumi:20210214155502p:plain
AO適用後

細かい影がくっきりついて、より立体感のある表現になりました。

5 Anti-aliasing

Anti-aliasingを設定してきます。

docs.unity3d.com

AAにはいくつか種類がありますが、今回は Temporal anti-aliasing (TAA) を利用します。

f:id:sakata_harumi:20210214161755p:plain

Temporal anti-aliasing (TAA)
他のAA手法では、現在のフレームのみを元にして計算されますが、TAAでは過去のフレームの情報も利用してエッジをなめらかにします。

MSAAと比較しても動きのあるエッジのスムージングに優れているようです。 ただし、Motion Vectorの設定がなされていないとオブジェクトが速く動いた場合などでアーティファクトが発生してしまうようなので、注意が必要です。

f:id:sakata_harumi:20210214164150p:plain

f:id:sakata_harumi:20210214161706p:plain
TAA適用後

6 Reflection Probe

Reflection Probeを設定し、鏡面反射成分の反映をしていきたいと思います。

1つのReflection Probeでシーン全体をカバーすることもできますが、そうすると本来反射するべきでないものも反射されてしまう・されるべきものが反映されないという事になります。

例えば、こちらはシーン全体で1つのRPを設置(中央)したときのあるお店の壁です。
映り込みがほぼありません。

f:id:sakata_harumi:20210214171827p:plain

一方、シーン全体で5つほどのRPを並べたときの同じお店の壁です。
こちらだといい感じに反射しているように見えます。

f:id:sakata_harumi:20210214171952p:plain

この辺りはよき匙加減でやる必要がありそうです。

f:id:sakata_harumi:20210214175350p:plain
RP適用後

7 Fog

軽くFogを追加していきます。

f:id:sakata_harumi:20210214183547p:plain

明るいシーンであれば、Volumetric Fogを利用することでライトシャフトを表現できたりとビジュアルが向上するんですが、今回は暗いシーンなのであまり関係はありません。

f:id:sakata_harumi:20210214183559p:plain
Fog追加後

8 Post Processing

最後にPost Processをかけて全体的な見た目を調整していきます。

8.1 Bloom

光がにじみ出ているような表現をするフィルターです。
とりあえずこれを入れて置いたらそれっぽくなります。

f:id:sakata_harumi:20210214190010p:plain

f:id:sakata_harumi:20210214190018p:plain

8.2 Depth of Field

被写界深度の設定です。これを設定することで、カメラのピントがあってない部分がぼやけるような表現が可能になります。

DoFの設定には大きくManualとUse Physical Cameraの二種類があります。
Manualでは焦点のあうRangeを手動で決定できますが、ちゃんと調整しないと違和感の強いものになってしまいます。

そこで、今回はUse Physical Cameraモードを用います。こちらはCameraの物理的な設定値を利用することでDoFをコントロールすることができます。

Cameraの設定からAperture(絞り)をコントロールすることで、ボケの効果を調節できます。
値が大きいほど(左にスライド)、全体的にボケるようになります。

さらにその下、Apertune ShapeのBlade CountとCurvatureでボケの形状の調節ができます。

f:id:sakata_harumi:20210214192906p:plain
Post processの設定
f:id:sakata_harumi:20210214192932p:plain
Cameraの設定

f:id:sakata_harumi:20210214192944p:plain
DoF適用後

結果

(before) f:id:sakata_harumi:20210215013756p:plain

(after) f:id:sakata_harumi:20210215013738p:plain

www.youtube.com

大分良い感じになりました('ω')

次は番外編として、キャラクターを出してプレイアブルなフィールドにしてみたいと思います。

【光りを放つものを作っていく】HDRPで絵作りに挑戦していく【Part 2】

前回の続きです。

sakataharumi.hatenablog.jp

3. 光りを放つものを作っていく

今回のシーンでは、光を放つものが3つあります。

1つが既に発光している店の扉。もう1つが提灯。最後が店の看板です。

今回は、この3つについて設定をしていきたいと思います。

3.1 店の扉

店の中から発される光を設定していきます。

本当は扉の裏に光源を設置し、扉がその光を透通すような表現をするのが正しいのかもしれないんですけど、今回は扉自体が光を放出しているとして、Emissionの設定を行っていきます。

Standard ShaderのDocumentを見てみると

エミッシブマテリアルは、通常、内側から照らされているように見えるゲームオブジェクトに使用されます。例えば、暗闇の中で見えるモニターの画面、高速時にブレーキをかけている車のディスクブレーキ、コントロールパネルの光るボタン、モンスターの目などです。

とあるので、今回のユースケースにも適していそうです。

docs.unity3d.com

一方、物理的に正しそうな表現(物体がその背後にある光源の光を透過させる)もLit Shaderでは可能です。
この後着手していく提灯は、そのように実装していきたいと思います。

Lit ShaderでEmissionの設定

Lit ShaderではEmission inputsの項目でEmissionの設定を行います。

f:id:sakata_harumi:20210210001454p:plain
ドアのLit Shader設定

Use Emission Intensityにチェックを入れると、Emissiveカラーと発光強度を別々に設定することができます。今回は利用していませんが、一通り完成後の調整で使うかもしれません。

Baked Emissionは、GIを利用する場合はチェックを入れないといけません。現状Realtime GIには対応していないようです(Unity 2021系では復活するのかな?)

以下、チェックを入れてないもの/入れたものの比較です。(Bake済み)

f:id:sakata_harumi:20210210000705p:plain
非 Baked Emission
f:id:sakata_harumi:20210210000716p:plain
Baked Emission

Baked Emissionにチェックが入っていない方では、GIが計算されないため段ボール、地面などに本来当たるはずの間接光が反映されていないことがわかります。

Bakedの方では、段ボールはもちろん、地面もうっすら照らされていることがわかります。

ここまで

ちょっと色味と強度を修正してベイクしてみました。 手前の段ボールなどにEmissiveカラーの間接光が反映されていることがわかります。

f:id:sakata_harumi:20210210010752p:plain

3.2 提灯

提灯は内部に光源となる電球が存在して、そこが発光し内部で光が散乱した後に外部へ出ていきます。

このような表現のメカニズムをSubsurface Scatteringといいます。Lit Shaderではこの表現が可能です。

このSubsurface Scatteringでよく例にあげられるのは「人間の肌」です。

たとえば、人間の肌の質感などがよい例である。人間の肌の表面は実は半透明であり、光は完全には反射されず、一度肌の内部に入り、内部において何度も何度も反射と散乱を繰り返した後に、外に出る。そこで出てきた光を見て、人間は色を感じている。(wikipediaから引用)

ja.wikipedia.org

Subsurface Scatteringの前にEmission

比較用に、Material TypeはStandardの状態で、光源を置くのではなくドア同様Emissionを設定してみます。

f:id:sakata_harumi:20210212140336p:plainf:id:sakata_harumi:20210212140344p:plainf:id:sakata_harumi:20210212140354p:plain

充分いい感じに見えますね。。

Subsurface Scattering

まずは、Lit ShaderのMaterial TypeをSSではなくStandardにした状態のまま内部に光源(Point Light)を設置してみます (Emissionの設定は元に戻しておきます)

内部に光源を配置するとき、Lit ShaderのDouble Sidedにチェックをつけておきます。 こうすることで、内部に配置した光源の光が外に漏れる事がなくなります。

f:id:sakata_harumi:20210212114019p:plain

f:id:sakata_harumi:20210212113927p:plain

Surface TypeがOpaque(不透明)のため、内部に光源を配置しても光が一切提灯の中から漏れることがありません。

ここでMaterial TypeをStandard→Subsurface Scatteringに変更します。

すると、内部の光源がいい感じに放出されているような見た目になります。

f:id:sakata_harumi:20210212171232p:plain

ただし、Diffusion Profileが設定されていないので緑色になってしまっています。 新しくDiffusion Profileを作成し、設定していきたいと思います。

・・の前に、少し逸れてしまいますがまずは内部の電球のパラメータを調整していきましょう。

Point Light

提灯の内部にPoint Lightを生成しておきます。 ググると、提灯用LED電球を発見しましたので、これが中に入っているということにして数値を設定していきます。

www.monotaro.com

Radius
光源の半径です。今回は直径45mmの電球とのことなので半径は22.5mm。メートルに換算して0.0255mとなります。

Temperature
色温度。スペックに書いてある通り2200Kelvinを設定します

Intensity
こちらもスペックに書いてある通り340Lumenを設定します。

Range
光の最大到達距離を設定します。
この記事曰く、光源から2mほど離れると照度はほぼ0になるらしいので、Rangeは2に設定してみました。

xtech.nikkei.com

f:id:sakata_harumi:20210212201744p:plain

Diffusion Profile

Diffusion Profileは Create → Rendering → Diffusion Profileから作成できます。 作成後、使用するためにはVolumeのDiffusion Profile Overrideに追加したProfileを登録しておく必要があります。

f:id:sakata_harumi:20210212171902p:plain

Parameters

Scattering Distance
ここをコントロールしてDiffusion Profileの形、ぼかしなどを設定します。 物理的に正しい値をどう設定するのか・・など詳しい事はわかりませんでした。 とりあえず見た目で良い感じになるように設定しています。

Index of Refraction
値を大きくすると鏡面反射の強度が上がります。 今回は光源が内部にあるので関係なさそうです。

Subsurface Scattering only
Post-scatterだとsubsurface scattering適用後にマテリアルにAlbedoを適用します。よって、テクスチャがくっきり表示されます。
Pre and post scatterでは、名前の通りAlbedo適用前後でsubsurface scatteringを適用します。よって、テクスチャがぼやけソフトな感じになります。

Transmission only Parameters
以下はthicknessの利用がONになっているときのみ有効なパラメータです。

今回はONですが、tchikness mapがないのでmaterial全体で均一の厚さ(1.0)の設定にしています。

f:id:sakata_harumi:20210212182741p:plain

Transmission Mode

厚い素材の場合はThick Objectに設定するよう。すると、シャドウマップを利用してSSの計算をするらしい・・ 今回は薄い素材なのでThin Objectに設定しました。

Thickness Remap Values
素材の厚さのmin-maxをmm単位で指定します。 thickness mapの情報を元に0.0~1.0の厚さが設定されているはずなので、そこにThickness Remap Valuesが乗算された値が計算上の厚さになります。

今回は均一でthicknessが1.0のためmaxのみ利用されることになります。
厚めの和紙がだいたい0.3mmくらいみたいなので、そのように設定してみました。

f:id:sakata_harumi:20210212202050p:plain

とりあえずここまでで、提灯がいい感じに発行するようになりました。
Emissionと比べて、やわらかい感じが出ている気がします。

f:id:sakata_harumi:20210212202814p:plain

3.3 看板

看板は普通にEmissionを設定していこうと思います

結果(1)

全ての提灯、看板にライトの設定を入れました。

結果がこちら。

f:id:sakata_harumi:20210213192815p:plain

一部光っていない提灯があるので、原因を探っていきます。

Sceneと表示モードをLight Overlapにします。
この中で、赤い電球マークになっているものがあるのがわかります。

f:id:sakata_harumi:20210213192912p:plain

Shadow maskテクスチャでは、最大4つの重なり合うライト情報までしか格納できません。そのため、それ以上のライトについてはBakeされます。

Light Overlapモードで表示される赤い電球マークは、Bakeされる光源を意味しています。

Bakeされた光源については、リアルタイムで処理されないため光が内部で拡散せず、提灯が光っていないのだと推測してます。

じゃあ全部Realtime Lightにすれば良いのでは?と思うんですが、それはそれでshadow mapが使われたり使われなかったりするせいか狂います

www.youtube.com

といわけで 諦めます v(゚∀。)v

結局、提灯にもEmissionの設定をすることにしました。

結果(2)

f:id:sakata_harumi:20210213215705p:plain

紆余曲折ありましたが、結局Emissionの設定だけに落ち着きました・・

次回はambient occlusionやpost processの設定などやっていきます。

【準備~環境光、ボリュームの初期設定まで】HDRPで絵作りに挑戦していく【Part 1】

今回から何回かにわたり、HDRPを利用したシーンのライティングに挑戦していきたいと思います。

自分もHDRP素人というか、そもそもライティングからよくわからない人間なので、勉強したことの備忘録という感じです。

間違っている箇所もあるかと思いますので、ご了承下さい。

1. アセットとシーンを準備

1.1 アセットのインポートとマテリアル変換

今回利用したアセットはこちら。 https://assetstore.unity.com/packages/3d/environments/urban/japanese-alley-162907

https://www.youtube.com/watch?v=7IWHoTkw_sQ&feature=emb_title

このアセットをインポートすると、Materialsが真っピンクになっていると思います

f:id:sakata_harumi:20210207235931p:plain

これは、ShaderがHDRPに対応していないために起こってます。

HDRPは自動でbuilt-in shaderをHDRP shaderにコンバートしてくれる機能が存在していますので、そちらを利用します。

※ build-in shader以外が使われている場合は手動でHDRP shaderに変換をしないといけないようです。今回利用するアセットは問題ありませんでした

"Render Pipeline Wizard" → "Upgrade Project Materials To High Definition Materials" もしくは "Edit" → "Render Pipeline" → "HD Render Pipeline" → "Upgrade from build in pipeline" → "Upgrade Project Materials To High Definition Materials" からコンバートが可能です

f:id:sakata_harumi:20210208000917p:plain

コンバートすると、概ね正常にプレビューが表示されるようになりました。

f:id:sakata_harumi:20210208001004p:plain

一部、正常にプレビューが読み込めていないものがあります。

こちらは、ピンク色になっているPrefabを選択し、Resetを選択することで解消されるようです。

Resetは赤線の部分から選択できます。

f:id:sakata_harumi:20210208001401p:plain

これで、アセットが利用可能になりました。

1.2 シーンにオブジェクトを設置していく

インポートしたアセットを並べて、シーンを作っていきます。

ここで、電球マークのチェックを外しておいてシーンのライティングが無効になった状態で作業を進めていくと見やすくて良いです。

f:id:sakata_harumi:20210208001812p:plain

いい感じにオブジェクトを置いてみました。 f:id:sakata_harumi:20210208002104p:plain

f:id:sakata_harumi:20210208002031p:plain

これで、準備は完了です。

2. Directional Lightと基本的なVolumeを設定

2.1 Directional Light

今回は夜のシーンを作ろうと思うので、Directional Lightもそれに合わせて設定します。

色温度は4500 kelvin、Intensityは0.5 Luxあたりに設定しました。 (※後で変更することになります)

f:id:sakata_harumi:20210208003530p:plain

こちらの値はUnityの公式ブログのチートシートを参考にできます。

f:id:sakata_harumi:20210209100632p:plain
(引用: https://blogs.unity3d.com/jp/2021/01/07/explore-learn-and-create-with-the-new-hdrp-scene-template/

他にも一般的なライトのIntensityやExposureの値(重要)なども参考にできるので、覚えるまではとても重宝します。。

blogs.unity3d.com

この通り、HDRPは現実世界の値をそのまま反映させることを念頭に実装されていますので、例えば現実世界の電球のパッケージに書かれている値などを参考にする・・等々もしていくと良さそうです。

LightのModeの違い

Lightには "Baked" "Mixed" "Realtime" が存在します。

Baked
ライトからの直接光と間接光をライトマップにベイクします。 Baked Global IlluminationがONのときのみ利用可能。

Global Illuminationとは、直接光だけでなく間接光も計算して表現しようとするレンダリング手法です。

GIにより、例えば直接光が赤色のオブジェクトにあたり、その光が反射して赤色の光を返す・・のような表現が可能になります。

docs.unity3d.com

Bakedモードでは当然ですが、ベイクされるのはstaticオブジェクトのみになります。

非staticなオブジェクトでBakedモードのライトに対してGIを表現しようとするにはライトプローブを利用します。

docs.unity3d.com

Realtime
直接光とシャドウはリアルタイムで処理されます。間接光はリアルタイムGIを有効にしたときのみ反映されます。

※ リアルタイムGIは以前、Enlighten Lightmapperで利用可能でしたが2020.2現在、非推奨であり新規で利用することはできません。Unity 2021.1に代替ソリューションを提供予定・・とのことです

blogs.unity3d.com

Mixed
Bakeするものとリアルタイムに計算するものを混合させることができます。 さらに以下の3つのMixed LightingモードがあってLigthing Settingから選択できます(HDRPではSubtractiveは選択できないっぽい?)

Subtractive
Baked Indirect
Shadowmask

詳細は割愛しますが、ざっくり理解はこんな感じです。

Substractive
Directional Light以外は直接光もBakeする。なので、非staticなオブジェクトからリアルタイムで影を落とせるのはDirectional Lightのものだけ、スペキュラの表現も不可能。
非staticなオブジェクトはライトプローブを利用してstaticオブジェクトからの影を受けることができる。

Baked Indirect
間接光のみを事前計算し、影の事前計算は行わず、Shadow Distanceの範囲でリアルタイムで描画。超えると影は作られない。

Shadowmask Mode: Shadowmask
間接光はベイク、影は事前計算されたシャドウマスクテクスチャを元に表示する。

Shadowmask Mode: Distance Shadowmask
間接光はベイク、影はShadow Distanceの範囲内はリアルタイム、それを超えたものはシャドウマスクテクスチャを利用して描画。

BuildRenderPipeline(以前のレンダーパイプライン)では、Shadowmaskのモードが2つに分かれていましたが、HDRPではShadowmaskモードでさえあれば個々のライトに応じてどちらを利用するか選択ができます。

今回は、Lighting Modeはshadowmask、Directional LightにはDistance Shadow Maskモードを設定しています。

f:id:sakata_harumi:20210208225906p:plain

f:id:sakata_harumi:20210208225923p:plain

2.2 Volumeの初期設定

Volumeはデフォルトのボリュームから必要な値だけオーバーライドする形になります。 ただ、今回はオーバーライドしてる、してないの認識統一が面倒なので、デフォルトのボリュームにこのシーンで使用するために新しく作成したボリュームを設定しておきます。

f:id:sakata_harumi:20210209011206p:plain

細かい調整は後でするとして、最低限SkyboxとExposure(露出)の設定を行います。

f:id:sakata_harumi:20210209014928p:plain

Mode
ModeではGlobal/Localが存在します。Localでは1シーン上にボリュームの設定を変えたい場合(例えば、外の部分と部屋の中の部分があるなど)に使用します。
今回は、外のみなのでシーン全体に適用されるようGlobalに設定しています。

Intensity(HDRI Sky)
先ほどのチートシートにも書かれてる通り、真夜中の環境光は0.001Lux程度とのことでした。 なので、Directional LightとSkyBoxの値を足して0.001Lux程度になるように設定します。
今回はDirectional LightのIntensityを0.0008Lux、Sky BoxのIntensityを0.0002Luxに設定してみました。

明るい昼間の外のシーンでは、太陽光:空=5:1くらいの割合になるようです。

Exposure
Volumeで最も需要なパラメータがExposure(露出)です。

カメラのレンズに入ってくる光の量は昼間の外だと12万Lux、今回のような真夜中のシーンだと0.0001Luxと大きな差があります。 そこで、撮影素子に当たる光の量をいい感じに調整するために露出のコントロールが必要になります。

現実の世界のカメラでは、シャッタースピードと絞り、ISO感度の3要素から適正露出を求めますが、Unityでは(三要素から決定することも可能ですが)露出値をダイレクトに入力できます。

ざっくりとEV値が低いほど明るい絵、高いほど暗い絵になります。

適正露出は先ほどのチートシートが参考になります。

深夜では-2程度が適正とのことでしたので、そのように設定してみました。

Mode Fixedはシーン全体で固定の露出値を使う設定です。 今のところ固定視点のシーンなのでこれで問題ないですが、本来であれば明るいところに視点があるとき、暗いところに視点がある時で露出値を調整しないといけません。

そこで、ExposureのModeにはAutomaticをはじめとする幾つかのモードがあります。こちらは余裕があれば次回以降の記事で見ていければと思います。

ここまでの結果

f:id:sakata_harumi:20210209020002p:plain

まだ暗すぎな感じがしますね。

このシーンには現状Directional Light以外のLightは存在しません。

ドアが光っているのはLitShaderのEmission Inputsが設定されているためです。

次回は主にLit Shaderの設定をみつつ、提灯に明かりを灯す作業をしていこうと思います。

stable baselines3+imitationで模倣学習(BC&AIRL)

stable baselines3では、模倣学習のfeatureがimitationというライブラリに移譲されることになりました。

stable-baselines3.readthedocs.io

github.com

これにより、(過渡期である事も要因であるとは思いますが)以前は非常に簡単にできていた模倣学習に一手間必要になりました。

そこで今回は、stable baselines3とimitationを使った模倣学習の実行について備忘録を残しておきたいと思います(これを書いている時点では、ドキュメントがほぼなかったので・・)


エキスパートデータの収集

言わずもかな逆強化学習ではまずエキスパートデータを用意する必要があります。 今回はatariのインベーダー環境に対して、人手で操作してエキスパートデータを生成してみたいと思います

stable baselinesの2系ではstable_baselines.gail.generate_expert_trajを使うことで簡単に軌跡の記録ができましたが、3系ではimitationのTrajectoryデータ型として記録していきます

imitation/types.py at bb363a6c1039f29d4670647734fd21de9aa2e85d · HumanCompatibleAI/imitation · GitHub

1つのエピソードに対して1つのTrajectoryオブジェクトとなり、複数エピソードではTrajectoryのArrayとして保存することになります。

以下は、実装例です

from imitation.data.types import Trajectory
from stable_baselines3.common.atari_wrappers import *
import gym
import pyglet
from pyglet.window import key
import time
import pickle

def get_key_state(win, key_handler):
    key_state = set()
    win.dispatch_events()
    for key_code, pressed in key_handler.items():
        if pressed:
            key_state.add(key_code)
    return key_state

def human_expert(_state, win, key_handler):
    key_state = get_key_state(win, key_handler)
    action = 0
    if key.SPACE in key_state:
        action = 1
    elif key.LEFT in key_state:
        action = 3
    elif key.RIGHT in key_state:
        action = 4
    time.sleep(1.0 / 30.0)
    return action

def main():
    record_episodes = 1
    ENV_ID = 'SpaceInvaders-v0'
    env = gym.make(ENV_ID)
    env.render()

    win = pyglet.window.Window(width=300, height=100, vsync=False)
    key_handler = pyglet.window.key.KeyStateHandler()
    win.push_handlers(key_handler)
    pyglet.app.platform_event_loop.start()
    while len(get_key_state(win, key_handler)) == 0:
        time.sleep(1.0 / 30.0)
    
    trajectorys = []
    for i in range(0, record_episodes):
        state = env.reset()
        actions = []
        infos = []
        observations = [state]
        while True:
            env.render()
            action = human_expert(state, win, key_handler)
            state, reward, done, info = env.step(action)
            actions.append(action)
            observations.append(state)
            infos.append(info)
            if done:
                ts = Trajectory(obs=np.array(observations), acts=np.array(actions), infos=np.array(infos))
                trajectorys.append(ts)
                break
    with open("invader_expert.pickle", mode="wb") as f:
        pickle.dump(trajectorys, f)
if __name__ == '__main__':
    main()

キー入力を受け取る部分の実装はnpakaさんの以下の記事を参考にさせて頂きました

note.com

インベーダーが下手すぎてエキスパートとは程遠いデータを収集することができました。

BCによる学習

こちらはimitationのquickstartと同様です。

https://github.com/HumanCompatibleAI/imitation/blob/master/examples/quickstart.py

with open("invader_expert.pickle", "rb") as f:
    trajectories = pickle.load(f)
transitions = rollout.flatten_trajectories(trajectories)

ENV_ID = 'SpaceInvaders-v0'
venv = util.make_vec_env(ENV_ID, n_envs=2)
logger.configure(".BC/")
bc_trainer = bc.BC(venv.observation_space, venv.action_space, expert_data=transitions)
bc_trainer.train(n_epochs=100)
bc_trainer.save_policy('space_invader_policy_v0')

保存したpolicyは以下のようにロードができます

bc_trainer = bc.reconstruct_policy("space_invader_policy_v0")

BC→順強化学習とモデルの実行

まず、BCの学習により得たポリシーを使ってインベーダーをプレイさせてみます。 こちらはポリシーのpredictを使って簡単に実行が可能です。

def main():
    ENV_ID = 'SpaceInvaders-v0'
    env = gym.make(ENV_ID)
    bc_trainer = bc.reconstruct_policy("space_invader_policy_v0")
    state = env.reset()
    while True:
        env.render()
        action = bc_trainer.predict(state)
        state, reward, done, info = env.step(action)
        if done:
            break
if __name__ == '__main__':
    main()

次に、このポリシーをベースに順強化学習をさせることも可能です。

エキスパートのデータを渡して、初期の探索を手助けしてあげた後、順強化学習による最適化を進めたい・・というのが直感的なニーズですが、良いのか悪いのかは正直よくわかりません。

こちらは、単純に強化学習モデルクラスの第一引数であるPolicyに先ほどのものを入れて初期化→再学習すればよさそうなんですが、素直に入れると動きません。

そこで、ハック的なテクニックですが以下のようにする必要があります。

class CopyPolicy(ActorCriticPolicy):
    def __new__(cls, *args, **kwargs):
        return bc_trainer.policy

model = sb3.PPO(CopyPolicy, venv, verbose=0)
model.learn(total_timesteps=128000, callback=callback)

このアプローチは以下のissueで言及されています

github.com

AIRLによる学習

こちらも、imitationのquickstartに記載されている通りで大丈夫です

https://github.com/HumanCompatibleAI/imitation/blob/master/examples/quickstart.py

最後に.gen_algo.saveでPPOモデルを保存します。 ゲームの実行にはこのモデルが必要になります。

venv = util.make_vec_env(ENV_ID, n_envs=2)
logger.configure(".AIRL/")
airl_trainer = adversarial.AIRL(
    venv,
    expert_data=transitions,
    expert_batch_size=32,
    gen_algo=sb3.PPO("MlpPolicy", venv, verbose=1, n_steps=1024),
)
airl_trainer.train(total_timesteps=2048)
airl_trainer.gen_algo.save("airl_trainer_gen_algo")

モデルの実行

PPO.loadで先ほど保存したモデルをロードしてきます。 後は特に違いはありません。

def main():
    ENV_ID = 'SpaceInvaders-v0'
    env = gym.make(ENV_ID)
    model = PPO.load("airl_trainer_gen_algo")

    state = env.reset()
    while True:
        env.render()
        action = model.predict(state)
        state, reward, done, info = env.step(action)
        if done:
            break
if __name__ == '__main__':
    main()

【TextMeshPro】入力文字列をTMP_InputField経由でなくTextMeshProUGUIを直接参照して取得してしまったばかりにゼロ幅スペースで苦しんだ記録

些細な話ですけど、ハマる人いると思うんで、備忘録として置いておきます

結論

TextMeshPro用のInputFieldであるTMP_InputFieldを使うときは、子GameObjectのTextMeshProUGUIを参照して入力された文字を取得してきてはいけない!ゼロ幅スペースが入ったりする。

正しくは、TMP_InputFieldのtextプロパティからとってくる。これだとゼロ幅スペースは入っていない。


以下、詳しい話

いきさつ

ZZ3Dというアプリ( https://apps.apple.com/jp/app/zz3d/id1475406292)にVRoidHub連携機能を導入しようとSDKを組み込んでいました。

連携には、認可コードをアプリ側から入力するフローがあるんですが、正しいはずの値を入力してもリジェクトされてしまう状況でした。

ひとしきり問題がありそうなところはデバッグしましたが、問題が見当たらず困っていました。

そこで、SDKとセットでついてきたExampleシーンを動かしたところ、こちらではうまく動いてしまいました。 つまり、向こうの認可サーバの問題やSDKのバグではないということがわかります。

となると、やっぱりこちらのソースコードが間違っているのかな・・?と思うわけなんですが、SDKの利用部分ではサンプル実装と全然違いがないんですね。困りました( ;∀;)

と、いうことがありまして、最後に、「ログに流れる文字列を見る限りでは認可コードは正しいように見えるけど、バイナリレベルで違っているんじゃないか?」ということを疑いました。

(結果、それが正解だったみたいです)

実際に確認

InputFieldに入力した文字列を取得してきたものと、正しい認可コードの文字列をバイナリエディタで比較してきました。

(InputField経由)
f:id:sakata_harumi:20201205233251p:plain (ただしいもの)
f:id:sakata_harumi:20201205233249p:plain

はい。「E2 80 8B」これが余計にくっついています( ;∀;) (0D 0Aは改行コードですが、これは2つの文字列を上下に並べたため挿入されたものですので関係ありません)

このE2 80 8B、どうやら「ゼロ幅スペース」というやつらしいです。

ja.wikipedia.org

くううううううううう!どこから生えてきたんだ!( ;∀;)

起きてしまった理由

結論にも書いたのですが「TMP_InputField経由でなくTextMeshProUGUIを直接参照して入力文字列を取得」したせいでした。

TMP_InputFieldのPrefabは以下ような構成になっています。

f:id:sakata_harumi:20201205235618p:plain

ここの「TextArea/Text」に入力された文字が入っている・・ように見えたので、ここから文字列を参照していました。

しかし、実際はそうではなく、ここには視覚化のためのテキストが入ってる、、とのことで実際に入力されたものとは差があるらしいです。

例えば、パスワード形式のInputFieldにした場合、ここには「***」という文字列が入ってくるらしいです。

forum.unity.com

そこで、入力された値を正しく取得するためにはTMP_InputFieldのtextプロパティから取得しないといけない、ということでした。

みなさんも気を付けてください( ;∀;)

ゲームを作った。一人は大変だ。だからディスコードサーバーをつくった。

ゲームを作ったよ('ω')

つくったゲームはこちら。

www.freem.ne.jp

ここ三か月くらい、平日の夜の時間などのスキマ時間でこつこつ作ってました。

クトゥルフっぽい世界観を目指した弾幕シューティングです。

30分くらいで終わるのでカジュアルに遊べます。

よかったら遊んでね!

一人で作りきるのは厳しい( ;∀;)

色々理由はありますが、「スキル不足で素材が作れない」「モチベーション維持が難しい」「かけられる時間の総量に限界がある」の三点が主でしょうか。

特にスキル不足が厳しい。 自分はUnity素人ですが、実装ならある程度何とでもなる・・気がしています。

問題が、素材まわり。

ほんとにセンスがないので、どうしよう・・という感じ。次はもっとがんばるぞ( ;∀;)

たまに一人で実装、デザイン、音楽すべてやってる人がいますがその人は超人です。超人になりたい・・。

なので、ディスコードコミュニティを作った(*ノωノ)

と、いうわけで、孤独なすべてのクリエイターのためのディスコードサーバーを作りました!

サーバーの目的(例)

  • 孤独なクリエイター同士で慣れ合う。進捗報告等してモチベをあげる。
  • 一緒にものづくりをする仲間を募集する
  • わからないところを質問、回答しあう
  • 通話をつないでもくもく作業をする
  • みんなで桃鉄する

同人サークルじゃないので、活動の強制はありません。まったりと自分のペースで創作に励むためのコミュニティです。

気が合う仲間がいれば、ぜひいっしょに何か作りましょう・・!

参加はディスボードから!

入ってください・・・。お願いします・・・。

disboard.org

それでも孤独に開発を続ける( ^)o(^ )

そもそも創作とは孤独なものなんだ!!!!!!!

引き続き、がんばっていきます( ;∀;)

レーダースキャンしてるっぽいシェーダーを作りたかった(失敗)

やりたいことができてないんだけど、未来への糧のために記録として残しておきます

今後は失敗の記録もしていく所存・・!

やりたかったこと

以前の記事で、遮蔽にキャラクターが隠れていても見えるようなレンダラを作成しました

sakataharumi.hatenablog.jp

ただ、上の記事の最後をみてもらうとわかるんですがOverrideしたマテリアルが微妙で、スキャンしてる感がありません・・。

そこで、スキャンしてる感があるシェーダーを作りたいな、と。

具体的には、「アウトラインが強調されて、アウトラインの中は適当な色で、ぐわんぐわんスキャンされてる感じ」をイメージしていました(語彙力がない)

できたもの(失敗)

youtu.be

スキャンしてるっぽい表現には成功していますが「アウトラインの表示」がうまくいってません。

ちなみに、シンプルなテクスチャであればよい感じにアウトラインだけが表示されて、スキャンされているっぽくなります。

youtu.be

試したアプローチ方法

今回作成したShaderGraphの全容を掲載します。

f:id:sakata_harumi:20201123203028p:plain

スキャンエフェクト部(赤の部分)とアウトライン抽出部(青の部分)から成ります。



スキャンエフェクト部の実装はシンプルで、Photoshopでグラデーションの画像を用意してUVアニメーションさせて、それをアルファにセットしてるだけです。

アウトライン抽出部ではCustomFunctionを利用しています。こちらを利用するとHLSLで書かれた関数を実行することができます。

docs.unity3d.com

今回は、微分フィルタによるエッジ抽出で、アウトラインを表示させるというアプローチを試しました。 微分フィルタには代表的なソーベルフィルタを採用しました。

ソーベルフィルタでは、縦用と横用で別のカーネルを用います。カーネルの具体的な値は以下のwikipediaを参照してみてください。

Sobel operator - Wikipedia

ソーベルフィルタの実装はこちらのブログを参考にさせて頂きました。

pond-comfat.hatenablog.com

今回実装したCustomFunction(sobel.hlsl)

#ifndef MYHLSLINCLUDE_INCLUDED
#define MYHLSLINCLUDE_INCLUDED

void sobel_float(float2 uv, Texture2D tex, SamplerState s, out float3 Out)
{
    float2 delta = float2(0.01, 0.01);

    float2 uv0 = uv + delta;
    float2 uv1 = uv + float2(0, -delta.y);
    float2 uv2 = uv + float2(delta.x, -delta.y);
    float2 uv3 = uv + float2(-delta.x, 0);
    float2 uv4 = uv + float2(0, 0);
    float2 uv5 = uv + float2(delta.x, 0);
    float2 uv6 = uv + float2(-delta.x, delta.y);
    float2 uv7 = uv + float2(0, delta.y);
    float2 uv8 = uv + float2(delta.x, delta.y);

    float3 col0 = tex.Sample(s, uv0);
    float3 col1 = tex.Sample(s, uv1);
    float3 col2 = tex.Sample(s, uv2);
    float3 col3 = tex.Sample(s, uv3);
    float3 col4 = tex.Sample(s, uv4);
    float3 col5 = tex.Sample(s, uv5);
    float3 col6 = tex.Sample(s, uv6);
    float3 col7 = tex.Sample(s, uv7);
    float3 col8 = tex.Sample(s, uv8);

    float cgx = col0 * -1 + col2 * 1 + col3 * -2 + col5 * 2 + col6 * -1 + col8 * 1;
    float cgy = col0 * -1 + col1 * -2 + col2 * -1 + col6 * 1 + col7 * 2 + col8 * 1;
    float cg = sqrt(cgx * cgx + cgy * cgy);
    Out = float3(1, 0, 0) * (cg - 0.2);
}
#endif

やっていることは、wikipediaに書かれていたソーベルフィルタのカーネルを着目するUV座標を軸として畳み込んでいるだけです。

縦のエッジと横のエッジでそれぞれ別のカーネルになっているので、それぞれについて計算した上で合成する必要があることにだけ気を付けないといけません。


最後に、スキャン部とアウトライン部をMultiply Nodeで合成してAlphaに設定してあげれば完成です。(完成じゃないんだけど)

おわりに

複雑なテクスチャに対して微分フィルタでアウトライン表示させてみるというアプローチは多分うまくいかないことがわかりました。

別の方法を探りたい。。。誰か何か良い方法を知ってたら教えてください( ;∀;)