C#のガベージコレクションって何してるの?

はじめに

どうも、にとです!
C#ってメモリ解放とかを勝手にやってくれるのですが、中でどんな風に動いているのか気になったので本棚に眠っていたオライリーの本を参考にして簡単にまとめてみました。

ガベージコレクションとは

ヒープブロックの割り当てを解除するタイミングを決定する機能。

ヒープ

ヒープとは動的に確保と解放を繰り返せるメモリ領域のことです。インスタンスをnewするなどで新しいヒープブロックが割り当てられます。
ヒープブロックにはオブジェクトの静的ではないすべてのフィールド(配列ならすべての要素)とヘッダが含まれる

1.オブジェクトの生存を決定する

まずは使っていないオブジェクトを判別するにはルートを決めます。

ルート

まずルートとは何かというと、「到達可能なオブジェクトへの参照」です。つまり今どこかで使われていて生存しているオブジェクトへの参照のことです。
他にも参照型の静的フィールドなど、プログラムが別のオブジェクト参照を経由しなくても将来必ず利用できるオブジェクトへの参照もルートになります。

ルートであるかの判定

ルートであるかは以下の条件で判断されます。

  • そのオブジェクトへの参照がnullでなければルート
  • ルートであるオブジェクトが参照しているオブジェクトもルート

※ローカル変数、メソッド引数はその生存期間だけルートになる(引数は渡した時点で終了)
※式の評価の結果として生成される一時オブジェクトも評価が完了するまで生存する

弱参照

弱参照を使うとオブジェクトへの到達可能性が若干変わります。

普通の参照と何が違う?

オブジェクトが弱参照からしか到達できないときはGCで割り当てを解放します。

つまり

  • 「自分以外が全員参照を手放したら、その時点で解放対象にしてほしい」
  • 「オブジェクトを参照しているけれど、GC的には参照してることにしないでほしい」

ときに使う

弱参照の宣言

WeakReference<T> hoge = new WeakReference<T>(value);
// コンストラクタ引数に渡したもので初期化される
// Tの部分は任意の型を指定する

2.メモリの回収

オブジェクトが死亡するとその分のメモリが回収され、ヒープに空き領域ができます。このとき新しく必要になったメモリを空のブロックにそのまま割り当てると無駄が多くなってしまいます。

そこで登場するのがコンパクションという処理です。

コンパクション

コンパクションとはヒープの空き領域が連続的になる状態にする処理のことです。これをすることで、

  • 新しいメモリを無駄なく割り当てられる
  • 関連するデータが近くになるので効率が良くなる

という利点があります。

ブロックを移動したときにはすべてのポインタを調整します。

この方法で万事解決...と思いきやもう一つ問題があります。それは古いオブジェクトが到達不能になったときにコンパクションに時間がかかってしまうというものです。

このようにオブジェクトの生存期間によってコンパクションのコストが変わってしまいます。そこでオブジェクトを世代に分けて考えます。

世代

オブジェクトの生存期間によるコンパクションのコストの違いを考慮し、オブジェクトを3つの世代に分け、世代によってGCのタイミングを変えます。

  • 世代0 … まだ一度もGCを経験していないオブジェクト
  • 世代1 … 短命か長命かを見極める待期期間

ここまでが短命世代と呼ばれ、世代1までの領域が閾値を超えると通常GCが発生します。

  • 世代2 … もっとも古い世代
    世代2まで生き残ったオブジェクトは
    • フルGCのときのみメモリが回収される
    • コンパクションに時間がかかる
    • CPUのキャッシュが消えていてコピーに時間がかかる

    (※世代2に入るがそれほど寿命が長くないオブジェクトができることを中年の危機と呼ぶらしい)

世代1まではプログラムが動いているときのGCによってメモリを回収し、世代2はフルGCのときのみメモリが回収されます。

大規模オブジェクトヒープ(LOH)

サイズが大きいオブジェクト(85000バイト以上)は大規模オブジェクトヒープと呼ばれるヒープに割り当てられます。コストがかかるため通常GCではコンパクションをせず、フルGCのときに明示的に要求しないとコンパクションはできません。

感想

今まで裏で動いているものや実際の開発でそれほど使わないような知識は結構すっ飛ばしていたんですが、こうやって調べてみると意外と面白かったです。Unity開発でもプロファイラを見るとたまにGCによってパフォーマンスがガクッと下がっていることがあるので、GCの仕組みと特性を生かした対処ができたらいいなと思いました。

参考

www.amazon.co.jp

LightshipARDKを使って初めてのAR開発

Lightship ARDKとは

