20210604のUnityに関する記事は7件です。

Texture Streaming Mipmapの使用に関する疑問

今回の主な話題:Texture Streaming Mipmapの使用に関する疑問、UE4マテリアルで平行光の向きを取得する方法、SubmeshのあるSkinnedMeshRendererがBakeMeshを使う問題、PPS v2 後処理の最適化、動的にロードされたシャドウのベイク情報が無効する。 Texture Streaming Q1: プロジェクトは 2018.4 にアップグレードしました。今年中にリリースする予定があります。 1、Unity が StreamingMipmap ストリームを追加したのを見て、loadedMipmapLevelがテクスチャによって現在使用されている Mipmap levelであるかどうかを知りたいです。公式からの説明は「Which mipmap level is currently loaded by the streaming system.」です。試してみました、1つのオブジェクトに近づいても離れても、この値は固定です。そうではない場合には、現在テクスチャのMipmap levelを取得するにはどうすればよいですか? 2、Unity 2018.4はJob Systemを使えますか?公式が「これは実験的なものでクラッシュする可能性がある」と言いていますが、誰かがこれまたはStreamingMipmapをオンにしたリリースしたプロジェクトありますか? この値は実機では正確ですが、エンジン端末でいくらのロードBugが存在します。私たちのテストにもこの問題がありました。当時は、プロジェクトを再び創建して、再インポートした後に実機で正しく表示しました。 UWA DAY 2019 では、Texture Streamingのテストで発生した問題のいくつかと多数のStreamingロジックの説明について詳しく説明しました。問題主は興味があれば、確認することもできます、「Unityエンジンのローディングモジュール とメモリ管理の数量化分析や最適化方法」(中国語注意)。不必要なテストや手探りの時間を大幅に節約でき、キーボードを壊したり、コンピューターを投げたりするほどのクラッシュから解放されます。 (信じてください、この機能を調査していたとき、私たちの気分はこんな感じでした!) 2つ目のご質問についてですが、回答が難しいです。今まで、リリースしたプロジェクトでは見ることはありませんでした。ここで1つの記事があって、問題主や現在TextureStremingJobを使っているチームにお勧めします。「TextureStreamingJob クラッシュ分析」 Rending Q2: Unity では、_WorldSpaceLightPos0を介して平行光の向きを取得できます。これは、Unity がこのuniformが頻繁に使われていると考え、自動的に渡されるためです。 Unreal のマテリアル ノードで長い間検索しましたが、Light Vectorのような平行光の方向を示すノードが見つかりませんでした。Unrealでは、Blueprintを介して自分が外部から渡せますだけですか?SetGlobalVector のように、Blueprint + Material Parameter Collectionsは私の要件に満たすことができることが知っています。ですから、マテリアル ノードから平行光の方向を直接取得できるかどうかを聞きたいです、自分が外部から渡したくないです。 方法ならあります。下図のように、1つのcustomノードを作成し、パラメーターに図のようなコードを入力すればいいです。Material Functionに作成すると、どこでも再利用できます。 しかし、個人的には推奨していません。明らかにこれは非常にhackな方法です。この方法は、最下層Shaderの実現に依頼し、すべてのバージョンで使用できると保証できません(4.22 テスト)。 その原因は、Unrealはマテリアルエディターが提供する主なノードが、すべてのライティング プロセス(forward shading/defer shading)に適用すると仮定しましたからです。しかし、Light Directionを取得する設定は、明らかに複数の光源を持つDeferred Shadingに適応していません。ですから、公式自体はノードを提供しません。 ちなみに、このやり方はForward Shadingでしか使えません。Unityに似ていないで、自分が作成したShaderに対して、Epic公式は互換性を気にしません。このバージュンで使用できますが、次のバージュンがエンジンShaderを変更する場合には、まだ使用できるかどうかは分かりません(しかし、このコードはかなりの数のバージョンに適応できようです)。 ですから、問題主が言及した方法は、公式からの推奨です。 コード、 code #ifdef EyeAdaptationStruct #if MATERIALBLENDING_ANY_TRANSLUCENT return TranslucentBasePass.Shared.Forward.DirectionalLightDirection; #else return OpaqueBasePass.Shared.Forward.DirectionalLightDirection; #endif #else return 0; #endif Shader Q3: サブメッシュを持つ SkinnedMeshRenderer が BakeMesh を使用して残像を作成すると、サブメッシュの最初のメッシュのみがベイクされることがわかりました。このキャラクターには 4 つのサブグリッドがあります。同様の問題が発生しましたか? (Unityバージュンは2017.4.18f1であり) 本来の実現方式はこのように、Graphicsを使ってMeshを直接描画することです。これは、結果から見るとサブメッシュをサポートしません。 Graphics.DrawMesh(ghostMesh.mesh, ghostMesh.position, ghostMesh.rotation, ghostMesh.material, 0, Camera.main); 修正した後、GameObjectをインスタンス化し、サブメッシュの数に対応するマテリアルを掛けたら、正常に表示できます。 public void InstanceGhost() { if (mesh.subMeshCount > 0) { ghostGo = new GameObject("ghostGo"); SkinnedMeshRenderer smr = ghostGo.AddComponent<SkinnedMeshRenderer>(); smr.sharedMesh = mesh; Material[] mats = new Material[smr.sharedMesh.subMeshCount]; for (int i = 0; i < mesh.subMeshCount; i++) { mats[i] = material; } smr.materials = mats; ghostGo.transform.parent = GetGhostRoot(); ghostGo.transform.position = position; ghostGo.transform.rotation = rotation; } } Rending Q4: 皆さんにお聞きたいです。現在の後処理の最適化は、この方面から始めますか? ① Unity PostProcessは、コード方面での最適化スペースが限られており、プロジェクトの使用によってコードから若干の詳細を削除できます。この前にコード上の最適化はあまり多くないで、主にBugを解決するためでした。 使用上では、ポストエフェクトを計画できます。最も重要なポストエフェクトはBloomと ColoeGrading であります。その中、Bloomのパフォーマンスコストはより高いで、画面品質の高い場合にオンになれます。中品質では、ColorGraidingのみを使用し、画面の一貫性を保持します。低配置ではポストエフェクトをオフになされます。そして、他の画面を改善できる効果も多いです。例えば、被写界深度、SSAO、アンチエイリアス、Chromatic Aberration、Vignetteなど、状況によりオンやオフをデザインできます。デフォルトではオフです。 ② PostProcess Stack V2 を使用している場合は、ここを参照できます。 https://www.bilibili.com/video/av53641372/?share_medium=web(中国語注意) この中の部分は、パフォーマンスに影響の大きいオプションについてたくさん説明しました。 Lightmap Q5: 現在、ベイク情報をシーンから分離する要件があります。つまり、私のプレハブはあらゆるシーンでシャドウを表示する必要があります。 インターネットで情報を検索したところ、プレハブのライトデータのlightmapIndexとlightmapScaleOffsetを記録し、ライトマップの設定をlightmapsettingにロードすればいいです。 しかし、テスト過程に、ここの設定は成功しました。 ここでも設定されていますが、有効になりません。 プレハブ全体はまだ黒です。 この設定を任意のデフォルトDataにすれば、前の流れで行われば有効になります。これはなぜですか? シーンを開いた後、またはPrefabをインスタンス化した後、手動で値を一回割り当てる必要があります。 UWA Technologyは、モバイル/VRなど様々なゲーム開発者向け、パフォーマンス分析と最適化ソリューション及びコンサルティングサービスを提供している会社でございます。 今なら、UWA GOTローカルツールが15日間に無償試用できます!! よければ、ぜひ! UWA公式サイト:https://jp.uwa4d.com UWA GOT OnlineレポートDemo:https://jp.uwa4d.com/u/got/demo.html UWA公式ブログ:https://blog.jp.uwa4d.com
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Unity 一定時間毎に動作させるコルーチン(Coroutine)の使い方 IEnumeratorとwhileの組み合わせ

