20200703のUnityに関する記事は11件です。

Unity で等加速度直線運動を考える

自由落下

 $\mathbf{g}$ を重力の定数とすれば、加速度に関して下記の式が成り立つ。

\frac{d^2\mathbf{x}}{dt^2} = \mathbf{g}

 上式の積分から

\begin{eqnarray}
\mathbf{v}_{\rm Integral}(t) &=& \mathbf{v}_{0} + \mathbf{g} \dot{} t,\\
\mathbf{x}_{\rm Integral}(t) &=& \mathbf{x}_{0} + \mathbf{v}_{0} \dot{} t + \frac{1}{2} \mathbf{g} \dot{} t^2 .
\end{eqnarray}

 一方で、数値計算においては、時間が連続でない(離散的)ので、あるフレーム $f$ における速度や位置は

\begin{align*}
\mathbf{v}_{{\rm Discrete},f} &= \mathbf{v}_{{\rm D},f-1} + \mathbf{g} \dot{} (T_{f} - T_{f-1}),\\
\mathbf{x}_{{\rm Discrete},f} &= \mathbf{x}_{{\rm D},f-1} + \mathbf{v}_{{\rm D},f-1} \dot{} (T_{f} - T_{f-1})
\end{align*}

とも表現できる。
 今回はボールの自由落下を上記の2つの積分による運動と、Unity の物理エンジンによる自由落下を比較してみることにする。

Unity Project : [Github] note-nota/UniformAcceleration

自由落下の比較

 時間方向の離散間隔を変えて、2つの動画を GIF にしてみました。2つの動画共に左から順に【Unity Rigidbody による落下】、【積分で計算した方程式による落下】、【離散的時間積分による落下】の計算をしたボールになります。

フレーム間隔:0.20[s]

y2zw4-1x0wa.gif

フレーム間隔:0.66[s]

txoe7-4jsle.gif

  • 【Unity Rigidbody による落下】と【積分で計算した方程式による落下】は比較的よく似ている
    • 多少【積分で計算した方程式による落下】の方が遅い
  • フレーム間隔が大きくなると【離散的時間積分による落下】のズレが大きくなる

 フレーム間隔を UI から操作したかったので、コルーチンを組んで時間間隔を変更できるように実装してます。(slider.onValueChanged を使ってることで、動かしている間に書き換わっていくのは実装上は良くない気もしてます…が、一旦…ね。)

FrameControlPanel.cs
        void Start()
        {
            slider.onValueChanged.AddListener(var => 
            {
                deltaTimeText.text = GetDeltaTimeTextFormat(var);
                StartTimer(var);
            });
        }

        void StartTimer(float seconds)
        {
            if (timer != null) StopCoroutine(timer);
            timer = CountTime(seconds);
            StartCoroutine(timer);
        }

        IEnumerator CountTime(float seconds)
        {
            while (true)
            {
                OnFrameEvent.Invoke();
                var now = Time.time;
                Debug.Log("WaitTime :" + (now - lasttime));
                lasttime = now;
                yield return new WaitForSeconds(seconds);
            }
        }

 Rigidbody による落下は rigidbody.useGravity の操作だけ。

RigidbodyDropMove.cs
        protected override void StartAction()
        {
            ActionFrame();
            rigidbody.useGravity = true;
            frameCtr.SetFrameEvent(ActionFrame);
        }

        protected override void ResetAction()
        {
            base.ResetAction();
            rigidbody.useGravity = false;
            rigidbody.velocity = Vector3.zero;
        }

…おっと、rigidbody.velocity の初期化も忘れずに…。
 積分による方程式系は

GravityMoveEquation.cs
        public override void Update(float time)
        {
            nowVelocity = GRAVITY * (time - startTime) + initVelicity;
            nowPosition = GRAVITY * Mathf.Pow(time - startTime, 2.0f) / 2 + initVelicity * (time - startTime) + initPosition;
        }

最初の位置・速度を保持し、経過時間から求められる。
 一方、時間積分の方は

GravityMoveDeltime.cs
        public override void Update(float time)
        {
            nowPosition += nowVelocity * (time - lastTime);
            nowVelocity += GRAVITY * (time - lastTime);
            lastTime = time;
        }

ひとつ前のフレームの値に足し上げていく操作を行う。

簡単な説明

 基本的には Unity の Rigidbody による自由落下と積分した方程式による落下は同じになるハズ…、と思ったんですがね?ちょっと差が出てるので、ココは今後も考えてみたいと思います。

~~ チョット考え中 ~~
考えられる原因の候補

  • フレームレートの違い
  • 重力定数の違い
  • 累積誤差の違い
  • 計算実行順による違い

~~~ 一旦、保留 ~~~

さてさて、以降は【離散的時間積分による落下】について何が起きていたかを見ていきたいと思います。
 下に2つのグラフを作成しました。1つ目は落下速度のグラフ。2つ目は位置のグラフ。方程式によるグラフと時間間隔の違う2つの状態をプロットしてます。

[Qiita] 落下速度のグラフ.jpg

[Qiita] 落下位置のグラフ.jpg

 落下速度に関するグラフを見ると、0.20 [s] も 0.66 [s] もともに方程式と同じ値をとっていることが分かる。階段状になっているのは、時間間隔を空けて計算しているためで、各フレームにおいては計算された速度は方程式に一致している。
 一方で、位置に関するグラフを見れば、時間間隔が長い方が方程式からのズレが大きいことがうかがえる。ある時間での落下位置を順に並べれば、【方程式】<【0.20 [s]】<【0.66 [s]】となる。

 0.20 [s] と 0.66 [s] それぞれある時間での落下位置と速度との関係を図示してみよう。

[Qiita] 落下のグラフ-2.jpg

[Qiita] 落下のグラフ.jpg

上の図で赤い矢印の落下位置の計算を、速度のグラフで表現すると赤い面積になる。これは、GravityMoveDeltime.cs での nowPosition += nowVelocity * (time - lastTime); からも同じ様に導かれる。

[Qiita] 落下のグラフ-4.jpg

[Qiita] 落下のグラフ-3.jpg

