home / skills / charleswiltgen / axiom / axiom-scenekit-ref

This skill maps SceneKit concepts to RealityKit equivalents, guiding migration, API references, and scene graph translations for efficient iOS software

npx playbooks add skill charleswiltgen/axiom --skill axiom-scenekit-ref

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
13.1 KB
---
name: axiom-scenekit-ref
description: SceneKit → RealityKit concept mapping, complete API cross-reference for migration, scene graph API, materials, lighting, camera, physics, animation, constraints
license: MIT
compatibility: [iOS 8+, macOS 10.8+, tvOS 9+]
metadata:
  version: "1.0.0"
---

# SceneKit API Reference & Migration Mapping

Complete API reference for SceneKit with RealityKit equivalents for every major concept.

## When to Use This Reference

Use this reference when:
- Looking up SceneKit → RealityKit API equivalents during migration
- Checking specific SceneKit class properties or methods
- Planning which SceneKit features have direct RealityKit counterparts
- Understanding architectural differences between scene graph and ECS

---

## Part 1: SceneKit → RealityKit Concept Mapping

### Core Architecture

| SceneKit | RealityKit | Notes |
|----------|-----------|-------|
| `SCNScene` | `RealityViewContent` / `Entity` (root) | RealityKit scenes are entity hierarchies |
| `SCNNode` | `Entity` | Lightweight container in both |
| `SCNView` | `RealityView` (SwiftUI) | `ARView` for UIKit on iOS |
| `SceneView` (SwiftUI) | `RealityView` | SceneView deprecated iOS 26 |
| `SCNRenderer` | `RealityRenderer` | Low-level Metal rendering |
| Node properties | Components | ECS separates data from hierarchy |
| `SCNSceneRendererDelegate` | `System` / `SceneEvents.Update` | Frame-level updates |
| `.scn` files | `.usdz` / `.usda` files | Convert with `xcrun scntool` |

### Geometry & Rendering

| SceneKit | RealityKit | Notes |
|----------|-----------|-------|
| `SCNGeometry` | `MeshResource` | RealityKit generates from code or loads USD |
| `SCNBox`, `SCNSphere`, etc. | `MeshResource.generateBox()`, `.generateSphere()` | Similar built-in shapes |
| `SCNMaterial` | `SimpleMaterial`, `PhysicallyBasedMaterial` | PBR-first in RealityKit |
| `SCNMaterial.lightingModel = .physicallyBased` | `PhysicallyBasedMaterial` | Default in RealityKit |
| `SCNMaterial.diffuse` | `PhysicallyBasedMaterial.baseColor` | Different property name |
| `SCNMaterial.metalness` | `PhysicallyBasedMaterial.metallic` | Different property name |
| `SCNMaterial.roughness` | `PhysicallyBasedMaterial.roughness` | Same concept |
| `SCNMaterial.normal` | `PhysicallyBasedMaterial.normal` | Same concept |
| Shader modifiers | `ShaderGraphMaterial` / `CustomMaterial` | No direct port — must rewrite |
| `SCNProgram` (custom shaders) | `CustomMaterial` with Metal functions | Different API surface |
| `SCNGeometrySource` | `MeshResource.Contents` | Low-level mesh data |

### Transforms & Hierarchy

| SceneKit | RealityKit | Notes |
|----------|-----------|-------|
| `node.position` | `entity.position` | Both SCNVector3 / SIMD3<Float> |
| `node.eulerAngles` | `entity.orientation` (quaternion) | RealityKit prefers quaternions |
| `node.scale` | `entity.scale` | Both SIMD3<Float> |
| `node.transform` | `entity.transform` | 4×4 matrix |
| `node.worldTransform` | `entity.transform(relativeTo: nil)` | World-space transform |
| `node.addChildNode(_:)` | `entity.addChild(_:)` | Same hierarchy concept |
| `node.removeFromParentNode()` | `entity.removeFromParent()` | Same concept |
| `node.childNodes` | `entity.children` | Children collection |
| `node.parent` | `entity.parent` | Parent reference |
| `node.childNode(withName:recursively:)` | `entity.findEntity(named:)` | Named lookup |

### Lighting

| SceneKit | RealityKit | Notes |
|----------|-----------|-------|
| `SCNLight` (`.omni`) | `PointLightComponent` | Point light |
| `SCNLight` (`.directional`) | `DirectionalLightComponent` | Sun/directional light |
| `SCNLight` (`.spot`) | `SpotLightComponent` | Cone light |
| `SCNLight` (`.area`) | No direct equivalent | Use multiple point lights |
| `SCNLight` (`.ambient`) | `EnvironmentResource` (IBL) | Image-based lighting preferred |
| `SCNLight` (`.probe`) | `EnvironmentResource` | Environment probes |
| `SCNLight` (`.IES`) | No direct equivalent | Use light intensity profiles |

