ストリーミングはほとんどのブラウザと
Developerアプリで視聴できます。
-
RoomPlanにおける機能強化の詳細
RoomPlanのエキサイティングなアップデートをご確認ください。より詳細にエリアをスキャンし、複数の部屋をキャプチャし、個々のスキャンを1つの大きな構造物にマージする方法について解説します。また、RoomPlanの結果を既存の3Dモデルライブラリに統合する際のワークフローやベストプラクティスについても紹介します。
リソース
関連ビデオ
WWDC23
WWDC22
-
ダウンロード
♪ ♪
Alex: こんにちは Video Enginnering TeamのAlexです 今日は同僚のAntoineと私が RoomPlanの新機能を 紹介します RoomPlanはARKitを利用した 高度な機械学習アルゴリズムで 壁 窓 ドア 開口部や その他の部屋を定義する オブジェクトを検出します AppleのRoomCaptureView APIを使用すると スキャン体験をアプリに直接 統合できます スキャンが終わるとアプリは 出来上がった3Dモデルを提示し USDZファイルに エクスポートできます このセッションでは RoomPlanの 新機能についてお話しします カスタムのARSessionを使って RoomPlanとARKitを組み合わせる 新しい方法についても説明します 続いてマルチルームの対応についてです 新しいMultiRoom APIにより 個々のルームスキャンを 1つの大きな構造に統合することができます 次にRoomCaptureViewの新しい VoiceOverサポートを紹介します RoomPlan表現の改善と新しい ワークフローを可能にする エクスポート機能の強化 についてもお話しします
まずはカスタムのARSessionの サポートから始めていきます
前述したようにRoomPlanは ARKitからの情報を頼りにしており スキャン中に壁 窓 ドア 開口部 その他のオブジェクトを検出してます このためRoomCaptureSessionは デフォルトのARSessionで 動作してます iOS 17の新機能としてRoomPlanは ARWorldTrackingConfigurationで カスタムのARSessionを 使用できます これにより同じワークフロー内で RoomPlanとARKitを 組み合わせる新しい方法が いくつか可能になります いくつかの例を見てみましょう カスタムのARSessionでRoomPlanを 使用する1つの方法はRoomPlanの結果を ARKitのシーンジオメトリと平面検出と 組み合わせることで バーチャルコンテンツと 現実世界のジオメトリの間の インタラクションをより イマーシブなものにすることです さらにARKitの高画質画像キャプチャを 利用して空間の写真表現を収集し RoomPlanでよりリッチな 物件を作成することもできます また既存のAR体験の一部として RoomPlanを使用している場合は 既存のARAnchorを中断することなく RoomPlanの結果を 組み合わせることができます これらはカスタムのARSessionが提供できる 使用ケースのほんの一部です カスタムのARSession をどうRoomPlan に 渡すかいくつかのコードを見てみましょう 以前のRoomPlanのinitとstop関数です カスタムのARSessionをinit関数に 渡す方法は以下の通りです ARWorldTrackingConfigurationを持つ 任意のカスタムのARSessionは RoomCaptureSession内で 優先されます またstop関数には基礎となるARSessionを 一時停止するかを決定する 新しいオプションが追加されました RoomCaptureSessionが停止した後も ARSessionを継続させたい場合は ブール値をfalseに設定します 次のセクションでは ARSessionを使用することで 複数のスキャンを1つの大きな構造に 統合するような新しいワークフローが どのように可能になるかを見ていきます マルチルームのサポートに注目しましょう マルチルームサポートとはなんでしょうか? 以前のRoomPlanでは 1回のスキャンで1つの部屋の 3Dモデルを得ることができました 例えばダイニングルーム キッチン リビングルーム廊下 寝室など 家の中の様々な部屋を スキャンしたとします それらを統合したい場合 いくつかの難題に直面することになります 第一にこれらはすべて 独自の座標系にあり ワールド座標の原点と方向が 部屋ごとに異なります 第二に手作業でつなぎ合わせても 壁が重複しオブジェクトも 重複する可能性があります
座標系の違いに取り組んでみましょう ここで目指したいのは すべてのスキャンを同じ 座標系で撮影することです ここでは複数の部屋をスキャンするため 2つのアプローチを提案します はじめに連続したARSessionを使用します 次にARSessionの リローカリゼーションを使用します 連続したARSessionを使う方法を 見てみましょう 以前のRoomPlanではRoomCaptureSessionが 停止すると ARSessionは一時停止していました 各スキャンは 異なる座標系を持っています 新しいAPIではstop関数に 新しい引数が追加され 一時停止をfalseに 設定できるようになりました これによりARSessionは次のスキャンでも その次のスキャンでも 最終的にARSessionを一時停止するまで 実行し続けることができます
この方法を使えば複数のスキャンで 同じARSessionを確実に実行できます これにより 共通のワールド座標系を 持つことができます これがどのように機能するか コードで見てみましょう RoomCaptureSession を 連続したARSessionで実行する例です 最初のスキャンを RoomCaptureSession.runで開始しました ここからが肝心な部分です RoomCaptureSession.stopの 新しいAPIでは pauseARSessionをfalseに 設定する必要があります これによりARSessionは 次のスキャンまで実行し続けます その後同じroomCaptureSessionインスタンスを 2回目のスキャンに使用します そして2回目のスキャンを止めます 結果として1回目と2回目のスキャン結果を 同じ座標系で得ることができました 同じ座標空間内で個々の部屋の スキャンを行うもう一つの方法は ARSessionのリローカリゼーションを 使用することです この方法は翌日や翌週に 同じ場所に戻ってくるなど 個々の部屋を スキャンする場合に最適です この仕組みを見てみましょう 繰り返しますがこれは1つの部屋の 3Dモデルを得るための1回のスキャンです RoomCaptureSessionを停止し ARSessionを一時停止するので 今後のスキャンでリローカライズが 機能するためには ARWorldMapをディスクに 保存する必要があります ARSessionが一時停止しているときに 前回のスキャンを続行したい場合は 一時停止しているARSessionから ARWorldMapをロードして スキャンを再開できます
このARWorldMapを使えば前回の スキャンの環境に対して リローカライズを行い一連のスキャンが すべて共通の座標系を 共有するようにできます リローカリゼーションを使用した スキャンワークフローの サンプルコードを見てみましょう まず1回目のスキャンを実行します そしてARSessionを一時停止して 最初のスキャンを停止します リローカリゼーションを行うには ARSessionが一時停止したときに ARWorldMapを保存する 必要があります
2回目のスキャンを実行する前に 前のARWorldMapを 復元する必要があります まずARWorldMapを読み込みます そして読み込んだARWorldMapを ARWorldTrackingConfiguration .initialWorldMapに指定します ARSessionを実行します リローカリゼーションが完了すると 前のARSessionが読み込まれます 現在のワールド座標を前の ワールド座標に合わせて 前のワールド座標に合わせます それから2回目のスキャンを 実行しましょう 最後に2回目のスキャンを中止します 以上の手順で1回目と2回目の スキャン結果は同じ 3Dワールド座標になります 同じ3D座標系で複数の スキャンをリンクさせる 2つのアプローチを見てきました 次に新しいMultiRoom APIを使用して それらを1つの結合された構造に マージする方法を見ていきます 各スキャンに対してRoomBuilder APIを実行し 個々のCapturedRoomを 生成することができます 先に示したように連続的なARSessionと ARSessionのリローカリゼーションでは すべてのCapturedRoomは 同じ3Dワールド空間にあります これは3つのキャプチャルームを持つ RoomBuilderからの出力です 今我々は新しいマージAPIである StructureBuilderを提供し すべてを1つの大きな構造体である CapturedStructureに 統合します StructureBuilder APIの サンプルコードを見てみましょう ここではStructureBuilder APIを 使用して複数のスキャンを 統合する方法を説明します まず設定オプションを持つ StructureBuilder インスタンスを作成します 次に先程スキャンした複数の CapturedRoomを読み込む 配列を作成します その後StructureBuilder APIを 呼び出して 統合結果である capturedStructureを取得して 最後に capturedStructureを USDZファイルにエクスポートします これが CapturedStructureの 定義です まず部屋という特性があります CapturedRoom インスタンスの配列です そして統合された壁 ドア 窓 開口部 オブジェクトのプロパティがあります 最後にUSDZファイルに エクスポートする機能があります
それでは実際にMultiRoomを 使用してみましょう StructureBuilder APIを使用して 複数のスキャンを統合しUSDZファイルに エクスポートできるサンプルアプリを 提供しています USDZファイルはiOSとmacOSの 両方でプレビューできます USDZファイルをBlenderのような デジタルコンテンツ作成ツールに 読み込むことで さらに一歩進めることができます
3Dモデルに美化処理を施したら さらに見栄えがよくなりました
最後にMultiRoom対応で最高の MultiRoom体験を得るための 注意点について 説明していきましょう MultiRoomは1~4つのベッドルーム リビングルーム キッチン ダイニングルームを備えた 一般的な平屋住宅に 最適です 個々の部屋をスキャンして統合する場合 最大総面積は2,000平方フィート (約186平方メートル)をお勧めします RoomPlanがクリアなビデオストリームと 良好なARトラッキング性能で スキャンできるように 50ルクス以上の良好な 照明があることが推奨されます さてここでAntoineがiOS 17での RoomPlanのさらなる 強化について教えてくれます Antoine: Alex ありがとう アクセシビリティをピックアップして 話していきましょう レンダリングといえばビジュアルモダリティを 思い浮かべる人が多いでしょうが 弱視者にとってこのモダリティは あまり意味がありません 今年RoomPlanはVoiceOverが 有効な場合に音声フィードバックを追加し 携帯電話がスキャンに関する ガイドラインを提供し 見たものを説明できるようにしました 音声: デバイスを開始位置に移動します。 カメラを壁の下の端に向けます 暖炉 壁 窓 Antoine: それではRoomPlanが部屋から 収集できる新しい情報と それがどのように レンダリングされるかについて 説明しましょう RoomPlanなら 様々な部屋を スキャンすることができます しかしこれまでは限られた部屋の状況を 正確に表現するには限界がありました RoomPlanは斜めの壁や曲線の壁だけでなく 食器洗い機 オーブン シンクなどに 対応できるようになりました 埋め込み式のキッチン要素も含む さらに多様な部屋に 対応するようになりました RoomPlanはまた与えられたカテゴリの オブジェクトの構成を検出するように 改良されました 例えば一人掛けからL字型 シンプルな四角いソファまでRoomPlanが 新バージョンで検出する ソファの種類が多くなりました RoomPlanを紹介したとき RoomPlanがスキャンできる2つの要素 サーフェスとオブジェクトについて 話しました 今私たちは部屋の中のエリアを 表現するための新しい要素を 追加していて それをセクションと呼んでいます 壁はポリゴンとして記述できるようになり 斜めの壁や梁のある壁など 一様でない壁を扱えるようになりました これまでは湾曲した壁や窓は データのみのAPIに含まれていました これでRoomCaptureViewの結果は 湾曲した壁もレンダリング できるようになり 表面のカテゴリに加え床のカテゴリも 多角形として表現できます オブジェクトに属性が追加され カテゴリ内のさまざまな構成を より適切に表現できるようになりました サーフェスとオブジェクトに 新しい親変数を持つようになりました この識別子には 親の識別子が含まれます 例えば窓の親は壁であり 椅子の親はテーブルであり 食器洗い機の親は 収納庫であるように これらの改善点について 例を挙げて詳しく説明しましょう 各セクションではみなさんの部屋や家の 様々なエリアについて説明しています セクションにはリビングルーム ベッドルーム バスルーム キッチン ダイニングルームの ラベルがあります それは所定の位置で所定の階にあります
一様でない壁はpolygonCorner 変数を使ってポリゴンとして レンダリングできます フロアはスキャン中は矩形として表現され スキャン終了時には多角形として 美化されます 食器洗い機 オーブン 洗面台の親が レンダリングで彫られるようになりました RoomPlanを導入したとき オブジェクトを記述するために カテゴリが使用されました しかしこの表現には限界があります 椅子を例にとってみましょう 例えばスツール ダイニングチェア オフィスチェアなどです どれも目的が違うのです オブジェクトをよりよく表現するために 属性を追加することにしました この例では属性を使用することで スキャンされた内容をより忠実に 理解することができます RoomPlan API では属性は enum の 多相配列を通して利用可能です しかし列挙型は属性を理解する 最良の方法ではありません 次のセクションでは より魅力的な表現にたどり着くための 新たな方法を探っていきます 属性はスキャン中に取得された その他の新しい情報とともに エクスポートされた結果に 含めることができるようになりました USDZノードからメタデータを 検索するファイルと エクスポートされたUSDZを モデルでリッチ化する構造です ルームをメッシュとして エクスポートする場合 サーフェスとオブジェクトのノードの ツリーを含むUSDZを作成します 今年はセクションセンターを含む セクショングループが加わりました しかしそうすると壁やオブジェクトの寸法 オブジェクトの属性など スキャンから相当量の情報が 欠落してしまいます 部屋のエクスポート時に マッピングファイルの作成が できるようになりました これはUSDZ一意なノード名と その識別子によって一意に識別される CapturedRoom要素との間の ブリッジを作成する文字列から UUIDへのエンコードされた辞書です
RoomPlan APIでどのように 展開するか見てみましょう RoomPlan の以前のバージョンでは 部屋は2つの方法でエクスポートできました USDZ としてエクスポート関数を介して JSON または Plist として CapturedRoom構造体をエンコードします iOS 17の新機能として エクスポート機能でUSDZに 部屋をマッピングするための メタデータURLを指定すると 2つのエクスポート情報を 関連付けることができます こうすることでスキャンした部屋を レンダリングする際に サーフェスやオブジェクトに関する 追加情報を照会できます 新しいマッピングファイルとともに モデルプロバイダを導入しオブジェクトを これまでボックスとして表現されていた スキャンされた属性に一致するモデルで 置き換えます ここで行いたいのはオブジェクトを 3Dモデルに関連付けることであり より具体的には そのURLに関連付けることです こうすることでオブジェクトを表現していた バウンディングボックスをより説得力のある 真実味のあるレンダリングで 置き換えることができます そのためにRoomPlanでは 新しい構造が用意されています モデルプロバイダ ModelProviderはカテゴリと 属性のセットをモデルURLに マッピングします カテゴリと属性を持つオブジェクトから ModelProviderに対応するモデルの URLが与えられるようになります これを部屋全体に一般化する 部屋はカテゴリと属性を持つ セクション サーフェス オブジェクトの 集合体である ModelProviderインスタンスは部屋の 各オブジェクトに3Dモデルの URLを関連付けることができます モデルの関連付けを処理する 構造ができたので モデルプロバイダとどのように 連携できるかを見てみましょう ModelProviderに入力する方法は いろいろあります 例えばデータベースから 属性のセットに一致するモデルを 取得したり既存のカタログに 注釈を付けたりすることができます ここでは非常にシンプルな例として 独自の小さなアセットカタログを 作成する方法を説明します カタログを4つのステップで作成し 使用します まずRoomPlanがサポートする カテゴリと属性を解析します 次に各カテゴリと属性セットに モデルを関連付けます その後 ModelProviderを インスタンス化します 最後にそれを使って部屋を エクスポートしましょう 属性を発見しいくつかのモデルを使って モデルプロバイダを 作成する方法を見てみましょう まずRoomPlanがサポートしている すべてのカテゴリを繰り返します 対応するカテゴリごとに フォルダを作成します 各フォルダには後でモデルを追加できます 次に各カテゴリについて RoomPlanがサポートする 属性の組み合わせをリクエストします サポートされている属性のセットごとに フォルダを作成します それぞれのフォルダにカテゴリや 属性のセットに対応する 3Dモデルを追加できます カタログのコンテンツが 準備できたところで インデックスファイルを 作成する必要があります カタログインデックスを扱う構造体の例です これは要素の配列を含み 各要素はカテゴリまたは属性の セットを含みます 各要素は対応するモデルの パスへの参照を持っています これでインデックスファイルを plistとしてカタログを バンドルとして保存できます モデル付きの部屋を エクスポートしたいときや モデルプロバイダを使って オブジェクトをモデルに関連付けたいときは このカタログバンドルを使います 行うべきことはカタログの カテゴリと属性を繰り返し 対応するモデルのURLを見つけ 属性がなければモデルのURLを カテゴリに関連付け 属性のセットにモデルのURLを 関連付けることだけです 最後のステップは出力URL モデルプロバイダインスタンス .modelオプションを指定して エクスポート関数を呼び出すことです。 出来上がり!みなさんのスキャンに対応する 3Dモデルを含むUSDZができあがります さらにUSDZを BelnderのようなDCCツールに渡し ライトやシャドウを 追加することもできます 説得力のある結果を作成するために サンプルコードにあらかじめ入力された カタログを追加しました 再び出番ですよ Alex! Alex: ありがとうAntoine 今日私たちが共有したことを 振り返ってみましょう 第一にカスタムのARSessionの サポートによりスキャンと同時に 高品質の画像やビデオを キャプチャして物件を改善するといった 新しい使用例が可能になります また複数の部屋をスキャンし 新しいStructureBuilder APIを 使っての家全体の統合3D モデル生成もできます 弱視ユーザーのスキャン体験を 向上させる目的でRoomPlanは RoomCaptureView使用時に VoiceOver対応するようになりました RoomPlanの新しいオブジェクト属性は スキャンされた部屋を より正確に表現することができます そして最後に新しい エクスポートAPIを使って カスタムカタログの3Dモデルに 対応するスキャンした オブジェクトに割り当てることが できるようになりました こちらがiOS 17のRoomPlanです みなさんがRoomPlanで何を創り出すか 待ちきれないです
-
-
3:00 - RoomPlan with custom ARSession
// RoomCaptureSession public class RoomCaptureSession { // Init: ARSession is an optional input for RoomCaptureSession public init(arSession: ARSession? = nil) { ... } // Stop: pauseARSession is used for whether to continue ARSession experience public func stop(pauseARSession: Bool = true) { ... } }
-
5:50 - MultiRoom support with Continuous ARSession
// Continuous ARSession // start 1st scan roomCaptureSession.run(configuration: captureSessionConfig) // stop 1st scan with continuing ARSession roomCaptureSession.stop(pauseARSession: false) // start 2nd scan roomCaptureSession.run(configuration: captureSessionConfig) // stop 2nd scan (pauseARSession = true by default) roomCaptureSession.stop()
-
7:30 - MultiRoom capture with loading ARWorldMap
// Capture with loading ARWorldMap // load ARWorldMap let arWorldMap = try NSKeyedUnarchiver.unarchivedObject(ofClass: ARWorldMap.self, from: data) // run ARKit relocalization let arWorldTrackingConfig = ARWorldTrackingConfiguration() arWorldTrackingConfig.initialWorldMap = arWorldMap roomCaptureSession.init() roomCaptureSession.arSession.run(arWorldTrackingConfig, options: []) // Wait for relocalization to complete // start 2nd scan roomCaptureSession.run(configuration: captureSessionConfig) // stop 2nd scan roomCaptureSession.stop()
-
9:40 - StructureBuilder
// StructureBuilder // create structureBuilder instance let structureBuilder = StructureBuilder(option: [.beautifyObjects]) // load multiple capturedRoom results to capturedRoomArray var capturedRoomArray: [CapturedRoom] = [] // run structureBuilder API to get capturedStructure let capturedStructure = try await structureBuilder.capturedStructure(from: capturedRoomArray) // export capturedStructure to usdz try capturedStructure.export(to: destinationURL)
-
10:11 - CapturedStructure
// CapturedStructure public struct CapturedStructure: Codable, Sendable { public var rooms: [CapturedRoom] public var walls: [Surface] public var doors: [Surface] public var windows: [Surface] public var openings: [Surface] public var objects: [Object] public var floors: [Surface] public var sections: [Section] public func export(to url: URL, metadataURL: URL? = nil, modelProvider: ModelProvider? = nil, exportOptions: USDExportOptions = .mesh) throws }
-
19:20 - Parse attributes and categories to create folder hierarchy
// Parse attributes and categories to create folder hierarchy for category in CapturedRoom.Object.Category.allCases { let url = generateFolderURL(category: category, attributes: []) FileManager.default.createDirectory(at: url, withIntermediateDirectories: true) for attributes in category.supportedCombinations { let url = generateFolderURL(category: category, attributes: attributes) FileManager.default.createDirectory(at: url, withIntermediateDirectories: true) } }
-
20:00 - Create a Catalog index
// Create a Catalog index struct RoomPlanCatalog: Codable { let categoryAttributes: [RoomPlanCatalogCategoryAttribute] } struct RoomPlanCatalogCategoryAttribute: Codable { enum CodingKeys: String, CodingKey { case folderRelativePath case category case attributes case modelFilename } let category: CapturedRoom.Object.Category let attributes: [any CapturedRoomAttribute] let folderRelativePath: String private(set) var modelFilename: String? = nil func encode(to encoder: Encoder) throws { … } }
-
20:15 - Create a Catalog bundle
// Create a Catalog bundle let catalog = RoomPlanCatalog(categoryAttributes: categoryAttributes) let plistEncoder = PropertyListEncoder() let data = try plistEncoder.encode(catalog) let catalogURL = inputURL.appending(path: "catalog.plist") try data.write(to: catalogURL) let fileWrapper = try FileWrapper(url: inputURL) try fileWrapper.write(to: outputURL, options: [.atomic, .withNameUpdating], originalContentsURL: nil)
-
20:22 - Instantiate a Model Provider from a Catalog
// Instantiate a Model Provider from a Catalog for categoryAttribute in catalog.categoryAttributes { guard let modelFilename = categoryAttribute.modelFilename else { continue } let folderRelativePath = categoryAttribute.folderRelativePath let modelURL = url.appending(path: folderRelativePath).appending(path: modelFilename) if categoryAttribute.attributes.isEmpty { try modelProvider.setModelFileURL(modelURL, for: categoryAttribute.category) } else { try modelProvider.setModelFileURL(modelURL, for: categoryAttribute.attributes) } }
-
20:47 - Exporting a captured room to usdz with models
// Exporting a captured room to usdz with models try capturedRoom.export(to: outputURL, modelProvider: modelProvider, exportOptions: .model)
-
-
特定のトピックをお探しの場合は、上にトピックを入力すると、関連するトピックにすばやく移動できます。
クエリの送信中にエラーが発生しました。インターネット接続を確認して、もう一度お試しください。