0.66 [s] においても同様に、青い矢印での落下位置の計算には、速度に関するグラフの青い面積の部分によって計算される。

 さて、話を戻せば、速度に関しては 0.20 [s] であれ 0.66 [s] であれどちらも各フレームにおいて方程式と同じ値をとっている。一方で、落下位置に関しては、0.66 [s] の方が方程式から離れた値をとっている。これは、先ほどの面積で考えるととてもわかりやすい。次の図は方程式の系統で同じことを考えた図です。

[Qiita] 落下のグラフ.jpg

[Qiita] 落下のグラフ.jpg

上の黄色い矢印を求めるために、下の図での黄色い三角形の面積を考える必要があります。
 ということは、時間フレームを切った【離散的時間積分による落下】で正確な落下を表現するには、「時間間隔を“極めて”小さく区切って三角形に近い形になるようにすればよい」ことが分かります。今回作成した Unity のプロジェクトで時間間隔を小さくして確かめてみてください。
(とは言いつつ、やはり時間間隔を小さくする限界があるますが、雰囲気だけでも感じてもらえればと思います。)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[UWA Tech Q&A]ナビメッシュ情報導出

今回の主な話題:「ナビメッシュ情報導出」、「LoadLevelAsyncロード効率」、「IL2CPP使用後のMonoメモリ理解」。


ロード

Q1:携帯でLoadLevelAsyncとLoadLevelのロードスピードをテストします。同じシーンで、LoadLevelAsyncはLoadLevelより40%くらい長く時間をかかります。これは正しいですか。LoadLevelには重い時があるのため、Loadingプログレスバーがスムーズではないが、LoadLevelAsyncを採用したらLoading時間が増えるはずです。
プロジェクトに動的シーンをロードする私のやり方は、オブジェクトをPrefabにします、使ったのはResources.LoadAsyncです。今にも、大きなオブジェクトをロードする場合、Red mi 2のようなローエンドモデルではまだ重っています、200ms以上かかります。ですからこのロードをスムーズするために、何かいい方法ありますか。

一般的にLoadLevelAsyncはLoadLevelより時間かかりますが、40%遅いかどうかは、次々のシーンがロードすべき量で決定します。定数ではありません。
実は、LoadLevelとLoadLevelAsync最も根本的な違いは、前者は必ず次のフレームが終わる前にロードを完成させます。だからシーンが大きい時、後の単フレームコストも大きくなります。後者にはこのような限制はありません。エンジンは現在の使用状況やThreadPriority(この値はLoadLevelAsyncに影響あるかどうかはまだ実験されていませんけど、確かにLoadAsyncに影響があります)により、自分で調整できます。しかし、Asyncを使ったら、絶対にスムーズになることではありません。下記の図に赤枠にのはLoadLevel、緑枠にのはLoadLevelAsync操作です。
1.png
本格的な「スムーズ」非同期ロード方式のは、今のUnityエンジンにはありません。複雑なPrefab(大きなテクスチャや複数のAnimationClipなど)にあう時、ロードにまだ重い状況はあります。もしこの問題を解決したいなら、UWAからのアドバイスは:
1.先に具体的にどちらのPrefabロードのCPU時間コストは高いかを特定します。
2.このPrefabに時間のかかる動作(大きなテクスチャ、複数のAnimationClipまたはシェーダーなど)が多いかどうかを確認します。
3.特定された動作を前期プリロードすることを試み、Prefabロード時の一部分の高コストを解決します。


メモリ

Q2:私はIL2CPPを使った後、Monoメモリはもう存在しするか。IL2CPPを用いて,Profilerツールで取得したmanagedObject(int32[]など)はどのようなメモリですか。

簡単に認められるのは、IL2CPPはただMonoの仮想マシンの実現を取り替えました。だからMonoメモリを配分すべき場所は同じように配分します(些細なディテールは違うかもしれません)。Monoメモリ配分にIL2CPPとMono最も違うのはReserved Totalは低下できます、MonoのReserved Totalは上昇のみ低下できません。


レンダリング

Q3:シーンが自動的にベイクしたナビメッシュ情報はNavMesh.assetの中に保存されてあります、開くと下記のようなデータが見られます:
deta.png
プロジェクトはサーバーでロケーションする必要があり、できる人にこのデータの解析やレセt用のナビメッシュへの還元を手伝っていただきたいです。Unityバージョンは5.5.1です、Unity4.2にこの解析コードはありましたけど、新版には様式は変わりました。

まずはNavMesh.CalculateTriangulationですべての頂点と索引データを取得します、これは完全な、構築したNavMesh、多辺形情報(最大辺数は6の凸多辺形)が含まれてあります。しかしこのデータにのすべての多辺形の辺は共有していません(多辺形の辺は重複のデータ)。ですからまずは重複の辺を合併します、RacastNavigationが提供したコードを利用できます、rcBuildPolyMeshに基づいて一つの修正版を作って(例えばrcBuildPolyMeshBySeparatePolyTriangleと呼ぶ)、目的は既有の多辺形の重複の辺を合併します。これが終わったら、RacastNavigationが残った構築流れを利用できます:
1.NavMeshを作成し,以上のPolyMeshを用いて初期化します(dtCreateNavMeshData,dtNavMesh::Init)。
2.既存のNavMeshによるエンクロースボックスを更新します。
3.既存のNavMeshに従って、一つ一つのTileをファイルに書き込みます(その前にカスタマイズされたヘッダ情報を書き込むことができ、読み取りやすくなります)。
4.サーバーに蓄積されたデータを読み込み,初期化してNavMeshを生成すると、findpathが行えます。(このデータをRacastNavigationのDemoに導入し、可視化状況を見ることもできます)。

以上の1〜4はRacastNavigation例で流れ構築の一部です、重点はrcBuildPolyMeshBySeparatePolyTriangleで多辺形を合併すればいいんです。このような利点は,導出されたNavMeshとUnityに構築されたNavMesh,多辺形は同じです。DetailMeshに支持するのは、まだできませんらしいです。
また,サーバを直接利用するには問題があるかもしれないが,Unityが導出したデータが細長い三角形のものが多い場合,RacastNavigationの結果がUnityと大きく異なり,RacastNavigationのコードを少し修正する必要があります。


レンダリング