### Camera

| SceneKit | RealityKit | Notes |
|----------|-----------|-------|
| `SCNCamera` | `PerspectiveCamera` entity | Entity with camera component |
| `camera.fieldOfView` | `PerspectiveCameraComponent.fieldOfViewInDegrees` | Same concept |
| `camera.zNear` / `camera.zFar` | `PerspectiveCameraComponent.near` / `.far` | Clipping planes |
| `camera.wantsDepthOfField` | Post-processing effects | Different mechanism |
| `camera.motionBlurIntensity` | Post-processing effects | Different mechanism |
| `allowsCameraControl` | Custom gesture handling | No built-in orbit camera |

### Physics

| SceneKit | RealityKit | Notes |
|----------|-----------|-------|
| `SCNPhysicsBody` | `PhysicsBodyComponent` | Component-based |
| `.dynamic` | `.dynamic` | Same mode |
| `.static` | `.static` | Same mode |
| `.kinematic` | `.kinematic` | Same mode |
| `SCNPhysicsShape` | `CollisionComponent` / `ShapeResource` | Separate from body in RealityKit |
| `categoryBitMask` | `CollisionGroup` | Named groups vs raw bitmasks |
| `collisionBitMask` | `CollisionFilter` | Filter-based |
| `contactTestBitMask` | `CollisionEvents.Began` subscription | Event-based contacts |
| `SCNPhysicsContactDelegate` | `scene.subscribe(to: CollisionEvents.Began.self)` | Combine-style events |
| `SCNPhysicsField` | `PhysicsBodyComponent` forces | Apply forces directly |
| `SCNPhysicsJoint` | `PhysicsJoint` | Similar joint types |

### Animation

| SceneKit | RealityKit | Notes |
|----------|-----------|-------|
| `SCNAction` | `entity.move(to:relativeTo:duration:)` | Transform animation |
| `SCNAction.sequence` | Animation chaining | Less declarative in RealityKit |
| `SCNAction.group` | Parallel animations | Apply to different entities |
| `SCNAction.repeatForever` | `AnimationPlaybackController` repeat | Different API |
| `SCNTransaction` (implicit) | No direct equivalent | Explicit animations only |
| `CAAnimation` bridge | `entity.playAnimation()` | Load from USD |
| `SCNAnimationPlayer` | `AnimationPlaybackController` | Playback control |
| Morph targets | Blend shapes in USD | Load via USD files |

### Interaction

| SceneKit | RealityKit | Notes |
|----------|-----------|-------|
| `hitTest(_:options:)` | `RealityViewContent.entities(at:)` | Different API |
| Gesture recognizers on SCNView | `ManipulationComponent` | Built-in drag/rotate/scale |
| `allowsCameraControl` | Custom implementation | No built-in orbit |

### AR Integration

| SceneKit | RealityKit | Notes |
|----------|-----------|-------|
| `ARSCNView` | `RealityView` + `AnchorEntity` | Legacy → modern |
| `ARSCNViewDelegate` | `AnchorEntity` auto-tracking | Event-driven |
| `renderer(_:didAdd:for:)` | `AnchorEntity(.plane)` | Declarative anchoring |
| `ARWorldTrackingConfiguration` | `SpatialTrackingSession` | iOS 18+ |

---

## Part 2: Scene Graph API

### SCNScene

```swift
// Loading
let scene = SCNScene(named: "scene.usdz")!
let scene = try SCNScene(url: url, options: [
    .checkConsistency: true,
    .convertToYUp: true
])

// Properties
scene.rootNode                    // Root of node hierarchy
scene.background.contents        // Skybox (UIImage, UIColor, MDLSkyCubeTexture)
scene.lightingEnvironment.contents // IBL environment map
scene.fogStartDistance            // Fog near
scene.fogEndDistance              // Fog far
scene.fogColor                   // Fog color
scene.isPaused                   // Pause simulation
```

### SCNNode

```swift
// Creation
let node = SCNNode()
let node = SCNNode(geometry: SCNBox(width: 1, height: 1, length: 1, chamferRadius: 0))

// Transform
node.position = SCNVector3(x, y, z)
node.eulerAngles = SCNVector3(pitch, yaw, roll)
node.scale = SCNVector3(1, 1, 1)
node.simdPosition = SIMD3<Float>(x, y, z)  // SIMD variants available
node.pivot = SCNMatrix4MakeTranslation(0, -0.5, 0) // Offset pivot point

// Visibility
node.isHidden = false
node.opacity = 1.0
node.castsShadow = true
node.renderingOrder = 0   // Lower = rendered first

// Hierarchy
node.addChildNode(child)
node.removeFromParentNode()
node.childNodes
node.childNode(withName: "name", recursively: true)
node.enumerateChildNodes { child, stop in }
```