ポケモンGOなどで知られているNiantic社のAR開発ツール。 Lightship ARDKの強みとしては

があります。

Lightship VPS

Visual Positioning Systemのこと。ある特定のスポットを特定、把握することでデバイスを現実の位置と同期させることができる。
例えば、ある場所にARオブジェクトを隠してそれを他のユーザーに見つけてもらう、みたいな宝探しゲームもできちゃったりする。

導入方法

  1. Lightshipアカウントを作る
  2. ARDKをダウンロードする
  3. Unityプロジェクトを作る
    ※UnityHubでAndroid Build Supportを入れておく
  4. 2でダウンロードしたARDKのunityパッケージをインポートする

APIキーの認証

  1. アカウントダッシュボードAPIキーを作る
  2. APIキーを認証する
    1. Resources/ARDKフォルダにArdkAuthConfigアセットを追加
    2. ArdkAuthConfigのインスペクタのApiKeyにさっき作ったAPIキーを入力する
    3. Unityを再起動

平面検知をしてオブジェクトを配置する

こんなものを作ります
(参考: チュートリアル: 基本の配置 — Niantic Lightship Augmented Reality Developer Kit release-2.4.1 ドキュメント)

www.youtube.com

カメラの設定

シーンを開いたらまずMainCameraに以下のコンポーネントをアタッチする

そしたらインスペクタのCameraのところにMainCameraをドラッグアンドドロップ

Cameraコンポーネントは以下のように設定する

セッションマネージャー

シーンに空のゲームオブジェクトを作って、名前をSessionManagerにします(名前はなんでもいい)。
SessionManagerには以下のコンポーネントをアタッチしていきます。

  • ARSessionManagerコンポーネント
    • ARセッションのセットアップを処理する
  • ARPlaneManager
    • シーン内のARオブジェクトを保存・トラッキングしてくれる
    • Planeに基づく配置が可能に
    • 配置したオブジェクトの保存
    • 配置したオブジェクトの固定(たぶんカメラを動かしたときに位置を一定に保つ)
  • AndroidPermissionRequester(Android向けのみ)
    • 実行時にユーザーに対してカメラ使用許可を求めるようになる
    • PermissionsにCameraを追加
  • ARCursorRenderer
    • カメラからRayみたいなのを飛ばして、物を検知するとそこにCursorObjectが表示される
  • ARHitTester
    • タップしたところにプレハブを配置する

ビルド

今回はAndroidでのビルドをしていきます。

設定

Project Settings

  • Player→Other Settings
    • GraphicsAPIsからVulkanを削除
    • Scripting BackendはIL2CPPを選択
    • Target ArchitecturesはARMv7とARM64を選択
  • Player → Publishing settings
    • Custom Main ManifestとCustom Base Gradle Templateにチェックを入れる
    • 追加されたAndroidManifest.xmlに以下を追記する

        <queries>
                <package android:name="com.google.ar.core" />
        </queries>
      

Gradleの設定

  • gradleをダウンロードして解凍(たぶん新しいので大丈夫)
  • 追加されたbaseProjectTemplate.gradle(Assets->Plugins->Androidの中)を編集
    • classpathを 'com.android.tools.build:gradle:4.2.0’にする
  • Preferences → External ToolsでGradle Installed with Unityのチェックを外してさっき解凍したフォルダを選択

ビルド

  1. スマホを開発者モードにする(「設定」アプリ内で「ビルド番号」を7回連続してタップ)
  2. スマホで開発者向けオプションのUSBデバッグをONにする
  3. PCとスマホをUSBで接続する
  4. UnityのBuild SettingsでSwitch platform
  5. Run Deviceで自分の端末を選択する
  6. Build And Runで実行!

動いた!

www.youtube.com

感想

まさかコードを書かずに動くとは...! 開発環境を整えるのが結構簡単だったのと、公式のチュートリアルも細かく説明してくれているのですぐに実機で動かせることができました。もっといろんな機能があるのでチュートリアルを進めながら試していきたいと思います。早く自由にARゲーム作れるようになりたいな~

最後にちょっと自分語り

ARやろうと思ったきっかけ

「誰でも楽しめるXRコンテンツを作りたい」という目標ができたのがきっかけです。最近いろいろあって親孝行したいなっていう感情が芽生えまして...。お金稼げるようになってからいろいろ買ってあげようとか考えてはいるんですが、大学生のうちからできることないかなーとか思ったんですよね。でもVRHMDが無いとできないし、慣れてないと疲れちゃうし...とか考えた結果、ARなら家族でも楽しめるコンテンツを作れるんじゃないか、ってことでAR開発を勉強することになりました。そこからいろいろ広がって、誰でも楽しめるXRコンテンツを作りたいという目標ができたわけです。
もちろんVR楽しいので今まで通りVR開発していきますが、ARのほうもちょっとずつやっていきたいなと思います。

