Communitygithub.com

dembsky/PropMotion

Claude Code 技能:讓一個 3D 物體在你的 SwiftUI 應用程式中表演。包含 SceneKit 舞台、預先編排的動作、自製物理效果和真實陰影。

PropMotion 是什麼?

PropMotion 是一個專門用於在 SwiftUI 應用程式中處理單一 3D 主角物體(例如產品展示、角色表演)的生產級方案集合。它基於已經進入維護模式的 SceneKit 框架,並將其定位為在 SwiftUI 中嵌入裝飾性 3D 元素的捷徑,特別適合需要一個精心編排的英雄物體,而非完整的互動世界的場景。該技能的核心是一套完整的舞台搭建方案,包括透明的 `SCNView`、擁有場景圖的 `@MainActor` 協調器、固定視角的相機、多光源系統(包含獨立陰影光)、接觸陰影和陰影捕捉器,並透過預先烘焙的關鍵幀動畫驅動角色表演。除了核心方案,技能還深入探討了多個常見但耗時的陷阱,例如延遲陰影在 MSAA 下不顯示、新 `SCNView` 的首幀卡頓、動畫清理、材質反射、以及從照片重現物體運動時的曲線問題。參考文檔目錄包含了舞台搭建、首幀預熱、陰影與燈光、烘焙動畫、從參考資料還原運動、無物理引擎的物理模擬、手勢與觸覺回饋、多舞台序列、建模演員、相機編排、實例化集群以及多角色物理模擬等詳細主題,為開發者提供了全面的實戰指南。

相容平台✓Claude Code~Codex CLI~Cursor
npx skills add dembsky/PropMotion

在你喜歡的 AI 中提問

開啟一個已預先載入此 Agent Skill 的新對話。

說明文件

PropMotion 是做什麼的?

在你的 SwiftUI 應用程式中,讓一個 3D 物體表演。為特定、常見的工作提供生產級模式:在透明的 SceneKit 舞台上呈現一個精緻的產品演員——包含進場、出場、拋擲、陰影、觸覺回饋,以及那些耗費數日的無聲陷阱。

何時使用 SceneKit

對該框架的定位要誠實:

  • SceneKit 處於維護模式。對於需要大量 3D 功能(資產管線、USD/USDZ、AR、大型世界)的新應用程式,優先選擇 RealityKit。
  • SceneKit 仍然是將裝飾性 3D 演員嵌入 SwiftUI 應用程式的最快路徑:透明的 SCNView 可覆蓋在任何 SwiftUI 佈局之上,幾何著色器修飾符只是一個 MSL 字串,CoreAnimation 互操作性成熟,且此處所有內容都在純 UIKit 視圖上運行,無需任何會話設定。
  • 如果 3D 元素是一個帶有編排動作的英雄物體,此技能的配方可直接套用。如果是一個完整的互動世界,請先停下來考慮 RealityKit。

核心舞台配方

一個舞台包含:透明的 SCNView、一個擁有場景圖的 @MainActor 協調器、一個位於人眼高度的相機、一個三燈光系統加上一個專用陰影光、一個接觸影塊以及一個陰影捕捉器。演員透過預先烘焙的關鍵幀動畫來表演。

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)
        // 首幀預熱,雙鑰匙點火:在關鍵路徑之外編譯著色器,
        // 將演員停在幕後,並將首次進場門控在最短延遲和準備完成兩者之上。
        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)
    }
}

按照建構順序,各個部件如下:

  1. 透明視圖標誌(backgroundColor = .clear,isOpaque = false),MSAA 4x。主機螢幕提供背景;場景沒有盒子。
  2. 協調器擁有節點層級結構,為每個動作關注點(平移、升降、偏航、旋轉)分解為一個節點,使獨立的動畫永遠不會為了一個單一變換而互相競爭。
  3. 相機:projectionDirection = .horizontal,使演員大小跟隨舞台寬度;抬高位置並帶有輕微向下俯角,看起來像一個站立的觀察者。保持靜態;移動的相機看起來不自然。
  4. 燈光:暖色主光、冷色弱輔光、硬邊輪廓光、僅作為基礎值的環境光,再加上一個單獨的陰影投射方向光,從演員的後上方照射,使其永遠不會干擾可見的造型。
  5. 地面接觸:演員下方一個柔和的暗色漸層平面(接觸影塊),加上一個用於真實陰影的不可見的 .shadowOnly 捕捉器平面。
  6. 反射材質取樣一個小型程式化環境貼圖,而非照片。保持相機後方的區域為暗色。
  7. 所有編排動作都是烘焙的 CAKeyframeAnimation,受到生成令牌的保護,因此任何新的節拍都能取代正在進行的工作。

包含程式碼的完整細節: references/stage-recipe.md

耗費最多時間的陷阱

