ハイライト
Max Cobb 氏が、SceneKit の代表的なサンプルゲーム(火山ステージで赤いレッサーパンダ PyroPanda がパズルを解く作品)を RealityKit に丸ごと移植し、アセット・シーン・アニメーション・ライティング・オーディオ・ビジュアルエフェクトの 6 領域を網羅的に解説します。
主要内容
SceneKit は OS X Mountain Lion でのリリース以来、すでに 13 年の歴史があります。当時はネイティブな 3D エンジンを Apple が直接提供し、サードパーティ製ゲームエンジンを同梱せずに済み、ノード(node)一つですべてを完結させられる仕組みは理にかなっていました。しかし、この 13 年で Apple のエコシステムは大きく変化しました。新しいプログラミングパラダイム、新しいハードウェア、新しいプラットフォームが次々と登場するなか、SceneKit のアーキテクチャでは既存プロジェクトを壊さずに追従していくことが難しくなってきました。今年、Apple は SceneKit を全プラットフォームでソフト deprecation(soft deprecation)扱いにすることを正式に発表しました。既存アプリは引き続き動作しますが、新規プロジェクトや大規模リニューアルでの利用は推奨されません。
その代替として Apple が示したのが RealityKit です。Max Cobb 氏は、当時の赤いレッサーパンダ PyroPanda の火山パズルサンプルを RealityKit に完全移行し、その過程を通じて 4 種類の核となる概念差(アーキテクチャ、座標系、アセット、ビュー)と 6 つの移行ステップ(アセット、シーン、アニメーション、ライティング、オーディオ、ビジュアルエフェクト)を順に提示してくれます。SceneKit は node-based で、すべてのオブジェクトはノードであり、ジオメトリ、アニメーション、オーディオ、物理、ライトはノードのプロパティとしてぶら下がります。一方の RealityKit は ECS(Entity Component System)で、各オブジェクトが Entity となり、振る舞いは Component を介して付加します。座標系は完全に一致しており(Y 軸が上、Z 軸がカメラ方向)、ここはコストゼロで移行できます。ビュー層は SCNView/SceneView から RealityView に統一され、visionOS の立体レンダリングにも自動的に対応します。
詳細
アセットフォーマットは SCN から USD へ。 SceneKit は複数のモデルフォーマットを取り込んで SCN にシリアライズしていましたが、RealityKit は Pixar が 2012 年に発表したオープン標準 USD(Universal Scene Description)を中心に設計されています。移行のルートは 3 つあります。元の DCC ファイル(たとえば Blender)が手元に残っているなら、そこから直接 USD を書き出すのが最も安定します。SCN しか残っていない場合は、Xcode でアセットを選択して File -> Export から Universal Scene Description Package を選びます。コマンドラインで一括処理したいときは xcrun scntool --convert を使い、--append-animation を組み合わせれば複数の SCN ファイルに分散したアニメーションを 1 つの USD 出力に統合できます(10:55)。
アニメーションは AnimationLibraryComponent 経由でアクセス。 USD ファイル内のアニメーションは、ロード時に Entity の AnimationLibraryComponent へ自動的に登録されるため、名前で取り出して再生するだけで済みます(16:33)。
// RealityKit
guard let max = scene.findEntity(named: "Max") else { return }
guard let library = max.components[AnimationLibraryComponent.self],
let spinAnimation = library.animations["spin"]
else { return }
max.playAnimation(spinAnimation)
ポイント:
scene.findEntity(named:)は名前でシーン内の Entity を検索するメソッドで、SceneKit のchildNode(withName:recursively:)に相当します。max.components[AnimationLibraryComponent.self]は ECS で標準的な Component の取得方法で、添字に Component 型を渡します。library.animations["spin"]はアニメーション名をキーにAnimationResourceを取得します。ノードを走査して SCNPlayer を探す必要はありません。max.playAnimation(...)は Entity 上のメソッドです。Component 機構のおかげで、再生呼び出しと Component の保持が疎結合になります。
ライトも Component。 SceneKit では SCNNode を作ってから SCNLight を取り付ける必要がありました。RealityKit では Component を持った Entity を直接生成します(18:18)。
// RealityKit
let lightEntity = Entity(components:
DirectionalLightComponent(),
DirectionalLightComponent.Shadow()
)
ポイント:
Entity(components:)で複数の Component を一度に新しい Entity に組み込めるため、段階的な add を避けられます。DirectionalLightComponentは方向光そのもの(色、強度、方向)を表現します。DirectionalLightComponent.Shadow()は影専用の Component です。光と影が分離されているため、影の ON/OFF は Component の追加・削除だけで切り替えられます。
Bloom のポストプロセスは PostProcessEffect + Metal Performance Shaders で実装。 RealityKit は PostProcessEffect プロトコルを公開しており、Metal command buffer を開発者に渡してカスタムなポストプロセスを記述できます(24:37)。
final class BloomPostProcess: PostProcessEffect {
let bloomThreshold: Float = 0.5
let bloomBlurRadius: Float = 15.0
func postProcess(context: borrowing PostProcessEffectContext<any MTLCommandBuffer>) {
// Create metal texture of the same format as 'context.sourceColorTexture'.
var bloomTexture = ...
// Write brightest parts of 'context.sourceColorTexture' to 'bloomTexture'
// using 'MPSImageThresholdToZero'.
// Blur 'bloomTexture' in-place using 'MPSImageGaussianBlur'.
// Combine original 'context.sourceColorTexture' and 'bloomTexture'
// using 'MPSImageAdd', and write to 'context.targetColorTexture'.
}
}
// RealityKit
content.renderingEffects.customPostProcessing = .effect(
BloomPostProcess()
)
ポイント:
PostProcessEffectプロトコルのpostProcess(context:)を実装するだけでポストプロセスパイプラインに組み込めます。contextは source/target テクスチャと command buffer を提供します。MPSImageThresholdToZeroは閾値より暗いピクセルをゼロクリアし、ハイライト領域だけを残す処理です。bloom のハイライト抽出ステージに相当します。MPSImageGaussianBlurでハイライトテクスチャに Gaussian blur をかけ、光のにじみを生成します。MPSImageAddでぼかしたハイライトを元の画像に加算し、context.targetColorTextureに書き戻して合成を完了します。content.renderingEffects.customPostProcessing = .effect(...)で RealityView の content にアタッチすれば反映されます。
シーン構築は Reality Composer Pro で。 Reality Composer Pro は Xcode と DCC ツールの橋渡し役で、マテリアル、シェーダー、パーティクル、ライト、オーディオをビジュアルに編集できます。プロジェクト自体が Swift Package となっており、local dependency として Xcode プロジェクトに取り込みます。ランタイムでは Entity(named:in:) の一行でシーン全体をロードできます。
重要ポイント
-
やること: 移行に着手する前にアセット一覧を作成しておく。なぜ価値があるか: SceneKit プロジェクトでは SCN、アニメーション SCN、パーティクル、カスタムシェーダーが分散しているため、見切り発車で移行すると見落としが発生しやすいからです。始め方: リポジトリで
*.scnや*.dae、SCNProgramを grep して一覧化し、「元の DCC ファイルが残っているか」で分類します。元ファイルがあるものは元から USD へ書き出し、ないものはxcrun scntool --convertで変換します。 -
やること: アニメーションは
--append-animationで USD にまとめる。なぜ価値があるか: SceneKit ではキャラクターとモーションが複数の SCN に分散しているのが普通で、移行後もそのまま分散させておくとランタイムで手動バインドが必要になり、AnimationLibraryComponent の利点を失ってしまうからです。始め方:xcrun scntool --convert max.scn --format usdz --append-animation max_spin.scn --append-animation max_walk.scn ...を実行して 1 つの USD を出力し、ランタイムではアニメーション名で取り出して再生します。 -
やること: SceneKit のカスタムポストプロセスを
PostProcessEffect+ MPS に置き換える。なぜ価値があるか: MPS は Apple が手書きで最適化した Metal カーネルであり、bloom やぼかし、しきい値処理など定番のエフェクトをシェーダー自作なしで実現できるからです。始め方: Bloom の 3 ステップテンプレート(threshold -> gaussian -> add)から始め、まずデモシーンで動作確認してから本番プロジェクトに組み込みます。 -
やること: ビュー層を RealityView に統一する。なぜ価値があるか: RealityView は visionOS の立体レンダリング、今年新たに加わった tvOS、iOS、macOS に自動対応するため、SCNView/SceneView/ARSCNView の 3 系統に分かれた分岐コードを排除できるからです。始め方: エントリポイントの View を
RealityView { content in ... }に置き換え、Entity ロードのロジックを closure に詰め込み、その後インタラクションを順次 SwiftUI gesture に書き換えていきます。
関連セッション
- What’s new in RealityKit — Lawrence Wong 氏が今年の RealityKit に追加された機能を紹介します。移行が完了したら合わせて視聴する価値があります。
- Combine Metal 4 machine learning and graphics — グラフィックスレンダリングに機械学習を組み込むセッション。RealityKit のポストプロセスと組み合わせれば、より高度な映像表現が実現できます。
- Discover Metal 4 — RealityKit の基盤は Metal です。Metal 4 の新機能を押さえておくと、カスタム PostProcessEffect を書くときに役立ちます。
- Explore Metal 4 games — ゲーム側の Metal 4 最適化手法。RealityKit に移行した後でも引き続き活用できます。
- Engage players with the Apple Games app — RealityKit によるレンダリングと App Store Tags、Apple Games app が一連のディスカバリーチェーンを形成します。
コメント
GitHub Issues · utterances