IVRCに参加した話(~SEED stage編)

はじめに

この記事は投稿される2か月くらい前に書いたものです。一応Leapステージが終わってからのほうがいいのかなと思って温めておきました。
この時の僕はLeapステージに進出できるとは知る由もなかった...

IVRCとは

「学生を中心としたチームでインタラクティブ作品を企画・制作するチャレンジ」(公式より)
主にxR技術を用いた作品が出展されています。今回のSEED stageでは23チーム+復活枠4チームが出場しました。審査を通過するとLEAP stageに進むことができます。

制作したもの

僕の所属したチームでは『自宅でも遭難がしたい!』という作品を展示しました(どこかで聞いたことあるような...?)。この作品ではリアルな遭難を体験することができ、実際に登山をしないと気づかないような体の変化や危険を感じることができます。

www.youtube.com

やっぱり遭難という実際には体験できない(というかしたくない)ものでも簡単に体験できるのがVRの面白いところですよね~
ちなみにこのアイデアは、メンバーの中学校の時の先生が山で遭難して、暗闇の中石を投げることで方向を確認しながら自力で山を降りた、というぶっ飛んだエピソードから発案されました。

実装した機能

足踏みで移動

Viveトラッカーを2つ使い、足踏みでの移動を実装しました(本当はHaritoraを使いたかった)。実際に足を動かすことで没入感と疲労感を高めることができました。

疲労度に応じた身体の変化

1. 身体が重くなる

体験者にリュックを背負ってもらい、疲労度が溜まるとポンプによってリュックの中の容器に水が流れ、徐々に身体が重くなっていきます。
水を使うということもあり、何度かブルーシートが水浸しになったりもしました。また水が流れる速度や量は何度も調整しました。

2. 身体が冷えてくる

首に装着したペルチェ素子によって低体温症による身体の冷えを再現します。首元を冷やすだけで結構寒さを感じます(個人差あり)。
また低体温症による手の震えをモーターで再現しています。疲労度がしきい値を超えるとモーターが動き出します。

3. 目が見づらくなる

URPのPost processingを使って疲労度による視界の悪化を再現しています。Depth of fieldで目がぼやける感じを出し、Vignetteで視界が狭まる感じを表現しました。

4. 音がぼやけてくる

UnityのAudio Filterを使って高い周波数と低い周波数を削っていくことで音がぼやける感じを表現しています。

僕(にと)が担当した部分

僕たちのチームはソフト班とハード班に分かれて開発をしました。
僕は主に植物のグラフィック関係を担当しました。↓のような感じです。

  • 草のグラフィック

ShaderGraphを使ったリアルな草の作り方① ~Blenderでメッシュを作る~ - にとのーと

  • 草を手でかき分けたときの動き
  • URPのライティング設定
  • 虫のVFX
  • タイトルシーンのステージ作り
  • ステージのパフォーマンス改善

この他にも、出没動物のアニメーション、方向指示UI、アイテムの見た目、アイテムの取り出し、手のアニメーションなどを担当しました。

体験審査会

9月3日、東京のポートシティ竹芝で体験審査会がありました。先輩二人と僕の計三人で参加し、組み立てからアテンドまで協力して取り組みました。

いきなりトラブル発生...

最初の体験者でなぜかトラッカーが反応しなくなるというバグが発生。さらに二人目の体験者のときにはOculus Linkができなくなり、PCの再起動をすることになりました。正直ここで心折れかけました...。

なんとか持ち直した

その後は大きな不具合が起きることなく、順調に体験していただくことができました。毎回ペットボトルに入った水を容器に戻す作業があるので自分たちの疲労度も順調に溜まっていきました。

嬉しいことも

体験したあとに感想やフィードバックをいただけるんですが、そのときにハード部分はもちろんのこと、「草をかき分けるのが楽しい」や「山の雰囲気がリアル」など自分が携わった部分が褒められて泣きそうなくらい嬉しくなりました。

他チームの作品

審査が終わった後、他の参加チームの作品を体験することができました。僕が体験したのは『壁歩き体験~ヤモリになろう!~』という作品で、その名の通りヤモリになって床を這ったり壁を歩いたりするというものでした。バランスボールを回すことによって、確かに「登っている」という感覚があり、高いところに登ったときのスリルも味わえて楽しかったです。

感想

