axiom-scenekit

安装量: 92
排名: #8804

安装

npx skills add https://github.com/charleswiltgen/axiom --skill axiom-scenekit
SceneKit Development Guide
Purpose
Maintain existing SceneKit code safely and plan migration to RealityKit
iOS Version
iOS 8+ (SceneKit), deprecated iOS 26+
Xcode
Xcode 15+
When to Use This Skill
Use this skill when:
Maintaining existing SceneKit code
Building a SceneKit prototype (with awareness of deprecation)
Planning migration from SceneKit to RealityKit
Debugging SceneKit rendering, physics, or animation issues
Integrating SceneKit content with SwiftUI
Loading 3D models via Model I/O or SCNSceneSource
Do NOT use this skill for:
New 3D projects (use
axiom-realitykit
)
AR experiences (use
axiom-realitykit
)
visionOS development (use
axiom-realitykit
)
SpriteKit 2D games (
axiom-spritekit
)
Metal shader programming (
axiom-metal-migration-ref
)
Deprecation Context
SceneKit is
soft-deprecated as of iOS 26
(WWDC 2025). This means:
Existing apps continue to work
No new features or general bug fixes
Only critical security patches
SceneView
(SwiftUI) is formally deprecated in iOS 26
Apple's forward path is RealityKit.
All new 3D projects should use RealityKit. SceneKit knowledge remains valuable for maintaining legacy code and understanding concepts during migration.
In RealityKit
ECS architecture replaces scene graph. See
axiom-scenekit-ref
for the complete concept mapping table.
1. Mental Model
Scene Graph Architecture
SceneKit uses a
tree of nodes
(SCNNode) attached to a root node in an SCNScene. Each node has a transform (position, rotation, scale) relative to its parent.
SCNScene
└── rootNode
├── cameraNode (SCNCamera)
├── lightNode (SCNLight)
├── playerNode (SCNGeometry + SCNPhysicsBody)
│ ├── weaponNode
│ └── particleNode (SCNParticleSystem)
└── environmentNode
├── groundNode
└── wallNodes
In RealityKit
Entities replace nodes. Components replace node properties. The hierarchy concept persists, but behavior is driven by Systems rather than node callbacks.
Coordinate System
SceneKit uses a
right-handed Y-up
coordinate system:
+Y (up)
|
|
+──── +X (right)
/
/
+Z (toward viewer)
This matches RealityKit's coordinate system, so spatial concepts transfer directly during migration.
Transform Hierarchy
Transforms cascade parent → child. A child's world transform = parent's world transform × child's local transform.
let
parent
=
SCNNode
(
)
parent
.
position
=
SCNVector3
(
10
,
0
,
0
)
let
child
=
SCNNode
(
)
child
.
position
=
SCNVector3
(
0
,
5
,
0
)
parent
.
addChildNode
(
child
)
// child.worldPosition = (10, 5, 0)
// child.position (local) = (0, 5, 0)
In RealityKit
Same concept.
entity.position
is local,
entity.position(relativeTo: nil)
gives world position.
2. Scene Setup and Rendering
SCNView (UIKit)
let
sceneView
=
SCNView
(
frame
:
view
.
bounds
)
sceneView
.
scene
=
SCNScene
(
named
:
"scene.scn"
)
sceneView
.
allowsCameraControl
=
true
sceneView
.
showsStatistics
=
true
sceneView
.
backgroundColor
=
.
black
view
.
addSubview
(
sceneView
)
SceneView (SwiftUI) — Deprecated iOS 26
// Still works but deprecated. Use SCNViewRepresentable for new code.
import
SceneKit
SceneView
(
scene
:
scene
,
pointOfView
:
cameraNode
,
options
:
[
.
allowsCameraControl
,
.
autoenablesDefaultLighting
]
)
SCNViewRepresentable (SwiftUI replacement)
struct
SceneKitView
:
UIViewRepresentable
{
let
scene
:
SCNScene
func
makeUIView
(
context
:
Context
)
->
SCNView
{
let
view
=
SCNView
(
)
view
.
scene
=
scene
view
.
allowsCameraControl
=
true
view
.
autoenablesDefaultLighting
=
true
return
view
}
func
updateUIView
(
_
view
:
SCNView
,
context
:
Context
)
{
}
}
In RealityKit
Use
RealityView
in SwiftUI — no UIViewRepresentable needed.
3. Geometry and Materials
Built-in Geometries
let
box
=
SCNBox
(
width
:
1
,
height
:
1
,
length
:
1
,
chamferRadius
:
0.1
)
let
sphere
=
SCNSphere
(
radius
:
0.5
)
let
cylinder
=
SCNCylinder
(
radius
:
0.3
,
height
:
1
)
let
plane
=
SCNPlane
(
width
:
2
,
height
:
2
)
let
torus
=
SCNTorus
(
ringRadius
:
1
,
pipeRadius
:
0.3
)
let
capsule
=
SCNCapsule
(
capRadius
:
0.3
,
height
:
1
)
let
cone
=
SCNCone
(
topRadius
:
0
,
bottomRadius
:
0.5
,
height
:
1
)
let
tube
=
SCNTube
(
innerRadius
:
0.3
,
outerRadius
:
0.5
,
height
:
1
)
let
text
=
SCNText
(
string
:
"Hello"
,
extrusionDepth
:
0.2
)
PBR Materials
let
material
=
SCNMaterial
(
)
material
.
lightingModel
=
.
physicallyBased
material
.
diffuse
.
contents
=
UIColor
.
red
// or UIImage
material
.
metalness
.
contents
=
0.8
material
.
roughness
.
contents
=
0.2
material
.
normal
.
contents
=
UIImage
(
named
:
"normal_map"
)
material
.
ambientOcclusion
.
contents
=
UIImage
(
named
:
"ao_map"
)
let
node
=
SCNNode
(
geometry
:
sphere
)
node
.
geometry
?
.
firstMaterial
=
material
In RealityKit
Use
PhysicallyBasedMaterial
with similar properties but different API surface. See
axiom-scenekit-ref
Part 1 for the mapping.
Shader Modifiers
SceneKit supports GLSL/Metal shader snippets injected at specific entry points:
// Fragment modifier — custom effect on surface
material
.
shaderModifiers
=
[
.
fragment
:
"""
float stripe = sin(_surface.position.x * 20.0);
_output.color.rgb *= step(0.0, stripe);
"""
]
Entry points:
.geometry
,
.surface
,
.lightingModel
,
.fragment
In RealityKit
Use
ShaderGraphMaterial
with Reality Composer Pro, or
CustomMaterial
with Metal functions.
4. Lighting
Light Types
Type
Description
Shadows
.omni
Point light, radiates in all directions
No
.directional
Parallel rays (sun)
Yes
.spot
Cone-shaped beam
Yes
.area
Rectangle emitter (soft shadows)
Yes
.IES
Real-world light profile
Yes
.ambient
Uniform, no direction
No
.probe
Environment lighting from cubemap
No
let
light
=
SCNLight
(
)
light
.
type
=
.
directional
light
.
intensity
=
1000
light
.
castsShadow
=
true
light
.
shadowRadius
=
3
light
.
shadowSampleCount
=
8
let
lightNode
=
SCNNode
(
)
lightNode
.
light
=
light
lightNode
.
eulerAngles
=
SCNVector3
(
-
Float
.
pi
/
4
,
0
,
0
)
scene
.
rootNode
.
addChildNode
(
lightNode
)
In RealityKit
Use
DirectionalLightComponent
,
PointLightComponent
,
SpotLightComponent
as components on entities. Image-based lighting via
EnvironmentResource
.
5. Animation
SCNAction (Declarative)
let
moveUp
=
SCNAction
.
moveBy
(
x
:
0
,
y
:
2
,
z
:
0
,
duration
:
1
)
let
fadeOut
=
SCNAction
.
fadeOut
(
duration
:
0.5
)
let
sequence
=
SCNAction
.
sequence
(
[
moveUp
,
fadeOut
]
)
let
forever
=
SCNAction
.
repeatForever
(
moveUp
.
reversed
(
)
)
node
.
runAction
(
sequence
)
Implicit Animation (SCNTransaction)
SCNTransaction
.
begin
(
)
SCNTransaction
.
animationDuration
=
0.5
node
.
position
=
SCNVector3
(
0
,
5
,
0
)
node
.
opacity
=
0.5
SCNTransaction
.
commit
(
)
Explicit Animation (CAAnimation bridge)
let
animation
=
CABasicAnimation
(
keyPath
:
"rotation"
)
animation
.
toValue
=
NSValue
(
scnVector4
:
SCNVector4
(
0
,
1
,
0
,
Float
.
pi
*
2
)
)
animation
.
duration
=
2
animation
.
repeatCount
=
.
infinity
node
.
addAnimation
(
animation
,
forKey
:
"spin"
)
Loading Animations from Files
let
scene
=
SCNScene
(
named
:
"character.dae"
)
!
let
animationPlayer
=
scene
.
rootNode
.
childNode
(
withName
:
"mixamorig:Hips"
,
recursively
:
true
)
!
.
animationPlayer
(
forKey
:
nil
)
!
characterNode
.
addAnimationPlayer
(
animationPlayer
,
forKey
:
"walk"
)
animationPlayer
.
play
(
)
In RealityKit
Use
entity.playAnimation()
with animations loaded from USD files. Transform animations via
entity.move(to:relativeTo:duration:)
.
6. Physics
Physics Bodies
// Dynamic — simulation controls position
node
.
physicsBody
=
SCNPhysicsBody
(
type
:
.
dynamic
,
shape
:
SCNPhysicsShape
(
geometry
:
node
.
geometry
!
,
options
:
nil
)
)
// Static — immovable collision surface
ground
.
physicsBody
=
SCNPhysicsBody
(
type
:
.
static
,
shape
:
nil
)
// Kinematic — code controls position, participates in collisions
platform
.
physicsBody
=
SCNPhysicsBody
(
type
:
.
kinematic
,
shape
:
nil
)
Collision Categories
struct
PhysicsCategory
{
static
let
player
:
Int
=
1
<<
0
// 1
static
let
enemy
:
Int
=
1
<<
1
// 2
static
let
projectile
:
Int
=
1
<<
2
// 4
static
let
wall
:
Int
=
1
<<
3
// 8
}
playerNode
.
physicsBody
?
.
categoryBitMask
=
PhysicsCategory
.
player
playerNode
.
physicsBody
?
.
collisionBitMask
=
PhysicsCategory
.
wall
|
PhysicsCategory
.
enemy
playerNode
.
physicsBody
?
.
contactTestBitMask
=
PhysicsCategory
.
enemy
|
PhysicsCategory
.
projectile
Contact Delegate
class
GameScene
:
SCNScene
,
SCNPhysicsContactDelegate
{
func
setupPhysics
(
)
{
physicsWorld
.
contactDelegate
=
self
}
func
physicsWorld
(
_
world
:
SCNPhysicsWorld
,
didBegin contact
:
SCNPhysicsContact
)
{
let
nodeA
=
contact
.
nodeA
let
nodeB
=
contact
.
nodeB
// Handle collision
}
}
In RealityKit
Use
PhysicsBodyComponent
,
CollisionComponent
, and collision event subscriptions via
scene.subscribe(to: CollisionEvents.Began.self)
.
7. Hit Testing and Interaction
// In SCNView tap handler
let
results
=
sceneView
.
hitTest
(
tapLocation
,
options
:
[
.
searchMode
:
SCNHitTestSearchMode
.
closest
.
rawValue
,
.
boundingBoxOnly
:
false
]
)
if
let
hit
=
results
.
first
{
let
tappedNode
=
hit
.
node
let
worldPosition
=
hit
.
worldCoordinates
}
In RealityKit
Use
ManipulationComponent
for drag/rotate/scale gestures, or collision-based hit testing.
8. Asset Pipeline
Supported Formats
Format
Extension
Notes
USD/USDZ
.usdz
,
.usda
,
.usdc
Preferred format, works in both SceneKit and RealityKit
Collada
.dae
Legacy, still supported
SceneKit Archive
.scn
Xcode-specific, not portable to RealityKit
Wavefront OBJ
.obj
Geometry only, no animations
Alembic
.abc
Animation baking
Loading Models
// From bundle
let
scene
=
SCNScene
(
named
:
"model.usdz"
)
!
// From URL
let
scene
=
try
SCNScene
(
url
:
modelURL
,
options
:
nil
)
// Via Model I/O (for format conversion)
let
asset
=
MDLAsset
(
url
:
modelURL
)
let
scene
=
SCNScene
(
mdlAsset
:
asset
)
Migration tip
Convert
.scn
files to
.usdz
using
xcrun scntool --convert file.scn --format usdz
before migrating to RealityKit.
9. ARKit Integration (Legacy)
// ARSCNView — SceneKit + ARKit (legacy approach)
let
arView
=
ARSCNView
(
frame
:
view
.
bounds
)
arView
.
delegate
=
self
arView
.
session
.
run
(
ARWorldTrackingConfiguration
(
)
)
// Adding virtual content at anchors
func
renderer
(
_
renderer
:
SCNSceneRenderer
,
didAdd node
:
SCNNode
,
for
anchor
:
ARAnchor
)
{
let
box
=
SCNBox
(
width
:
0.1
,
height
:
0.1
,
length
:
0.1
,
chamferRadius
:
0
)
node
.
addChildNode
(
SCNNode
(
geometry
:
box
)
)
}
In RealityKit
Use
RealityView
with
AnchorEntity
types. ARSCNView is legacy — all new AR development should use RealityKit.
10. Anti-Patterns
Anti-Pattern 1: Starting New Projects in SceneKit
Time cost
Weeks of rework when you eventually must migrate
SceneKit is deprecated. New projects should use RealityKit from the start, even if the learning curve is steeper initially.
Anti-Pattern 2: Using .scn Files Without USDZ Conversion
Time cost
Hours when migration begins
.scn
files are SceneKit-specific and cannot be loaded in RealityKit. Convert early:
xcrun scntool
--convert
model.scn
--format
usdz
--output
model.usdz
Anti-Pattern 3: Deep Shader Modifier Customization
Time cost
Complete rewrite during migration
SceneKit shader modifiers use a proprietary entry-point system. Heavy investment here has zero portability to RealityKit's
ShaderGraphMaterial
.
Anti-Pattern 4: Relying on SCNRenderer for Custom Pipelines
Time cost
Architecture redesign during migration
If you need custom render pipelines, build on Metal directly or use
RealityRenderer
(RealityKit's Metal-level API).
Anti-Pattern 5: Ignoring Deprecation Warnings
Time cost
Surprise breakage when Apple removes APIs
Track
SceneView
deprecation warnings and plan UIViewRepresentable fallback or RealityKit migration.
Anti-Pattern 6: Creating Hundreds of Nodes in a Loop
Time cost
2-4 hours debugging frame drops, often misdiagnosed as GPU issue
// ❌ WRONG: Each SCNNode has overhead (transform, bounding box, hit test)
for
i
in
0
..<
500
{
let
node
=
SCNNode
(
geometry
:
SCNSphere
(
radius
:
0.05
)
)
node
.
position
=
randomPosition
(
)
scene
.
rootNode
.
addChildNode
(
node
)
// 500 nodes = terrible frame rate
}
// ✅ RIGHT: Use SCNParticleSystem for particle-like effects
let
particles
=
SCNParticleSystem
(
)
particles
.
birthRate
=
500
particles
.
particleSize
=
0.05
particles
.
emitterShape
=
SCNBox
(
width
:
5
,
height
:
5
,
length
:
5
,
chamferRadius
:
0
)
particleNode
.
addParticleSystem
(
particles
)
// ✅ RIGHT: Use geometry instancing for identical objects
let
source
=
SCNGeometrySource
(
/ instance transforms /
)
geometry
.
levelsOfDetail
=
[
SCNLevelOfDetail
(
geometry
:
lowPoly
,
screenSpaceRadius
:
20
)
]
Rule
If >50 identical objects, use SCNParticleSystem or flatten geometry. If different objects, use
SCNNode.flattenedClone()
to reduce draw calls.
11. Migration Decision Tree
Should you migrate to RealityKit?
├─ Is this a new project?
│ └─ YES → Use RealityKit from the start. No question.
├─ Does the app need AR features?
│ └─ YES → Migrate. ARSCNView is legacy, RealityKit is the only forward path.
├─ Does the app target visionOS?
│ └─ YES → Must migrate. SceneKit doesn't support visionOS spatial features.
├─ Is the codebase heavily invested in SceneKit?
│ ├─ YES, and app is stable → Maintain in SceneKit for now, plan phased migration.
│ └─ YES, but needs new features → Migrate incrementally (new features in RealityKit).
├─ Is performance a concern?
│ └─ YES → RealityKit is optimized for Apple Silicon with Metal-first rendering.
└─ Is the app in maintenance mode?
└─ YES → Keep SceneKit until critical. Security patches will continue.
12. Pressure Scenarios
Scenario 1: "Just Use SceneKit, It Works Fine"
Pressure
Team familiarity with SceneKit, deadline to ship
Wrong approach
Start new project in SceneKit because the team knows it.
Correct approach
Invest in RealityKit learning. SceneKit will receive no new features. The longer you wait, the larger the migration debt.
Push-back template
"SceneKit is deprecated as of iOS 26. Starting new work in it creates migration debt that grows with every feature we add. RealityKit's ECS model is different but learnable — let's invest the time now."
Scenario 2: "We Don't Have Time to Learn RealityKit"
Pressure
Tight deadline, team unfamiliar with ECS
Wrong approach
Build everything in SceneKit to meet the deadline.
Correct approach
Build the prototype in SceneKit if necessary, but document every SceneKit dependency and plan the migration. Use USDZ assets from the start so they're portable.
Push-back template
"Let's use USDZ assets and keep the SceneKit layer thin. When we migrate, the assets transfer directly and only the code layer changes."
Scenario 3: "Port Everything At Once"
Pressure
Desire for a clean migration
Wrong approach
Attempt to rewrite the entire SceneKit codebase in RealityKit at once.
Correct approach
Migrate incrementally. New features in RealityKit. Existing SceneKit code stays until it needs changes. Modularize with Swift packages (per Apple's migration guide).
Push-back template
"Apple's own migration guide recommends modularizing into Swift packages and migrating system by system. A big-bang rewrite risks introducing new bugs across the entire app."
Code Review Checklist
No new SceneKit code in projects targeting iOS 26+ without migration plan
Assets in USDZ format (not .scn) for portability
No deep shader modifier customization without RealityKit equivalent identified
SCNTransaction used for implicit animations (not direct property changes without animation context)
Physics categoryBitMask explicitly set (not relying on defaults)
Contact delegate set and protocol conformance added
[weak self]
in completion handlers and closures
Debug overlays enabled during development (
showsStatistics = true
)
Resources
WWDC
2014-609, 2014-610, 2017-604, 2019-612
Docs
/scenekit, /scenekit/scnscene, /scenekit/scnnode, /scenekit/scnmaterial, /scenekit/scnphysicsbody
Skills
axiom-scenekit-ref, axiom-realitykit, axiom-realitykit-ref
返回排行榜