Q4:現在、私が使用しているUnityバージョンは2017.1.0f3 for MACであり、iOSプラットフォームでBake Environment Reflectionsを行う際にHDR情報を失いました。操作手順は下記のように:
⑴Standalone Platformを使ったとき、HDR付きSkyBox CubeMapマップ(デフォルト圧縮方式BC6H)を使いました。生成されたReflectionProbe-0.exrは対象に顕著なハイライトを与えます。
2.png
⑵iOS Platformに切り替えた後,再び同じSkyBox CubeMapパッチ(圧縮方式はPVRTC, 圧縮しないRGB24 bitも試みた)でReflectionProbe−0.exrをベイクすると,この効果は消えました。
3.png
⑶このとき,私が前のStandaloneが生成したReflectionProbe−0.exrを置き換えますと,Standaloneと同様のハイライトが得られます。これをiPhone 7Plusで動作させますと,「ステップ⑴」と同じ効果が得られます。ReflectionProbe-0中のHDR情報は保存されましたと分かりました。しかし、iOS Platformでは、ReflectionProbe−0のデフォルト圧縮方式はPVRTCやRGB 24 bitであり,どちらの方式もHDR情報を保存できません。
4.png
⑷2枚のReflectionProbe−0.exrスタンプを比較しますと、ファイルのサイズは違う、Standaloneのスタンプの方がちっと大きいです。これはUnityのBugですか。
11.jpg
こちらに私が例のプロジェクトexr_EnvRef_iOS.zipをアップロードしました。デフォルトはPC Standaloneで、Platformを手動的に切り替え、後に再びGenerate Lightingの必要があります。

問題主のプロジェクトファイルを使ってみました、確かに同じ問題が出てきました、そしてAndroidでも同様です。今から見るとReflection-Probeがベイクされた時2セットの異なる操作が使われているとしか判断できません。
もう一つの手がかりを提供します、renderdoc−>texture viewerで画素値を見ることができます。
5.png
Standalone(PC)
6.png
Android
上の図から分かれるのは、似たようなエリアに対してStandaloneのベイク結果の値はAndroidより高いです。

Unity5.6で効果は同じと発見しました、結論は問題主と同じです:MobileプラットフォームでベイクされたのはLDRなProbeです。彼は輝度を高めましたが、実にはもっと簡単な方法があります:高mipを見ればいいです。まずはPCでの効果です。
7.png
そしてAndroidでの:
8.png
輝度上の違いはPCでは輝度>1画素(一般的には太陽など)があり、畳み込み時の値は大きくなりました;Androidでは相対的に低いです。Mip=0の場合には誰も0−1ですから見えません。今の結論はモバイルプラットフォームでベイクされたのはまだLDRなProbeです。これは間違いです。暫定的な解決策はStandalone でベイクしたらコピーします。

問題主からの補充:新しい発見はありました、ほぼbugが確認できます。2枚のreflectionProbe—0.exrスタンプは確かに違います。サイズ以外、iOSプラットフォームに切り替わった後に生成されたexrは、ハイライト部分のHDR情報を失っています。
12.jpg
一見するとこの2枚の画像は同じですが、
13-11.png
exposureを調整し,exposureを−5にする(openexrを用いたが,psを用いて直接曝露度を調整することも可能です)。
14-1.png
上図には2つの 鮮明なスポットが見られますが,このスポットは非常に明るいため,物体に強いライトを生成できます。しかし、iOS Platformでベイクするとき、Unityはこの情報を失ってしまいました。ただし,すべてのexrにこのような問題が生じるわけではありません、局所的に強力な光源を持つHDR画像だけが現れます。


UWA Technologyは、モバイル/VRなど様々なゲーム開発者向け、パフォーマンス分析最適化ソリューション及びコンサルティングサービスを提供している会社でございます。

UWA公式サイト:https://jp.uwa4d.com
UWA公式ブログ:https://blog.jp.uwa4d.com

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

アセット管理方式

今回の主な話題:「アセット管理方式」、「 Unity 2017のUIカメラのパフォーマンスコスト」、「Standardシェーダーは効果を刷新していない」、「カメラHDRの開くがゲームパフォーマンスへの影響」。


レンダリング

Q1:たくさんの格好の良い効果はUnity後処理で実現されており、これはカメラにHDRをオンにすると要求されています。しかし、この機能のコストはよく分かりませんのため、ローエンドモデルで実行できますか。また、HDRを開いたら、本当にカメラのRendering PathはForwardを選択できませんか。

HDRを使用する時、Forwardも選択できます。Rendering Pathとお互いに影響しません。ただのHDR(fp 16)はパフォーマンスへの影響は大きくないですが、様々な後処理Passのコストは最適化の対象であります。一般的にHDRを開いたけど後処理が使用していない場合はあまりありません。発熱量などの要因を考えますと、ローエンドモデルでの使用は推薦しません。他のスマホに対してはHDRと後処理を同時に使用でき、また後処理に対するの基本的ま最適化も必要です。例えば:適当に処理解像度を下げ、なるべく複数の処理を一つのPassに合併しますなど。


レンダリング

Q2:Unity 5.5が実行時、シェーダーのGIオプションがベイクする時、Rendererモジュールが採用したMaterial属性、SetColor(“_EmissionColor”)、リアルタイムの効果リフレッシュはしていません。
コードは下記のように、

Renderer[] renderers = (Renderer[])mesh.GetComponentsInChildren<Renderer>();foreach(Renderer r in renderers)
{
r.sharedMaterial.SetColor("_EmissionColor" , new Vector4(0.5f, 0, 0, 1));
}

テストでは、必ずエディターでこのシェーダーを選択しなければリフレッシュできないことが分かりました。

シェーダー中のGIを「Baked」に設定しますと、もしEmissionColorの初期値が0であれば、Unityは直接にStandard Shaderの「_EMISSION」Keywordを閉じることになります。これで、_EmissionColorの設定がレンダリング結果への影響もなしになります。解決策は「Emission」Keywordを同時に開くと設定します。すなわち、「r.sharedMaterial.EnableKeyword("_EMISSION");」を加えます。


レンダリング