IVRCのような大学外のイベントに出場するのは初めてなのでとてもワクワクしながら開発に取り組むことができました。ハードウェア部分と連携した開発というのも初めてでしたが、VRと装置が連携して動いているのを見てVRの無限の可能性に感動しました。体験会では実際に体験してもらって感想やフィードバックをいただけたり、他の参加者との交流もあったりして良い経験になりました。また個人的なことを言うと、今までゲーム開発においてコードを書くことにしか興味がなかったのですが、今回「リアルな」体験を制作するにあたってグラフィック関連にもこだわることができたので楽しかったです。特にShaderGraphやVisualEffectGraphは今後のゲーム開発でも大きな武器になると感じました。

最後に

IVRCを運営していただいている方々、メンバーとして誘ってくれた先輩、一緒に開発に携わったメンバーに感謝。
LEAP stage通ってほしいな~

ShaderGraphを使ったリアルな草の作り方② ~風でなびかせる~

風でなびかせたい

↓の動画のように風でなびく感じをShaderGraphを使って表現していこうと思います。
youtu.be

前回は草のメッシュを作りました。 niton.hatenadiary.com

今回の記事は以下の動画を参考にしています www.youtube.com

1. ShaderGraphを使えるようにする

UnityでShaderGraphの機能が使うにはURPというレンダリングパイプラインを使用する必要があります。導入はとても簡単です。

Unityのプロジェクトを作成するときに3D(URP)を選択するだけです! ※既存のプロジェクトにURPを導入する方法はこちらの記事を参考にしてみてください。↓
【Unity】Unity 2021.2で変更されたURP設定方法まとめ|yugaki|note

2. ShaderGraphの基礎

では早速ShaderGraphを使ってみましょう。
Projectウィンドウで右クリックをして、Create->Shader Graph->URP->Lit Shader Graphを選択します。(Litにすると周りの光の影響を受け、Unlitは影響を受けません)

ウィンドウの見方

ShaderGraphを作成すると↓のような画面が出てきます

  • MasterStack…シェーダーへの最終的な出力
    • Vertexは頂点の操作を出力する
    • Fragmentは色やテクスチャなどを出力する
  • Blackboard…プロパティとキーワードのリスト
    • プロパティを追加するとインスペクタで編集できるようになる
    • ReferenceでC#で使うプロパティ名を変更できる
  • GraphInspector…ノードごとのインスペクタ
  • MainPreview…プレビュー

ノード

ウィンドウ内で右クリック -> Create Node、もしくはSpaceキーを押すとノードの種類がたくさん表示されます。ここから必要なノードを選んでShaderを作っていきます。

では試しにAddノードを作ってみましょう。上の検索欄で「Add」と入力すると簡単に見つけることができます。
Addを選択すると↓のようなノードが出てきます。

左側のAとBが入力で、右側のOutが出力です。この入出力の部分に他のノードを結びつけて作っていきます。

ではShaderGraphの基本的な使い方がわかったところで、今回の目的である「風になびく草」を作っていきましょう!

3. 全体像

今回は大きく3つのステップでShaderGraphを作っていきたいと思います。(参考動画より)

Step1. メッシュの頂点にワールド座標基準でUV座標を割り当て、UVスクロールする

Step2. ノイズテクスチャを適用して頂点を左右に揺らす

Step3. これだと草の根本も一緒に動いちゃうのでy軸にグラデーションをかけて根本の変化を0にする

4. 解説

準備

まずは前回作ったメッシュとテクスチャをインポートします。Projectウィンドウで右クリックをしてImport New Assetsを選択し、前回作ったメッシュとテクスチャをインポートします。

  • Sample Texture 2Dノードと、BaseTextureプロパティを作りGraph Inspectorでテクスチャを設定します。
  • ShaderGraphのプレビューもあるんですが、シーンのほうが動きがわかりやすいのでシーンに草を配置して確認できるようにします。
    Mesh FilterとMesh Rendererコンポーネントを追加し、画像のように前回作ったメッシュとShaderGraphを適用させたマテリアルを設定します。
  • このままでは後ろから見たときにテクスチャが表示されないので、ShaderGraphのウィンドウのGraphSettingでRender Faceにチェックを入れます。
    また、透過部分を表示しないように、Alpha Clippingにもチェックを入れます。

Step1. 頂点にUV座標を割り当て、UVスクロールする

このようにノードをつなげます。

この部分はUVスクロールのテンプレ的な感じですね。頂点にワールド座標基準でUV座標を割り当てて、それをスクロールしています。
Tiling And OffsetノードのOffsetにTimeノードを作用させることで時間によってUVがスクロールする動きをします。ここではBlackboardで「WindMovement」というVector2型のプロパティを作って時間と乗算することでスクロールの速度を調整しています。

