注意

Mixed Reality Academy チュートリアルでは、HoloLens として設計された (第 1 世代) と混在の現実イマーシブ ヘッドセットに注意してください。The Mixed Reality Academy tutorials were designed with HoloLens (1st gen) and Mixed Reality Immersive Headsets in mind. そのため、これらのデバイス向けの開発にガイダンスがまだ必要な開発者のための場所でこれらのチュートリアルのままにすることが重要と思われます。As such, we feel it is important to leave these tutorials in place for developers who are still looking for guidance in developing for those devices. これらのチュートリアルは いない 最新のツールセットや相互作用が使用されている HoloLens 2 で更新されます。These tutorials will not be updated with the latest toolsets or interactions being used for HoloLens 2. サポートされているデバイスで作業を続行するが保持されます。They will be maintained to continue working on the supported devices. 一連の新しい HoloLens 2 を開発する方法を示すチュートリアルは、今後投稿があります。There will be a new series of tutorials that will be posted in the future that will demonstrate how to develop for HoloLens 2. この通知が投稿されるときにこれらのチュートリアルへのリンクが更新されます。This notice will be updated with a link to those tutorials when they are posted.


MR 空間 230:空間マッピングMR Spatial 230: Spatial mapping

空間マッピングホログラム環境に関する指示することによって、現実の世界と仮想世界をまとめて結合します。Spatial mapping combines the real world and virtual world together by teaching holograms about the environment. MR 空間 230 (プロジェクト プラネタリウム用) で説明する方法。In MR Spatial 230 (Project Planetarium) we'll learn how to:

  • 開発用コンピューターに、HoloLens の環境と転送データをスキャンします。Scan the environment and transfer data from the HoloLens to your development machine.
  • シェーダーを調査し、自分のスペースを視覚化するために使用する方法について説明します。Explore shaders and learn how to use them for visualizing your space.
  • メッシュの処理を使用して単純な平面に部屋メッシュに分解します。Break down the room mesh into simple planes using mesh processing.
  • 学習した配置の手法よりも高度MR 基本 101ホログラムを環境に配置できる場所についてのフィードバックを提供します。Go beyond the placement techniques we learned in MR Basics 101, and provide feedback about where a hologram can be placed in the environment.
  • 遮蔽効果、探索、ホログラムが現実世界のオブジェクトの背後にあると、表示できるようにも、x 線画像のビジョンを!Explore occlusion effects, so when your hologram is behind a real-world object, you can still see it with x-ray vision!

デバイスのサポートDevice support

コースCourse HoloLensHoloLens イマーシブ ヘッドセットImmersive headsets
MR 空間 230:空間マッピングMR Spatial 230: Spatial mapping ✔️✔️

開始前の作業Before you start

前提条件Prerequisites

プロジェクト ファイルProject files

  • ダウンロード、ファイルプロジェクトに必要です。Download the files required by the project. Unity 2017.2 またはそれ以降が必要です。 Requires Unity 2017.2 or later.
    • Unity 5.6 のサポートを引き続き必要がある場合を使用してくださいこのリリースします。If you still need Unity 5.6 support, please use this release.
    • Unity 5.5 のサポートを引き続き必要がある場合を使用してくださいこのリリースします。If you still need Unity 5.5 support, please use this release.
    • Unity 5.4 のサポートを引き続き必要がある場合を使用してくださいこのリリースします。If you still need Unity 5.4 support, please use this release.
  • 解除アーカイブをデスクトップまたは場所に到達する簡単なその他のファイル。Un-archive the files to your desktop or other easy to reach location.

注意

をダウンロードする前に、ソース コードを検索する場合があるGitHub で入手できますします。If you want to look through the source code before downloading, it's available on GitHub.

メモNotes

  • 無効にする Visual Studio のニーズに「マイ コードのみを有効化」(unchecked) [ツール] > オプション >、コードにブレークポイントをヒットするためにデバッグします。"Enable Just My Code" in Visual Studio needs to be disabled (unchecked) under Tools > Options > Debugging in order to hit breakpoints in your code.

Unity のセットアップUnity setup

  • 開始Unityします。Start Unity.
  • 選択新規新しいプロジェクトを作成します。Select New to create a new project.
  • プロジェクトに名前をプラネタリウム用します。Name the project Planetarium.
  • いることを確認、 3D設定を選択します。Verify that the 3D setting is selected.
  • クリックしてプロジェクトを作成するします。Click Create Project.
  • Unity が起動したらに移動して編集 > プロジェクトの設定 > Playerします。Once Unity launches, go to Edit > Project Settings > Player.
  • インスペクターパネルで検索し、緑色Windows ストアアイコン。In the Inspector panel, find and select the green Windows Store icon.
  • 展開他の設定します。Expand Other Settings.
  • レンダリング セクションで、チェック、仮想現実サポートオプション。In the Rendering section, check the Virtual Reality Supported option.
  • いることを確認Windows Holographicの一覧に表示される仮想現実 Sdkします。Verify that Windows Holographic appears in the list of Virtual Reality SDKs. そうでない場合は、選択、 + 一覧の下部にあるボタンをクリックし、選択Windows Holographicします。If not, select the + button at the bottom of the list and choose Windows Holographic.
  • 展開公開設定します。Expand Publishing Settings.
  • 機能セクションで、次の設定を確認します。In the Capabilities section, check the following settings:
    • internetClientServerInternetClientServer
    • privateNetworkClientServerPrivateNetworkClientServer
    • マイクMicrophone
    • SpatialPerceptionSpatialPerception
  • 移動して編集 > プロジェクトの設定 > 品質Go to Edit > Project Settings > Quality
  • インスペクターパネルの [Windows ストア] アイコンの下の 'Default' の行の黒のドロップダウン矢印とする既定の設定を変更するVery Lowします。In the Inspector panel, under the Windows Store icon, select the black drop-down arrow under the 'Default' row and change the default setting to Very Low.
  • 移動して資産 > パッケージのインポート > カスタム パッケージします。Go to Assets > Import Package > Custom Package.
  • 移動し、 ...\HolographicAcademy-Holograms-230-SpatialMapping\Startingフォルダー。Navigate to the ...\HolographicAcademy-Holograms-230-SpatialMapping\Starting folder.
  • をクリックしてPlanetarium.unitypackageします。Click on Planetarium.unitypackage.
  • [開く] をクリックします。Click Open.
  • Unity パッケージのインポートウィンドウが表示されます をクリックして、インポートボタンをクリックします。An Import Unity Package window should appear, click on the Import button.
  • このプロジェクトを完了する必要があります。 その資産のすべてをインポートする Unity を待ちます。Wait for Unity to import all of the assets that we will need to complete this project.
  • 階層パネルで、削除、 Main Cameraします。In the Hierarchy panel, delete the Main Camera.
  • プロジェクトパネル、 HoloToolkit-SpatialMapping-230\Utilities\Prefabsフォルダー、検索、 Main Cameraオブジェクト。In the Project panel, HoloToolkit-SpatialMapping-230\Utilities\Prefabs folder, find the Main Camera object.
  • ドラッグ アンド ドロップ、 Main Cameraにプレハブ、階層パネル。Drag and drop the Main Camera prefab into the Hierarchy panel.
  • 階層パネルで、削除、指向性光オブジェクト。In the Hierarchy panel, delete the Directional Light object.
  • プロジェクトパネル、ホログラムフォルダー、検索、カーソルオブジェクト。In the Project panel, Holograms folder, locate the Cursor object.
  • ドラッグ アンド ドロップ、カーソルにプレハブ、階層します。Drag & drop the Cursor prefab into the Hierarchy.
  • 階層パネルで、カーソルオブジェクト。In the Hierarchy panel, select the Cursor object.
  • インスペクターパネルで、をクリックして、レイヤードロップダウンを選択しますレイヤーを編集しています..In the Inspector panel, click the Layer drop-down and select Edit Layers....
  • 名前ユーザー層 31として"SpatialMapping"。Name User Layer 31 as "SpatialMapping".
  • 新しいシーンを保存します。ファイル > としてシーンを保存しています.Save the new scene: File > Save Scene As...
  • クリックして新しいフォルダーフォルダーの名前とシーンします。Click New Folder and name the folder Scenes.
  • ファイルに名前を"プラネタリウム用"で保存し、シーンフォルダー。Name the file "Planetarium" and save it in the Scenes folder.

