ストリーミングはほとんどのブラウザと
Developerアプリで視聴できます。
-
RealityKitによる空間描画アプリの構築
空間描画アプリの構築プロセスで、RealityKitを使いこなしましょう。RealityKitとARKitおよびSwiftUIの統合により生まれる、印象的で魅力的な空間体験の構築について、RealityKitにおいてリソースが動作する仕組みや、ユーザーのブラシの線の更新を高速化する低レベルのメッシュおよびテクスチャのAPIなどの機能を使用する方法を解説します。
関連する章
- 0:00 - Introduction
- 2:43 - Set up spatial tracking
- 5:42 - Build a spatial user interface
- 13:57 - Generate brush geometry
- 26:11 - Create a splash screen
リソース
関連ビデオ
WWDC24
- iOS、macOS、visionOS向けRealityKit APIの紹介
- Reality Composer Proにおけるインタラクティブな3Dコンテンツの作成
- RealityKitオーディオで空間コンピューティングアプリの質を向上
- visionOSにおけるカスタムホバーエフェクトの作成
WWDC23
-
ダウンロード
こんにちは Adrianです RealityKitチームのエンジニアです このセッションでは RealityKitの新機能を使用して visionOS用の空間描画アプリを 構築するプロセスについて説明します
RealityKitは 高性能な 3Dシミュレーションとレンダリング機能を iOS macOS visionOSに提供する フレームワークです visionOSでは RealityKitは アプリの空間機能の基盤となります
Apple Vision Proの発表以来 デベロッパの皆さんから 有益なフィードバックを多数受け取っており プラットフォームの機能を進化させながら そのフィードバックに対応するために 懸命に取り組んできました
本日は RealityKitで作成できるアプリの 限界を押し上げる 新しいAPIを紹介します 詳細を確認しましょう
Appleの空間描画アプリは RealityKitのパワフルな3Dの機能を SwiftUIおよびARKitと統合し 優れたユーザー体験を実現します カスタマイズされたメッシュやテクスチャ シェーダを構築して 洗練されたビジュアルデザインを 実現するのは 楽しい作業です
アプリを起動すると 目を引くスプラッシュ画面が表示されます
簡単な設定プロセスの後 作成の準備が整います
空中で指をつまむだけで すぐに描画を開始できます
ブラシストロークの外観は パレットビューで変更できます
このアプリは チューブのようなソリッドブラシタイプと
きらめくスパークルブラシを サポートしています
ブラシストロークはカスタマイズ可能で ストロークの色や太さを変更できます
他にもいろいろあります このアプリを一緒に構築していきましょう それでは始めましょう まず 空間トラッキングを設定して アプリが手と環境のデータを 認識できるようにします
次にUIを作成して ブラシとキャンバスを 制御できるようにします また パワフルな新機能を使って ホバー時のUIの外観をカスタマイズします
RealityKitでのメッシュの 仕組みを詳しく確認し アプリで新しいRealityKit APIを使用して Metalでブラシジオメトリを 効率的に生成する方法を紹介します
動的なテクスチャと空間UI要素を使用して 魅力的なスプラッシュ画面を作成し アプリに最後の仕上げを施します
描画アプリには つまんで手を動かして描くときの 手のポーズを認識させる必要があります そのためには ハンドアンカーの 空間トラッキングを設定します
visionOSでは アプリは SwiftUIまたはRealityKitコンテンツを ウインドウ ボリューム スペースに 配置できます
アプリでイマーシブ空間を使用すると アンカーを使用して空間トラッキング情報を 受け取ることができます
これには ワールドアンカーと プレーンアンカーを使用した シーンの理解に関する情報と ハンドアンカーを使用した ポーズに関する情報が含まれます
visionOS 1.0では ARKitを使って このデータにアクセスできました
visionOS 2.0ではRealityKitアプリで 空間トラッキングを使用するための より簡単な方法を導入しています このAPIを使って 描画アプリで空間ハンドトラッキングを設定しましょう
RealityKitでは AnchorEntityを使って RealityKitエンティティを ARアンカーに固定できます
この描画アプリでは それぞれの手に 2つのAnchorEntityが作成されます 1つは親指の先端に固定され もう1つは人差し指の先端に固定されます
空間トラッキングデータにアクセスするには アプリはユーザーからの許可を必要とします この描画アプリで ユーザーがをタップすると 関連する承認が要求されます
重要なのは ユーザーに許可を求めるタイミングです
承認はアプリが必要なときにのみ要求します ここでは ユーザーが 描画を開始するときです
RealityKitでトラッキングデータの 承認を要求するには SpatialTrackingSessionを使用します これはvisionOS 2.0の新しいAPIです
アプリに必要な トラッキング機能を宣言します この場合 アプリには手のデータが必要です
次にSpatialTrackingSessionで runを呼び出します この時点で このトラッキングを 承認するためのアラートが表示されます
run関数は未承認のトラッキング機能の リストを返します このリストをチェックして 許可の有無を確認できます
空間トラッキングが承認された場合は AnchorEntityのtransformを介して トラッキングデータにアクセスできます
許可が拒否された場合 AnchorEntityのtransformは 更新されません ただし AnchorEntityは ポーズを視覚的に更新します
要点をまとめましょう ImmersiveSpaceを アプリで使用すると RealityKitコンテンツを 現実世界に固定できます
AnchorEntityを使用すると RealityKitコンテンツで アンカーを設定できます
そして今年からは アプリでAnchorEntityの transformにアクセスする必要がある場合 SpatialTrackingSessionを 使用できます
さらにSpatialTrackingSessionを 使用すると AnchorEntitiesは RealityKitの物理システムと対話できます
次は このアプリのユーザー インターフェイスについて説明します
スプラッシュ画面でをタップすると イマーシブ空間に移動し 描画キャンバスが表示されます
キャンバスのサイズや位置は 球形のハンドルをドラッグすることで 変更できます
描画を開始する準備ができたら パレットビューが表示されます
ここでブラシの形状と色を設定できます
描画の準備ができたら キャンバス内に足を踏み入れるだけで 開始できます
このインターフェイスの構築方法を 詳しく見ていきましょう
まず キャンバス配置インターフェイスから 始めます このインターフェイスを使用すると 描画領域を定義できます
キャンバス配置中に イマーシブ空間を構成する要素は 2つあります 床には3D形状でキャンバスの端が描かれ ハンドルを使用すると キャンバスの位置を変更できます
まず境界メッシュについて考えてみましょう このメッシュはリアルタイムで生成されます 境界のサイズはスライダをドラッグして 変更できるためです
メッシュは2つの円で定義されます 左の図に示すように 外側の円が緑色 内側の円が赤色になります
この形状をSwiftUIパスとして定義できます
円は360度広がる円弧です そのため半径が異なる円弧を2つ作成します
次に 正規化された偶奇の 塗りつぶしモードを指定して 作成する形状を定義しています
RealityKitでメッシュを生成するには 今年の新しいAPIを使用できます MeshResource extrudingです MeshResource extrudingは パワフルなAPIで 2Dベクトルコンテンツを 3Dモデルに変換できます
必要なのは シェイプの奥行と 解像度を指定することだけです
覚えておくべき重要な考慮事項が 1つあります visionOSでは RealityKitは フォビエーションされたレンダラを使用します 周辺視野内の画像の領域は 低解像度でレンダリングされます これにより アプリのパフォーマンスが最適化されます シーンに高コントラストの 薄いジオメトリが含まれていると アーティファクトのちらつきに 気づくかもしれません この例では リングが薄すぎます このような薄いジオメトリック要素は 避けてください
高コントラストの領域では 特に注意が必要です
この問題に対処するには ジオメトリの厚みを増やし 高コントラストの薄いエッジを削除します 左側ではアーチファクトのちらつきが 軽減されています
空間コンテンツの エイリアシングについて詳しくは WWDC23の「Explore rendering for spatial computing」 をご覧ください
次にキャンバスハンドルについて説明します 1つ指摘しておきたいことが あります このハンドルを見つめると 青いハイライト効果が生じます
visionOSでは HoverEffectComponentは ユーザーがRealityKitのコンテンツを 見つめたときに視覚効果を追加します
visionOS 1.0では HoverEffectComponentは デフォルトの スポットライト効果を使用します
今年はさらに2種類のホバー効果を HoverEffectComponentに導入します ハイライト効果はエンティティに 均一なハイライト色を適用します
HoverEffectComponentを ShaderGraphシェーダと 併用できるようになりました シェーダベースのホバー効果は 非常に柔軟性が高いため ホバー時のエンティティの外観を 正確に制御できます
ハンドルの青いハイライトを実現できるのは ハイライトホバー効果のおかげです ハイライト効果を使用するには ドットハイライトを使用して HoverEffectComponentを初期化し ハイライト色を指定します
強度の値を変更して ハイライトを より鮮やかにすることもできます
キャンバス配置のUI要素が 背後の環境の上で 光っているように見えることに 気付いたかもしれません これは加算ブレンドモードで 設定されているためです 今年 RealityKitは UnlitMaterialや PhysicallyBasedMaterialなどの 組み込みマテリアルで 加算ブレンドモードを 新たにサポートするようになりました これを使うには まずProgramを作成し ブレンドモードをaddに設定します
ユーザーが描画キャンバスを選択したら いよいよメインイベントです パレットビューが表示され ユーザーはブラシの設定を開始できます
パレットビューはSwiftUIで構築されており ブラシのタイプとスタイルを カスタマイズできます パレットの下部には 選択できる プリセットブラシのセットがあります
ブラシプリセットビューに 特に注目したいと思います 各ブラシプリセットサムネールは 実際に完全な3次元形状であることに 注意してください
このメッシュは実際のブラシストロークと 同じ方法で生成されます SwiftUIとRealityKitは シームレスに統合されます ここでは各サムネールに RealityViewを使用します これにより RealityKitの すべての機能を活用できます
ブラシプリセットを見つめると 目を引くホバー効果がアクティブになり ブラシに沿って紫色の光が流れます
これは先ほど説明した シェーダベースのホバー効果です
この効果をどのように実現したかを 詳しく見ていきましょう
シェーダベースのホバー効果は シェーダグラフの Hover Stateノードで実現します このノードはシェーダに ホバー効果を統合するための 便利なツールを提供します
例えば Intensityはシステムが提供する値で 視線の状態に基づいて 0から1の間でアニメーション化されます Intensity値を使用すると キャンバスハンドルで先ほど説明したような ハイライト効果を再現できます
ただしプリセットビューでは もっと高度な効果を作りたいと思っています
グロー効果は ブラシストロークの最初から最後まで ブラシメッシュに沿って 流れる必要があります
この複雑な効果を実現するために シェーダグラフマテリアルを使います シェーダグラフを一緒に見ていきましょう
Hover Stateノードの Time Since Hover Start プロパティを使用します これはホバーイベントが始まってからの 秒単位での値です
これを使って グローハイライトの 曲線に沿った位置を定義します ホバーイベントが始まると グローの位置が曲線に沿って移動し始めます
ブラシストロークのメッシュを生成する際 アプリはCurveDistanceという 属性を提供します アプリはUV1チャネルを介して 各頂点のCurveDistance値を提供します
これは ブラシストロークの曲線距離を 視覚化したものです この値はストロークの長さに応じて 増加します
シェーダはグローハイライトの位置を 曲線距離と比較します
こうすることでシェーダは 現在のジオメトリに対する グローの位置を把握できます
次に グロー効果のサイズを定義します
現在のジオメトリは グロー位置の範囲内にあるときに光ります
これでイージング曲線を追加できます これはグローがジオメトリ上を 移動するときの ホバー効果のIntensityを定義します
最後の手順は 計算したIntensity値に応じて ホバー効果の色と 元のブラシストロークの色を 混合することです
これは良くできています
シェーダベースのホバー効果を使用するには ますシェーダ設定を使用して HoverEffectComponentを作成します
次にShaderGraphMaterialを使用します これがHover Stateノードの 更新を受け取ります
ユーザーがブラシを設定する方法を 構築したので 次はアプリのコアについて説明します 各ブラシストロークの ジオメトリを生成します
大まかに言うと メッシュは頂点と それらを接続する三角形などの プリミティブの集合です
各頂点は その頂点の位置やテクスチャ座標など 様々な属性に関連付けられています
これらの属性はデータによって記述されます 例えば 各頂点の位置は3次元ベクトルです
頂点データはGPUに送信できるように バッファに整理する必要があります
ほとんどのRealityKitメッシュでは データはメモリ内で連続して整理されます したがってメモリ内では 頂点位置0の次に頂点位置1が続き その次に頂点2というように続きます 他のすべての頂点属性についても同様です インデックスバッファは別々に配置され これにはメッシュ内の各三角形の 頂点インデックスが含まれます
RealityKitの標準メッシュレイアウトは 汎用性が高く 様々なユースケースに対応します ただし場合によっては ドメイン固有のアプローチの方が効率的です
描画アプリはカスタムビルドの ジオメトリ処理パイプラインを使用して ユーザーのブラシストロークの メッシュを作成します
例えば 各ブラシストロークを滑らかにして メッシュの曲率を改善します
このアルゴリズムは最適化されているため ブラシストロークの末尾に 点を追加する処理が 可能な限り高速になります レイテンシを最小限に抑えることが重要です
ブラシストロークメッシュでは 頂点のレイアウトに 1つのバッファが使用されます
ただし標準的な メッシュレイアウトとは異なり 各頂点はその個々の全体が 順に記述されます
そのため属性はインターリーブされます 最初の頂点の位置の次に その頂点の法線が続き 次に従接線が続くというように すべての属性が記述されるまで続きます その後で初めてバッファは 2番目以降の頂点の記述を開始します
これとは対照的に 標準の頂点バッファは 各属性のすべてのデータを 連続してレイアウトします ブラシ頂点バッファのレイアウトは 描画アプリに特に便利です
ブラシストロークを生成するとき アプリは常に頂点バッファの末尾に 頂点を追加します ブラシ頂点バッファは 古いデータの位置を変更せずに 新しい頂点を追加できることに 注意してください ただし標準の頂点バッファでこれを行うと バッファの増大に伴い ほとんどのデータを移動する必要があります ブラシ頂点には 標準レイアウトで表示されるものとは 異なる属性もあります
位置 法線 従接線などの 一部の属性は標準です
色 マテリアルプロパティ 曲線距離などはカスタム属性です
アプリのコードでは ブラシの頂点は Metal Shading Languageの この構造体として表されます
構造体の各エントリは 頂点の属性に対応します
そこで問題に直面します 一方では 高性能ジオメトリエンジンの 頂点レイアウトを保持し 不要な変換やコピーを 避けたいと考えています しかし一方で ジオメトリエンジンのレイアウトは RealityKitの標準レイアウトと 互換性がありません 必要なのは GPUバッファを そのままRealityKitに取り込み RealityKitにその読み取り方法を 指示する方法です
そして今 LowLevelMeshという 新しいAPIでそれが可能になりました
LowLevelMeshを使うと 様々な方法で頂点データを配置できます
頂点データには 4つの異なる Metalバッファを使用できます そのためRealityKitの標準レイアウトに似た レイアウトを使うことができます
ただしバッファを複数用意すると 便利な場合もあります 例えば テクスチャ座標を 他の属性よりも頻繁に 更新する必要があるとします その場合 この動的データを 独自のバッファに移動する方が効率的です
頂点バッファをインターリーブするように 並べ替えることができます インターリーブと非インターリーブの 組み合わせも可能です
また 三角形ストリップなどの Metalプリミティブタイプも使用できます
LowLevelMeshと そのカスタムバッファレイアウトが アプリにどのようなメリットをもたらすか 考えてみてください
メッシュデータは 独自のカスタムレイアウトを持つ バイナリファイルから 取得されているかもしれません これでそのデータを 変換のオーバーヘッドなしで 直接RealityKitに転送できます
またはデジタルコンテンツ作成ツールや CADアプリケーションで見られるような バッファレイアウトが事前定義された 既存のメッシュ処理パイプラインを RealityKitにブリッジする場合もあります
LowLevelMeshは ゲームエンジンから RealityKitにメッシュデータを効率的に ブリッジする方法としても使用できます
LowLevelMeshにより メッシュデータをRealityKitに 提供する方法の可能性が広がります 皆さんのアプリで何が実現できるか 楽しみにしています では コードでLowLevelMeshを 作成する方法について見ていきましょう
これでアプリは 余分な変換や不要なコピーなしで 頂点バッファをそのまま LowLevelMeshに提供できます
LowLevelMesh属性を使用して 頂点のレイアウト方法を記述します 迅速な拡張機能で属性リストを SolidBrushVertex構造体に設定します
まず位置の属性を宣言します
詳しく見ていきましょう 最初のステップはセマンティクスの定義です これはLowLevelMeshに 属性の解釈方法を指示します
この場合 属性は位置なので そのセマンティクスを使います
次に この属性の Metal頂点形式を定義します この場合 SolidBrushVertexの 定義と一致するように float3を選択する必要があります
次に 属性のオフセットを バイト単位で指定します
最後にレイアウトの インデックスを指定します 頂点レイアウトのリストに インデックスを付けます これについては後で説明します 描画アプリは単一のレイアウトのみを 使うため インデックス0を使用します
次にその他のメッシュ属性を宣言します
法線属性と従接線属性は 異なるメモリオフセットと セマンティクスが使用されることを除いて 位置に似ています
色属性には半精度浮動小数点値を使用します 今年は任意のMetal頂点形式を LowLevelMeshで使用できます これには圧縮された頂点形式が含まれます
他の2つのパラメータには セマンティクスUV1とUV3を使用します また今年新たに 最大8つのUVチャネルをLowLevelMesh で使用できるようになりました シェーダグラフのマテリアルは これらの値にアクセスできます これでLowLevelMeshオブジェクト 自体を作成できます これを行うには LowLevelMesh記述子を作成します LowLevelMesh記述子は 概念的には Metalの MTLVertexDescriptorに似ていますが RealityKitがメッシュを取り込むために 必要な情報も含まれています
まず頂点とインデックスバッファに 必要な容量を宣言します
次に頂点属性のリストを渡します これは前のスライドでまとめたリストです
次に 頂点レイアウトのリストを作成します 各頂点属性はレイアウトの1つを使用します
LowLevelMeshは頂点データ用に 最大4つのMetalバッファを提供します バッファインデックスは どのバッファを使用するかを宣言します
次に バッファオフセットと 各頂点のストライドを指定します ほとんどの場合 ここで行ったように 1つのバッファのみを使用します
これでLowLevelMeshを初期化できます
最後のステップは パーツのリストを入力することです 各パーツはインデックスバッファの 領域にまたがります
各メッシュパーツに 異なるRealityKit マテリアルインデックスを 割り当てることができます
またここでは メモリ効率を向上させるため 三角形のストリップトポロジを使用します
最後に LowLevelMeshから MeshResourceを作成し それをエンティティの ModelComponentに割り当てます
LowLevelMeshの頂点データを 更新するときは withUnsafeMutableBytes APIを使用できます
このAPIを使用すると 実際のバッファにアクセスできます さらに GPUに渡されて レンダリングされます そのためメッシュデータを更新する際の オーバーヘッドは最小限です
例えば メッシュのメモリレイアウトが 事前にわかっているため bindMemoryを使用して 提供された生のポインタを バッファポインタに変換できます
インデックスバッファデータについても 同様です LowLevelMeshインデックスバッファは withUnsafeMutableIndicesを使用して 更新できます
LowLevelMeshが アプリのメッシュ処理パイプラインを 高速化する強力なツールであることは 既に説明しました LowLevelMeshを使用すると 頂点またはインデックスバッファの更新を GPU演算で バックアップすることもできます 例を見てみましょう
これは描画アプリのスパークルブラシです ブラシストロークに追従する パーティクルフィールドを生成します このパーティクルフィールドは フレームごとに動的に更新されるため ソリッドブラシの場合とは異なる 更新スキームを使用します
メッシュ更新の頻度と複雑さを考えると GPUを使うのは理にかなっています
詳しく見ていきましょう スパークルブラシには 位置や色などのパーティクルごとの 属性のリストが含まれています 前と同様に curveDistanceパラメータと パーティクルのサイズも含まれています
GPUパーティクルシミュレーションでは SparkleBrushParticle型を使用して 各パーティクルの属性と速度を追跡します アプリはシミュレーションに SparkleBrushParticlesの 補助バッファを使用します
SparkleBrushVertex構造体は メッシュの頂点データに使用されます これには各頂点のUV座標が含まれており シェーダは3D空間でパーティクルを 方向付ける方法を理解できます パーティクルごとに 4つの頂点を持つ平面が作成されます
スパークルブラシメッシュの更新用に 2つのバッファを維持する必要があります SparkleBrushParticleで埋められた パーティクルシミュレーションバッファと SparkleBrushVerticesを含む LowLevelMesh頂点バッファです
ソリッドブラシと同様に 頂点バッファの仕様を LowLevelMesh属性のリストと 共に提供します
属性のリストは SparkleBrushVertexの メンバーに対応します
GPUでLowLevelMeshを 設定するときは Metalコマンドバッファと 演算コマンドエンコーダを使用します
バッファが処理を終えると RealityKitは自動的に変更を適用します コードでは次のようになります 前に述べたように ここではパーティクルシミュレーションに Metalバッファを使用し 頂点バッファにLowLevelMeshを使用します
Metalコマンドバッファと 演算コマンドエンコーダを設定します これでアプリは GPU演算カーネルを実行して メッシュを構築できるようになります
LowLevelMeshでreplaceを呼び出して コマンドバッファを提供します
Metalバッファが返されます この頂点バッファはRealityKitが レンダリングのために直接使用します
シミュレーションをGPUに ディスパッチした後 コマンドバッファをコミットします コマンドバッファの処理が完了すると RealityKitは更新された頂点データの使用を 自動的に開始します
高速で応答性の高い ブラシストローク生成により アプリの見た目が向上します では魅力的なスプラッシュ画面で アプリの最後の仕上げをしましょう
スプラッシュ画面は ユーザーをアプリの世界へ迎え入れる 最適な方法です 楽しみながらアプリのビジュアルスタイルを 披露する機会にもなります
アプリのスプラッシュ画面には 4つの視覚的要素があります
ロゴタイプには 2つの異なるフォントを使用した 「RealityKit Drawing App」という 3Dテキストが含まれています
ロゴマークも3D形状です
下部には ユーザーに描画の開始を促す ボタンがあります
そして背景には ユーザーの環境で光る 印象的なグラフィックがあります
ロゴタイプの作成から始めましょう
まずデフォルトのシステムフォントで 「RealityKit」のAttributed Stringを 作成します
今年新たに RealityKitのMeshResourceを MeshResource extrudingを使用して AttributedStringから 作れるようになりました
AttributedStringを使用しているので 異なるプロパティを持つテキスト行を 簡単に追加できます 「Drawing App」というテキストを 別のフォントで サイズを大きくして描きましょう
次に段落スタイルを使用して テキストを中央揃えにします
AttributedStringを使用して テキストのスタイルを 設定する方法については WWDC21の 「What’s new in Foundation」をご覧ください
これまでに作成したテキストを 拡大してみましょう 現時点では3Dモデルが 少し平坦に見えるので カスタマイズしましょう これを行うには ShapeExtrusionOptions構造体を MeshResource extrudingに渡します
より厚い3D形状を作成するために まず奥行きを大きくします 次に メッシュに 2つ目のマテリアルを追加します 前面 背面 側面に割り当てる マテリアルインデックスを指定できます
最後に テキストを正面から見たとき アウトラインマテリアルがより目立つように 微細な面取りを施します ここでは面取り半径を 0.1ポイントに指定します
このアプリでは MeshResource extrudingを使用して ロゴマークも生成します SwiftUIパスを使用するので 形状の定義方法には 非常に柔軟性があります ロゴマークは 一連のベジエ曲線として設定されます
SwiftUIパスの詳細については SwiftUIチュートリアル 「Drawing paths and shapes」 をご覧ください
次にスプラッシュ画面の 背景について説明します これはアプリの中でも 最も印象的な美的要素の1つです これを構築するために LowLevelTextureという 新しいAPIを使いました LowLevelTextureは LowLevelMeshと同じ 高速リソース更新セマンティクス を提供しますが テクスチャアセット用です
スプラッシュ画面では LowLevelTextureを使用して ピル型シェイプが連なる 一種の形状記述を生成します この形状記述は テクスチャの赤チャネルに保存されます
各ピルの内部には暗い領域があり ピルの外側は明るい領域です
テクスチャの緑チャネルには スプラッシュ画面の ビネットの記述が保存されます
このテクスチャは Reality Composer Proの シェーダグラフシェーダを介して 最終画像に解釈されます
LowLevelTextureは そのDescriptorから作成します LowLevelTexture記述子は Metalの MTLTextureDescriptorに相当します LowLevelMeshと同様に LowLevelTextureは ピクセル形式とテクスチャの使用を 詳細に制御します そしてRealityKitで圧縮ピクセル形式を 使用できるようになりました このスプラッシュ画面で必要なのは 赤と緑のチャネルだけなので ピクセル形式RG16Floatを使用します
記述子から LowLevelTextureを初期化できます 次に LowLevelTextureから RealityKitテクスチャリソースを作成します
これでこのテクスチャを マテリアルで使用する準備ができました
LowLevelMeshと同じように GPUでLowLevelTextureを更新します まず Metalコマンドバッファと 演算コマンドエンコーダを設定します
次にコマンドバッファを使って LowLevelTexture.replaceを 呼び出します 演算シェーダで書き込むことができる Metalテクスチャが返されます
最後に GPU演算をディスパッチし コマンドバッファをコミットします コマンドバッファの処理が完了すると Metalテクスチャが自動的に RealityKitに表示されます こうして完成したスプラッシュ画面の 見た目に非常に満足しています 目を引く背景とパーソナライズされた 3Dジオメトリを組み合わせることで 非常に独特な外観になっています このアプリに 最適な仕上げを施すことができました
以上で終わりです 今日は RealityKitでインタラクティブな 空間描画アプリを構築しました RealityKit 空間トラッキングAPIを使用して ユーザーが空間のどこに描画するかを アプリで検出できるようにしました SwiftUIと高度なホバー効果を使用して ブラシとスタイルをカスタマイズするための インタラクティブな空間UIを構築しました RealityKitでのリソース更新の仕組みを学び 高度な低レベルAPIを使用して メッシュとテクスチャを インタラクティブに生成しました 最後に新しいAPIを使って 空間体験向けに 2Dベクトルグラフィックスを インポートしました
今年のRealityKitの新機能の 詳細については 「Discover RealityKit APIs for iOS, macOS and visionOS」と 「Enhance your spatial computing app with RealityKit audio」をどうぞ
皆さんの成果に期待しています WWDC24の他のセッションも お楽しみください
-
-
4:18 - Using SpatialTrackingSession
// Retain the SpatialTrackingSession while your app needs access let session = SpatialTrackingSession() // Declare needed tracking capabilities let configuration = SpatialTrackingSession.Configuration(tracking: [.hand]) // Request authorization for spatial tracking let unapprovedCapabilities = await session.run(configuration) if let unapprovedCapabilities, unapprovedCapabilities.anchor.contains(.hand) { // User has rejected hand data for your app. // AnchorEntities will continue to remain anchored and update visually // However, AnchorEntity.transform will not receive updates } else { // User has approved hand data for your app. // AnchorEntity.transform will report hand anchor pose }
-
7:07 - Use MeshResource extrusion
// Use MeshResource(extruding:) to generate the canvas edge let path = SwiftUI.Path { path in // Generate two concentric circles as a SwiftUI.Path path.addArc(center: .zero, radius: outerRadius, startAngle: .degrees(0), endAngle: .degrees(360), clockwise: true) path.addArc(center: .zero, radius: innerRadius, startAngle: .degrees(0), endAngle: .degrees(360), clockwise: true) }.normalized(eoFill: true) var options = MeshResource.ShapeExtrusionOptions() options.boundaryResolution = .uniformSegmentsPerSpan(segmentCount: 64) options.extrusionMethod = .linear(depth: extrusionDepth) return try MeshResource(extruding: path, extrusionOptions: extrusionOptions)
-
9:33 - Highlight HoverEffectComponent
// Use HoverEffectComponent with .highlight let placementEntity: Entity = // ... let hover = HoverEffectComponent( .highlight(.init( color: UIColor(/* ... */), strength: 5.0) ) ) placementEntity.components.set(hover)
-
9:54 - Using Blend Modes
// Create an UnlitMaterial with Additive Blend Mode var descriptor = UnlitMaterial.Program.Descriptor() descriptor.blendMode = .add let prog = await UnlitMaterial.Program(descriptor: descriptor) var material = UnlitMaterial(program: prog) material.color = UnlitMaterial.BaseColor(tint: UIColor(/* ... */))
-
13:45 - Shader based hover effects
// Use shader-based hover effects let hoverEffectComponent = HoverEffectComponent(.shader(.default)) entity.components.set(hoverEffectComponent) let material = try await ShaderGraphMaterial(named: "/Root/SolidPresetBrushMaterial", from: "PresetBrushMaterial", in: realityKitContentBundle) entity.components.set(ModelComponent(mesh: /* ... */, materials: [material]))
-
16:56 - Defining a vertex buffer struct for the solid brush
struct SolidBrushVertex { packed_float3 position; packed_float3 normal; packed_float3 bitangent; packed_float2 materialProperties; float curveDistance; packed_half3 color; };
-
19:27 - Defining LowLevelMesh Attributes for solid brush
extension SolidBrushVertex { static var vertexAttributes: [LowLevelMesh.Attribute] { typealias Attribute = LowLevelMesh.Attribute return [ Attribute(semantic: .position, format: MTLVertexFormat.float3, layoutIndex: 0, offset: MemoryLayout.offset(of: \Self.position)!), Attribute(semantic: .normal, format: MTLVertexFormat.float3, layoutIndex: 0, offset: MemoryLayout.offset(of: \Self.normal)!), Attribute(semantic: .bitangent, format: MTLVertexFormat.float3, layoutIndex: 0, offset: MemoryLayout.offset(of: \Self.bitangent)!), Attribute(semantic: .color, format: MTLVertexFormat.half3, layoutIndex: 0, offset: MemoryLayout.offset(of: \Self.color)!), Attribute(semantic: .uv1, format: MTLVertexFormat.float, layoutIndex: 0, offset: MemoryLayout.offset(of: \Self.curveDistance)!), Attribute(semantic: .uv3, format: MTLVertexFormat.float2, layoutIndex: 0, offset: MemoryLayout.offset(of: \Self.materialProperties)!) ] } }
-
21:14 - Make LowLevelMesh
private static func makeLowLevelMesh(vertexBufferSize: Int, indexBufferSize: Int, meshBounds: BoundingBox) throws -> LowLevelMesh { var descriptor = LowLevelMesh.Descriptor() // Similar to MTLVertexDescriptor descriptor.vertexCapacity = vertexBufferSize descriptor.indexCapacity = indexBufferSize descriptor.vertexAttributes = SolidBrushVertex.vertexAttributes let stride = MemoryLayout<SolidBrushVertex>.stride descriptor.vertexLayouts = [LowLevelMesh.Layout(bufferIndex: 0, bufferOffset: 0, bufferStride: stride)] let mesh = try LowLevelMesh(descriptor: descriptor) mesh.parts.append(LowLevelMesh.Part(indexOffset: 0, indexCount: indexBufferSize, topology: .triangleStrip, materialIndex: 0, bounds: meshBounds)) return mesh }
-
22:28 - Creating a MeshResource
let mesh: LowLevelMesh let resource = try MeshResource(from: mesh) entity.components[ModelComponent.self] = ModelComponent(mesh: resource, materials: [...])
-
22:37 - Updating vertex data of LowLevelMesh using withUnsafeMutableBytes API
let mesh: LowLevelMesh mesh.withUnsafeMutableBytes(bufferIndex: 0) { buffer in let vertices: UnsafeMutableBufferPointer<SolidBrushVertex> = buffer.bindMemory(to: SolidBrushVertex.self) // Write to vertex buffer `vertices` }
-
23:07 - Updating LowLevelMesh index buffers using withUnsafeMutableBytes API
let mesh: LowLevelMesh mesh.withUnsafeMutableIndices { buffer in let indices: UnsafeMutableBufferPointer<UInt32> = buffer.bindMemory(to: UInt32.self) // Write to index buffer `indices` }
-
23:58 - Creating a particle brush using LowLevelMesh
struct SparkleBrushAttributes { packed_float3 position; packed_half3 color; float curveDistance; float size; }; // Describes a particle in the simulation struct SparkleBrushParticle { struct SparkleBrushAttributes attributes; packed_float3 velocity; }; // One quad (4 vertices) is created per particle struct SparkleBrushVertex { struct SparkleBrushAttributes attributes; simd_half2 uv; };
-
24:58 - Defining LowLevelMesh Attributes for sparkle brush
extension SparkleBrushVertex { static var vertexAttributes: [LowLevelMesh.Attribute] { typealias Attribute = LowLevelMesh.Attribute return [ Attribute(semantic: .position, format: .float3, layoutIndex: 0, offset: MemoryLayout.offset(of: \Self.attributes.position)!), Attribute(semantic: .color, format: .half3, layoutIndex: 0, offset: MemoryLayout.offset(of: \Self.attributes.color)!), Attribute(semantic: .uv0, format: .half2, layoutIndex: 0, offset: MemoryLayout.offset(of: \Self.uv)!), Attribute(semantic: .uv1, format: .float, layoutIndex: 0, offset: MemoryLayout.offset(of: \Self.attributes.curveDistance)!), Attribute(semantic: .uv2, format: .float, layoutIndex: 0, offset: MemoryLayout.offset(of: \Self.attributes.size)!) ] } }
-
25:28 - Populate LowLevelMesh on GPU
let inputParticleBuffer: MTLBuffer let lowLevelMesh: LowLevelMesh let commandBuffer: MTLCommandBuffer let encoder: MTLComputeCommandEncoder let populatePipeline: MTLComputePipelineState commandBuffer.enqueue() encoder.setComputePipelineState(populatePipeline) let vertexBuffer: MTLBuffer = lowLevelMesh.replace(bufferIndex: 0, using: commandBuffer) encoder.setBuffer(inputParticleBuffer, offset: 0, index: 0) encoder.setBuffer(vertexBuffer, offset: 0, index: 1) encoder.dispatchThreadgroups(/* ... */) // ... encoder.endEncoding() commandBuffer.commit()
-
27:01 - Use MeshResource extrusion to generate 3D text
// Use MeshResource(extruding:) to generate 3D text var textString = AttributedString("RealityKit") textString.font = .systemFont(ofSize: 8.0) let secondLineFont = UIFont(name: "ArialRoundedMTBold", size: 14.0) let attributes = AttributeContainer([.font: secondLineFont]) textString.append(AttributedString("\nDrawing App", attributes: attributes)) let paragraphStyle = NSMutableParagraphStyle() paragraphStyle.alignment = .center let centerAttributes = AttributeContainer([.paragraphStyle: paragraphStyle]) textString.mergeAttributes(centerAttributes) var extrusionOptions = MeshResource.ShapeExtrusionOptions() extrusionOptions.extrusionMethod = .linear(depth: 2) extrusionOptions.materialAssignment = .init(front: 0, back: 0, extrusion: 1, frontChamfer: 1, backChamfer: 1) extrusionOptions.chamferRadius = 0.1 let textMesh = try await MeshResource(extruding: textString extrusionOptions: extrusionOptions)
-
28:25 - Use MeshResource extrusion to turn a SwiftUI Path into 3D mesh
// Use MeshResource(extruding:) to bring SwiftUI.Path to 3D let graphic = SwiftUI.Path { path in path.move(to: CGPoint(x: -0.7, y: 0.135413)) path.addCurve(to: CGPoint(x: -0.7, y: 0.042066), control1: CGPoint(x: -0.85, y: 0.067707), control2: CGPoint(x: -0.85, y: 0.021033)) // ... } var options = MeshResource.ShapeExtrusionOptions() // ... let graphicMesh = try await MeshResource(extruding: graphic extrusionOptions: options)
-
29:44 - Defining a LowLevelTexture
let descriptor = LowLevelTexture.Descriptor(pixelFormat: .rg16Float, width: textureResolution, height: textureResolution, textureUsage: [.shaderWrite, .shaderRead]) let lowLevelTexture = try LowLevelTexture(descriptor: descriptor) var textureResource = try TextureResource(from: lowLevelTexture) var material = UnlitMaterial() material.color = .init(tint: .white, texture: .init(textureResource))
-
30:27 - Update a LowLevelTexture on the GPU
let lowLevelTexture: LowLevelTexture let commandBuffer: MTLCommandBuffer let encoder: MTLComputeCommandEncoder let computePipeline: MTLComputePipelineState commandBuffer.enqueue() encoder.setComputePipelineState(computePipeline) let writeTexture: MTLTexture = lowLevelTexture.replace(using: commandBuffer) encoder.setTexture(writeTexture, index: 0) // ... encoder.dispatchThreadgroups(/* ... */) encoder.endEncoding() commandBuffer.commit()
-
-
特定のトピックをお探しの場合は、上にトピックを入力すると、関連するトピックにすばやく移動できます。
クエリの送信中にエラーが発生しました。インターネット接続を確認して、もう一度お試しください。