0.0 はじめに コルーチンはオブジェクトを一定時間毎に動作させるなど便利な使い方ができます。基本的な使い方を備忘録としてここに記します。 1.0 コルーチンとは コルーチンは1フレーム内では処理が終わらず、数フレームあるいはそれ以上かけて処理を実行する必要がある場合に用いられます。 イメージは処理を一時停止し、その間に別のプログラムを実行、終わったら処理を再開しているような感じで、処理方法に関係なく規則的に動作するオブジェクトやUIを作るときに役に立ちます。 データ型のIEnumerator(イテレーター)を返り値としてもつメソッドをつくり、そのメソッドをStartCoroutine()で呼び出します。 2.0 毎フレーム実行 StartCoroutine()で一度Loop()を呼んであげると毎フレームループします。 yield returnのところで次のフレームまで待ちます。 test.cs void Start () { // コルーチンを設定 StartCoroutine(Loop()); } // 毎フレームループします private IEnumerator Loop() { while (true) { yield return null; Debug.Log("Loop"); } } 3.0 指定した回数だけ実行 下記は10フレームだけループするプログラムです。 同じくyield returnのところで次のフレームまで待ちます。 test.cs void Start () { // コルーチンを設定 StartCoroutine(Loop(10)); } // frameで指定したフレームだけループします private IEnumerator Loop(int frame) { while (frame > 0) { yield return null; Debug.Log("Loop"); frame--; } } 4.0 秒数を指定して実行 yield return のところをWaitForSecondsにします。引数(float)で秒の指定が出来ます。 指定した秒数だけ待ちます。 test.cs void Start () { // コルーチンを設定 StartCoroutine(Loop(10f)); } // secondで指定した秒数ループします private IEnumerator Loop(float second) { while (true) { yield return WaitForSeconds(second); Debug.Log("Loop"); } }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

DrawMeshInstanced が Unity2018 で有効にならない問題

今回の主な話題:Unity2018 でDrawMeshInstancedのアップグレードが有効にならない問題、UnityのShaderローディング解析が重複する問題、解像度をロックする方法を使用してパフォーマンスをテストする問題、動的アトラスの作成時にエラーが発生する問題、同じShaderが同じアトラスをマージできない問題。 DrawCall Q1: Shaderのパッケージ化に対する私たちのアプローチは、通常、プロジェクトのすべてのShaderを1つのAssetBundleにパッケージします。これにより、Shaderの冗長性を回避し、Shaderもホットアップデートできるようになります。Unity 5.5.4でこうすると問題ありません、すべてのShaderは上手くレンダリングでき、Instancingやホットアップデートに使えます。 ただし、Unity 2018.3 では、シェーダーとそれが使用するShaderが同じ AssetBundle にない場合、正常にレンダリング できますが、このShaderを使ってGPUInstancingを行うと有効になれません。Unity 2018.4 を試しましたが、同じでした。 テストプロジェクトは質問ページで取れ、Unityを使ってPCプラットフォームで再現できます。これはUnityのBugであるかどうかを確認できますか?それとも、Shaderのパッケージ化と使用方法に何か問題がありますか? Unity が Shader Stripping 機能を追加した後、多くのKeywords損失の問題が発生します。 GPU Instancingを成功に行ったShaderにはKeywords:INSTANCING_ONがあることがわかりますが、Shaderの分離パッケージ化に実行するShaderをロードしますと、Keywordsが失います。 ですから、解決策の1つは、Project SettingにStripping機能をキャンセルし、Keep Allを選択することです。 2 番目の解決策は、GPU Instancing機能が使用されているシーンにパッケージ化することです。これで、Keywordsはstripされません。当然、解決策2は少し実用的ではありません。まだMaterialを保持し、一緒にパッケージすることをお勧めします。 アトラス Q2: 私たちのプロジェクトのShaderのローディングと解析は ShaderVariantCollectionを使用していますが、ロードする時にそのような問題が見つかりました。 Shader的Bundleをロードすると、Shaderを執行するParse方法が見つけられ、すべてのShaderを解析しました。 そして、WarmUpを執行する際、もう一回解析しました。 Unity5.X 以降のShaderのローディングと解析が分離されていると聞きましたが、混乱です。誰かが教えていただきませんか?(Unityバージョン:2018.2.19f1) 個人的な理解は以下とおります。 1、ShaderとShadervariantが同じBundleにパッケージし、ShadervariantにShaderを編集することに対して、Shadervariantには依存性があります。ゲームが起動する時、ShaderのあるBundleをロードし、中からShadervariantをロードします。依頼しているShaderのParseが自動的にトリガーされ、対応する変体は1つの変体Shaderを生成してGPUに提出しますが、この時の変体数は完全ではありません。 2、Shadervariant.warmupを執行する時、残った変体の生成をトリガーし、GPUに提出します。 3、2ステップの操作を行います。1つ目はShader.Parseであり、部分の変体Shaderも生成します。2つ目に、Warmupが残った変体を生成します。 以下は私の個人的なテストのスクリーンショットです。 4、コール回数を比較します。分かりやすいのは、1回目のShader.Parseはすべての変体の生成を全部執行きっていませんでした。ShaderのあるBundleを直接にロードし、AllAssetをロードすることもテストしましたが、すべてのShader解析をトリガーし、Shadervariantと関係していますかどうかが問いません。 すべてのShaderがコンパイルされている場合でも、Warmupはまだ変体Shaderの生成を執行することが分かりやすいです。ですから、最初のステップでロードする時に、すべての変体Shaderの生成過程を執行きりではないようです。 パフォーマンステスト Q3: 現在、プロジェクトがパフォーマンス テストを行う時に、SoC(System on a Chip)によって分割し、同時に固定的な解像度(例えば1024*768)にロックします。この方法で取ったデータは正しいかどうかを知りたいです。 たとえば、2 つのスマホの配置は同じで、1つは4K 画面であり、も1つは1080P画面です。1024*768解像度にロックすると、パフォーマンス上は同じですか? このような画面の拡大縮小方法はたくさんのプロジェクトによく見えます。レンダリング 内容が同じ場合に、4K画面も1K画面もGPUの計算圧力は同じです。唯一の違いは最終的な拡張RTサイズが違いますが、この部分のコストは低いです。ただし、設備のGPU能力の違う(一般的に、4K画面の持つ設備は、GPUパフォーマンス も強い)次第で、最終的にGPUでの時間コストが異なります。 Overdrawに対しては、同じように画面解像度の違いによっても異なります。UWA のパフォーマンス レポートでは、本物のOverDrawはただ画面の1ピースということが見られます。 Unreal Q4: 動的アトラスの作成中にエラーが発生しました。Unsupported texture format – needs to be ARGB32, RGBA32, RGB24, Alpha8 or one of float formats. 本来なら、動的アトラスを利用してDrawCallとメモリを最適化したいですが、上記の形式のTexture2Dしか作成できないようです。他の形式を使用すればtexture.SetPixels時に上記のエラーが出てしまいます。しかし、上記の形式のメモリ占用は高すぎですが、何か他の形式のテクスチャを生成する方法ありませんか? コード、 code Texture2D texture = new Texture2D(textureSize, textureSize, TextureFormat.DXT5, false); Color[] fillColor = texture.GetPixels(); for (int i = 0; i < fillColor.Length; ++i) fillColor[i] = Color.clear; texture.SetPixels(fillColor); まず、問題を区別する必要があります。これはオフラインで生成するものですか、それともRuntimeで生成しますか?オフラインの場合、生成する方法が多すぎるのため、ここで問題主はRuntime生成と仮定します。SetPixelsに対して、Unityドキュメントには「This function works only on RGBA32, ARGB32, RGB24 and Alpha8 texture formats. For other formats SetPixels is ignored. The texture also has to have Is Readable flag set in the import settings.」と明確に書いてあります。そして、問題主New Texture2Dが使うのはDXT5形式です、これは一般的にPCでのみ使用されます。 1 つの方法は、RGBA32 を使用して生成し、圧縮後にインポートすることです。しかし、私の知る限り、Unity ネイティブは実行時に画面圧縮することをサポートしていません。したがって、個人的には、もう1つの方法を使うことをお勧めします。つまり、RenderTextureを使用してピクセルをコピーすることです。CPUのSetPixelsに比べて、この方式はより早いです。フォーマットに関する、RenderTextureにはたくさんのフォーマットが選択できます。例えば、RGB565やARGB4444など、メモリ上はRGB32より小さいです。 Mesh Q5: マップノードのDrawCall を処理する時に、アトラスもShaderも同じ2つのアトラスがマージできません。「Objects have different materials.」と表示します。 次に、メモリによって確認すると、この引用がたくさんあることが分かりました。それぞれの中には具体的に引用するアセットです。 ① この原因は現在見つかりました。その原因は、組み込みのシェーダーがさまざまなプレハブで使用されているため、1つずつにシェーダー情報があります。異なるMaterialsを導きます。これを解決するために、これらのプレハブを同じアセットに引用する必要があります。 しかし問題があります。アセットが組み込みのShaderとTextureを引用するため、組み込んだ内容をファイルにインポートしてから引用をリセットする必要ありますか?そしてインポートした内容をAssetBundleパッケージにしますか?では、AssetBundleにパッケージ化するはずなのに、なぜEditor目次に置きますか?最後に、引用も設置きりましたが、なぜ復元する必要がありますか? ② 復元する方法は、最初バージョンが採用した方法です、もう廃棄しました。現在は、直接プロジェクトに置くことをお勧めします。これにより、新たに創建したオブジェクトは抽出されたShaderとマテリアルを直接使用します。抽出されたアセットの名前と組み込みアセットを同じように保持すれば、新しく作成されたアセットがAssetsのほうを優先に使用します。 UWA Technologyは、モバイル/VRなど様々なゲーム開発者向け、パフォーマンス分析と最適化ソリューション及びコンサルティングサービスを提供している会社でございます。 今なら、UWA GOTローカルツールが15日間に無償試用できます!! よければ、ぜひ! UWA公式サイト:https://jp.uwa4d.com UWA GOT OnlineレポートDemo:https://jp.uwa4d.com/u/got/demo.html UWA公式ブログ:https://blog.jp.uwa4d.com
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Oculus Quest 2 で話してみよう!Part 2

目次 いいタイミングで頷いてもらおう!(Part1) 「好き」と言ったら「好き」と返して欲しいんだ…。(Part2) 「好き」と言ったら「好き」と返して欲しいんだ…。 今回のPartの内容 今回は特定の単語やフレーズに対して、アニメーションをとってもらいます。 本当はTransformerをもちいたモデルはあるので、APIを作ろうかと思ったのですが、アニメーションの作成上の問題により、それは今度に回します(笑)。 データの流れとしては、 音声入力 -> 音声保存 -> 一定時間以上の録音ならAzureのCognitiveService -> 単語・フレーズが含まれているかのチェック -> 特定の単語・フレーズが含まれているならば、アニメーションを取る となります。 つまり、今回は - 音声のwavファイルの保存 - AzureのCognitiveServiceのSpeechToTextにより、音声を文章化する。 - 特定の単語かフレーズが含まれているかをチェックする。 の3つの機能を実装していきます。 音声の保存 今回はwavファイルをunity上で保存します。 ネット上にある様々な方の力をお借りしました。 簡単なコードの説明をしたいと思います。 まず、以下のスクリプトでwavファイルの保存開始時にHeaderを書き込みます。 wavファイルの保存終了時にファイルのHeaderを書き込むので、Headerの確保だけをしています。 using (var fileStream = new FileStream(SSTFileName, FileMode.Create)) { byte[] headerSpace = new byte[HEADER_SIZE]; fileStream.Write(headerSpace, 0, headerSpace.Length); } つぎに録音中に音声データを保存します。 positionにはMicrophoneで取った音声データの位置が記録されています。 このpositionの位置をもとにwavファイルにGetDataから、データを取り出し、保存します。 実際にwavファイルに書き込みを行っているのはWavBufferWrite関数で行っています。 int position = Microphone.GetPosition(null); if (position < 0 || head == position) { return; } if (isRecording) { var waves = new float[listenSource.clip.samples * listenSource.clip.channels]; listenSource.clip.GetData(waves, 0); //GetDataだとrecordSeconds の間のデータがすべて入っている。 List<float> SSTAudioData = new List<float>(); if (head < position) { for (int i = head; i < position; i++) { SSTAudioData.Add(waves[i]); } } else { for (int i = head; i < waves.Length; i++) { SSTAudioData.Add(waves[i]); } for (int i = 0; i < position; i++) { SSTAudioData.Add(waves[i]); } } using (var fileStream = new FileStream(SSTFileName, FileMode.Append)) { WavBufferWrite(fileStream, SSTAudioData); } } head = position; } つぎに、音声の保存終了時です。  using (var fileStream = new FileStream(SSTFileName, FileMode.Open)) {          WavHeaderWrite(fileStream, listenSource.clip.channels, SAMPLING_FREQUENCY);       } 以下にある、WavHeaderWriteにより、Header部分に書き込みをしています。 private void WavBufferWrite(FileStream fileStream, List<float> dataList) { foreach (float data in dataList) { Byte[] buffer = BitConverter.GetBytes((short)(data * RESCALE_FACTOR)); fileStream.Write(buffer, 0, 2); } fileStream.Flush(); } private void WavHeaderWrite(FileStream fileStream, int channels, int samplingFrequency) { //サンプリング数を計算 var samples = ((int)fileStream.Length - HEADER_SIZE) / 2; fileStream.Seek(0, SeekOrigin.Begin); Byte[] riff = System.Text.Encoding.UTF8.GetBytes("RIFF"); fileStream.Write(riff, 0, 4); Byte[] chunkSize = BitConverter.GetBytes(fileStream.Length - 8); fileStream.Write(chunkSize, 0, 4); Byte[] wave = System.Text.Encoding.UTF8.GetBytes("WAVE"); fileStream.Write(wave, 0, 4); Byte[] fmt = System.Text.Encoding.UTF8.GetBytes("fmt "); fileStream.Write(fmt, 0, 4); Byte[] subChunk1 = BitConverter.GetBytes(16); fileStream.Write(subChunk1, 0, 4); UInt16 _one = 1; Byte[] audioFormat = BitConverter.GetBytes(_one); fileStream.Write(audioFormat, 0, 2); Byte[] numChannels = BitConverter.GetBytes(channels); fileStream.Write(numChannels, 0, 2); Byte[] sampleRate = BitConverter.GetBytes(samplingFrequency); fileStream.Write(sampleRate, 0, 4); Byte[] byteRate = BitConverter.GetBytes(samplingFrequency * channels * 2); fileStream.Write(byteRate, 0, 4); UInt16 blockAlign = (ushort)(channels * 2); fileStream.Write(BitConverter.GetBytes(blockAlign), 0, 2); UInt16 bps = 16; Byte[] bitsPerSample = BitConverter.GetBytes(bps); fileStream.Write(bitsPerSample, 0, 2); Byte[] datastring = System.Text.Encoding.UTF8.GetBytes("data"); fileStream.Write(datastring, 0, 4); Byte[] subChunk2 = BitConverter.GetBytes(samples * channels * 2); fileStream.Write(subChunk2, 0, 4); fileStream.Flush(); } SpeechToTextによる音声の文章化 今回はAzureのCognitiveServiceを利用します。 公式ドキュメントを参考にして、SpeechToTextのAPI Keyを取得してください。 また、RestAPIではなくSDKを利用しますので、公式ドキュメントを参考にして、SpeechToTextSDKをUnityのプロジェクトにImportします。 次に、非同期処理を行い、文章を取得するので、UniTaskのPackageをImportしてください。 つぎに、以下のSTTSDK.csを用意します。 subscription_keyはご自身のAPIのkeyにし、regionは"japaneast"か"japanwest"の適切な方にしてください。 using System.Collections; using System.Collections.Generic; using System.IO; using Cysharp.Threading.Tasks; using Microsoft.CognitiveServices.Speech; using Microsoft.CognitiveServices.Speech.Audio; using UnityEngine; public class STTSDK { string subscription_key = "YourAPIKey"; string region = "YourLocation"; string location = "ja-JP"; public async UniTask<string> STT(string wavFilePath) //通常用。SamplingRate = 16000[Hz] { var speechConfig = SpeechConfig.FromSubscription(subscription_key, region); speechConfig.SpeechRecognitionLanguage = location; // Speech config 直下にLocationを設定して言語を設定する。 using var audioConfig = AudioConfig.FromWavFileInput(wavFilePath); using var recognizer = new SpeechRecognizer(speechConfig, audioConfig); var result = await recognizer.RecognizeOnceAsync(); Debug.Log($"Recognized Line: = {result.Text}"); return result.Text; } } 以上で、SpeechToTextの下準備は完了です。 特定の単語・フレーズのチェック 特定の単語やフレーズが文章に含まれているかを判定します。 以下のWordControllder.csを用意してください。 using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Text; using UnityEngine; using UnityEngine.Scripting; using UnityEngine.UI; public class WordController { // Start is called before the first frame update public List<String> wordList; private List<String> paramNameList; private List<int> paramIntList; private TextAsset csvFile; // Update is called once per frame public void CheckWord(string sentence, out string paramName ,out int paramInt) { paramName = "none"; paramInt = 0; for(int i= 0; i<wordList.Count; i++) { var word = wordList[i]; if (sentence.Contains(word)) { paramName = paramNameList[i]; paramInt = paramIntList[i]; break; } } } public void ReadFile() { wordList = new List<string>(); paramNameList = new List<string>(); paramIntList = new List<int>(); csvFile = Resources.Load("wordAnimation") as TextAsset; ; StringReader reader = new StringReader(csvFile.text); while(reader.Peek() >= 0) { string[] cols = reader.ReadLine().Split(','); if(cols.Length!=3){ Debug.Log("Wrong CSV Format!"); } wordList.Add(cols[0]); paramNameList.Add(cols[1]); paramIntList.Add(int.Parse(cols[2])); } } } 内容としては、 ReadFile関数でResourcesファイルからCSVファイルを読み取り、初期化を行う WordListには判別したい単語・フレーズが入る paramNameにはAnimatorの変数名が入る paramIntにはAnimatorのConditionsで用いる数字が入る CheckWord関数で単語・フレーズが含まれているかをチェックする。 の以上5点があり、何がしたいのかわかると思います(笑)。 つまり、特定の単語やフレーズに応じて、Animatorの変数名とConditionsで用いるInt型の数値が返るので、それを用いて、Animationを取るようにする、ということです。 3つを組み合わせてみよう! 今までの3つの機能を前回使用した、GetVoice.csに統合したいと思います。 統合後のGetVoice.csファイルが以下になります。 using System; using System.Collections; using System.Collections.Generic; using System.IO; using UnityEngine; using UnityEngine.UI; public class GetVoice1 : MonoBehaviour { [SerializeField] AudioSource listenSource; [SerializeField] Animator animator; //private fields private float gain = 8000.0f; float volume; float frequency; bool isSpeaking; bool isRecording; float bufferTimer; //話した後の余韻の時間 or 言葉に詰まった時の時間 の計測時間 float speakingTimer; //実際に話している時間 int head; private string SSTFileName; private string CSVFilePath; //constant values const int RECORD_SECONDS = 2; const int SAMPLING_FREQUENCY = 16000; // MSのSSTはサンプリングレートが16000Hz const int FFTSAMPLES = 2 << 8; //256bitのサンプルをFFTでは選んでとる。 const int HEADER_SIZE = 44; const float RESCALE_FACTOR = 32767; const float MIN_FREQ = 100.0f; //母音のFrequency は 100Hz 以上 1400Hz以下。 ただし、子音字は異なる。 const float MAX_FREQ = 1400.0f; const float MIN_VOLUME = 1.0f; const float BUFFER_TIME = 1.6f; //話した後の余韻の時間 or 言葉に詰まった時の時間。 const float SST_SPEAKING_TIME = 4.0f; //my defined class // private STTSDK sttsdk; private WordController wc; // Start is called before the first frame update void Start() { #if UNITY_EDITOR gain = 2000.0f; #endif SSTFileName = System.IO.Path.Combine(Application.persistentDataPath, "SST.wav"); #if UNITY_EDITOR SSTFileName = System.IO.Path.Combine("C:\\Users\\Atsuya\\UnityProjects", "TEMPTATION", "SST.wav"); #endif sttsdk = new STTSDK(); wc = new WordController(); wc.ReadFile(); //ファイルの読み込み GetMic(); } void Update() { Waiting(); } private void GetMic() { while (Microphone.devices.Length< 1) { } string device = Microphone.devices[0]; listenSource.loop = true; listenSource.clip = Microphone.Start(device, true, RECORD_SECONDS, SAMPLING_FREQUENCY); while (!(Microphone.GetPosition(device) > 0)) { } listenSource.Play(); } async private void Waiting() { CalculateVowel(); if (MIN_FREQ < frequency && frequency < MAX_FREQ && MIN_VOLUME < volume) //しゃべり始めの時間 { isSpeaking = true; bufferTimer = 0.0f; speakingTimer += Time.deltaTime; //始めてレコーディングを開始したとき if (!isRecording) { isRecording = true; bufferTimer = 0.0f; speakingTimer = 0.0f; using (var fileStream = new FileStream(SSTFileName, FileMode.Create)) { byte[] headerSpace = new byte[HEADER_SIZE]; fileStream.Write(headerSpace, 0, headerSpace.Length); } } } else if (isSpeaking && volume > MIN_VOLUME) { //子音字をしゃべっていると判定 bufferTimer = 0.0f; speakingTimer += Time.deltaTime; } else if (isSpeaking && bufferTimer < BUFFER_TIME) // 余韻の時間 { bufferTimer += Time.deltaTime; speakingTimer += Time.deltaTime; } else { bufferTimer = 0.0f; // 後で、speakingTimerの条件処理あり! isSpeaking = false; if (isRecording) { isRecording = false; using (var fileStream = new FileStream(SSTFileName, FileMode.Open)) { WavHeaderWrite(fileStream, listenSource.clip.channels, SAMPLING_FREQUENCY); } if (speakingTimer > SST_SPEAKING_TIME) //もし、一定時間以上話していたら、音声を認識する。 { Debug.Log("Send Message! \n Speaking time is" + speakingTimer); string sentence; string paramName; int paramInt ; sentence = await sttsdk.STT(SSTFileName); wc.CheckWord(sentence, out paramName, out paramInt); if (paramInt != 0) { Debug.Log("Start Animation"); animator.SetInteger(paramName, paramInt); speakingTimer = 0.0f; } } AizuchiAnimation(speakingTimer); } speakingTimer = 0.0f; } int position = Microphone.GetPosition(null); if (position < 0 || head == position) { return; } if (isRecording) { var waves = new float[listenSource.clip.samples * listenSource.clip.channels]; listenSource.clip.GetData(waves, 0); //GetDataだとrecordSeconds の間のデータがすべて入っている。 //head と position がおかしい? List<float> SSTAudioData = new List<float>(); if (head < position) { for (int i = head; i < position; i++) { SSTAudioData.Add(waves[i]); } } else { for (int i = head; i < waves.Length; i++) { SSTAudioData.Add(waves[i]); } for (int i = 0; i < position; i++) { SSTAudioData.Add(waves[i]); } } using (var fileStream = new FileStream(SSTFileName, FileMode.Append)) { WavBufferWrite(fileStream, SSTAudioData); } } head = position; } private void WavBufferWrite(FileStream fileStream, List<float> dataList) { foreach (float data in dataList) { Byte[] buffer = BitConverter.GetBytes((short)(data * RESCALE_FACTOR)); fileStream.Write(buffer, 0, 2); } fileStream.Flush(); } private void WavHeaderWrite(FileStream fileStream, int channels, int samplingFrequency) { //サンプリング数を計算 var samples = ((int)fileStream.Length - HEADER_SIZE) / 2; fileStream.Seek(0, SeekOrigin.Begin); Byte[] riff = System.Text.Encoding.UTF8.GetBytes("RIFF"); fileStream.Write(riff, 0, 4); Byte[] chunkSize = BitConverter.GetBytes(fileStream.Length - 8); fileStream.Write(chunkSize, 0, 4); Byte[] wave = System.Text.Encoding.UTF8.GetBytes("WAVE"); fileStream.Write(wave, 0, 4); Byte[] fmt = System.Text.Encoding.UTF8.GetBytes("fmt "); fileStream.Write(fmt, 0, 4); Byte[] subChunk1 = BitConverter.GetBytes(16); fileStream.Write(subChunk1, 0, 4); UInt16 _one = 1; Byte[] audioFormat = BitConverter.GetBytes(_one); fileStream.Write(audioFormat, 0, 2); Byte[] numChannels = BitConverter.GetBytes(channels); fileStream.Write(numChannels, 0, 2); Byte[] sampleRate = BitConverter.GetBytes(samplingFrequency); fileStream.Write(sampleRate, 0, 4); Byte[] byteRate = BitConverter.GetBytes(samplingFrequency * channels * 2); fileStream.Write(byteRate, 0, 4); UInt16 blockAlign = (ushort)(channels * 2); fileStream.Write(BitConverter.GetBytes(blockAlign), 0, 2); UInt16 bps = 16; Byte[] bitsPerSample = BitConverter.GetBytes(bps); fileStream.Write(bitsPerSample, 0, 2); Byte[] datastring = System.Text.Encoding.UTF8.GetBytes("data"); fileStream.Write(datastring, 0, 4); Byte[] subChunk2 = BitConverter.GetBytes(samples * channels * 2); fileStream.Write(subChunk2, 0, 4); fileStream.Flush(); } private void CalculateVowel() { //ここが処理の重さ的にやばいかも? var max_volume = 0.0f; var max_index = 0; var total_volume = 0.0f; //録音時間*サンプリング周波数の個数のデータがほしい! float[] temp = new float[FFTSAMPLES]; listenSource.GetSpectrumData(temp, 0, FFTWindow.Blackman); for (int i = 0; i < temp.Length; i++) { if (max_volume < temp[i]) { max_index = i; max_volume = temp[i]; } total_volume += Mathf.Abs(temp[i]); } if (temp.Length > 0) { frequency = max_index * AudioSettings.outputSampleRate / 2 / temp.Length; volume = total_volume / temp.Length * gain; } } private void AizuchiAnimation(float speakingTime) { if (2.0f < speakingTime && speakingTime < 5.0f) { animator.SetInteger("aizuchi", 1); } else if (5.0f < speakingTime && speakingTime < 8.0f) { animator.SetInteger("aizuchi", 2); } else if (8.0f < speakingTime) { animator.SetInteger("aizuchi", 3); } } } また、Resorces以下に保存するwordAnimation.csvの形式は以下のようにしてください。 単語・フレーズ,変数名,数値  大好き,word,1                                   #具体例 AnimatorとAnimationClipを用意しよう! Part1と同様の方法で、AnimatorとAnimationClipを用意してください。 ただ、変数名等がaizuchiから、wordになっています。 これで、以上になります。 実際にアプリをビルドして試してみてください! 最後に 今回は実際に頷いてもらったり、相槌をうってもらったり、「好き」と言ったら「好き」と言ってもらえるようにしました。 嫁に「大好き」と言ったら「大好き」と返ってくるなんて、とてもうれしくなります(^^)/ ぜひ、みなさんも自分の嫁の3Dモデルをつくって、「愛してる」と言ってみてください(笑) ついでに旧版の嫁(鷹白一夜) や新版の嫁をプレイして、もっと知っていただけると嬉しいです。 今回使用したGithub
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Oculus Quest 2 で話してみよう!Part1

目次 いいタイミングで頷いてもらおう!(Part1) 「好き」と言ったら「好き」と返して欲しいんだ…。(Part2) やること 今回は前回作成した、Oculus Quest 2のアプリに追加機能をつけていきたいと思います。 実際に付ける内容は、 - 一定時間話しかけた後で、頷くor 相槌をうってもらう - 特定のフレーズや単語に対して、反応してもらう の2つの機能をつけていきます。 では、さっそくやっていきましょう! いいタイミングで頷いてもらおう! マイクを取り付けよう! まず、音声の取得に必要なマイクをunityに入れていきます。 OVRCamearaRigの子に空のオブジェクトを作成して、名前をMICとしてください。 音声を取得してみよう! 以下のGetVoice.csファイルをMICにアタッチしてください。 using System; using System.Collections; using System.Collections.Generic; using System.IO; using UnityEngine; using UnityEngine.UI; public class GetVoice : MonoBehaviour { [SerializeField] AudioSource listenSource; [SerializeField] Animator animator; //private fields private float gain = 8000.0f; float volume; float frequency; bool isSpeaking; bool isRecording; float bufferTimer; //話した後の余韻の時間 or 言葉に詰まった時の時間 の計測時間 float speakingTimer; //実際に話している時間 //constant values const int RECORD_SECONDS = 2; const int SAMPLING_FREQUENCY = 16000; // MSのSSTはサンプリングレートが16000Hz const int FFTSAMPLES = 2 << 8; //256bitのサンプルをFFTでは選んでとる。 const float MIN_FREQ = 100.0f; //母音のFrequency は 100Hz 以上 1400Hz以下。 ただし、子音字は異なる。 const float MAX_FREQ = 1400.0f; const float MIN_VOLUME = 1.0f; const float BUFFER_TIME = 1.6f; //話した後の余韻の時間 or 言葉に詰まった時の時間。 // Start is called before the first frame update void Start() { #if UNITY_EDITOR gain = 2000.0f; #endif GetMic(); } void Update() { Waiting(); } private void GetMic() { while (Microphone.devices.Length< 1) { } string device = Microphone.devices[0]; listenSource.loop = true; listenSource.clip = Microphone.Start(device, true, RECORD_SECONDS, SAMPLING_FREQUENCY); while (!(Microphone.GetPosition(device) > 0)) { } listenSource.Play(); } async private void Waiting() { CalculateVowel(); if (MIN_FREQ < frequency && frequency < MAX_FREQ && MIN_VOLUME < volume) //しゃべり始めの時間 { isSpeaking = true; bufferTimer = 0.0f; speakingTimer += Time.deltaTime; //始めてレコーディングを開始したとき if (!isRecording) { isRecording = true; bufferTimer = 0.0f; speakingTimer = 0.0f; } } else if (isSpeaking && volume > MIN_VOLUME) { //子音字をしゃべっていると判定 bufferTimer = 0.0f; speakingTimer += Time.deltaTime; } else if (isSpeaking && bufferTimer < BUFFER_TIME) // 余韻の時間 { bufferTimer += Time.deltaTime; speakingTimer += Time.deltaTime; } else { bufferTimer = 0.0f; // 後で、speakingTimerの条件処理あり! isSpeaking = false; if (isRecording) { isRecording = false; AizuchiAnimation(speakingTimer); } speakingTimer = 0.0f; } } private void CalculateVowel() { //ここが処理の重さ的にやばいかも? var max_volume = 0.0f; var max_index = 0; var total_volume = 0.0f; //録音時間*サンプリング周波数の個数のデータがほしい! float[] temp = new float[FFTSAMPLES]; listenSource.GetSpectrumData(temp, 0, FFTWindow.Blackman); for (int i = 0; i < temp.Length; i++) { if (max_volume < temp[i]) { max_index = i; max_volume = temp[i]; } total_volume += Mathf.Abs(temp[i]); } if (temp.Length > 0) { frequency = max_index * AudioSettings.outputSampleRate / 2 / temp.Length; volume = total_volume / temp.Length * gain; } } private void AizuchiAnimation(float speakingTime) { if (2.0f < speakingTime && speakingTime < 5.0f) { animator.SetInteger("aizuchi", 1); } else if (5.0f < speakingTime && speakingTime < 8.0f) { animator.SetInteger("aizuchi", 2); } else if (8.0f < speakingTime) { animator.SetInteger("aizuchi", 3); } } } スクリプトをアタッチ後に、AudioSourceにMICをいれて、AnimatorにはVRMのModelを入れてください。 以下に、各機能についての説明です。 gain   取得した音声のVolumeを計算後に何倍するかを示しています。 PCでのテスト時には2000倍、Oculus Quest 2 での実行時には8000倍となるようにしています。 Waiting 音声を実際に取っているかを判定するところです。 ある一定の音量以上で、第一周波数(f0)が100Hz以上、1400Hz以下の時にしゃべっていると認識しています。 このf0の基準は日本語の母音の周波数帯が100Hz以上、1400Hz以下だからです。 AizuchiAnimation 実際に相槌や頷きのアニメーションを行う部分です。 秒数 動作 2s ~ 5s 「うん」と頷く 5s = 8s 「へー、そうなんだ。」と相槌をうつ 8s ~ 「なるほどね」と相槌をうつ キャラクターの声だけを出力しよう! 上記の設定で、ご自身の声が入力できるようになったと思います。 しかし、同時にキャラクターの声と混じって、自分の声が出力されてしまいます。 なので、AudioMixerを使って、キャラクターの声だけが出力されるようにします。 まずProjectから、右クリックして Create > AudioMixer を作ってください。 つぎに、Audio Mixerを開いて、Groupsの+ボタンを押し、2つのグループのMicとVoiceを追加してください。 そうしましたら、写真のようにAudioMixerを設定してください。 Micの-80dBにより、自分の声が出力されないようになります。 最後にモデルにアタッチされている Audio Source の Output に Voiceを指定し、MICにアタッチされている Audio Source の Output に Micを指定します。 これで、設定が完了しました。 Animatorを準備しよう! Oculus Quest 2でいちゃいちゃしてみよう!Part4でのやりかたと、同一のやり方でアニメーションを作成していきます。 まず、AnimatorのParametersから、Int型のパラメータを追加し、名前をaizuchiにします。 次に、AnimationClipを3つつくり、AnimatorにDrag&Dropします。 Drag&DropしたそれぞれのAnimationClipとWaiting(待機モーション)をそれぞれに双方向につなぎます。 Waiting -> AnimationClipの方向の矢印を右クリックして、InspectorからConditionsを追加します。 Conditionsの内容は aizuchi Equals 1~3までの数字です。 つぎに、AninmationClipをクリックして、Add Behaviourからスクリプトを追加します。 前回使用した、ExitAnimation.csに以下の行を追加して、アタッチしてください。 animator.SetInteger("aizuchi", 0) Animation Clipを編集しよう! Oculus Quest 2でいちゃいちゃしてみよう!Part4でのAnimationClipを作成したときとやり方は同じです。 TimeLineで音声付きのAnimationClipを編集してください。 Animation Clipに音声をつけよう! Oculus Quest 2でいちゃいちゃしてみよう!Part4で使用した、Voice_Controller.csに以下のスクリプトを追加します。 [SerializeField] public AudioClip[] aizuchiClips; \\\ void AizuchiPlayBack(int aizuchiIndex) { audioSource.PlayOneShot(aizuchiClips[aizuchiIndex-1], 1.0f); } AnimationClipをダブルクリックして、AnimationWindowを開き、AddEventをクリックして、イベントを追加してください。 追加したEventについて、FunctionをAizuchiPlayBackとして、IntをVoiceController中のIndex+1の値に設定してください。 最後にアプリを起動させて、試してみてください。 お疲れ様でした。 次回に続きます。 今回のアプリのGitHub (git_temp2)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ゲームプログラミング初学者のための実用数学 ベクトル編

この記事について この記事は「プログラミング初めてやるけど数学がわからない!」という人向けの解説です。【LogiX】とついている項目はLogiXというプログラミングのための説明なので関係ない場合は読み飛ばして問題ありません。 やる気がでてきたら画像を作って追加するかもしれません。 以下のことに注意してください。 わかりやすくするために一部端折っていたり厳密には間違っていたりします。(実用上は問題ないです) 今回は2次元及び3次元のみを考えます。 ベクトルが1つの時は$\vec{V}$、2つ以上の時は$\vec{A},\vec{B}...$と表します。 一度ですべてを覚えるのは無理があるのでなんとなく覚えておいて必要になったときに見直すといいかもしれません。 ベクトルの基本 ベクトル/Vectorとは ベクトルとは「大きさ」と「向き」をもつ量です。 対して「向き」を持たない実数をスカラーと言います。 質量や距離は向きを持たないためスカラーで、速度や加速度は向きと大きさを持つためベクトルです。 ベクトルの成分表示 1つの実数だけでは向きを表すことができません。そこで各座標軸ごとの成分を(x,y)のように並べて書くことでベクトルを表します。これをベクトルの成分表示と言います。 基本ベクトル (1,0)や(0,1,0)などの各座標軸の正方向で大きさが1のベクトルを基本ベクトルと言います。 【LogiX】関連ノード Unpack XY :ベクトルを成分に分解します。 Pack XY :成分をベクトルとして組み立てます。 ベクトルの大きさ ベクトルの大きさは$|\vec{V}|$と書かれ、大きさはベクトルを表した線分の長さと一致します。それぞれの成分の二乗を足した値の平方根がそのベクトルの大きさです。 例えば成分表示が(a,b)というベクトルの場合$\sqrt{a^2 + b^2}$が大きさとなります。 【LogiX】関連ノード $|V|$ :ベクトルの大きさを出力します。 $|V|^2$ :ベクトルの大きさの二乗を出力します。 ベクトルの拡大・縮小 ベクトルにスカラーをかけると大きさを変えることができます。$\vec{V} \times a$をおこなうと$\vec{V}$の大きさはa倍となり、$\vec{V}$の成分もそれぞれa倍となります。 単位ベクトル 大きさが1のベクトルを単位ベクトルと言います。基本ベクトルと似ていますが、単位ベクトルは向きを限定されません。基本ベクトルは単位ベクトルに含まれます。 単位ベクトルは純粋な向きであると考えると扱いやすいです。 正規化/Normalize ベクトルの向きをそのままに大きさを1にすること、つまり単位ベクトルにすることを正規化と言います。正規化するにはベクトルをそのベクトルの大きさで割ります。 $\vec{V}_{normalized} = \frac{\vec{V}}{|\vec{V}|}$ 【LogiX】関連ノード $\times$ :掛けた値を出力します。ベクトルとベクトルの場合はそれぞれの成分どうしを掛けます。 Normalized :正規化されたベクトルを出力します。扱いやすく言い換えると、ベクトルから向きのみを取り出します。 移動と位置を表すベクトル ベクトルは大きさと向きのみを持つものであり、二点間の"移動"を表します。 例えば点Aから点Bへの移動量をベクトル$\vec{V}$とすると、$-\vec{V}$は点Bから点Aへの移動量です。 ベクトルの加法・減法 ベクトルは足し算と引き算を行うことができ、結果は新たな移動を表すベクトルとなります。 点Aから点Bへの移動ベクトルと点Bから点Cへの移動ベクトルを足すと、点Aから点Cへの移動ベクトルになります。 位置ベクトル 原点からある位置までのベクトルを位置ベクトルといいます。座標上の全ての点は位置ベクトルで表すことができます。 位置ベクトルと言うとわかりづらいですが計算上は位置/Positionとほぼ同じです。 点から点へのベクトル 2点の位置ベクトルがわかっている時、終点にしたい位置ベクトルから始点にしたい位置ベクトルを引くと2点間のベクトルを求めることができます。 【LogiX】関連ノード + :足した値を出力します。float3型などをそのままつなげることができます。 - :引いた値を出力します。float3型などをそのままつなげることができます。 -n :大きさはそのままで向きが逆のベクトルを出力します。 ベクトルの正射影 物体に光を当ててスクリーンに影を作ることを射影といいます。特に、あるベクトルに対して垂直な方向からの光による他のベクトルの射影を正射影といいます。 スクリーンは、ベクトルというよりもそのベクトルを含む直線であると考えるといいかもしれません。 【LogiX】関連ノード Project :入力2をスクリーンとした入力1の正射影を出力します。 ベクトルの内積 ベクトルの学習において最初の大きな壁となるのが内積です。ゲームプログラミング実用上は内積の意味を頑張って理解するよりも内積の式と性質を理解する方が速いし楽なので今回は深い部分にあまり触れません。(ベクトルをある程度理解してから学ぶことをおすすめします。) 内積はドット積や点乗積ともいい、"・"を使って表します。内積の出力はスカラーです。 内積の式 2つのベクトル$\vec{A},\vec{B}$の始点を同じ位置にした時の角度をθとすると $$\vec{A}・\vec{B} = |\vec{A}||\vec{B}|cosθ$$ 2ベクトルの大きさの絶対値とcosθを掛けています。 また、ベクトルの成分がわかっている場合は同じ軸の成分同士を掛けてそれを足しても内積がでます。 成分表示がそれぞれ$(a_1,a_2),(b_1,b_2)$の二次元ベクトル$\vec{A},\vec{B}$の内積は $$\vec{A}・\vec{B} = a_1b_1 + a_2b_2$$ 成分表示がそれぞれ(a_1,1_2,a_3),(b_1,b_2,b_3)の三次元ベクトル$\vec{A},\vec{B}$の内積は $$\vec{A}・\vec{B} = a_1b_1 + a_2b_2 + a_3b_3$$ となります。 単位ベクトルの内積 単位ベクトルの大きさは1であるため、単位ベクトル同士の内積はcosθとなります。 $$\begin{align} \vec{A}・\vec{B} &= 1 \times 1 \times cosθ \\ &= cosθ \end{align}$$ cosの性質よりベクトル間の角度が0°,90°,180°の時、内積は以下のようになります。 $$\begin{align}&\vec{A}・\vec{B} = cos0° = 1 \\ &\vec{A}・\vec{B} = cos90° = 0\\ &\vec{A}・\vec{B} = cos180° = -1\end{align}$$ 内積は2ベクトル間の角度が小さいほど1に近づき、大きいほど-1に近づきます。また、垂直な場合は0になります。 単位ベクトルではなくても2つのベクトルの大きさで内積を割ることでcosθをだすことができます。 【LogiX】関連ノード ・ :入力1と入力2の内積を出力します。 。 :入力1と入力2の度数法の角度を出力します。 Acos :cosを弧度法の角度に変換します。 弧度法と度数法 弧度法や度数法とは角度の表し方のことです。 半径と等しい長さの弧に対する中心角の大きさを1としたものが弧度法で、単位はrad(ラジアン)です。私たちが普段使っている 45°,180°のような表し方を度数法といいます。 $π_{rad} = 180°$です。 基本的に数学では弧度法が使われています。 内積での条件判定 単位ベクトルの内積は2つの向きの角度の大きさを-1~1で出力するため、条件判定にとても便利です。 例えば両方の手のひらに垂直なベクトルの内積が‐1に近ければ手のひらが向かい合っていることがわかります。さらに両手の距離や、片方の手のひらのベクトルと片手を始点としたもう片方の手へのベクトルの内積を見ることで、「手のひらをあわせている」などの条件を判定することができます。 【LogiX】関連ノード > :入力1が入力2より大きい場合Trueを出力します。 < :入力1が入力2より小さい場合Trueを出力します。 = :入力1が入力2と等しい場合Trueを出力します。 ≈ :入力1と入力2の差が入力3以下の場合Trueを出力します。 IsBetween :入力1が入力2以上、入力3以下の場合Trueを出力します。 ベクトルの外積 ベクトルの2つ目の大きい壁が外積です。 外積は内積よりも意味を理解しづらく、特に二次元での外積は外積の定義にや分野によって存在したりしなかったりするので調べるのも難しいです。今回は三次元の外積のみを考えます。 外積はクロス積ともいい、"×"を使って表します。外積の出力はベクトルです。 外積の大きさ 2つのベクトルが作る平行四辺形の面積が外積の大きさです。 $$\vec{A} \times \vec{B} = |\vec{A}||\vec{B}|sinθ$$ で求めることができます。 外積の向き 外積の向きは2つのベクトルに対して垂直な向きです。$\vec{A}\times\vec{B}$の場合$\vec{A}$から$\vec{B}$へ向けてねじを回した時にねじが進む方向です。そのため$\vec{B}\times\vec{A}$とすると向きが反転します。 外積の求め方 $\vec{A}(a_1,a_2,a_3),\vec{B}(b_1,b_2,b_3)$の外積の成分は次の式で求めることができます。 $$\vec{A}\times\vec{B} = (a_2b_3-b_2a_3,a_3b_1-b_3a_1,a_1b_2-b_1a_2)$$ 求める成分以外の成分をたすき掛けします。(すぐに覚えなくとも問題はないです) 【LogiX】関連ノード cross :入力1と入力2の外積を出力します。 終わりに ベクトル編はここまでです。ベクトルの回転や座標の話も入れようかと思いましたが既にかなり長くなってしまったので今回入れるのはやめました。座標は図形の性質編、回転は行列編で説明しようかと思います。(やるきがあれば)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

無限発生するApplication.Reload

概要 Unity2019のプロジェクトをUnity2021にアップグレードしたら、 Application.Reloadのプログレスバーが連続で発生することがありました。 発生手順 1.シーンを開く 2.シーン内の何らかのインスペクタの値を編集する 3.保存する ↓ Application.Reload -> Application.Reload -> ... 一度発生するとUnityを閉じるまで止まらないようです。 解決方法 ・シーンにある全オブジェクトを新しいシーンにコピーし、古いシーンファイルを破棄する もっと簡単な方法があればご教示ください。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む