PropMotion は何をしますか?
1つの3DオブジェクトをあなたのSwiftUIアプリでパフォーマンスさせましょう。特定の一般的なタスクのためのプロダクションパターン:洗練されたプロダクトアクターを透明なSceneKitステージ上で演出 - 登場、退場、投げ、影、ハプティクス、そして数日を無駄にする静かな落とし穴。
そもそもSceneKitをいつ使うべきか
フレームワークの立ち位置について正直になりましょう:
- SceneKitはメンテナンスモードです。新しいアプリで本格的な3Dニーズ(アセットパイプライン、USD/USDZ、AR、大規模な世界)がある場合は、RealityKitを優先してください。
- SceneKitは、SwiftUIアプリ内に装飾的な3Dアクターを組み込むための最も高速な方法です。透明な
SCNViewは任意のSwiftUIレイアウト上に合成でき、ジオメトリシェーダー修飾子は単一のMSL文字列で済み、CoreAnimationとの相互運用は成熟しており、ここにあるすべてはセッション設定なしでプレーンなUIKitビュー上で動作します。 - 3D要素が1つのヒーローオブジェクトで振り付けられた動きを持つ場合、このスキルのレシピが直接適用できます。それが本格的なインタラクティブワールドである場合は、一旦立ち止まって最初にRealityKitを検討してください。
コアステージレシピ
ステージとは:透明なSCNView、シーングラフを所有する@MainActorコーディネーター、立っている目の高さのカメラ、3灯ライトリグと専用の影ライト、コンタクトブロブ、そして影キャッチャー。アクターはベイクドキーフレームアニメーションによってパフォーマンスします。
struct ProductStage: UIViewRepresentable {
let item: Item
// キーは、シーンが目に見えて変化する必要がある場合にのみ変更する。
private var stateKey: String { "\(item.id)" }
func makeCoordinator() -> Coordinator { Coordinator() }
func makeUIView(context: Context) -> SCNView {
let view = SCNView()
view.backgroundColor = .clear // ステージはSwiftUI上に合成される
view.isOpaque = false
view.antialiasingMode = .multisampling4X
view.scene = context.coordinator.buildScene()
view.pointOfView = context.coordinator.cameraNode
context.coordinator.install(item)
context.coordinator.markState(stateKey)
// 初回フレームのウォームアップ、2キーイグニッション:クリティカルパスから
// シェーダーコンパイルを外し、アクターをステージオフに配置し、最初の
// 登場を最小遅延とprepareの完了の両方でゲートする。
context.coordinator.parkOffstage()
if let scene = view.scene {
view.prepare([scene]) { _ in
DispatchQueue.main.async { context.coordinator.markPipelinesWarm() }
}
}
context.coordinator.scheduleFirstEntrance()
return view
}
func updateUIView(_ view: SCNView, context: Context) {
// ステートキーの差分チェック:SwiftUIの再レンダリングで登場が再実行されてはならない。
guard context.coordinator.stateKey != stateKey else { return }
context.coordinator.markState(stateKey)
context.coordinator.transition(to: item)
}
}
ビルド順の構成要素:
- 透明ビューフラグ(
backgroundColor = .clear、isOpaque = false)、MSAA 4x。ホスト画面が背景を提供し、シーンにはボックスがない。 - コーディネーターがノード階層を所有し、1つのモーション懸念事項ごとに1ノードに分解(移動、リフト、ヨー、スピン)することで、独立したアニメーションが単一のトランスフォームを巡って競合しない。
- カメラ:
projectionDirection = .horizontalでアクターのサイズがステージ幅に追従するようにする。少し下向きのピッチで高い位置に配置すると、立っている観察者のように見える。静的を保つこと。動くカメラは人工的に見える。 - ライト:暖色キー、クールなローフィル、ハードリム、アンビエントはフロア値のみ、さらにアクターの背後上方から照射される専用の影投影用ディレクショナルライト。これにより可視の彫刻を妨げない。
- 接地コンタクト:アクターの下にあるソフトダークグラデーションプレーン(コンタクトブロブ)と、実際の影のための不可視の
.shadowOnlyキャッチャープレーン。 - 反射マテリアルは、写真ではなく小さなプログラムによる環境マップをサンプリングする。カメラの背後は暗く保つ。
- すべての振り付けはベイクド
CAKeyframeAnimationであり、新しいビートが保留中の作業を上書きできるように、世代トークンでガードされる。
完全な詳細とコード:references/stage-recipe.md
最も時間を費やす落とし穴
| 落とし穴 | 修正方法 |
|---|---|
| MSAAがオンの場合、ディファードシャドウがレンダリングされず、コンソールエラーもゼロ | shadowMode = .forwardと.shadowOnlyキャッチャーを使用する。欠落している影は、最初にシーンに可視のグレーのランバート床を与えることでデバッグする。 |
新しいSCNViewの最初のフレームは、登場アニメーションの真っ最中にMetalパイプラインをコンパイルする | バックグラウンドでprepare([scene])を実行し、アクターをステージオフに配置し、最初の登場を遅延させる。フレーム1でアニメーションを開始してはならない。 |
fillMode = .forwards + isRemovedOnCompletion = falseはプレゼンテーションを固定し、削除はオブジェクトごとに行われる | すべてのエントリポイントから呼び出され、ノード、すべての子ジオメトリ、ライト、実行中のアクションを一掃する中央のclearAnimations。 |
| 真正面のリアルメタルはグレーまたは黒い穴としてレンダリングされる | 真正面から見たミラーは、カメラの後ろの環境ゾーンを反射する。承認されたペイントベースを保持し、独自の反射マップを持つ薄い加算レイヤーを追加する。 |
castsShadowをトグルすると、ぼやけた半影が1フレームでポップする。shadowBiasとライトのcategoryBitMaskはフォワードディレクショナルシャドウでは無視される | castsShadowを永続的にオンのままにし、モーションに同期してshadowColorのアルファをアニメーションさせる。 |
| 固定のウォームアップ遅延でも、コールドデバイスとシミュレーターでヒッチが発生する。パーティクルエフェクトの最初のフレームは独自のパイプラインをコンパイルし、発火した瞬間にドロップする | 2キーイグニッション:最小遅延とprepareの完了ハンドラの両方で登場をゲートする。実際のエフェクトと同じフラグを持つゼロ不透明度のバーストでパーティクルパイプラインをウォームアップする。 |
デフォルトのUIGraphicsImageRendererフォーマットはスクリーンスケールを継承し、生成されるすべてのテクスチャをピクセルで9倍に膨張させる - ライブで再レンダリングされるテクスチャ(キーストロークごとの彫刻)にとって致命的 | レンダラーフォーマットのスケールを1に固定し、キャンバスをアクターの画面上の投影に合わせてサイズ設定する。複数入力のリターゲットを更新パスごとに1つのレンダリングに統合する。 |
| 写真から実際のオブジェクトの動きを再現すると、自信に満ちたリグが作成されるが、それらは横方向に失敗する - 修正するたびに新しい間違いが明らかになる | スチル写真はポーズを伝えるが、パスやメカニズムは伝えない。エンドポーズのフィットは軌道を決定しない。パスをモデル化する:直接的なポーズ制御を備えたキャリブレーションリグ、所有者が物理オブジェクトに対してキーフレームを作成する。 |
| キーフレーム化されたモーションが、そのキーフレームでリズミカルにスタッターし、すべてのレンダリングとタイミングの修正でも改善されない | ジャークはカーブに焼き付けられている:不均等な間隔のキーフレーム上の均一なCatmull-Rom基底は、すべてのノットで速度をステップさせる。距離ベースのフェーズにわたってスパン加重のHermite接線(または自然3次スプライン)で補間し、数値の連続性チェックでゲートする。 |
| より大きなモデルから切り取られたサブメッシュが、エラーなしでオブジェクト全体であるかのように測定される | カットはインデックスバッファのみをトリミングした。頂点バッファは元のすべての頂点を保持している。サブメッシュインデックスによって参照される頂点のみを測定する。 |
scene.lightingEnvironmentに割り当てられた正方形の画像は、静かに無視される - 反射ゼロ、すべてのミラーマテリアルが黒をレンダリング、コンソール出力なし | 認識可能なキューブマップレイアウトで環境マップをペイントする。最も簡単なのは2:1の球状キャンバス(1024x512)。material.reflectiveのみが正方形の球面マップを受け入れる。 |
リファレンスマップ
- references/stage-recipe.md: 完全なステージ、ビュー設定、コーディネーター、ノード階層、カメラ、ライティングリグ、コンタクトブロブとキャッチャー、プログラムによる環境マップとテクスチャ、ステージ上のインパクトパーティクル、ステージアクター上のシェーダー修飾子(線形空間ユニフォームの落とし穴)、ステージのSwiftUI IDの維持(再マウントと更新ストームの落とし穴)。
- references/first-frame-and-warmup.md: 最初の描画時のMetalパイプラインコンパイルヒッチとその両方の治療法、遅延された登場によるウォームアップ(prepareの完了でゲートされる2キーイグニッションにアップグレード)、またはシーンをマウントしたままウォームに保ちリターゲットする方法、パーティクルパイプラインウォームアップ、サインポストとデバイス上のヒッチトレースで治療法を証明する。
- references/shadows-and-lights.md: すべての静かな影の落とし穴、信頼性の高いforward + shadowOnlyの組み合わせ、影の可視性のアニメーション、ニュートラルなライトバジェット、真正面のメタル。
- references/baked-animation.md: ベイクドキーフレームがタイマーよりも優れている理由、ベイクする前にマルチフェーズビートを設計するためのキュ―シート、シームの分類(C1 vs 接触インパルス)、重みの設計、密なサンプリング、クリーンアップのブックキーピング、世代トークン、アニメーション中のノードの横取り、滑ることなく転がる、床パスに沿った転がり(ステアリング、スクリーンスペースステージング、デバッグトレイル)、トランスフォームを超えたチャンネル(モーファーウェイト、レンズ値、シェーダーユニフォーム)、オーサリング素材としてのスプリング、1プロパティ1オーナー。
- references/motion-from-reference.md: 実物のモーションの再現 - スチル写真とビデオが教えてくれることと教えてくれないこと、メカニズムではなくパスのモデル化、キャリブレーションリグ(所有者がアクターをポーズしキーフレームを保存する)、手動保存されたポーズの距離パラメータ化とスパン加重補間、均一Catmull-Romの落とし穴、数値連続性ゲート、現在のフェーズからの出口を備えたシームレスなコサイン状態ループ、トリミングされたサブメッシュの測定。
- references/physics-without-engine.md: キーフレームにベイクされた手作りの固定ステップ積分器、単なる数値としての壁と床、接触駆動ハプティクス、リムピボット転倒、そしてこれが振付シーンでSCNPhysicsよりも優れている理由。
- references/gestures-and-haptics.md: ヒットテストなしのパンで掴む、保持中のソフトクランプ、リリース速度、インパクトハプティクス、ライブフィンガーに耐えるスクリプト化されたビート(制限付きリトライ、トークンポーリング、非表示アクタージェスチャーガード、横取りによるホールド契約の終了)、動作軽減設定の分類法、不可視ステージへのVoiceOverアクセス、SwiftUIジェスチャーとの共存。
- references/sequencing-stages.md: 複数のステージを1つのフィルムとして演出 - データとしてのビートを持つマスタークロック、壁時間ドリフトの落とし穴、出口クリア後のモーションカット、ウォームアップのために次のステージを事前マウント、自身のカットより長生きするステージ(残る煙)、ユーザーインタラクションでのタイムラインの再ベース、録画リード、フレームごとのカット検証。
- references/modeling-actors.md: プリミティブから信頼できるヒーローオブジェクトを構築する - 目見当の前の実世界の比率、凹面のための環、チューブプラストーラスのシルエット、放射状パターンの可読性、凹面のためのインスタンスごとの傾き、開いたギャップ、ジオメトリとしてのレリーフフィーチャー(決してペイントしない)、暗いステージ上のサテンメタルアルベド、ファイルとして到着するアクターのゲート。
- references/camera-choreography.md: パフォーマーとしてのカメラ - オービットリグ、ベイクドリビールフライト、ドリーに追従するフォーカス、慣性とホーム復帰を備えたドラッグオービット、グラウンディングとしてのSCNFloor反射、ダウンライト下のマットステージング、トラッキングリグのための制約(そしてなぜそれらがアクターに決して適用されないか)。
- references/instancing-and-swarms.md: 一度に数十のアクター - 群れのための1つのジオメトリ、物理ではなくスロットベースのパイル、着地地点から逆算して解かれたパラボラ、静止ポーズにブレンドされたタンブル、シードされたランダム性、スケジューリングによるスケジュール、バジェットノート。
- references/multi-actor-physics.md: 1つのベイクドシミュレーション内の複数のアクター - 1つのクロック上のN個の状態ベクトル、ペアワイズ衝突(分離、その後接近時に交換)、フリーズ・ザ・ワールドグラブ、どのアクターを指が選んだかのヒットテスト、スクイーズとハプティクスのためのアクターごとの接触リスト。
レビューチェックリスト
ステージをリリースする前に、以下を確認する:
- SCNViewで
backgroundColor = .clearとisOpaque = falseが設定されている。ステージは可視のボックスや継ぎ目なしで合成される。 - ビューの最初のフレームでアニメーションが開始されない。
prepareが実行され、最初の登場は遅延されるか、シーンはマウントされたままウォームに保たれる。 - 影は
.shadowOnlyキャッチャーと共にフォワードモードを使用する。ディファードシャドウ、shadowBias、またはライトのcategoryBitMaskゲーティングに依存するものはない。 - アルファテクスチャプレーン(ラベル、デカール)には
castsShadow = falseが設定されている。そうしないと、正方形の影が投影される。 - 影の可視性の変更は
shadowColorのアルファをランプする。castsShadowは実行時に決してトグルされない。 - シーンがテクスチャを色忠実にレンダリングする必要がある場合、アンビエント+ディレクショナル×cos(入射角) は平坦な表面に対してニュートラル(1000)に合計される。クリップされたライトグレーがない。
-
lightingEnvironment(またはbackground)上の画像は、認識可能なキューブマップレイアウト(2:1球状、6:1または1:6ストリップ、または6つの画像)を使用する。そこにある正方形の画像は静かに無視され、反射マテリアルは黒をレンダリングする。 - すべての非同期継続(遅延された登場、スケジュールされたハプティクス、連鎖トランジション)は世代トークンをキャプチャし、アクション前に再チェックする。
- アニメーションノードを横取りするコードは、
removeAllAnimationsを呼び出す前に.presentation値をモデルにスナップショットする。 - ベイクドアニメーションは、
addAnimationの前にモデルを最終ポーズに設定する。fillMode = .forwardsに依存するものはすべてクリーンアップスイープを行う。 - マルチフェーズビートには、ポーズ関数の上に、フェーズ境界構造体と一致するキュ―シートコメントがある。フェーズとキュ―の時間はその1つの構造体に存在し、サンプルループやスケジュールされたハプティクスにリテラルとして決して現れない。すべてのシームは分類される:デザインシームはC1、インパルスシームは接触イベントでのみ発生し、各々はキュ―を着地させる。
- 回転オブジェクトは、スピンを距離÷半径に従属させる。何も滑らない。
- 実物からコピーされたモーションは、キャリブレーションリグ(直接ポーズ制御、保存されたポーズ、所有者の目)を通じてキーフレームとして作成される。
…
[翻訳のため一部省略]