Q3:下記の二つの図はmate9+OpenGLが我がチームのプロジェクトの最高画質を実行する時のProflierスクリーンであります(Unity 5.6.Op4)。図1の二番目のCamera.Renderには相当長いウェイトが観察され、これは私たちのUIカメラです。図から見えるのは、他のカメラがレンダリングタスクを完了するのを待って,自分のメインルートレンダリングタスクの実行を開始する。
1.png
そしてこれはほとんどのUIをオフしたとき、UIカメラはまたやっていますけど、あの異常のウェイトは出て来ませんでした。見えるのは、上記の図に二番目のCamera.Renderの最初の任務はWaitfofJogGroup。そしてJobThreadにも同時にParticleJobが出現しました。私たちは「このカメラにparticle systemが出たらこの状況が引き起こされます」と推測しますが、まだ確認していません。皆さんはこのような状況にあったことありませんか。そして本当にUnityにこの問題が存在していますか。もしこれは5.6バージョンの問題なら、次のバージョンに解決されましたか。
2.png
この状況はUnity5.6バージョンによく見られます。5.6.0でも最新の5.6.3でも、この問題は一切あります。下記の図には私たちが最適化した一つのプロジェクトのTimelineスクリーンショットです(Unity5.6.3)。5.6以前のバージョンにこのような問題はあまり見られません。
3.png

私たちから見ると、主な原因は二つあります。
⑴Unity5.6では、もっと多くのレンダリングコンピューティングをメインスレッドから子スレッドに移って処理します。メインスレッドは更に子スレッドの結果を待つ必要があり、それは図2のような「ウェイト」現象を引き起こします。つまりCamera.RenderのSelfコストであります。
⑵根本的な原因は単フレームのレンダリングの量は大きすぎです。例えば質問者があったUIレンダリング、粒子システムレンダリングやスキンメッシュなど。もしこれらの計算量が多きなら、子スレッドに大量のストレスを連れてあげる可能性があります。だから多くのプロジェクトがマルチスレッドレンダリング機能を開いたが、レンダリングの量に注意してください。
Unity 2017のプロジェクトとの接触はまだあまりありませんから、今は統計データを出すことはできません。質問者は先にUnity5.5の最新版に戻してやってみて、問題が解決できるかどうかを観察してください。

補充:
最後のテストが「Unity2017.1にも問題があります」と証明しました。ただウェイトのタイミングが変わりました:renderにはもうウェイトをしませんが、particlesystem.deactivateの時にウェイトをします。ですからこのようなtimelineは現れるかもしれません。
4.png

2017.2はテストしていませんけど、楽観はできませんと推測します。


ロード

Q4:下記の図のように、アセットが解放されていますが、テクスチャはまだメモリの中にあり、参照数は0であります。これはをのように破棄しますか。テクスチャのUIはDestroyで破棄しました、Resources.UnloadUnusedAssets();も執行しました。
5.png

Ref Countは0の場合にはResources.UnloadUnusedAssetsやResources.UnloadAssetでアンインストールできるはずです。この状況に私たちにアドバイスは:
⑴ Resources.UnloadUnusedAssetsをDestroy後の数フレームに執行してみます;
⑵ロード中にこれらのアセットを直接取得し、Destroy後にResources.UnloadAssetで指定されたアセットをアンインストールします。

⑴から試すのはUWAのおすすめです。


UWA Technologiesは、モバイル/VRなど様々なゲーム開発者向け、パフォーマンス分析最適化ソリューション及びコンサルティングサービスを提供している会社でございます。

UWA公式サイト:https://jp.uwa4d.com
UWA公式ブログ:https://blog.jp.uwa4d.com

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[UWA Tech Q&A]アセット管理方式

今回の主な話題:「アセット管理方式」、「 Unity 2017のUIカメラのパフォーマンスコスト」、「Standardシェーダーは効果を刷新していない」、「カメラHDRの開くがゲームパフォーマンスへの影響」。


レンダリング

Q1:たくさんの格好の良い効果はUnity後処理で実現されており、これはカメラにHDRをオンにすると要求されています。しかし、この機能のコストはよく分かりませんのため、ローエンドモデルで実行できますか。また、HDRを開いたら、本当にカメラのRendering PathはForwardを選択できませんか。

HDRを使用する時、Forwardも選択できます。Rendering Pathとお互いに影響しません。ただのHDR(fp 16)はパフォーマンスへの影響は大きくないですが、様々な後処理Passのコストは最適化の対象であります。一般的にHDRを開いたけど後処理が使用していない場合はあまりありません。発熱量などの要因を考えますと、ローエンドモデルでの使用は推薦しません。他のスマホに対してはHDRと後処理を同時に使用でき、また後処理に対するの基本的ま最適化も必要です。例えば:適当に処理解像度を下げ、なるべく複数の処理を一つのPassに合併しますなど。


レンダリング

Q2:Unity 5.5が実行時、シェーダーのGIオプションがベイクする時、Rendererモジュールが採用したMaterial属性、SetColor(“_EmissionColor”)、リアルタイムの効果リフレッシュはしていません。
コードは下記のように、

Renderer[] renderers = (Renderer[])mesh.GetComponentsInChildren<Renderer>();foreach(Renderer r in renderers)
{
r.sharedMaterial.SetColor("_EmissionColor" , new Vector4(0.5f, 0, 0, 1));
}

テストでは、必ずエディターでこのシェーダーを選択しなければリフレッシュできないことが分かりました。

シェーダー中のGIを「Baked」に設定しますと、もしEmissionColorの初期値が0であれば、Unityは直接にStandard Shaderの「_EMISSION」Keywordを閉じることになります。これで、_EmissionColorの設定がレンダリング結果への影響もなしになります。解決策は「Emission」Keywordを同時に開くと設定します。すなわち、「r.sharedMaterial.EnableKeyword("_EMISSION");」を加えます。


レンダリング