陷阱修復方法
當 MSAA 開啟時,延遲陰影從不渲染,且無任何控制台錯誤使用 shadowMode = .forward 加上一個 .shadowOnly 捕捉器。除錯缺失的陰影時,首先給場景一個可見的灰色 lambert 地板。
全新 SCNView 的首幀在你的進場動畫中途編譯 Metal 管線在背景執行 prepare([scene]),將演員停在幕後,延遲首次進場。永遠不要在第一幀就開始動畫。
fillMode = .forwards + isRemovedOnCompletion = false 會鎖定呈現結果,且移除是針對每個物件的建立一個中央的 clearAnimations,從每個入口點呼叫,負責清除節點、每個子幾何體、燈光和正在進行的動作。
正面朝向的真實金屬渲染成灰色或黑色空洞從正面觀看的鏡子會反射相機後方環境區域。保留已核准的塗裝基底,並添加一個帶有自身反射貼圖的薄加法層。
切換 castsShadow 會在一幀內彈出模糊的半影;shadowBias 和燈光的 categoryBitMask 對於前向方向光會被忽略保持 castsShadow 永久開啟,並跟隨動作同步動畫化 shadowColor 的 alpha 值。
固定的預熱延遲在冷設備和模擬器上仍然會卡頓;粒子效果的首幀會編譯自己的管線,並在觸發時正好掉幀雙鑰匙點火:將進場門控在最短延遲和 prepare 的完成處理器兩者之上。使用一個符合真實效果標誌的零透明度爆發來預熱粒子管線。
預設的 UIGraphicsImageRenderer 格式會繼承螢幕縮放比例,使每個生成的紋理像素增加 9 倍——對於即時重新渲染的紋理(例如每次按鍵的雕刻)是致命的將渲染器格式的縮放比例固定為 1,並將畫布大小設定為演員在螢幕上的投影大小;將多輸入重定向合併為每次更新傳遞時的一次渲染。
從照片重現真實物體的運動會產生自信的骨架,但結果卻側向失敗——每次修復都會揭露一個新的錯誤靜態照片包含姿勢,而非路徑或機制;終點姿勢擬合無法決定軌跡。對路徑進行建模:建立一個具有直接姿勢控制的校準骨架,由擁有者針對實際物體編寫關鍵幀。
關鍵幀動畫在其關鍵幀處有節奏地卡頓,且所有渲染和時間修復都無效卡頓是烘焙在曲線中的:在不均勻間隔的關鍵幀上使用均勻 Catmull-Rom 基函數,會在每個節點處使速度產生階躍。使用基於距離階段的加權跨度 Hermite 切線(或自然三次樣條)進行插值,並在數值連續性檢查通過後才觸發。
從較大模型中切割出的子網格,其測量結果就好像它是整個物體,且沒有任何錯誤訊息切割僅修剪了索引緩衝區;頂點緩衝區仍然保留原始模型的每個頂點。僅測量子網格索引引用的頂點。
賦值給 scene.lightingEnvironment 的方形圖像會被靜默忽略——零反射,每個鏡面材質渲染為黑色,無控制台輸出使用公認的立方體貼圖佈局來繪製環境貼圖,最簡單的是 2:1 的球面畫布(1024x512)。只有 material.reflective 接受方形球體貼圖。