Step2. ノイズテクスチャを使って頂点を左右に揺らす

3つのノードと2つのプロパティを追加します。先程作ったTiling And OffsetノードのOutからGradient NoiseノードのUVにつなげます。
Gradient Noiseはノイズテクスチャを生成してくれます。Scaleはノイズの大きさで、今回は風の密度?を決定しています。
Substractノードは値の引き算をします。ここで0.5を引いているのはおそらく白黒の割合の調整です。ちなみに値的には黒が0、白が1を表しています(結構大事かも)。
Multiplyノードは乗算をします。このノードによって白黒のメリハリを調整しています(大きい値を乗算するとメリハリが強くなる) 。ここではWindStrengthによって風の強さを決定しています。

ではついでに今の状態での出力までしてみましょう。

4つのノードをこのようにつなげて先ほどのMultiplyノードのOutをAddのAにつなげましょう。また、CombineのRGBA出力からVertexのPositionにつなげてみましょう。

Splitノードは入力ベクトルをR,G,B,Aに分割します。Positionを入力とした場合、RGBはそれぞれxyz軸を表します。ここではx成分だけを取り出して先程作ったゆらぎを加算しています。
CombineノードはRGBAの入力から新しいベクトルを作成します。ここではx成分の計算をした後に元のy,z成分をそのまま入力として受け取り、RGBAに合成しています。

今の状態だと↓のような動きになってしまいます。
youtu.be
根本まで一緒に動いていてちょっと気持ち悪いですね笑

Step3. 根元の方を動かさないようにする

では↓の赤線で囲まれている部分を追加していきましょう。

UVノードとSplitノードを組み合わせるとグラデーションを作ることができます。RGBのうちGを使った場合は縦方向のグラデーションになります。
Lerpノードは2つの入力の線形補完を計算します。ここでは縦方向のグラデーションを入力として、元の頂点座標とゆらぎを計算した頂点座標の間を線形補間しています。こうすることで根本に近いところでは元の頂点座標を採択し、葉っぱの先に近いところではゆらぎを計算した頂点の座標を採択します。

完成!

いっぱい配置してライティングを調整してみました。
youtu.be

ShaderGraphを使ったリアルな草の作り方① ~Blenderでメッシュを作る~

草を生やしたい

IVRCに向けた開発でリアリティのある植物を作る機会があったので、UnityのShaderGraphの勉強をしながら立体的で風になびく草を作ってみました。

youtu.be

やりたいこと

  • 軽量でリアリティのある草を生やす
  • 風で動いている感じ
  • プレイヤーが通った時に少し倒れる

1. メッシュを作る

まずは草の形を作っていきます。
Blenderを起動し、最初にあるCubeを削除したらShift+Aを押してPlaneを作成します。

Editモードに切り替えて移動、回転、拡大、縮小をしながら形を整えます。

これを繰り返してPlaneをいい感じに配置していきます

2. UV展開をしてテクスチャを張り付ける

※草の透過画像を用意しておく
こんな感じのやつ Ornamental Grass Clip Art - Tall Grass Clipart – Stunning free transparent png clipart images free download

まずblenderのウィンドウの上のほうにあるUV Editingに移動します

UVマッピングウィンドウ(左側)の上のほうでOpen Imageから草のテクスチャ画像を選択します

テクスチャが見えるように右のマテリアルタブから新しいマテリアルを作成し、BaseColorでImageTextureを選択します。

そうしたら先ほど選択した画像があるのでそれを選択します。

↓のように描画されれば成功です!
(テクスチャが見えないときはZキーを押してビューモードをMaterial Previewにしましょう)

もしテクスチャが逆向きになっていたら左のUVウィンドウで対象の面を回転させて調整しましょう。

3. FBXファイルをエクスポート

完成したらFile->Export->FBXを選択します

出てきたウィンドウの右側でExportの設定をします。

  • ObjectTypesをMeshのみにする
  • Apply Transformにチェックを入れる(一応)

    これでBlenderでの作業は終わりです。

次:niton.hatenadiary.com

ShaderGraphを使って風になびかせる

はじめました

自己紹介

はじめまして。一般男子大学生のにとです。 会津大学のA-PxLに所属しており、主にVRゲームの開発を行っています。

このブログについて

当ブログでは主にUnityやC#、xRについての記事を書いていこうと思っています。それ以外でも自分が興味を持った技術についてはどんどん書いていきたいです。

リンク

twitter.com