---

## Part 3: Materials

### Lighting Models

| Model | Description | Use Case |
|-------|-------------|----------|
| `.physicallyBased` | PBR metallic-roughness | Realistic rendering (recommended) |
| `.blinn` | Blinn-Phong specular | Simple shiny surfaces |
| `.phong` | Phong specular | Classic specular highlight |
| `.lambert` | Diffuse only, no specular | Matte surfaces |
| `.constant` | Unlit, flat color | UI elements, debug visualization |
| `.shadowOnly` | Invisible, receives shadows | AR ground plane |

### Material Properties

```swift
let mat = SCNMaterial()
mat.lightingModel = .physicallyBased

// Textures or scalar values
mat.diffuse.contents = UIImage(named: "albedo")    // Base color
mat.metalness.contents = 0.0                        // 0 = dielectric, 1 = metal
mat.roughness.contents = 0.5                        // 0 = mirror, 1 = rough
mat.normal.contents = UIImage(named: "normal")      // Normal map
mat.ambientOcclusion.contents = UIImage(named: "ao") // AO map
mat.emission.contents = UIColor.blue                // Glow
mat.displacement.contents = UIImage(named: "height") // Height map

// Options
mat.isDoubleSided = false        // Render both sides
mat.writesToDepthBuffer = true
mat.readsFromDepthBuffer = true
mat.blendMode = .alpha           // .add, .subtract, .multiply, .screen
mat.transparencyMode = .aOne     // .rgbZero for pre-multiplied alpha
```

---

## Part 4: Physics

### Body Types and Properties

```swift
// Dynamic body with custom shape
let shape = SCNPhysicsShape(geometry: SCNSphere(radius: 0.5), options: nil)
let body = SCNPhysicsBody(type: .dynamic, shape: shape)
body.mass = 1.0
body.friction = 0.5
body.restitution = 0.3       // Bounciness
body.damping = 0.1            // Linear damping
body.angularDamping = 0.1     // Angular damping
body.isAffectedByGravity = true
body.allowsResting = true     // Sleep optimization
node.physicsBody = body

// Compound shapes
let compound = SCNPhysicsShape(shapes: [shape1, shape2],
    transforms: [transform1, transform2])

// Concave (static only)
let concave = SCNPhysicsShape(geometry: mesh, options: [
    .type: SCNPhysicsShape.ShapeType.concavePolyhedron
])
```

### Joint Types

| Joint | Description |
|-------|-------------|
| `SCNPhysicsHingeJoint` | Single-axis rotation (door) |
| `SCNPhysicsBallSocketJoint` | Free rotation around point (pendulum) |
| `SCNPhysicsSliderJoint` | Linear movement along axis (drawer) |
| `SCNPhysicsConeTwistJoint` | Limited rotation (ragdoll limb) |

---

## Part 5: Animation API

### SCNAction Catalog

| Category | Actions |
|----------|---------|
| Movement | `move(by:duration:)`, `move(to:duration:)` |
| Rotation | `rotate(by:around:duration:)`, `rotateTo(x:y:z:duration:)` |
| Scale | `scale(by:duration:)`, `scale(to:duration:)` |
| Fade | `fadeIn(duration:)`, `fadeOut(duration:)`, `fadeOpacity(to:duration:)` |
| Visibility | `hide()`, `unhide()` |
| Audio | `playAudio(source:waitForCompletion:)` |
| Custom | `run { node in }`, `customAction(duration:action:)` |
| Composition | `sequence([])`, `group([])`, `repeat(_:count:)`, `repeatForever(_:)` |
| Control | `wait(duration:)`, `removeFromParentNode()` |

### Timing Functions

```swift
action.timingMode = .linear        // Default
action.timingMode = .easeIn        // Slow start
action.timingMode = .easeOut       // Slow end
action.timingMode = .easeInEaseOut // Slow start and end
action.timingFunction = { t in     // Custom curve
    return t * t  // Quadratic ease-in
}
```

---

## Part 6: Constraints