Q3:下記の二つの図はmate9+OpenGLが我がチームのプロジェクトの最高画質を実行する時のProflierスクリーンであります(Unity 5.6.Op4)。図1の二番目のCamera.Renderには相当長いウェイトが観察され、これは私たちのUIカメラです。図から見えるのは、他のカメラがレンダリングタスクを完了するのを待って,自分のメインルートレンダリングタスクの実行を開始する。
1.png
そしてこれはほとんどのUIをオフしたとき、UIカメラはまたやっていますけど、あの異常のウェイトは出て来ませんでした。見えるのは、上記の図に二番目のCamera.Renderの最初の任務はWaitfofJogGroup。そしてJobThreadにも同時にParticleJobが出現しました。私たちは「このカメラにparticle systemが出たらこの状況が引き起こされます」と推測しますが、まだ確認していません。皆さんはこのような状況にあったことありませんか。そして本当にUnityにこの問題が存在していますか。もしこれは5.6バージョンの問題なら、次のバージョンに解決されましたか。
2.png
この状況はUnity5.6バージョンによく見られます。5.6.0でも最新の5.6.3でも、この問題は一切あります。下記の図には私たちが最適化した一つのプロジェクトのTimelineスクリーンショットです(Unity5.6.3)。5.6以前のバージョンにこのような問題はあまり見られません。
3.png

私たちから見ると、主な原因は二つあります。
⑴Unity5.6では、もっと多くのレンダリングコンピューティングをメインスレッドから子スレッドに移って処理します。メインスレッドは更に子スレッドの結果を待つ必要があり、それは図2のような「ウェイト」現象を引き起こします。つまりCamera.RenderのSelfコストであります。
⑵根本的な原因は単フレームのレンダリングの量は大きすぎです。例えば質問者があったUIレンダリング、粒子システムレンダリングやスキンメッシュなど。もしこれらの計算量が多きなら、子スレッドに大量のストレスを連れてあげる可能性があります。だから多くのプロジェクトがマルチスレッドレンダリング機能を開いたが、レンダリングの量に注意してください。
Unity 2017のプロジェクトとの接触はまだあまりありませんから、今は統計データを出すことはできません。質問者は先にUnity5.5の最新版に戻してやってみて、問題が解決できるかどうかを観察してください。

補充:
最後のテストが「Unity2017.1にも問題があります」と証明しました。ただウェイトのタイミングが変わりました:renderにはもうウェイトをしませんが、particlesystem.deactivateの時にウェイトをします。ですからこのようなtimelineは現れるかもしれません。
4.png

2017.2はテストしていませんけど、楽観はできませんと推測します。


ロード

Q4:下記の図のように、アセットが解放されていますが、テクスチャはまだメモリの中にあり、参照数は0であります。これはをのように破棄しますか。テクスチャのUIはDestroyで破棄しました、Resources.UnloadUnusedAssets();も執行しました。
5.png

Ref Countは0の場合にはResources.UnloadUnusedAssetsやResources.UnloadAssetでアンインストールできるはずです。この状況に私たちにアドバイスは:
⑴ Resources.UnloadUnusedAssetsをDestroy後の数フレームに執行してみます;
⑵ロード中にこれらのアセットを直接取得し、Destroy後にResources.UnloadAssetで指定されたアセットをアンインストールします。

⑴から試すのはUWAのおすすめです。


UWA Technologyは、モバイル/VRなど様々なゲーム開発者向け、パフォーマンス分析最適化ソリューション及びコンサルティングサービスを提供している会社でございます。

UWA公式サイト:https://jp.uwa4d.com
UWA公式ブログ:https://blog.jp.uwa4d.com

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Unity]マウスカーソルのサイズ変更 C# Windows実行環境

最初に

 ・Unityのバージョンは[2019.4.0f1]です!
 ・Windows環境のみです。

やってること

 ・画像を縮小拡大してマウスカーソルにあげるだけです。

スクリプト

GameSetting.cs
using UnityEngine;
using UnityEngine.AddressableAssets;

public class CursorManager : MonoBehaviour {
    public Texture2D sprite;
    public CursorMode cursorMode = CursorMode.ForceSoftware;
    public Vector2 hotSpot = Vector2.zero;
    public int size;
    private void Update() {
        if(Input.GetKeyDown(KeyCode.D)) {
            Cursor.SetCursor(ResizeTexture(sprite,size,size)), hotSpot, cursorMode);
        }
    }
    static Texture2D ResizeTexture(Texture2D srcTexture, int newWidth, int newHeight) {
        var resizedTexture = new Texture2D(newWidth, newHeight, TextureFormat.RGBA32, false);
        Graphics.ConvertTexture(srcTexture, resizedTexture);
        return resizedTexture;
    }
}

(使い方)上記のスクリプトを適当な汎用Objにアタッチします。

なんやかんやセット
無題.png

結果

Dキーをプッシュ
無題.png

※何度も変更していると画像がぼやけるのでソースを修正。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【C#】シェーカーソートとは(コメント付き)

シェーカーソートとは?

バブルソートを、効率がよくなるように改良したもの。別名は、双方向バブルソート。
場合によっては普通のバブルソートより遅くなります。

アルゴリズム

バブルソートで1回スキャンを行うと、最後の要素1個がスキャン範囲中最大であることが分かり次回のスキャン範囲を1狭めることができる。さらに、このスキャンの最後で連続してm個の要素の交換が行われていなければ、そのm個についてはソート済みであることが分かるので、次回のスキャン範囲をm狭めることができる。この工夫で、後半が殆ど整列済みのデータに対してバブルソートが高速に行えるようになる。by.wiki

実行結果

しぇーかーそーと1.gif

サンプルコード

bool swapFlag = false;
int[] ShakerSort(int[] _array)
{
    while (true)
    {
        swapFlag = false;
        //配列の回数分回す
        for (int i = 0; i < _array.Length-1; i++)
        {
            //比較元より大きければ入れ替え
            if (_array[i] > _array[i + 1])
            {
                int x = _array[i];
                _array[i] = _array[i + 1];
                _array[i + 1] = x;
                swapFlag = true;
            }
        }
        for (int i = _array.Length - 1; i > 0; i--)
        {
            //比較元より大きければ入れ替え
            if (_array[i] < _array[i - 1])
            {
                int x = _array[i];
                _array[i] = _array[i - 1];
                _array[i - 1] = x;
                swapFlag = true;
            }
        }
            //一度も入れ替え処理が通らなければ
        if (swapFlag==false)
        {
            break;
        }
    }
        //Sortした結果を返す
    return _array;
}

まとめ