第 1 章 - スキャンChapter 1 - Scanning

目標Objectives

  • SurfaceObserver、および設定への影響を経験する方法とパフォーマンスについて説明します。Learn about the SurfaceObserver and how its settings impact experience and performance.
  • スキャンの部屋のメッシュを収集するエクスペリエンスのルームを作成します。Create a room scanning experience to collect the meshes of your room.

手順Instructions

  • プロジェクトパネルHoloToolkit-SpatialMapping-230\SpatialMapping\Prefabsフォルダー、検索、 SpatialMapping prefab します。In the Project panel HoloToolkit-SpatialMapping-230\SpatialMapping\Prefabs folder, find the SpatialMapping prefab.
  • ドラッグ アンド ドロップ、 SpatialMappingにプレハブ、階層パネル。Drag & drop the SpatialMapping prefab into the Hierarchy panel.

ビルドおよび展開 (パート 1)Build and Deploy (part 1)

  • Unity では、次のように選択します。ファイル > Build Settingsします。In Unity, select File > Build Settings.
  • クリックして開くシーンを追加を追加する、プラネタリウム用シーンをビルドします。Click Add Open Scenes to add the Planetarium scene to the build.
  • 選択ユニバーサル Windows プラットフォームで、プラットフォームを一覧表示し、クリックしてスイッチ プラットフォームします。Select Universal Windows Platform in the Platform list and click Switch Platform.
  • 設定SDKユニバーサル 10UWP ビルドの種類D3Dします。Set SDK to Universal 10 and UWP Build Type to D3D.
  • 確認UnityC#プロジェクトします。Check Unity C# Projects.
  • [Build] をクリックします。Click Build.
  • 作成、新しいフォルダー "App"という名前です。Create a New Folder named "App".
  • 1 回のクリック、アプリフォルダー。Single click the App folder.
  • キーを押して、フォルダーの選択ボタンをクリックします。Press the Select Folder button.
  • Unity が完了すると、ファイル エクスプ ローラー ウィンドウが表示されます。When Unity is done building, a File Explorer window will appear.
  • ダブルクリックして、アプリフォルダーを開きます。Double-click on the App folder to open it.
  • ダブルクリックしてPlanetarium.sln Visual Studio でプロジェクトを読み込みます。Double-click on Planetarium.sln to load the project in Visual Studio.
  • Visual Studio で、上部のツールバーを使用して構成を変更するリリースします。In Visual Studio, use the top toolbar to change the Configuration to Release.
  • 変更するためのプラットフォームx86します。Change the Platform to x86.
  • ' ローカル コンピューター] の右側の下矢印をクリックし、[リモート マシンします。Click on the drop-down arrow to the right of 'Local Machine', and select Remote Machine.
  • 入力デバイスの IP アドレスアドレス フィールドし、する認証モードを変更するユニバーサル (暗号化されていないプロトコル) します。Enter your device's IP address in the Address field and change Authentication Mode to Universal (Unencrypted Protocol).
  • クリックしてデバッグ]、[デバッグなしで開始またはキーを押しますctrl キーを押しながら f5 キーを押してします。Click Debug -> Start Without debugging or press Ctrl + F5.
  • ウォッチ、出力パネルの Visual Studio でのビルドと配置状態。Watch the Output panel in Visual Studio for build and deploy status.
  • アプリがデプロイされると、室内をについて説明します。Once your app has deployed, walk around the room. 黒と白のワイヤー フレームのメッシュに覆わ周囲の画面が表示されます。You will see the surrounding surfaces covered by black and white wireframe meshes.
  • 環境をスキャンします。Scan your surroundings. 必ず、壁、天井、フロアを見てください。Be sure to look at walls, ceilings, and floors.

ビルドおよび展開 (パート 2)Build and Deploy (part 2)

これで空間マッピングに与える影響についてパフォーマンスを見てみましょう。Now let's explore how Spatial Mapping can affect performance.

  • Unity では、次のように選択します。ウィンドウ > Profilerします。In Unity, select Window > Profiler.
  • クリックしてProfiler の追加 > GPUします。Click Add Profiler > GPU.
  • クリックしてActive Profiler > します。Click Active Profiler > .
  • 入力、 IP アドレスHoloLens の。Enter the IP address of your HoloLens.
  • [接続] をクリックします。Click Connect.
  • フレームのレンダリングに GPU の所要時間をミリ秒数を確認します。Observe the number of milliseconds it takes for the GPU to render a frame.
  • デバイスで実行されているアプリケーションを停止します。Stop the application from running on the device.
  • Visual Studio に戻り、開くSpatialMappingObserver.csします。Return to Visual Studio and open SpatialMappingObserver.cs. これは、アセンブリ CSharp (ユニバーサル Windows) プロジェクトの HoloToolkit\SpatialMapping フォルダーに表示されます。You will find it in the HoloToolkit\SpatialMapping folder of the Assembly-CSharp (Universal Windows) project.
  • 検索、 Awake() 関数は、し、次のコード行を追加します。TrianglesPerCubicMeter 1200; を =Find the Awake() function, and add the following line of code: TrianglesPerCubicMeter = 1200;
  • お使いのデバイスにプロジェクトを再デプロイし、プロファイラーを再接続します。Re-deploy the project to your device, and then reconnect the profiler. フレームのレンダリング時間 (ミリ秒) の数の変更を確認します。Observe the change in the number of milliseconds to render a frame.
  • デバイスで実行されているアプリケーションを停止します。Stop the application from running on the device.

保存して Unity で読み込むSave and load in Unity

最後に、みましょう、部屋のメッシュを保存し、Unity に読み込みます。Finally, let's save our room mesh and load it into Unity.

  • Visual Studio に戻り、削除、 TrianglesPerCubicMeterで追加した行、 Awake() 前のセクションの中に機能します。Return to Visual Studio and remove the TrianglesPerCubicMeter line that you added in the Awake() function during the previous section.
  • デバイスにプロジェクトを再デプロイします。Redeploy the project to your device. 私たちは現在実行されてで500立方メーターごとの三角形です。We should now be running with 500 triangles per cubic meter.
  • ブラウザーを開きに移動する、HoloLens の ip アドレスの入力、 Windows Device Portalします。Open a browser and enter in your HoloLens IPAddress to navigate to the Windows Device Portal.
  • 選択、 3D 表示左側のパネルのオプション。Select the 3D View option in the left panel.
  • 再構築のサーフェス選択、更新ボタン。Under Surface reconstruction select the Update button.
  • 表示ウィンドウに表示される、HoloLens のスキャンが完了する領域をご覧ください。Watch as the areas that you have scanned on your HoloLens appear in the display window.
  • 部屋のスキャンを保存するキーを押して、保存ボタンをクリックします。To save your room scan, press the Save button.
  • 開く、ダウンロードルームが保存されたモデルを見つけてフォルダー SRMesh.objします。Open your Downloads folder to find the saved room model SRMesh.obj.
  • コピー SRMesh.obj資産Unity プロジェクトのフォルダー。Copy SRMesh.obj to the Assets folder of your Unity project.
  • Unity では、選択、 SpatialMappingオブジェクト、階層パネル。In Unity, select the SpatialMapping object in the Hierarchy panel.
  • 検索、オブジェクトのサーフェイスのオブザーバー (スクリプト) コンポーネント。Locate the Object Surface Observer (Script) component.
  • 右側に円をクリックして、ルーム モデルプロパティ。Click the circle to the right of the Room Model property.
  • 検索し、選択、 SRMeshオブジェクトし、ウィンドウを閉じます。Find and select the SRMesh object and then close the window.
  • いることを確認、ルーム モデルプロパティ、インスペクターパネルに設定されていますSRMeshします。Verify that the Room Model property in the Inspector panel is now set to SRMesh.
  • キーを押して、再生Unity のプレビュー モードを開始するボタンをクリックします。Press the Play button to enter Unity's preview mode.
  • SpatialMapping コンポーネントは、Unity で使用することができます、部屋の保存されたモデルから、メッシュを読み込まれます。The SpatialMapping component will load the meshes from the saved room model so you can use them in Unity.
  • 切り替えるシーンワイヤー フレーム シェーダーで表示されるルーム モデルのすべてを表示するビュー。Switch to Scene view to see all of your room model displayed with the wireframe shader.
  • キーを押して、再生プレビュー モードを終了するには、もう一度ボタンをクリックします。Press the Play button again to exit preview mode.

注: Unity では、プレビュー モードを入力すること、次回保存ルーム メッシュ既定で読み込まれます。NOTE: The next time that you enter preview mode in Unity, it will load the saved room mesh by default.

第 2 章 - 視覚エフェクトChapter 2 - Visualization

目標Objectives

  • シェーダーの基本について説明します。Learn the basics of shaders.
  • 環境を視覚化します。Visualize your surroundings.

手順Instructions

  • Unity の階層パネルで、 SpatialMappingオブジェクト。In Unity's Hierarchy panel, select the SpatialMapping object.
  • インスペクターパネルで、検索、空間マッピング マネージャー (スクリプト) コンポーネント。In the Inspector panel, find the Spatial Mapping Manager (Script) component.
  • 右側に円をクリックして、画面マテリアルプロパティ。Click the circle to the right of the Surface Material property.
  • 検索し、選択、 BlueLinesOnWallsマテリアルと、ウィンドウを閉じます。Find and select the BlueLinesOnWalls material and close the window.
  • プロジェクトパネルシェーダーフォルダーをダブルクリックして、 BlueLinesOnWallsを Visual Studio でシェーダーを開きます。In the Project panel Shaders folder, double-click on BlueLinesOnWalls to open the shader in Visual Studio.
  • これは、簡単なピクセル (頂点のフラグメントを)、次のタスクを実行するには、シェーダー。This is a simple pixel (vertex to fragment) shader, which accomplishes the following tasks:
    1. ワールド空間には、頂点の位置を変換します。Converts a vertex's location to world space.
    2. 頂点の通常ピクセルが垂直方向であるかどうかを確認します。Checks the vertex's normal to determine if a pixel is vertical.
    3. レンダリングのピクセルの色を設定します。Sets the color of the pixel for rendering.

構築し、デプロイBuild and Deploy

  • Unity とキーを押してに戻る再生プレビュー モードを入力します。Return to Unity and press Play to enter preview mode.
  • 青い線は、ルーム メッシュ (これは、保存済みのスキャン データから自動的に読み込まれます) のすべての垂直画面に表示されます。Blue lines will be rendered on all vertical surfaces of the room mesh (which automatically loaded from our saved scanning data).
  • 切り替えて、シーン タブを部屋の表示を調整および Unity での部屋全体、メッシュの表示方法を参照してください。Switch to the Scene tab to adjust your view of the room and see how the entire room mesh appears in Unity.
  • プロジェクトパネルで、検索、資料フォルダーと選択、 BlueLinesOnWallsマテリアル。In the Project panel, find the Materials folder and select the BlueLinesOnWalls material.
  • 一部のプロパティを変更して、Unity エディターで変更の表示方法を参照してください。Modify some properties and see how the changes appear in the Unity editor.
    • インスペクターパネルで、調整、 LineScale線が太くまたは幅が狭いほどを表示するのには値。In the Inspector panel, adjust the LineScale value to make the lines appear thicker or thinner.
    • インスペクターパネルで、調整、 LinesPerMeter各壁に表示される行の数を変更する値。In the Inspector panel, adjust the LinesPerMeter value to change how many lines appear on each wall.
  • クリックして再生プレビュー モードを終了するには、もう一度です。Click Play again to exit preview mode.
  • ビルド、HoloLens に展開し、実際のサーフェスにレンダリング シェーダーを表示する方法を確認します。Build and deploy to the HoloLens and observe how the shader rendering appears on real surfaces.

Unity は、素材のプレビューの良い仕事をするが、常に、デバイスでのレンダリングをチェック アウトすることをお勧めします。Unity does a great job of previewing materials, but it's always a good idea to check-out rendering in the device.

第 3 章 - 処理Chapter 3 - Processing

目標Objectives

  • アプリケーションで使用するための空間マッピング データを処理するための手法について説明します。Learn techniques to process spatial mapping data for use in your application.
  • 平面を見つけて、三角形を削除する空間のマッピング データを分析します。Analyze spatial mapping data to find planes and remove triangles.
  • ホログラムを配置するためには、平面を使用します。Use planes for hologram placement.

手順Instructions

  • Unity のプロジェクトパネル、ホログラムフォルダー、検索、 SpatialProcessingオブジェクト。In Unity's Project panel, Holograms folder, find the SpatialProcessing object.
  • ドラッグ アンド ドロップ、 SpatialProcessingオブジェクトを階層パネル。Drag & drop the SpatialProcessing object into the Hierarchy panel.

SpatialProcessing プレハブには、空間のマッピング データを処理するためのコンポーネントが含まれます。The SpatialProcessing prefab includes components for processing the spatial mapping data. SurfaceMeshesToPlanes.cs見つけて平面空間マッピング データに基づいて生成されます。SurfaceMeshesToPlanes.cs will find and generate planes based on the spatial mapping data. 平面を壁、フロア、シーリングを表すアプリケーションで使用します。We will use planes in our application to represent walls, floors and ceilings. このプレハブも含まれています。 RemoveSurfaceVertices.cs空間マッピング メッシュから頂点を削除することができます。This prefab also includes RemoveSurfaceVertices.cs which can remove vertices from the spatial mapping mesh. これは、メッシュ内の穴を作成または削除 (平面は、代わりに使用できる) ためには必要なくなりましたされる余分な三角形に使用できます。This can be used to create holes in the mesh, or to remove excess triangles that are no longer needed (because planes can be used instead).

  • Unity のプロジェクトパネル、ホログラムフォルダー、検索、 SpaceCollectionオブジェクト。In Unity's Project panel, Holograms folder, find the SpaceCollection object.
  • ドラッグ アンド ドロップ、 SpaceCollectionオブジェクトを階層パネル。Drag and drop the SpaceCollection object into the Hierarchy panel.
  • 階層パネルで、 SpatialProcessingオブジェクト。In the Hierarchy panel, select the SpatialProcessing object.
  • インスペクターパネルで、検索、プレイ領域マネージャー (スクリプト) コンポーネント。In the Inspector panel, find the Play Space Manager (Script) component.
  • ダブルクリックしてPlaySpaceManager.cs Visual Studio で開きます。Double-click on PlaySpaceManager.cs to open it in Visual Studio.

PlaySpaceManager.cs には、アプリケーション固有のコードが含まれています。PlaySpaceManager.cs contains application-specific code. 機能は、次の動作を有効にするには、このスクリプトを追加します。We will add functionality to this script to enable the following behavior:

  1. スキャンの時間制限 (10 秒) を超えた後空間マッピング データの収集を停止します。Stop collecting spatial mapping data after we exceed the scanning time limit (10 seconds).
  2. 空間マッピング データを処理します。Process the spatial mapping data:
    1. 平面 (壁、フロア、シーリングなど) として、世界中の単純な表現を作成するのにには、SurfaceMeshesToPlanes を使用します。Use SurfaceMeshesToPlanes to create a simpler representation of the world as planes (walls, floors, ceilings, etc).
    2. RemoveSurfaceVertices を使用すると、面の境界内にある表面の三角形を削除します。Use RemoveSurfaceVertices to remove surface triangles that fall within plane boundaries.
  3. 世界でホログラムのコレクションを生成し、ユーザーに近い壁面と床面の平面上に配置します。Generate a collection of holograms in the world and place them on wall and floor planes near the user.

PlaySpaceManager.cs でマークされているコーディングの練習を完了またはスクリプトを次の完成版ソリューションを置き換え。Complete the coding exercises marked in PlaySpaceManager.cs, or replace the script with the finished solution from below:

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Windows.Speech;
using Academy.HoloToolkit.Unity;

/// <summary>
/// The SurfaceManager class allows applications to scan the environment for a specified amount of time 
/// and then process the Spatial Mapping Mesh (find planes, remove vertices) after that time has expired.
/// </summary>
public class PlaySpaceManager : Singleton<PlaySpaceManager>
{
    [Tooltip("When checked, the SurfaceObserver will stop running after a specified amount of time.")]
    public bool limitScanningByTime = true;

    [Tooltip("How much time (in seconds) that the SurfaceObserver will run after being started; used when 'Limit Scanning By Time' is checked.")]
    public float scanTime = 30.0f;

    [Tooltip("Material to use when rendering Spatial Mapping meshes while the observer is running.")]
    public Material defaultMaterial;

    [Tooltip("Optional Material to use when rendering Spatial Mapping meshes after the observer has been stopped.")]
    public Material secondaryMaterial;

    [Tooltip("Minimum number of floor planes required in order to exit scanning/processing mode.")]
    public uint minimumFloors = 1;

    [Tooltip("Minimum number of wall planes required in order to exit scanning/processing mode.")]
    public uint minimumWalls = 1;

    /// <summary>
    /// Indicates if processing of the surface meshes is complete.
    /// </summary>
    private bool meshesProcessed = false;

    /// <summary>
    /// GameObject initialization.
    /// </summary>
    private void Start()
    {
        // Update surfaceObserver and storedMeshes to use the same material during scanning.
        SpatialMappingManager.Instance.SetSurfaceMaterial(defaultMaterial);

        // Register for the MakePlanesComplete event.
        SurfaceMeshesToPlanes.Instance.MakePlanesComplete += SurfaceMeshesToPlanes_MakePlanesComplete;
    }

    /// <summary>
    /// Called once per frame.
    /// </summary>
    private void Update()
    {
        // Check to see if the spatial mapping data has been processed
        // and if we are limiting how much time the user can spend scanning.
        if (!meshesProcessed && limitScanningByTime)
        {
            // If we have not processed the spatial mapping data
            // and scanning time is limited...

            // Check to see if enough scanning time has passed
            // since starting the observer.
            if (limitScanningByTime && ((Time.time - SpatialMappingManager.Instance.StartTime) < scanTime))
            {
                // If we have a limited scanning time, then we should wait until
                // enough time has passed before processing the mesh.
            }
            else
            {
                // The user should be done scanning their environment,
                // so start processing the spatial mapping data...

                /* TODO: 3.a DEVELOPER CODING EXERCISE 3.a */

                // 3.a: Check if IsObserverRunning() is true on the
                // SpatialMappingManager.Instance.
                if(SpatialMappingManager.Instance.IsObserverRunning())
                {
                    // 3.a: If running, Stop the observer by calling
                    // StopObserver() on the SpatialMappingManager.Instance.
                    SpatialMappingManager.Instance.StopObserver();
                }

                // 3.a: Call CreatePlanes() to generate planes.
                CreatePlanes();

                // 3.a: Set meshesProcessed to true.
                meshesProcessed = true;
            }
        }
    }

    /// <summary>
    /// Handler for the SurfaceMeshesToPlanes MakePlanesComplete event.
    /// </summary>
    /// <param name="source">Source of the event.</param>
    /// <param name="args">Args for the event.</param>
    private void SurfaceMeshesToPlanes_MakePlanesComplete(object source, System.EventArgs args)
    {
        /* TODO: 3.a DEVELOPER CODING EXERCISE 3.a */

        // Collection of floor and table planes that we can use to set horizontal items on.
        List<GameObject> horizontal = new List<GameObject>();

        // Collection of wall planes that we can use to set vertical items on.
        List<GameObject> vertical = new List<GameObject>();

        // 3.a: Get all floor and table planes by calling
        // SurfaceMeshesToPlanes.Instance.GetActivePlanes().
        // Assign the result to the 'horizontal' list.
        horizontal = SurfaceMeshesToPlanes.Instance.GetActivePlanes(PlaneTypes.Table | PlaneTypes.Floor);

        // 3.a: Get all wall planes by calling
        // SurfaceMeshesToPlanes.Instance.GetActivePlanes().
        // Assign the result to the 'vertical' list.
        vertical = SurfaceMeshesToPlanes.Instance.GetActivePlanes(PlaneTypes.Wall);

        // Check to see if we have enough horizontal planes (minimumFloors)
        // and vertical planes (minimumWalls), to set holograms on in the world.
        if (horizontal.Count >= minimumFloors && vertical.Count >= minimumWalls)
        {
            // We have enough floors and walls to place our holograms on...

            // 3.a: Let's reduce our triangle count by removing triangles
            // from SpatialMapping meshes that intersect with our active planes.
            // Call RemoveVertices().
            // Pass in all activePlanes found by SurfaceMeshesToPlanes.Instance.
            RemoveVertices(SurfaceMeshesToPlanes.Instance.ActivePlanes);

            // 3.a: We can indicate to the user that scanning is over by
            // changing the material applied to the Spatial Mapping meshes.
            // Call SpatialMappingManager.Instance.SetSurfaceMaterial().
            // Pass in the secondaryMaterial.
            SpatialMappingManager.Instance.SetSurfaceMaterial(secondaryMaterial);

            // 3.a: We are all done processing the mesh, so we can now
            // initialize a collection of Placeable holograms in the world
            // and use horizontal/vertical planes to set their starting positions.
            // Call SpaceCollectionManager.Instance.GenerateItemsInWorld().
            // Pass in the lists of horizontal and vertical planes that we found earlier.
            SpaceCollectionManager.Instance.GenerateItemsInWorld(horizontal, vertical);
        }
        else
        {
            // We do not have enough floors/walls to place our holograms on...

            // 3.a: Re-enter scanning mode so the user can find more surfaces by 
            // calling StartObserver() on the SpatialMappingManager.Instance.
            SpatialMappingManager.Instance.StartObserver();

            // 3.a: Re-process spatial data after scanning completes by
            // re-setting meshesProcessed to false.
            meshesProcessed = false;
        }
    }

    /// <summary>
    /// Creates planes from the spatial mapping surfaces.
    /// </summary>
    private void CreatePlanes()
    {
        // Generate planes based on the spatial map.
        SurfaceMeshesToPlanes surfaceToPlanes = SurfaceMeshesToPlanes.Instance;
        if (surfaceToPlanes != null && surfaceToPlanes.enabled)
        {
            surfaceToPlanes.MakePlanes();
        }
    }

    /// <summary>
    /// Removes triangles from the spatial mapping surfaces.
    /// </summary>
    /// <param name="boundingObjects"></param>
    private void RemoveVertices(IEnumerable<GameObject> boundingObjects)
    {
        RemoveSurfaceVertices removeVerts = RemoveSurfaceVertices.Instance;
        if (removeVerts != null && removeVerts.enabled)
        {
            removeVerts.RemoveSurfaceVerticesWithinBounds(boundingObjects);
        }
    }

    /// <summary>
    /// Called when the GameObject is unloaded.
    /// </summary>
    private void OnDestroy()
    {
        if (SurfaceMeshesToPlanes.Instance != null)
        {
            SurfaceMeshesToPlanes.Instance.MakePlanesComplete -= SurfaceMeshesToPlanes_MakePlanesComplete;
        }
    }
}

構築し、デプロイBuild and Deploy

  • キーを押して、HoloLens、展開する前に、再生再生モードに切り替わる Unity でボタンをクリックします。Before deploying to the HoloLens, press the Play button in Unity to enter play mode.
  • ルーム メッシュがファイルから読み込まれた後は、空間マッピング メッシュで処理を開始する前に 10 秒間待機します。After the room mesh is loaded from file, wait for 10 seconds before processing starts on the spatial mapping mesh.
  • 処理が完了したら、面は床、壁、ceiling などを表す表示されます。When processing is complete, planes will appear to represent the floor, walls, ceiling, etc.
  • 結局、平面の発見された、太陽系の床面のカメラの近くのテーブルに表示されることがわかります。After all of the planes have been found, you should see a solar system appear on a table of floor near the camera.
  • 2 つのポスターがすぎるの壁、カメラの近くに表示されます。Two posters should appear on walls near the camera too. 切り替えて、シーンタブに表示されない場合ゲームモード。Switch to the Scene tab if you cannot see them in Game mode.
  • キーを押して、再生再生モードを終了するには、もう一度ボタンをクリックします。Press the Play button again to exit play mode.
  • 構築し、通常どおり、HoloLens にデプロイします。Build and deploy to the HoloLens, as usual.
  • スキャンおよび完了する空間のマッピング データの処理を待ちます。Wait for scanning and processing of the spatial mapping data to complete.
  • 平面が表示されたら、世界では、太陽系とポスターを見つけようとします。Once you see planes, try to find the solar system and posters in your world.

第 4 章 - 配置Chapter 4 - Placement

目標Objectives

  • ホログラムがサーフェイス上に収まるかどうかを決定します。Determine if a hologram will fit on a surface.
  • ホログラムを画面に収まるできる/できない場合は、ユーザーにフィードバックを提供します。Provide feedback to the user when a hologram can/cannot fit on a surface.

手順Instructions

  • Unity の階層パネルで、 SpatialProcessingオブジェクト。In Unity's Hierarchy panel, select the SpatialProcessing object.
  • インスペクターパネルで、検索、平面に表面メッシュ (スクリプト) コンポーネント。In the Inspector panel, find the Surface Meshes To Planes (Script) component.
  • 変更、描画面プロパティをNothing選択を解除します。Change the Draw Planes property to Nothing to clear the selection.
  • 変更、描画面プロパティを、壁平面のみが表示されるようにします。Change the Draw Planes property to Wall, so that only wall planes will be rendered.
  • プロジェクトパネル、スクリプトフォルダーをダブルクリックして、 Placeable.cs Visual Studio で開きます。In the Project panel, Scripts folder, double-click on Placeable.cs to open it in Visual Studio.

Placeableスクリプトは、平面の検索が完了した後に作成されるポスターとプロジェクションのボックスに既にアタッチされています。The Placeable script is already attached to the posters and projection box that are created after plane finding completes. すべてを行う必要がありますが、いくつかのコードをコメント解除し、このスクリプトは、以下を実現します。All we need to do is uncomment some code, and this script will achieve the following:

  1. ホログラムが中心境界のキューブの 4 つの角からレイキャストによって、サーフェイス上に収まるかどうかを決定します。Determine if a hologram will fit on a surface by raycasting from the center and four corners of the bounding cube.
  2. サーフェスの法線でフラッシュ待つホログラムの十分な滑らかな判断を確認してください。Check the surface normal to determine if it is smooth enough for the hologram to sit flush on.
  3. 境界の周囲に配置されている中には、実際のサイズを表示するホログラム キューブを表示します。Render a bounding cube around the hologram to show its actual size while being placed.
  4. シャドウ/背後の下に floor/壁に配置される場所を表示するホログラムをキャストします。Cast a shadow under/behind the hologram to show where it will be placed on the floor/wall.
  5. 可能な場合、画面、または緑にホログラムを配置できませんがある場合は、赤、として影をレンダリングします。Render the shadow as red, if the hologram cannot be placed on the surface, or green, if it can.
  6. アフィニティを使用している画面の種類 (水平または垂直) と連携させるホログラムの向きを変更します。Re-orient the hologram to align with the surface type (vertical or horizontal) that it has affinity to.
  7. ジャンプまたはスナップ動作を回避するために選択した画面で、ホログラムがスムーズに配置します。Smoothly place the hologram on the selected surface to avoid jumping or snapping behavior.

コーディングの練習を下のすべてのコードのコメントを解除またはで完了したこのソリューションを使用して、 Placeable.cs:Uncomment all code in the coding exercise below, or use this completed solution in Placeable.cs:

using System.Collections.Generic;
using UnityEngine;
using Academy.HoloToolkit.Unity;

/// <summary>
/// Enumeration containing the surfaces on which a GameObject
/// can be placed.  For simplicity of this sample, only one
/// surface type is allowed to be selected.
/// </summary>
public enum PlacementSurfaces
{
    // Horizontal surface with an upward pointing normal.    
    Horizontal = 1,

    // Vertical surface with a normal facing the user.
    Vertical = 2,
}

/// <summary>
/// The Placeable class implements the logic used to determine if a GameObject
/// can be placed on a target surface. Constraints for placement include:
/// * No part of the GameObject's box collider impacts with another object in the scene
/// * The object lays flat (within specified tolerances) against the surface
/// * The object would not fall off of the surface if gravity were enabled.
/// This class also provides the following visualizations.
/// * A transparent cube representing the object's box collider.
/// * Shadow on the target surface indicating whether or not placement is valid.
/// </summary>
public class Placeable : MonoBehaviour
{
    [Tooltip("The base material used to render the bounds asset when placement is allowed.")]
    public Material PlaceableBoundsMaterial = null;

    [Tooltip("The base material used to render the bounds asset when placement is not allowed.")]
    public Material NotPlaceableBoundsMaterial = null;

    [Tooltip("The material used to render the placement shadow when placement it allowed.")]
    public Material PlaceableShadowMaterial = null;

    [Tooltip("The material used to render the placement shadow when placement it not allowed.")]
    public Material NotPlaceableShadowMaterial = null;

    [Tooltip("The type of surface on which the object can be placed.")]
    public PlacementSurfaces PlacementSurface = PlacementSurfaces.Horizontal;

    [Tooltip("The child object(s) to hide during placement.")]
    public List<GameObject> ChildrenToHide = new List<GameObject>();

    /// <summary>
    /// Indicates if the object is in the process of being placed.
    /// </summary>
    public bool IsPlacing { get; private set; }

    // The most recent distance to the surface.  This is used to 
    // locate the object when the user's gaze does not intersect
    // with the Spatial Mapping mesh.
    private float lastDistance = 2.0f;

    // The distance away from the target surface that the object should hover prior while being placed.
    private float hoverDistance = 0.15f;

    // Threshold (the closer to 0, the stricter the standard) used to determine if a surface is flat.
    private float distanceThreshold = 0.02f;

    // Threshold (the closer to 1, the stricter the standard) used to determine if a surface is vertical.
    private float upNormalThreshold = 0.9f;

    // Maximum distance, from the object, that placement is allowed.
    // This is used when raycasting to see if the object is near a placeable surface.
    private float maximumPlacementDistance = 5.0f;

    // Speed (1.0 being fastest) at which the object settles to the surface upon placement.
    private float placementVelocity = 0.06f;

    // Indicates whether or not this script manages the object's box collider.
    private bool managingBoxCollider = false;

    // The box collider used to determine of the object will fit in the desired location.
    // It is also used to size the bounding cube.
    private BoxCollider boxCollider = null;

    // Visible asset used to show the dimensions of the object. This asset is sized
    // using the box collider's bounds.
    private GameObject boundsAsset = null;

    // Visible asset used to show the where the object is attempting to be placed.
    // This asset is sized using the box collider's bounds.
    private GameObject shadowAsset = null;

    // The location at which the object will be placed.
    private Vector3 targetPosition;

    /// <summary>
    /// Called when the GameObject is created.
    /// </summary>
    private void Awake()
    {
        targetPosition = gameObject.transform.position;

        // Get the object's collider.
        boxCollider = gameObject.GetComponent<BoxCollider>();
        if (boxCollider == null)
        {
            // The object does not have a collider, create one and remember that
            // we are managing it.
            managingBoxCollider = true;
            boxCollider = gameObject.AddComponent<BoxCollider>();
            boxCollider.enabled = false;
        }

        // Create the object that will be used to indicate the bounds of the GameObject.
        boundsAsset = GameObject.CreatePrimitive(PrimitiveType.Cube);
        boundsAsset.transform.parent = gameObject.transform;
        boundsAsset.SetActive(false);

        // Create a object that will be used as a shadow.
        shadowAsset = GameObject.CreatePrimitive(PrimitiveType.Quad);
        shadowAsset.transform.parent = gameObject.transform;
        shadowAsset.SetActive(false);
    }

    /// <summary>
    /// Called when our object is selected.  Generally called by
    /// a gesture management component.
    /// </summary>
    public void OnSelect()
    {
        /* TODO: 4.a CODE ALONG 4.a */

        if (!IsPlacing)
        {
            OnPlacementStart();
        }
        else
        {
            OnPlacementStop();
        }
    }

    /// <summary>
    /// Called once per frame.
    /// </summary>
    private void Update()
    {
        /* TODO: 4.a CODE ALONG 4.a */

        if (IsPlacing)
        {
            // Move the object.
            Move();

            // Set the visual elements.
            Vector3 targetPosition;
            Vector3 surfaceNormal;
            bool canBePlaced = ValidatePlacement(out targetPosition, out surfaceNormal);
            DisplayBounds(canBePlaced);
            DisplayShadow(targetPosition, surfaceNormal, canBePlaced);
        }
        else
        {
            // Disable the visual elements.
            boundsAsset.SetActive(false);
            shadowAsset.SetActive(false);

            // Gracefully place the object on the target surface.
            float dist = (gameObject.transform.position - targetPosition).magnitude;
            if (dist > 0)
            {
                gameObject.transform.position = Vector3.Lerp(gameObject.transform.position, targetPosition, placementVelocity / dist);
            }
            else
            {
                // Unhide the child object(s) to make placement easier.
                for (int i = 0; i < ChildrenToHide.Count; i++)
                {
                    ChildrenToHide[i].SetActive(true);
                }
            }
        }
    }

    /// <summary>
    /// Verify whether or not the object can be placed.
    /// </summary>
    /// <param name="position">
    /// The target position on the surface.
    /// </param>
    /// <param name="surfaceNormal">
    /// The normal of the surface on which the object is to be placed.
    /// </param>
    /// <returns>
    /// True if the target position is valid for placing the object, otherwise false.
    /// </returns>
    private bool ValidatePlacement(out Vector3 position, out Vector3 surfaceNormal)
    {
        Vector3 raycastDirection = gameObject.transform.forward;

        if (PlacementSurface == PlacementSurfaces.Horizontal)
        {
            // Placing on horizontal surfaces.
            // Raycast from the bottom face of the box collider.
            raycastDirection = -(Vector3.up);
        }

        // Initialize out parameters.
        position = Vector3.zero;
        surfaceNormal = Vector3.zero;

        Vector3[] facePoints = GetColliderFacePoints();

        // The origin points we receive are in local space and we 
        // need to raycast in world space.
        for (int i = 0; i < facePoints.Length; i++)
        {
            facePoints[i] = gameObject.transform.TransformVector(facePoints[i]) + gameObject.transform.position;
        }

        // Cast a ray from the center of the box collider face to the surface.
        RaycastHit centerHit;
        if (!Physics.Raycast(facePoints[0],
                        raycastDirection,
                        out centerHit,
                        maximumPlacementDistance,
                        SpatialMappingManager.Instance.LayerMask))
        {
            // If the ray failed to hit the surface, we are done.
            return false;
        }

        // We have found a surface.  Set position and surfaceNormal.
        position = centerHit.point;
        surfaceNormal = centerHit.normal;

        // Cast a ray from the corners of the box collider face to the surface.
        for (int i = 1; i < facePoints.Length; i++)
        {
            RaycastHit hitInfo;
            if (Physics.Raycast(facePoints[i],
                                raycastDirection,
                                out hitInfo,
                                maximumPlacementDistance,
                                SpatialMappingManager.Instance.LayerMask))
            {
                // To be a valid placement location, each of the corners must have a similar
                // enough distance to the surface as the center point
                if (!IsEquivalentDistance(centerHit.distance, hitInfo.distance))
                {
                    return false;
                }
            }
            else
            {
                // The raycast failed to intersect with the target layer.
                return false;
            }
        }

        return true;
    }

    /// <summary>
    /// Determine the coordinates, in local space, of the box collider face that 
    /// will be placed against the target surface.
    /// </summary>
    /// <returns>
    /// Vector3 array with the center point of the face at index 0.
    /// </returns>
    private Vector3[] GetColliderFacePoints()
    {
        // Get the collider extents.  
        // The size values are twice the extents.
        Vector3 extents = boxCollider.size / 2;

        // Calculate the min and max values for each coordinate.
        float minX = boxCollider.center.x - extents.x;
        float maxX = boxCollider.center.x + extents.x;
        float minY = boxCollider.center.y - extents.y;
        float maxY = boxCollider.center.y + extents.y;
        float minZ = boxCollider.center.z - extents.z;
        float maxZ = boxCollider.center.z + extents.z;

        Vector3 center;
        Vector3 corner0;
        Vector3 corner1;
        Vector3 corner2;
        Vector3 corner3;

        if (PlacementSurface == PlacementSurfaces.Horizontal)
        {
            // Placing on horizontal surfaces.
            center = new Vector3(boxCollider.center.x, minY, boxCollider.center.z);
            corner0 = new Vector3(minX, minY, minZ);
            corner1 = new Vector3(minX, minY, maxZ);
            corner2 = new Vector3(maxX, minY, minZ);
            corner3 = new Vector3(maxX, minY, maxZ);
        }
        else
        {
            // Placing on vertical surfaces.
            center = new Vector3(boxCollider.center.x, boxCollider.center.y, maxZ);
            corner0 = new Vector3(minX, minY, maxZ);
            corner1 = new Vector3(minX, maxY, maxZ);
            corner2 = new Vector3(maxX, minY, maxZ);
            corner3 = new Vector3(maxX, maxY, maxZ);
        }

        return new Vector3[] { center, corner0, corner1, corner2, corner3 };
    }

    /// <summary>
    /// Put the object into placement mode.
    /// </summary>
    public void OnPlacementStart()
    {
        // If we are managing the collider, enable it. 
        if (managingBoxCollider)
        {
            boxCollider.enabled = true;
        }

        // Hide the child object(s) to make placement easier.
        for (int i = 0; i < ChildrenToHide.Count; i++)
        {
            ChildrenToHide[i].SetActive(false);
        }

        // Tell the gesture manager that it is to assume
        // all input is to be given to this object.
        GestureManager.Instance.OverrideFocusedObject = gameObject;

        // Enter placement mode.
        IsPlacing = true;
    }

    /// <summary>
    /// Take the object out of placement mode.
    /// </summary>
    /// <remarks>
    /// This method will leave the object in placement mode if called while
    /// the object is in an invalid location.  To determine whether or not
    /// the object has been placed, check the value of the IsPlacing property.
    /// </remarks>
    public void OnPlacementStop()
    {
        // ValidatePlacement requires a normal as an out parameter.
        Vector3 position;
        Vector3 surfaceNormal;

        // Check to see if we can exit placement mode.
        if (!ValidatePlacement(out position, out surfaceNormal))
        {
            return;
        }

        // The object is allowed to be placed.
        // We are placing at a small buffer away from the surface.
        targetPosition = position + (0.01f * surfaceNormal);

        OrientObject(true, surfaceNormal);

        // If we are managing the collider, disable it. 
        if (managingBoxCollider)
        {
            boxCollider.enabled = false;
        }

        // Tell the gesture manager that it is to resume
        // its normal behavior.
        GestureManager.Instance.OverrideFocusedObject = null;

        // Exit placement mode.
        IsPlacing = false;
    }

    /// <summary>
    /// Positions the object along the surface toward which the user is gazing.
    /// </summary>
    /// <remarks>
    /// If the user's gaze does not intersect with a surface, the object
    /// will remain at the most recently calculated distance.
    /// </remarks>
    private void Move()
    {
        Vector3 moveTo = gameObject.transform.position;
        Vector3 surfaceNormal = Vector3.zero;
        RaycastHit hitInfo;

        bool hit = Physics.Raycast(Camera.main.transform.position,
                                Camera.main.transform.forward,
                                out hitInfo,
                                20f,
                                SpatialMappingManager.Instance.LayerMask);

        if (hit)
        {
            float offsetDistance = hoverDistance;

            // Place the object a small distance away from the surface while keeping 
            // the object from going behind the user.
            if (hitInfo.distance <= hoverDistance)
            {
                offsetDistance = 0f;
            }

            moveTo = hitInfo.point + (offsetDistance * hitInfo.normal);

            lastDistance = hitInfo.distance;
            surfaceNormal = hitInfo.normal;
        }
        else
        {
            // The raycast failed to hit a surface.  In this case, keep the object at the distance of the last
            // intersected surface.
            moveTo = Camera.main.transform.position + (Camera.main.transform.forward * lastDistance);
        }

        // Follow the user's gaze.
        float dist = Mathf.Abs((gameObject.transform.position - moveTo).magnitude);
        gameObject.transform.position = Vector3.Lerp(gameObject.transform.position, moveTo, placementVelocity / dist);

        // Orient the object.
        // We are using the return value from Physics.Raycast to instruct
        // the OrientObject function to align to the vertical surface if appropriate.
        OrientObject(hit, surfaceNormal);
    }

    /// <summary>
    /// Orients the object so that it faces the user.
    /// </summary>
    /// <param name="alignToVerticalSurface">
    /// If true and the object is to be placed on a vertical surface, 
    /// orient parallel to the target surface.  If false, orient the object 
    /// to face the user.
    /// </param>
    /// <param name="surfaceNormal">
    /// The target surface's normal vector.
    /// </param>
    /// <remarks>
    /// The aligntoVerticalSurface parameter is ignored if the object
    /// is to be placed on a horizontalSurface
    /// </remarks>
    private void OrientObject(bool alignToVerticalSurface, Vector3 surfaceNormal)
    {
        Quaternion rotation = Camera.main.transform.localRotation;

        // If the user's gaze does not intersect with the Spatial Mapping mesh,
        // orient the object towards the user.
        if (alignToVerticalSurface && (PlacementSurface == PlacementSurfaces.Vertical))
        {
            // We are placing on a vertical surface.
            // If the normal of the Spatial Mapping mesh indicates that the
            // surface is vertical, orient parallel to the surface.
            if (Mathf.Abs(surfaceNormal.y) <= (1 - upNormalThreshold))
            {
                rotation = Quaternion.LookRotation(-surfaceNormal, Vector3.up);
            }
        }
        else
        {
            rotation.x = 0f;
            rotation.z = 0f;
        }

        gameObject.transform.rotation = rotation;
    }

    /// <summary>
    /// Displays the bounds asset.
    /// </summary>
    /// <param name="canBePlaced">
    /// Specifies if the object is in a valid placement location.
    /// </param>
    private void DisplayBounds(bool canBePlaced)
    {
        // Ensure the bounds asset is sized and positioned correctly.
        boundsAsset.transform.localPosition = boxCollider.center;
        boundsAsset.transform.localScale = boxCollider.size;
        boundsAsset.transform.rotation = gameObject.transform.rotation;

        // Apply the appropriate material.
        if (canBePlaced)
        {
            boundsAsset.GetComponent<Renderer>().sharedMaterial = PlaceableBoundsMaterial;
        }
        else
        {
            boundsAsset.GetComponent<Renderer>().sharedMaterial = NotPlaceableBoundsMaterial;
        }

        // Show the bounds asset.
        boundsAsset.SetActive(true);
    }

    /// <summary>
    /// Displays the placement shadow asset.
    /// </summary>
    /// <param name="position">
    /// The position at which to place the shadow asset.
    /// </param>
    /// <param name="surfaceNormal">
    /// The normal of the surface on which the asset will be placed
    /// </param>
    /// <param name="canBePlaced">
    /// Specifies if the object is in a valid placement location.
    /// </param>
    private void DisplayShadow(Vector3 position,
                            Vector3 surfaceNormal,
                            bool canBePlaced)
    {
        // Rotate and scale the shadow so that it is displayed on the correct surface and matches the object.
        float rotationX = 0.0f;

        if (PlacementSurface == PlacementSurfaces.Horizontal)
        {
            rotationX = 90.0f;
            shadowAsset.transform.localScale = new Vector3(boxCollider.size.x, boxCollider.size.z, 1);
        }
        else
        {
            shadowAsset.transform.localScale = boxCollider.size;
        }

        Quaternion rotation = Quaternion.Euler(rotationX, gameObject.transform.rotation.eulerAngles.y, 0);
        shadowAsset.transform.rotation = rotation;

        // Apply the appropriate material.
        if (canBePlaced)
        {
            shadowAsset.GetComponent<Renderer>().sharedMaterial = PlaceableShadowMaterial;
        }
        else
        {
            shadowAsset.GetComponent<Renderer>().sharedMaterial = NotPlaceableShadowMaterial;
        }

        // Show the shadow asset as appropriate.        
        if (position != Vector3.zero)
        {
            // Position the shadow a small distance from the target surface, along the normal.
            shadowAsset.transform.position = position + (0.01f * surfaceNormal);
            shadowAsset.SetActive(true);
        }
        else
        {
            shadowAsset.SetActive(false);
        }
    }

    /// <summary>
    /// Determines if two distance values should be considered equivalent. 
    /// </summary>
    /// <param name="d1">
    /// Distance to compare.
    /// </param>
    /// <param name="d2">
    /// Distance to compare.
    /// </param>
    /// <returns>
    /// True if the distances are within the desired tolerance, otherwise false.
    /// </returns>
    private bool IsEquivalentDistance(float d1, float d2)
    {
        float dist = Mathf.Abs(d1 - d2);
        return (dist <= distanceThreshold);
    }

    /// <summary>
    /// Called when the GameObject is unloaded.
    /// </summary>
    private void OnDestroy()
    {
        // Unload objects we have created.
        Destroy(boundsAsset);
        boundsAsset = null;
        Destroy(shadowAsset);
        shadowAsset = null;
    }
}

構築し、デプロイBuild and Deploy

  • 同様に、プロジェクトをビルドし、HoloLens を展開します。As before, build the project and deploy to the HoloLens.
  • スキャンおよび完了する空間のマッピング データの処理を待ちます。Wait for scanning and processing of the spatial mapping data to complete.
  • 太陽系を確認したら、下にある投影ボックス見つめます、周囲に移動して選択ジェスチャを実行します。When you see the solar system, gaze at the projection box below and perform a select gesture to move it around. プロジェクション ボックスを選択したら、外接するキューブは、プロジェクション ボックス表示にされます。While the projection box is selected, a bounding cube will be visible around the projection box.
  • 部屋に別の場所を見つめますにヘッドを移動します。Move you head to gaze at a different location in the room. プロジェクションのボックスは、視線の先で従う必要があります。The projection box should follow your gaze. プロジェクションのボックスの下の影が赤に変わり場合、は、その画面でホログラムを配置することはできません。When the shadow below the projection box turns red, you cannot place the hologram on that surface. プロジェクションのボックスの下の影が緑色に変わり場合、は、もう 1 つ選択ジェスチャを実行することによって、ホログラムを配置できます。When the shadow below the projection box turns green, you can place the hologram by performing another select gesture.
  • 検索し、新しい場所に移動する壁 holographic のポスターのいずれかを選択します。Find and select one of the holographic posters on a wall to move it to a new location. Floor、ceiling のポスターを配置することはできず、ラベルを保持する各壁を正しい方向を移動するように注意してください。Notice that you cannot place the poster on the floor or ceiling, and that it stays correctly oriented to each wall as you move around.

第 5 章 - 重なりChapter 5 - Occlusion

目標Objectives

  • ホログラムが空間マッピング メッシュによってオクルー ジョンかどうかを決定します。Determine if a hologram is occluded by the spatial mapping mesh.
  • 楽しいを実現するために異なるオクルー ジョン手法を適用効果。Apply different occlusion techniques to achieve a fun effect.

手順Instructions

最初に、ここでは現実の世界を occluding せず他ホログラムがメッシュの空間マッピングを許可します。First, we are going to allow the spatial mapping mesh to occlude other holograms without occluding the real world:

  • 階層パネルで、 SpatialProcessingオブジェクト。In the Hierarchy panel, select the SpatialProcessing object.
  • インスペクターパネルで、検索、プレイ領域マネージャー (スクリプト) コンポーネント。In the Inspector panel, find the Play Space Manager (Script) component.
  • 右側に円をクリックして、セカンダリ マテリアルプロパティ。Click the circle to the right of the Secondary Material property.
  • 検索し、選択、オクルー ジョンマテリアルと、ウィンドウを閉じます。Find and select the Occlusion material and close the window.

次に、ここ、地球に特別な動作を追加する (sun) などの別のホログラムまたは空間マッピング メッシュ、オクルー ジョンなるたびに青い枠があるようにします。Next, we are going to add a special behavior to Earth, so that it has a blue highlight whenever it becomes occluded by another hologram (like the sun), or by the spatial mapping mesh:

  • プロジェクト パネルで、ホログラムフォルダー、展開、 SolarSystemオブジェクト。In the Project panel, in the Holograms folder, expand the SolarSystem object.
  • をクリックして地球します。Click on Earth.
  • インスペクターパネルで、地球の資料 (下部にあるコンポーネント)。In the Inspector panel, find the Earth's material (bottom component).
  • シェーダー ドロップダウン、シェーダーを変更カスタム > OcclusionRimします。In the Shader drop-down, change the shader to Custom > OcclusionRim. 地球の周りの青の強調表示は、別のオブジェクトによってオクルー ジョンはそのたびにこのレンダリングされます。This will render a blue highlight around Earth whenever it is occluded by another object.

最後に、ここ、太陽系惑星の x 線画像視覚効果を有効にします。Finally, we are going to enable an x-ray vision effect for planets in our solar system. 編集する必要があります。 PlanetOcclusion.cs (Scripts\SolarSystem フォルダーにある)、次を実現するには。We will need to edit PlanetOcclusion.cs (found in the Scripts\SolarSystem folder) in order to achieve the following:

  1. 惑星が SpatialMapping レイヤー (ルーム メッシュと平面) によってオクルー ジョンかどうかを決定します。Determine if a planet is occluded by the SpatialMapping layer (room meshes and planes).
  2. SpatialMapping レイヤーによってオクルー ジョンはそのたびに、惑星のワイヤー フレームの表現を表示します。Show the wireframe representation of a planet whenever it is occluded by the SpatialMapping layer.
  3. SpatialMapping 層によってブロックされていない場合は、惑星のワイヤー フレームの表現を非表示にします。Hide the wireframe representation of a planet when it is not blocked by the SpatialMapping layer.

PlanetOcclusion.cs でコーディングの練習に従うか、次のソリューションを使用します。Follow the coding exercise in PlanetOcclusion.cs, or use the following solution:

using UnityEngine;
using Academy.HoloToolkit.Unity;

/// <summary>
/// Determines when the occluded version of the planet should be visible.
/// This script allows us to do selective occlusion, so the occlusionObject
/// will only be rendered when a Spatial Mapping surface is occluding the planet,
/// not when another hologram is responsible for the occlusion.
/// </summary>
public class PlanetOcclusion : MonoBehaviour
{
    [Tooltip("Object to display when the planet is occluded.")]
    public GameObject occlusionObject;

    /// <summary>
    /// Points to raycast to when checking for occlusion.
    /// </summary>
    private Vector3[] checkPoints;

    // Use this for initialization
    void Start()
    {
        occlusionObject.SetActive(false);

        // Set the check points to use when testing for occlusion.
        MeshFilter filter = gameObject.GetComponent<MeshFilter>();
        Vector3 extents = filter.mesh.bounds.extents;
        Vector3 center = filter.mesh.bounds.center;
        Vector3 top = new Vector3(center.x, center.y + extents.y, center.z);
        Vector3 left = new Vector3(center.x - extents.x, center.y, center.z);
        Vector3 right = new Vector3(center.x + extents.x, center.y, center.z);
        Vector3 bottom = new Vector3(center.x, center.y - extents.y, center.z);

        checkPoints = new Vector3[] { center, top, left, right, bottom };
    }

    // Update is called once per frame
    void Update()
    {
        /* TODO: 5.a DEVELOPER CODING EXERCISE 5.a */

        // Check to see if any of the planet's boundary points are occluded.
        for (int i = 0; i < checkPoints.Length; i++)
        {
            // 5.a: Convert the current checkPoint to world coordinates.
            // Call gameObject.transform.TransformPoint(checkPoints[i]).
            // Assign the result to a new Vector3 variable called 'checkPt'.
            Vector3 checkPt = gameObject.transform.TransformPoint(checkPoints[i]);

            // 5.a: Call Vector3.Distance() to calculate the distance
            // between the Main Camera's position and 'checkPt'.
            // Assign the result to a new float variable called 'distance'.
            float distance = Vector3.Distance(Camera.main.transform.position, checkPt);

            // 5.a: Take 'checkPt' and subtract the Main Camera's position from it.
            // Assign the result to a new Vector3 variable called 'direction'.
            Vector3 direction = checkPt - Camera.main.transform.position;

            // Used to indicate if the call to Physics.Raycast() was successful.
            bool raycastHit = false;

            // 5.a: Check if the planet is occluded by a spatial mapping surface.
            // Call Physics.Raycast() with the following arguments:
            // - Pass in the Main Camera's position as the origin.
            // - Pass in 'direction' for the direction.
            // - Pass in 'distance' for the maxDistance.
            // - Pass in SpatialMappingManager.Instance.LayerMask as layerMask.
            // Assign the result to 'raycastHit'.
            raycastHit = Physics.Raycast(Camera.main.transform.position, direction, distance, SpatialMappingManager.Instance.LayerMask);

            if (raycastHit)
            {
                // 5.a: Our raycast hit a surface, so the planet is occluded.
                // Set the occlusionObject to active.
                occlusionObject.SetActive(true);

                // At least one point is occluded, so break from the loop.
                break;
            }
            else
            {
                // 5.a: The Raycast did not hit, so the planet is not occluded.
                // Deactivate the occlusionObject.
                occlusionObject.SetActive(false);
            }
        }
    }
}

構築し、デプロイBuild and Deploy

  • ビルドし、通常どおり、HoloLens をアプリケーションに展開します。Build and deploy the application to HoloLens, as usual.
  • スキャンが完了する空間のマッピング データの処理を待機 (壁に表示される青い線が表示されます)。Wait for scanning and processing of the spatial mapping data to be complete (you should see blue lines appear on walls).
  • 太陽系の投影のボックスをオンにし、壁の横にある、またはカウンターの背後にあるボックスを設定します。Find and select the solar system's projection box and then set the box next to a wall or behind a counter.
  • 基本の重なりは、ポスターまたはプロジェクションのボックスにピアリングするサーフェスの背後にある非表示を表示できます。You can view basic occlusion by hiding behind surfaces to peer at the poster or projection box.
  • 地球を探して、別のホログラムまたは画面に移動するたびに、青の強調表示効果にする必要があります。Look for the Earth, there should be a blue highlight effect whenever it goes behind another hologram or surface.
  • 壁または部屋に他の面の背後にある惑星の移動を観察します。Watch as the planets move behind the wall or other surfaces in the room. X 線画像ビジョンなり、ワイヤー フレームのスケルトンを確認できます。You now have x-ray vision and can see their wireframe skeletons!

最後ですThe End

これで終了です。Congratulations! 完了したのでMR 空間 230。空間マッピングします。You have now completed MR Spatial 230: Spatial mapping.

  • Unity 環境と負荷の空間のマッピング、データをスキャンする方法を理解します。You know how to scan your environment and load spatial mapping data to Unity.
  • シェーダーとマテリアルを使用して、再世界を視覚化する方法の基本を理解します。You understand the basics of shaders and how materials can be used to re-visualize the world.
  • 平面を検索およびメッシュから三角形を削除するための新しい処理手法の学習しました。You learned of new processing techniques for finding planes and removing triangles from a mesh.
  • 移動し、理にかなっているサーフェイスにホログラムを配置できました。You were able to move and place holograms on surfaces that made sense.
  • 異なるオクルー ジョン手法が発生し、x 線画像のビジョンの電源が利用されています!You experienced different occlusion techniques and harnessed the power of x-ray vision!