| Constraint | Purpose |
|------------|---------|
| `SCNLookAtConstraint` | Node always faces target |
| `SCNBillboardConstraint` | Node always faces camera |
| `SCNDistanceConstraint` | Maintains min/max distance |
| `SCNReplicatorConstraint` | Copies transform of target |
| `SCNAccelerationConstraint` | Smooths transform changes |
| `SCNSliderConstraint` | Locks to axis |
| `SCNIKConstraint` | Inverse kinematics chain |

```swift
let lookAt = SCNLookAtConstraint(target: targetNode)
lookAt.isGimbalLockEnabled = true  // Prevent roll
lookAt.influenceFactor = 0.8       // Partial constraint
node.constraints = [lookAt]
```

**In RealityKit**: No direct constraint system. Implement with `System` update logic or `entity.look(at:from:relativeTo:)`.

---

## Part 7: Scene Configuration

### SCNView Configuration

| Property | Default | Description |
|----------|---------|-------------|
| `antialiasingMode` | `.multisampling4X` | MSAA level |
| `preferredFramesPerSecond` | 60 | Target frame rate |
| `allowsCameraControl` | `false` | Built-in orbit/pan/zoom |
| `autoenablesDefaultLighting` | `false` | Add default light if none |
| `showsStatistics` | `false` | FPS/node/draw count overlay |
| `isTemporalAntialiasingEnabled` | `false` | TAA smoothing |
| `isJitteringEnabled` | `false` | Temporal jitter for TAA |
| `debugOptions` | `[]` | `.showPhysicsShapes`, `.showBoundingBoxes`, `.renderAsWireframe` |

---

## Resources

**WWDC**: 2014-609, 2014-610, 2017-604, 2019-612

**Docs**: /scenekit, /scenekit/scnscene, /scenekit/scnnode, /scenekit/scnmaterial, /scenekit/scnphysicsbody, /scenekit/scnaction

**Skills**: axiom-scenekit, axiom-realitykit, axiom-realitykit-ref

Overview

This skill provides a complete cross-reference and migration guide from SceneKit to RealityKit, mapping every major SceneKit concept to its RealityKit counterpart. It is focused on practical, battle-tested recommendations for modern xOS development, covering scene graph vs ECS, materials, lighting, physics, animation, and constraints. Use it to speed migrations and avoid common pitfalls.

How this skill works

The reference inspects SceneKit classes, properties, and APIs and supplies direct RealityKit equivalents or recommended patterns when no direct mapping exists. It highlights architectural differences (scene graph vs entity-component-system), naming changes, and workflow shifts like USD-based assets and component-driven physics. Concrete code examples and tables show one-to-one mappings and alternative approaches.

When to use it

  • Migrating an app from SceneKit to RealityKit and needing exact API counterparts.
  • Looking up how SceneKit materials, lights, cameras, or physics map to RealityKit components.
  • Deciding whether to rewrite custom shaders or use RealityKit's CustomMaterial/ShaderGraph.
  • Planning AR updates to modern spatial tracking and anchor-based workflows.
  • Troubleshooting behavioral differences due to ECS separation of data and hierarchy.

Best practices

  • Prefer USD (.usdz/.usda) assets for models and animations when moving to RealityKit.
  • Adopt RealityKit’s component patterns: separate mesh, material, collision, and physics into components.
  • Use quaternions/orientations rather than Euler angles to avoid gimbal issues.
  • Replace SceneKit implicit/transac­tions with explicit AnimationPlaybackController or systems.
  • Implement constraints and look-at behavior in a custom System or use entity convenience APIs.

Example use cases

  • Convert an app using SCNScene/SCNNode hierarchies to an entity-based RealityView with AnchorEntity roots.
  • Replace SCNMaterial setups with PhysicallyBasedMaterial, mapping diffuse→baseColor, metalness→metallic, roughness→roughness.
  • Migrate physics bodies and shapes to PhysicsBodyComponent and CollisionComponent with ShapeResource and CollisionGroup filters.
  • Port SCNActions and CAAnimations to explicit RealityKit animations and AnimationPlaybackController with USD exports.
  • Swap ARSCNView integration for RealityView + AnchorEntity and SpatialTrackingSession for AR world tracking.

FAQ

Are there direct equivalents for all SceneKit features in RealityKit?

Many core features have direct equivalents, but some like area lights, SCNProgram shader modifiers, implicit SCNTransaction animations, and certain IES profiles require alternative approaches or rewrites.

How should I handle custom shaders from SceneKit?

Rewrite them using RealityKit’s CustomMaterial with Metal shader functions or ShaderGraphMaterial; there is no automatic port for SCNProgram or shader modifiers.

What’s the best way to migrate scene files (.scn)?

Export or convert assets to USD (.usdz/.usda) and reattach materials, animations, and blend shapes in RealityKit to preserve fidelity.