まだまだ早くなりますがとりあえずこれがシェーカーソートとなります。
時間があり次第これを改善したシェーカーソートを載せます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【C#】シェーカーソートとは(コード付き)

シェーカーソートとは?

バブルソートを、効率がよくなるように改良したもの。別名は、双方向バブルソート。
場合によっては普通のバブルソートより遅くなります。

アルゴリズム

バブルソートで1回スキャンを行うと、最後の要素1個がスキャン範囲中最大であることが分かり次回のスキャン範囲を1狭めることができる。さらに、このスキャンの最後で連続してm個の要素の交換が行われていなければ、そのm個についてはソート済みであることが分かるので、次回のスキャン範囲をm狭めることができる。この工夫で、後半が殆ど整列済みのデータに対してバブルソートが高速に行えるようになる。by.wiki

実行結果

しぇーかーそーと1.gif

サンプルコード

bool swapFlag = false;
int[] ShakerSort(int[] _array)
{
    while (true)
    {
        swapFlag = false;
        //配列の回数分回す
        for (int i = 0; i < _array.Length-1; i++)
        {
            //比較元より大きければ入れ替え
            if (_array[i] > _array[i + 1])
            {
                int x = _array[i];
                _array[i] = _array[i + 1];
                _array[i + 1] = x;
                swapFlag = true;
            }
        }
        for (int i = _array.Length - 1; i > 0; i--)
        {
            //比較元より大きければ入れ替え
            if (_array[i] < _array[i - 1])
            {
                int x = _array[i];
                _array[i] = _array[i - 1];
                _array[i - 1] = x;
                swapFlag = true;
            }
        }
            //一度も入れ替え処理が通らなければ
        if (swapFlag==false)
        {
            break;
        }
    }
        //Sortした結果を返す
    return _array;
}

まとめ

まだまだ早くなりますがとりあえずこれがシェーカーソートとなります。
時間があり次第これを改善したシェーカーソートを載せます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Unity Awakeより前にメソッドを呼ぶ

【Unity】ゲームの起動後 Awakeより前にメソッドを実行する
参考URL:http://tsubakit1.hateblo.jp/entry/2016/07/29/073000

1.タイプ指定しない(タイプ指定しないとOnEnableの後に呼び出される)

public class BootTest : MonoBehaviour
{
    void Awake()
    {
        Debug.Log("Awake");
    }

    void OnEnable()
    {
        Debug.Log("OnEnable");
    }

    [RuntimeInitializeOnLoadMethod()]
    static void Boot()
    {
        Debug.Log("Boot");
    }

    void Start()
    {
        Debug.Log("Start");
    }

    // Update is called once per frame
    void Update()
    {
        Debug.Log("Update");
    }
}

◆ログ

Awake

OnEnable

Boot

Start

Update

2.タイプ指定する(RuntimeInitializeLoadType.BeforeSceneLoad)

※Awakeより前になる

public class BootTest : MonoBehaviour
{
    void Awake()
    {
        Debug.Log("Awake");
    }

    void OnEnable()
    {
        Debug.Log("OnEnable");
    }

    [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
    static void Boot()
    {
        Debug.Log("Boot");
    }

    void Start()
    {
        Debug.Log("Start");
    }

    // Update is called once per frame
    void Update()
    {
        Debug.Log("Update");
    }
}

◆ログ

Boot

Awake

OnEnable

Start

Update

3.タイプ指定するが、Monovihaviorなし(RuntimeInitializeLoadType.BeforeSceneLoad)

public class NonMonobehavior
{
    [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
    public static void Initial()
    {
        Debug.LogError("Initial");
    }

}

◆ログ

Initial

Boot

Awake

OnEnable

Start

Update

※知らない間に呼び出されるので注意が必要、
 知ってないとバグと誤認されるし、この処理にバグがあったら気づくまで大変かも
https://www.urablog.xyz/entry/2018/02/11/164734

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【C#】バブルソートとは(コード付き)

バブルソート(BubbleSort)とは?

バブルソートとは、与えられたデータを大小などの順序通りになるよう並べ替えるソーアルゴリズムの最も基本的な手法の一つです、端から順番に隣接する要素同士を比較・交換していくもの。

どんな動きをするの?

Sort.gif

サンプルコード

    int[] BubbleSort(int[] _array)
    {
        //配列の回数分回す
        for (int i = 0; i < _array.Length; i++)
        {
            //配列の回数分回す
            for (int j = 0; j < _array.Length; j++)
            {
                //比較元より大きければ入れ替え
                if (_array[i] < _array[j])
                {
                    int x = _array[j];
                    _array[j] = _array[i];
                    _array[i] = x;
                }
            }
        }

        //Sortした結果を返す
        return _array;
    }

豆知識

実はこのバブルソートですがある一文字を変えるだけで劇的に入れかえ回数が変化します。

バブルソート改

for (int j = 0; j < _array.Length; j++)

この部分を

for (int j = i; j < _array.Length; j++)

とするだけですごく変わります!

実行結果

Sort1.gif

サンプルコード

    int[] BubbleSort(int[] _array)
    {
        //配列の回数分回す
        for (int i = 0; i < _array.Length; i++)
        {
            //配列の回数分回す
            for (int j = i; j < _array.Length; j++)
            {
                //比較元より大きければ入れ替え
                if (_array[i] < _array[j])
                {
                    int x = _array[j];
                    _array[j] = _array[i];
                    _array[i] = x;
                }
            }
        }

        //Sortした結果を返す
        return _array;
    }

まとめ

たった一か所変更しただけで半分近くに比較する回数を減らすことができました。
みなさんがバブルソートを使う場合このあたりを気を付けましょう。

追記

コメントにてもっと早くなる方法を記載してくださった方がいらっしゃいましたのでそちらの実行結果とソースをのせます!