參考資料地圖

  • references/stage-recipe.md:完整的舞台、視圖設定、協調器、節點層級結構、相機、燈光系統、接觸影塊和捕捉器、程式化環境貼圖和紋理、舞台上的衝擊粒子、舞台演員上的著色器修飾符(線性空間均勻陷阱)、保持舞台的 SwiftUI 標識(重新掛載和更新風暴陷阱)。
  • references/first-frame-and-warmup.md:首次繪製時的 Metal 管線編譯卡頓及兩種解決方案,帶有延遲進場的預熱(升級為門控在 prepare 完成處理器的雙鑰匙點火),或保持場景已掛載並預熱,然後重新定位它;粒子管線預熱;使用 signpost 和設備上的卡頓追蹤來證明解決方案。
  • references/shadows-and-lights.md:每個無聲的陰影陷阱、可靠的前向 + shadowOnly 組合、動畫化陰影可見性、中性的光照預算、正面朝向的金屬。
  • references/baked-animation.md:為什麼烘焙的關鍵幀優於計時器、設計多階段節拍並在烘焙前編寫提示表、接縫分類(C1 vs 接觸脈衝)、設計重量、密集取樣、清理記帳、生成令牌、在動畫中途竊取節點、滾動而不滑動、沿地板路徑滾動(轉向、螢幕空間定位、除錯軌跡)、超越變換的通道(變形器權重、鏡頭值、著色器統一值)、作為創作材料的彈簧、一個屬性一個擁有者。
  • references/motion-from-reference.md:重現真實物體的運動——靜止圖像和影片能告訴你什麼、不能告訴你什麼、對路徑而非機制進行建模、校準骨架(擁有者為演員擺姿勢並保存關鍵幀)、距離參數化和對手工保存姿勢進行加權跨度插值、均勻 Catmull-Rom 陷阱、數值連續性門控、無縫餘弦狀態循環及從當前階段退出、測量修剪後的子網格。
  • references/physics-without-engine.md:自製固定步長積分器並烘焙到關鍵幀、牆壁和地板作為純數值、接觸驅動的觸覺回饋、一種邊緣樞軸傾倒,以及為什麼這優於用於編排場景的 SCNPhysics。
  • references/gestures-and-haptics.md:無需命中測試的平移抓取、按住時的軟限制、釋放速度、衝擊觸覺、在即時手指操作下倖存的腳本化節拍(有限重試、令牌輪詢、隱藏演員手勢防護、竊取關閉持有契約)、減少動效作為一種分類法、對無形舞台的 VoiceOver 無障礙存取、與 SwiftUI 手勢共存。
  • references/sequencing-stages.md:將多個舞台作為一部電影來導演——帶有節拍作為數據的主時鐘、牆壁時間漂移陷阱、在出場動作清除後進行剪輯、預先掛載下一個舞台以進行預熱、舞台比其剪輯點活得更久(殘留的煙霧)、根據使用者互動重新設定時間軸、錄製引導、逐幀驗證剪輯。
  • references/modeling-actors.md:從基本幾何體構建可信的英雄物體——在目測之前的真實世界比例、用於凹陷面的環形面、管狀加環狀輪廓線、徑向圖案可讀性、每個實例的傾斜以適應凹面、開放間隙、作為幾何體的浮雕特徵(絕非繪製)、深色舞台上的緞面金屬反照率、封鎖作為檔案到達的演員。
  • references/camera-choreography.md:作為表演者的相機——軌道系統、烘焙的揭示飛行、跟隨推車的焦點、帶慣性拖動軌道並返回原位、SCNFloor 反射作為接地、下照燈下的啞光舞台、用於追蹤系統的限制(以及為什麼它們永遠不應用於演員)。
  • references/instancing-and-swarms.md:同時操作數十個演員——群組的一個幾何體、基於槽位的堆疊取代物理、從著陸點反向解決拋物線、翻滾混合到靜止姿勢、播種隨機性、透過排程交錯、預算說明。
  • references/multi-actor-physics.md:一個烘焙模擬中的多個演員——一個時鐘上的 N 個狀態向量、成對碰撞(分離,然後在接近時交換)、凍結世界的抓取、命中測試手指選中了哪個演員、用於擠壓和觸覺的每個演員接觸列表。

審查清單

在發布舞台之前,請驗證以下項目:

  • SCNView 上設定 backgroundColor = .clear 且 isOpaque = false;舞台合成時沒有可見的邊框或接縫。
  • 沒有動畫在視圖的首幀開始;prepare 已執行,且首次進場被延遲,或場景保持預熱掛載狀態。
  • 陰影使用前向模式搭配 .shadowOnly 捕捉器;不依賴延遲陰影、shadowBias 或燈光 categoryBitMask 門控。
  • 具有 Alpha 紋理的平面(標籤、貼花)已設定 castsShadow = false,否則它們會投射出方形陰影。
  • 陰影可見性變化是透過動畫化 shadowColor 的 alpha 值來實現的;castsShadow 在運行時永遠不會被切換。
  • 如果場景必須保真地渲染紋理顏色,則環境光加上方向光乘以入射角餘弦值,對於平坦表面應總和為中性值 (1000);沒有裁剪過的光線灰色區域。
  • lightingEnvironment(或 background)上的任何影像都使用公認的立方體貼圖佈局——2:1 球面、6:1 或 1:6 條帶、或六張圖像;該處的方形圖像會被靜默忽略,且反射材質會渲染為黑色。
  • 每個非同步續接(延遲進場、排程觸覺、鏈接轉換)都捕獲了一個生成令牌,並在執行前重新檢查它。
  • 任何竊取正在動畫的節點的程式碼,在呼叫 removeAllAnimations 之前,都會先將 .presentation 值快照到模型中。
  • 烘焙動畫在 addAnimation 之前將模型設定為最終姿勢;不依賴 fillMode = .forwards 而不進行清理清除。
  • 多階段節拍在姿勢函數上方有一個提示表註解,該註解與階段邊界結構體匹配;階段和提示時間僅存在於該一個結構體中,絕不會作為字面值出現在取樣迴圈或排程的觸覺中。每個接縫都已分類:設計接縫為 C1,脈衝接縫僅發生在接觸事件處,且每個事件都會產生一個提示。
  • 滾動物體將旋轉與距離除以半徑的結果關聯起來;沒有任何物體滑動。
  • 從真實物體複製的運動是透過校準骨架(直接姿勢控制、保存的姿勢、擁有者的視角)作為關鍵幀來創作的,

…

[因翻譯需求而截斷]

相關技能