 int[] BubbleSort(int[] _array)
    {
        //配列の回数分回す
        for (int i = 0; i < _array.Length; i++)
        {
            //配列の回数分回す
            for (int j = i+1; j < _array.Length; j++)
            {
                //比較元より大きければ入れ替え
                if (_array[i] < _array[j])
                {
                    int x = _array[j];
                    _array[j] = _array[i];
                    _array[i] = x;
                }
            }
        }

        //Sortした結果を返す
        return _array;
    }

Sort2.gif

まとめ2

少し早くなっていますね!
ソートってこういう少しの変更でも工程数が変化するのがいいですよね。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SteamVRで自作のデバイスやソフトを簡単にトラッカーとして認識させる方法

はじめに(OpenVR開発の難しさ)

SteamVRでVRゲームを遊んだり、またVRソフトウェアを自作していて、一度は思ったことがあるのではないでしょうか。
"自作の機器をつないでみたい"、"自作のソフトを仮想トラッカーにできたら"と。

SteamVRの公開仕様であるOpenVRを使うと、それはできます。
誰でもHMDやコントローラ、トラッカーを作れるように、ライブラリや仕様が公開されているからです。
https://github.com/ValveSoftware/openvr

APIも公開されている、サンプルもある。使いやすいものです。
サンプルをビルドし、数行書き換えれば動くようになります。

が、簡単に手が出るものではないのが現状ではないでしょうか。
主な原因は以下だと思います。

  • ドライバはDLLとしてSteamVRのVR Serverに読み込ませる必要があり、デバッグが難しい
  • コア部分はC++で書かなければならない。
  • サンプルはあるが極めてシンプルな内容しか無い。
  • ネット上にもあまり情報が揃っていない
  • 古い情報と新しい情報が混在している
  • 公式ドキュメントにはあまり詳しい内容が書いていない(書かれていないこともあります)
  • 行列計算の知識が必要
  • ドライバー座標とルーム座標の変換、対応関係の処理の知識が必要
  • 様々な用途に使えるように作られているが、何を取捨選択したらよいかわからない

では、他に手段があるかというと、あまりありませんでした。
一部特殊ドライバも存在していますが、程度が違えど同様の問題がありました。

VMT - Virtual Motion Tracker

そこで、私はバーチャルモーショントラッカーというものを作りました。
これはドライバのコア部分は完成済みで、姿勢や入力情報をOSC (Open Sound Control)で入力することで、SteamVRに伝えることができるドライバです。

HMDやコントローラとしての機能はあえて殆ど載せず、シンプルにトラッカーとして簡単に使用できることを目指しました。
特殊な機能も乗っていないため、安定して動く(当社比)ことが売りの一つです。

image.png

これにより何が嬉しいか。他の言語からとても簡単に仮想のトラッカーを作り出し、制御できるようになります。

どのくらい簡単かという問、Unityで以下のコードをGameObjectにアタッチするだけで
仮想のトラッカーとして動かすことができるほどです。

※OSC通信アセット: uOSCを導入してください。
 ポートは39570です。

sendme.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class sendme : MonoBehaviour
{
    uOSC.uOscClient client;
    void Start()
    {
        client = GetComponent<uOSC.uOscClient>();
    }

    void Update()
    {
        client.Send("/VMT/Room/Unity", (int)0, (int)1, (float)0f,
            (float)transform.position.x,
            (float)transform.position.y,
            (float)transform.position.z,
            (float)transform.rotation.x,
            (float)transform.rotation.y,
            (float)transform.rotation.z,
            (float)transform.rotation.w
        );
    }
}

C++、通信、その他のことをほとんど考える必要がないということがおわかりいただけますか?

image.png

VMTの仕組み

VMTは以下の構成になっています。

image.png

VMT Driver

C++製OpenVRドライバです。OSCでの仮想トラッカー姿勢の受信と、SteamVRへの姿勢の受け渡しを行います。
ルーム座標に合わせた座標変換や、コントローラ入力の処理、登録処理など行います。
仕組みはほぼOpenVRのサンプルどおりですが、OSC入出力が付いていることが特徴です。

VMT Manager

C#製管理ツールです。ドライバのインストールやアンインストール、設定や調整、動作確認に使用します。
このツールにより、開発者はインストールなどの処理を考えなくて済むようになるのと、
動作確認による切り分け、ドライバ単体ではできない空間の設定ができます。

User Application

姿勢や入力情報をOSCで送信するだけで簡単に作成できます。
OSCが送信できれば言語や環境は何でも構いません。

UnityでもUE4でも、C++でもC#でもJavaでも動くと思います。
同じホスト上にある必要もありません。マイコンなどから直接ドライバに送信することもできます。

補足

ManagerとDriverの間のプロトコルも含めてOSCであり、公開されていますので、
開発者が作成したプログラムでManagerを完全に代替することもできます。
(ただし、ドライバは応答を127.0.0.1に対して送信するため、Managerとして振る舞う場合は、
同一ホスト上で動作している必要があります。)

プロトコル

現時点でのプロトコルの一部を抜粋します。
(今後変更される可能性がありますので、最新版はgithubを参照ください。)
開発者が知る必要があるプロトコルはほぼ以下のみです。
(他に、コントローラとして機能させるためのものもありますが、バインディングを別途行う必要があります。)

識別子 内容
index int 識別番号。現在0~57まで利用できます。
enable int 有効可否。1で有効、0で無効(非接続・非トラッキング状態)
timeoffset float 補正時間。通常0です。
x,y,z float 座標
qx,qy,qz,qw float 回転(クォータニオン)

トラッカー制御

/VMT/Room/Unity index, enable, timeoffset, x, y, z, qx, qy, qz, qw

Unityと同じ左手系、かつ、ルーム空間(ルーム空間変換あり)で仮想トラッカーを操作します。
通常はこれを使用します。

おわりに

これで、簡単に仮想トラッカーや自作トラッカーを作成することができるようになったと思います。
すでに、Webカメラによるキャプチャからフルトラッキングを実現するソフトなどが出ています。

OpenVRのTrackingOverrides機能を使用すると、HMDやコントローラの代替として振る舞わせることも可能です。
https://github.com/ValveSoftware/openvr/wiki/TrackingOverrides

また、今回作成したドライバはMITライセンスで公開しています。
雛形として利用しやすいかとも思いますので、ドライバ開発の参考としてもご活用ください。

参考

過去に書いた記事は以下です。
OpenVR Driver開発メモ

開発には以下の記事やサイトを参考にさせていただきました。
OpenVR driver を作るメモ
Driver Documentation
OpenVR-driver-for-DIY
polygraphene/ALVR
ValveSoftware/driver_hydra

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SteamVRで自作のデバイスやソフトを簡単にトラッカーとして認識させる方法(バーチャルモーショントラッカー)

はじめに(OpenVR開発の難しさ)

SteamVRでVRゲームを遊んだり、またVRソフトウェアを自作していて、一度は思ったことがあるのではないでしょうか。
"自作の機器をつないでみたい"、"自作のソフトを仮想トラッカーにできたら"と。

SteamVRの公開仕様であるOpenVRを使うと、それはできます。
誰でもHMDやコントローラ、トラッカーを作れるように、ライブラリや仕様が公開されているからです。
https://github.com/ValveSoftware/openvr

APIも公開されている、サンプルもある。使いやすいものです。
サンプルをビルドし、数行書き換えれば動くようになります。

が、簡単に手が出るものではないのが現状ではないでしょうか。
主な原因は以下だと思います。

  • ドライバはDLLとしてSteamVRのVR Serverに読み込ませる必要があり、デバッグが難しい
  • コア部分はC++で書かなければならない。
  • サンプルはあるが極めてシンプルな内容しか無い。
  • ネット上にもあまり情報が揃っていない
  • 古い情報と新しい情報が混在している
  • 公式ドキュメントにはあまり詳しい内容が書いていない(書かれていないこともあります)
  • 行列計算の知識が必要
  • ドライバー座標とルーム座標の変換、対応関係の処理の知識が必要
  • 様々な用途に使えるように作られているが、何を取捨選択したらよいかわからない

では、他に手段があるかというと、あまりありませんでした。
一部特殊ドライバも存在していますが、程度が違えど同様の問題がありました。

VMT - Virtual Motion Tracker

そこで、私はバーチャルモーショントラッカーというものを作りました。
これはドライバのコア部分は完成済みで、姿勢や入力情報をOSC (Open Sound Control)で入力することで、SteamVRに伝えることができるドライバです。

HMDやコントローラとしての機能はあえて殆ど載せず、シンプルにトラッカーとして簡単に使用できることを目指しました。
特殊な機能も乗っていないため、安定して動く(当社比)ことが売りの一つです。

image.png

これにより何が嬉しいか。他の言語からとても簡単に仮想のトラッカーを作り出し、制御できるようになります。

どのくらい簡単かというと、Unityで以下のコードをGameObjectにアタッチするだけで
仮想のトラッカーとして動かすことができるほどです。

※OSC通信アセット: uOSCを導入してください。
 ポートは39570です。

あらかじめ説明書に従ってVMTのインストールと設定を済ませてください

sendme.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class sendme : MonoBehaviour
{
    uOSC.uOscClient client;
    void Start()
    {
        client = GetComponent<uOSC.uOscClient>();
    }

    void Update()
    {
        client.Send("/VMT/Room/Unity", (int)0, (int)1, (float)0f,
            (float)transform.position.x,
            (float)transform.position.y,
            (float)transform.position.z,
            (float)transform.rotation.x,
            (float)transform.rotation.y,
            (float)transform.rotation.z,
            (float)transform.rotation.w
        );
    }
}

C++、通信、その他のことをほとんど考える必要がないということがおわかりいただけますか?

image.png

VMTの仕組み

VMTは以下の構成になっています。

image.png

VMT Driver

C++製OpenVRドライバです。OSCでの仮想トラッカー姿勢の受信と、SteamVRへの姿勢の受け渡しを行います。
ルーム座標に合わせた座標変換や、コントローラ入力の処理、登録処理など行います。
仕組みはほぼOpenVRのサンプルどおりですが、OSC入出力が付いていることが特徴です。

VMT Manager

C#製管理ツールです。ドライバのインストールやアンインストール、設定や調整、動作確認に使用します。
このツールにより、開発者はインストールなどの処理を考えなくて済むようになるのと、
動作確認による切り分け、ドライバ単体ではできない空間の設定ができます。

User Application

姿勢や入力情報をOSCで送信するだけで簡単に作成できます。
OSCが送信できれば言語や環境は何でも構いません。

UnityでもUE4でも、C++でもC#でもJavaでも動くと思います。
同じホスト上にある必要もありません。マイコンなどから直接ドライバに送信することもできます。

補足

ManagerとDriverの間のプロトコルも含めてOSCであり、公開されていますので、
開発者が作成したプログラムでManagerを完全に代替することもできます。
(ただし、ドライバは応答を127.0.0.1に対して送信するため、Managerとして振る舞う場合は、
同一ホスト上で動作している必要があります。)

プロトコル

現時点でのプロトコルの一部を抜粋します。
(今後変更される可能性がありますので、最新版はgithubを参照ください。)
開発者が知る必要があるプロトコルはほぼ以下のみです。
(他に、コントローラとして機能させるためのものもありますが、バインディングを別途行う必要があります。)

識別子 内容
index int 識別番号。現在0~57まで利用できます。
enable int 有効可否。1で有効、0で無効(非接続・非トラッキング状態)
timeoffset float 補正時間。通常0です。
x,y,z float 座標
qx,qy,qz,qw float 回転(クォータニオン)

トラッカー制御

/VMT/Room/Unity index, enable, timeoffset, x, y, z, qx, qy, qz, qw

Unityと同じ左手系、かつ、ルーム空間(ルーム空間変換あり)で仮想トラッカーを操作します。
通常はこれを使用します。

おわりに

これで、簡単に仮想トラッカーや自作トラッカーを作成することができるようになったと思います。
すでに、Webカメラによるキャプチャからフルトラッキングを実現するソフトなどが出ています。

OpenVRのTrackingOverrides機能を使用すると、HMDやコントローラの代替として振る舞わせることも可能です。
https://github.com/ValveSoftware/openvr/wiki/TrackingOverrides

また、今回作成したドライバはMITライセンスで公開しています。
雛形として利用しやすいかとも思いますので、ドライバ開発の参考としてもご活用ください。

参考

過去に書いた記事は以下です。
OpenVR Driver開発メモ

開発には以下の記事やサイトを参考にさせていただきました。
OpenVR driver を作るメモ
Driver Documentation
OpenVR-driver-for-DIY
polygraphene/ALVR
ValveSoftware/driver_hydra

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む