scenekit

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

SceneKit

SceneKit

Apple's high-level 3D rendering framework for building scenes and visualizations on iOS using Swift 6.3. Provides a node-based scene graph, built-in geometry primitives, physically based materials, lighting, animation, and physics.
Deprecation notice (WWDC 2025): SceneKit is officially deprecated across all Apple platforms and is now in maintenance mode (critical bug fixes only). Existing apps continue to work. For new projects or major updates, Apple recommends RealityKit. See WWDC 2025 session 288 for migration guidance.
这是Apple推出的高级3D渲染框架,可在iOS平台上使用Swift 6.3构建场景与可视化内容。它提供基于节点的场景图、内置几何基元、基于物理的材质、光照、动画及物理模拟功能。
弃用通知(WWDC 2025): SceneKit已在所有Apple平台上正式被弃用,目前处于维护模式(仅修复关键漏洞)。现有应用可继续正常运行。对于新项目或重大更新,Apple推荐使用RealityKit。迁移指南可查看WWDC 2025的第288场会议。

Contents

目录

Scene Setup

场景搭建

SCNView in UIKit

UIKit中的SCNView

swift
import SceneKit

let sceneView = SCNView(frame: view.bounds)
sceneView.scene = SCNScene()
sceneView.allowsCameraControl = true
sceneView.autoenablesDefaultLighting = true
sceneView.backgroundColor = .black
view.addSubview(sceneView)
allowsCameraControl
adds built-in orbit, pan, and zoom gestures. Typically disabled in production where custom camera control is needed.
swift
import SceneKit

let sceneView = SCNView(frame: view.bounds)
sceneView.scene = SCNScene()
sceneView.allowsCameraControl = true
sceneView.autoenablesDefaultLighting = true
sceneView.backgroundColor = .black
view.addSubview(sceneView)
allowsCameraControl
会添加内置的轨道旋转、平移和缩放手势。在需要自定义相机控制的生产环境中通常会禁用该属性。

Creating an SCNScene

创建SCNScene

swift
let scene = SCNScene()                                          // Empty
guard let scene = SCNScene(named: "art.scnassets/ship.scn")     // .scn asset catalog
    else { fatalError("Missing scene asset") }
let scene = try SCNScene(url: Bundle.main.url(                  // .usdz from bundle
    forResource: "spaceship", withExtension: "usdz")!)
swift
let scene = SCNScene()                                          // 空场景
guard let scene = SCNScene(named: "art.scnassets/ship.scn")     // 从资源目录加载.scn文件
    else { fatalError("Missing scene asset") }
let scene = try SCNScene(url: Bundle.main.url(                  // 从Bundle加载.usdz文件
    forResource: "spaceship", withExtension: "usdz")!)

Nodes and Geometry

节点与几何

Every scene has a
rootNode
. All content exists as descendant nodes. Nodes define position, orientation, and scale in their parent's coordinate system. SceneKit uses a right-handed coordinate system: +X right, +Y up, +Z toward the camera.
swift
let parentNode = SCNNode()
scene.rootNode.addChildNode(parentNode)

let childNode = SCNNode()
childNode.position = SCNVector3(0, 1, 0)  // 1 unit above parent
parentNode.addChildNode(childNode)
每个场景都有一个
rootNode
,所有内容都作为子节点存在。节点定义了自身在父节点坐标系中的位置、方向和缩放比例。SceneKit采用右手坐标系:+X为右,+Y为上,+Z指向相机方向。
swift
let parentNode = SCNNode()
scene.rootNode.addChildNode(parentNode)

let childNode = SCNNode()
childNode.position = SCNVector3(0, 1, 0)  // 位于父节点上方1个单位
parentNode.addChildNode(childNode)

Transforms

变换操作

swift
node.position = SCNVector3(x: 0, y: 2, z: -5)
node.eulerAngles = SCNVector3(x: 0, y: .pi / 4, z: 0)  // 45-degree Y rotation
node.scale = SCNVector3(2, 2, 2)
node.simdPosition = SIMD3<Float>(0, 2, -5)  // Prefer simd for performance
swift
node.position = SCNVector3(x: 0, y: 2, z: -5)
node.eulerAngles = SCNVector3(x: 0, y: .pi / 4, z: 0)  // Y轴旋转45度
node.scale = SCNVector3(2, 2, 2)
node.simdPosition = SIMD3<Float>(0, 2, -5)  // 优先使用simd提升性能

Built-in Primitives

内置几何基元

SCNBox
,
SCNSphere
,
SCNCylinder
,
SCNCone
,
SCNTorus
,
SCNCapsule
,
SCNTube
,
SCNPlane
,
SCNFloor
,
SCNText
,
SCNShape
(extruded Bezier path).
swift
let node = SCNNode(geometry: SCNSphere(radius: 0.5))
SCNBox
SCNSphere
SCNCylinder
SCNCone
SCNTorus
SCNCapsule
SCNTube
SCNPlane
SCNFloor
SCNText
SCNShape
(拉伸贝塞尔路径)。
swift
let node = SCNNode(geometry: SCNSphere(radius: 0.5))

Finding Nodes

查找节点

swift
let maxNode = scene.rootNode.childNode(withName: "Max", recursively: true)
let enemies = scene.rootNode.childNodes { node, _ in
    node.name?.hasPrefix("enemy") == true
}
swift
let maxNode = scene.rootNode.childNode(withName: "Max", recursively: true)
let enemies = scene.rootNode.childNodes { node, _ in
    node.name?.hasPrefix("enemy") == true
}

Materials

材质

SCNMaterial
defines surface appearance. Use
firstMaterial
for single-material geometries or the
materials
array for multi-material.
SCNMaterial
用于定义表面外观。单材质几何可使用
firstMaterial
,多材质几何可使用
materials
数组。

Color and Texture

颜色与纹理

swift
let material = SCNMaterial()
material.diffuse.contents = UIColor.systemBlue     // Solid color
material.diffuse.contents = UIImage(named: "brick") // Texture
material.normal.contents = UIImage(named: "brick_normal")
sphere.firstMaterial = material
swift
let material = SCNMaterial()
material.diffuse.contents = UIColor.systemBlue     // 纯色
material.diffuse.contents = UIImage(named: "brick") // 纹理
material.normal.contents = UIImage(named: "brick_normal")
sphere.firstMaterial = material

Physically Based Rendering (PBR)

基于物理的渲染(PBR)

swift
let pbr = SCNMaterial()
pbr.lightingModel = .physicallyBased
pbr.diffuse.contents = UIImage(named: "albedo")
pbr.metalness.contents = 0.8       // Scalar or texture
pbr.roughness.contents = 0.2       // Scalar or texture
pbr.normal.contents = UIImage(named: "normal")
pbr.ambientOcclusion.contents = UIImage(named: "ao")
swift
let pbr = SCNMaterial()
pbr.lightingModel = .physicallyBased
pbr.diffuse.contents = UIImage(named: "albedo")
pbr.metalness.contents = 0.8       // 标量或纹理
pbr.roughness.contents = 0.2       // 标量或纹理
pbr.normal.contents = UIImage(named: "normal")
pbr.ambientOcclusion.contents = UIImage(named: "ao")

Lighting Models

光照模型

.physicallyBased
(metalness/roughness),
.blinn
(default),
.phong
,
.lambert
(diffuse-only),
.constant
(unlit),
.shadowOnly
.
Each material property is an
SCNMaterialProperty
accepting
UIColor
,
UIImage
,
CGFloat
scalar,
SKTexture
,
CALayer
, or
AVPlayer
.
.physicallyBased
(金属度/粗糙度)、
.blinn
(默认)、
.phong
.lambert
(仅漫反射)、
.constant
(无光照)、
.shadowOnly
每个材质属性都是
SCNMaterialProperty
,可接受
UIColor
UIImage
CGFloat
标量、
SKTexture
CALayer
AVPlayer
作为内容。

Transparency

透明度

swift
material.transparency = 0.5
material.transparencyMode = .dualLayer
material.isDoubleSided = true
swift
material.transparency = 0.5
material.transparencyMode = .dualLayer
material.isDoubleSided = true

Lighting

光照

Attach an
SCNLight
to a node. The light's direction follows the node's negative Z-axis.
SCNLight
附加到节点上,光照方向遵循节点的负Z轴方向。

Light Types

光照类型

swift
// Ambient: uniform, no direction
let ambient = SCNLight()
ambient.type = .ambient
ambient.color = UIColor(white: 0.3, alpha: 1)

// Directional: parallel rays (sunlight)
let directional = SCNLight()
directional.type = .directional
directional.castsShadow = true

// Omni: point light, all directions
let omni = SCNLight()
omni.type = .omni
omni.attenuationEndDistance = 20

// Spot: cone-shaped
let spot = SCNLight()
spot.type = .spot
spot.spotInnerAngle = 20
spot.spotOuterAngle = 60
Attach to a node:
swift
let lightNode = SCNNode()
lightNode.light = directional
lightNode.eulerAngles = SCNVector3(-Float.pi / 3, 0, 0)
lightNode.position = SCNVector3(0, 10, 10)
scene.rootNode.addChildNode(lightNode)
swift
// 环境光:均匀分布,无方向
let ambient = SCNLight()
ambient.type = .ambient
ambient.color = UIColor(white: 0.3, alpha: 1)

// 方向光:平行光线(如太阳光)
let directional = SCNLight()
directional.type = .directional
directional.castsShadow = true

// 点光源:向所有方向发射光线
let omni = SCNLight()
omni.type = .omni
omni.attenuationEndDistance = 20

// 聚光灯:锥形光线
let spot = SCNLight()
spot.type = .spot
spot.spotInnerAngle = 20
spot.spotOuterAngle = 60
附加到节点:
swift
let lightNode = SCNNode()
lightNode.light = directional
lightNode.eulerAngles = SCNVector3(-Float.pi / 3, 0, 0)
lightNode.position = SCNVector3(0, 10, 10)
scene.rootNode.addChildNode(lightNode)

Shadows

阴影

swift
light.castsShadow = true
light.shadowMapSize = CGSize(width: 2048, height: 2048)
light.shadowSampleCount = 8
light.shadowRadius = 3.0
light.shadowColor = UIColor(white: 0, alpha: 0.5)
swift
light.castsShadow = true
light.shadowMapSize = CGSize(width: 2048, height: 2048)
light.shadowSampleCount = 8
light.shadowRadius = 3.0
light.shadowColor = UIColor(white: 0, alpha: 0.5)

Category Bit Masks

类别位掩码

swift
light.categoryBitMask = 1 << 1     // Category 2
node.categoryBitMask = 1 << 1      // Only lit by category-2 lights
SceneKit renders a maximum of 8 lights per node. Use
attenuationEndDistance
on point/spot lights so SceneKit skips them for distant nodes.
swift
light.categoryBitMask = 1 << 1     // 类别2
node.categoryBitMask = 1 << 1      // 仅受类别2的光照影响
SceneKit每个节点最多支持8个光照。对点光源/聚光灯设置
attenuationEndDistance
,可让SceneKit忽略距离过远的光源。

Cameras

相机

Attach an
SCNCamera
to a node to define a viewpoint.
swift
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.position = SCNVector3(0, 5, 15)
cameraNode.look(at: SCNVector3Zero)
scene.rootNode.addChildNode(cameraNode)
sceneView.pointOfView = cameraNode
SCNCamera
附加到节点上,以此定义视角。
swift
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.position = SCNVector3(0, 5, 15)
cameraNode.look(at: SCNVector3Zero)
scene.rootNode.addChildNode(cameraNode)
sceneView.pointOfView = cameraNode

Configuration

配置

swift
camera.fieldOfView = 60                        // Degrees
camera.zNear = 0.1
camera.zFar = 500
camera.automaticallyAdjustsZRange = true

// Orthographic
camera.usesOrthographicProjection = true
camera.orthographicScale = 10
Depth-of-field (
wantsDepthOfField
,
focusDistance
,
fStop
) and HDR effects (
wantsHDR
,
bloomIntensity
,
bloomThreshold
,
screenSpaceAmbientOcclusionIntensity
) are configured directly on
SCNCamera
.
swift
camera.fieldOfView = 60                        // 角度
camera.zNear = 0.1
camera.zFar = 500
camera.automaticallyAdjustsZRange = true

// 正交投影
camera.usesOrthographicProjection = true
camera.orthographicScale = 10
景深(
wantsDepthOfField
focusDistance
fStop
)和HDR效果(
wantsHDR
bloomIntensity
bloomThreshold
screenSpaceAmbientOcclusionIntensity
)可直接在
SCNCamera
上配置。

Animation

动画

SceneKit provides three animation approaches.
SceneKit提供三种动画实现方式。

SCNAction (Declarative, Game-Oriented)

SCNAction(声明式、面向游戏)

Reusable, composable animation objects attached to nodes.
swift
let move = SCNAction.move(by: SCNVector3(0, 2, 0), duration: 1)
let rotate = SCNAction.rotateBy(x: 0, y: .pi, z: 0, duration: 1)
node.runAction(.group([move, rotate]))

// Sequential
node.runAction(.sequence([.fadeOut(duration: 0.3), .removeFromParentNode()]))

// Infinite loop
let pulse = SCNAction.sequence([
    .scale(to: 1.2, duration: 0.5),
    .scale(to: 1.0, duration: 0.5)
])
node.runAction(.repeatForever(pulse))
可复用、可组合的动画对象,附加到节点上使用。
swift
let move = SCNAction.move(by: SCNVector3(0, 2, 0), duration: 1)
let rotate = SCNAction.rotateBy(x: 0, y: .pi, z: 0, duration: 1)
node.runAction(.group([move, rotate]))

// 序列动画
node.runAction(.sequence([.fadeOut(duration: 0.3), .removeFromParentNode()]))

// 无限循环
let pulse = SCNAction.sequence([
    .scale(to: 1.2, duration: 0.5),
    .scale(to: 1.0, duration: 0.5)
])
node.runAction(.repeatForever(pulse))

SCNTransaction (Implicit Animation)

SCNTransaction(隐式动画)

swift
SCNTransaction.begin()
SCNTransaction.animationDuration = 1.0
node.position = SCNVector3(5, 0, 0)
node.opacity = 0.5
SCNTransaction.completionBlock = { print("Done") }
SCNTransaction.commit()
swift
SCNTransaction.begin()
SCNTransaction.animationDuration = 1.0
node.position = SCNVector3(5, 0, 0)
node.opacity = 0.5
SCNTransaction.completionBlock = { print("Done") }
SCNTransaction.commit()

Explicit Animations (Core Animation)

显式动画(Core Animation)

swift
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")
swift
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")

Physics

物理模拟

Physics Bodies

物理体

swift
node.physicsBody = SCNPhysicsBody(type: .dynamic, shape: nil)   // Forces + collisions
floor.physicsBody = SCNPhysicsBody(type: .static, shape: nil)    // Immovable
platform.physicsBody = SCNPhysicsBody(type: .kinematic, shape: nil) // Code-driven
When
shape
is
nil
, SceneKit derives it from geometry. For performance, use simplified shapes:
swift
let shape = SCNPhysicsShape(
    geometry: SCNBox(width: 1, height: 2, length: 1, chamferRadius: 0),
    options: nil
)
node.physicsBody = SCNPhysicsBody(type: .dynamic, shape: shape)
node.physicsBody?.mass = 2.0
node.physicsBody?.restitution = 0.3
swift
node.physicsBody = SCNPhysicsBody(type: .dynamic, shape: nil)   // 受外力影响并参与碰撞
floor.physicsBody = SCNPhysicsBody(type: .static, shape: nil)    // 不可移动
platform.physicsBody = SCNPhysicsBody(type: .kinematic, shape: nil) // 由代码驱动移动
shape
nil
时,SceneKit会根据几何形状自动生成物理形状。为提升性能,建议使用简化的形状:
swift
let shape = SCNPhysicsShape(
    geometry: SCNBox(width: 1, height: 2, length: 1, chamferRadius: 0),
    options: nil
)
node.physicsBody = SCNPhysicsBody(type: .dynamic, shape: shape)
node.physicsBody?.mass = 2.0
node.physicsBody?.restitution = 0.3

Applying Forces

施加力

swift
node.physicsBody?.applyForce(SCNVector3(0, 10, 0), asImpulse: false) // Continuous
node.physicsBody?.applyForce(SCNVector3(0, 5, 0), asImpulse: true)   // Instant
node.physicsBody?.applyTorque(SCNVector4(0, 1, 0, 2), asImpulse: true)
swift
node.physicsBody?.applyForce(SCNVector3(0, 10, 0), asImpulse: false) // 持续力
node.physicsBody?.applyForce(SCNVector3(0, 5, 0), asImpulse: true)   // 瞬时冲量
node.physicsBody?.applyTorque(SCNVector4(0, 1, 0, 2), asImpulse: true)

Collision Detection

碰撞检测

swift
struct PhysicsCategory {
    static let player:     Int = 1 << 0
    static let enemy:      Int = 1 << 1
    static let ground:     Int = 1 << 2
}

playerNode.physicsBody?.categoryBitMask = PhysicsCategory.player
playerNode.physicsBody?.collisionBitMask = PhysicsCategory.ground | PhysicsCategory.enemy
playerNode.physicsBody?.contactTestBitMask = PhysicsCategory.enemy

scene.physicsWorld.contactDelegate = self

func physicsWorld(_ world: SCNPhysicsWorld, didBegin contact: SCNPhysicsContact) {
    handleCollision(between: contact.nodeA, and: contact.nodeB)
}
swift
struct PhysicsCategory {
    static let player:     Int = 1 << 0
    static let enemy:      Int = 1 << 1
    static let ground:     Int = 1 << 2
}

playerNode.physicsBody?.categoryBitMask = PhysicsCategory.player
playerNode.physicsBody?.collisionBitMask = PhysicsCategory.ground | PhysicsCategory.enemy
playerNode.physicsBody?.contactTestBitMask = PhysicsCategory.enemy

scene.physicsWorld.contactDelegate = self

func physicsWorld(_ world: SCNPhysicsWorld, didBegin contact: SCNPhysicsContact) {
    handleCollision(between: contact.nodeA, and: contact.nodeB)
}

Gravity

重力

swift
scene.physicsWorld.gravity = SCNVector3(0, -9.8, 0)
node.physicsBody?.isAffectedByGravity = false
swift
scene.physicsWorld.gravity = SCNVector3(0, -9.8, 0)
node.physicsBody?.isAffectedByGravity = false

Particle Systems

粒子系统

SCNParticleSystem
creates effects like fire, smoke, rain, and sparks.
swift
let particles = SCNParticleSystem()
particles.birthRate = 100
particles.particleLifeSpan = 2
particles.particleSize = 0.1
particles.particleColor = .orange
particles.emitterShape = SCNSphere(radius: 0.5)
particles.particleVelocity = 2
particles.isAffectedByGravity = true
particles.blendMode = .additive

let emitterNode = SCNNode()
emitterNode.addParticleSystem(particles)
scene.rootNode.addChildNode(emitterNode)
Load from Xcode particle editor with
SCNParticleSystem(named: "fire.scnp", inDirectory: nil)
. Particles can collide with geometry via
colliderNodes
.
SCNParticleSystem
可创建火焰、烟雾、雨水、火花等特效。
swift
let particles = SCNParticleSystem()
particles.birthRate = 100
particles.particleLifeSpan = 2
particles.particleSize = 0.1
particles.particleColor = .orange
particles.emitterShape = SCNSphere(radius: 0.5)
particles.particleVelocity = 2
particles.isAffectedByGravity = true
particles.blendMode = .additive

let emitterNode = SCNNode()
emitterNode.addParticleSystem(particles)
scene.rootNode.addChildNode(emitterNode)
可通过
SCNParticleSystem(named: "fire.scnp", inDirectory: nil)
从Xcode粒子编辑器加载粒子系统。粒子可通过
colliderNodes
与几何形状发生碰撞。

Loading Models

加载模型

SceneKit loads
.usdz
,
.scn
,
.dae
,
.obj
, and
.abc
. Prefer
.usdz
.
swift
guard let scene = SCNScene(named: "art.scnassets/ship.scn") else { return }
let scene = try SCNScene(url: Bundle.main.url(
    forResource: "model", withExtension: "usdz")!)
guard let modelNode = scene.rootNode.childNode(withName: "mesh", recursively: true) else { return }
Use
SCNReferenceNode
with
.onDemand
loading policy for large models. Use
SCNSceneSource
to inspect or selectively load entries from a file.
SceneKit支持加载.usdz、.scn、.dae、.obj和.abc格式的模型,优先推荐.usdz格式。
swift
guard let scene = SCNScene(named: "art.scnassets/ship.scn") else { return }
let scene = try SCNScene(url: Bundle.main.url(
    forResource: "model", withExtension: "usdz")!)
guard let modelNode = scene.rootNode.childNode(withName: "mesh", recursively: true) else { return }
对于大型模型,可使用
SCNReferenceNode
并设置
.onDemand
加载策略。可使用
SCNSceneSource
检查或选择性加载文件中的内容。

SwiftUI Integration

SwiftUI集成

SceneView
embeds SceneKit in SwiftUI:
swift
import SwiftUI
import SceneKit

struct SceneKitView: View {
    let scene: SCNScene = {
        let scene = SCNScene()
        let sphere = SCNNode(geometry: SCNSphere(radius: 1))
        sphere.geometry?.firstMaterial?.lightingModel = .physicallyBased
        sphere.geometry?.firstMaterial?.diffuse.contents = UIColor.systemBlue
        sphere.geometry?.firstMaterial?.metalness.contents = 0.8
        scene.rootNode.addChildNode(sphere)
        return scene
    }()

    var body: some View {
        SceneView(scene: scene,
                  options: [.allowsCameraControl, .autoenablesDefaultLighting])
    }
}
Options:
.allowsCameraControl
,
.autoenablesDefaultLighting
,
.jitteringEnabled
,
.temporalAntialiasingEnabled
.
For render loop control, wrap
SCNView
in
UIViewRepresentable
with an
SCNSceneRendererDelegate
coordinator. See references/scenekit-patterns.md.
SceneView
可在SwiftUI中嵌入SceneKit:
swift
import SwiftUI
import SceneKit

struct SceneKitView: View {
    let scene: SCNScene = {
        let scene = SCNScene()
        let sphere = SCNNode(geometry: SCNSphere(radius: 1))
        sphere.geometry?.firstMaterial?.lightingModel = .physicallyBased
        sphere.geometry?.firstMaterial?.diffuse.contents = UIColor.systemBlue
        sphere.geometry?.firstMaterial?.metalness.contents = 0.8
        scene.rootNode.addChildNode(sphere)
        return scene
    }()

    var body: some View {
        SceneView(scene: scene,
                  options: [.allowsCameraControl, .autoenablesDefaultLighting])
    }
}
可选配置:
.allowsCameraControl
.autoenablesDefaultLighting
.jitteringEnabled
.temporalAntialiasingEnabled
如需控制渲染循环,可将
SCNView
包装在
UIViewRepresentable
中,并搭配
SCNSceneRendererDelegate
协调器使用。详情可查看references/scenekit-patterns.md

Common Mistakes

常见错误

Not adding a camera or lights

未添加相机或光照

swift
// DON'T: Scene renders blank or black -- no camera, no lights
sceneView.scene = scene

// DO: Add camera + lights, or use convenience flags
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.position = SCNVector3(0, 5, 15)
scene.rootNode.addChildNode(cameraNode)
sceneView.pointOfView = cameraNode
sceneView.autoenablesDefaultLighting = true
swift
// 错误做法:场景渲染空白或全黑——缺少相机和光照
sceneView.scene = scene

// 正确做法:添加相机和光照,或使用便捷配置项
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.position = SCNVector3(0, 5, 15)
scene.rootNode.addChildNode(cameraNode)
sceneView.pointOfView = cameraNode
sceneView.autoenablesDefaultLighting = true

Using exact geometry for physics shapes

使用精确几何形状作为物理形状

swift
// DON'T
node.physicsBody = SCNPhysicsBody(type: .dynamic,
    shape: SCNPhysicsShape(geometry: complexMesh))

// DO: Simplified primitive
node.physicsBody = SCNPhysicsBody(type: .dynamic,
    shape: SCNPhysicsShape(
        geometry: SCNBox(width: 1, height: 2, length: 1, chamferRadius: 0),
        options: nil))
swift
// 错误做法
node.physicsBody = SCNPhysicsBody(type: .dynamic,
    shape: SCNPhysicsShape(geometry: complexMesh))

// 正确做法:使用简化的基元形状
node.physicsBody = SCNPhysicsBody(type: .dynamic,
    shape: SCNPhysicsShape(
        geometry: SCNBox(width: 1, height: 2, length: 1, chamferRadius: 0),
        options: nil))

Modifying transforms on dynamic bodies

修改动态物理体的变换属性

swift
// DON'T: Resets physics simulation
dynamicNode.position = SCNVector3(5, 0, 0)

// DO: Use forces/impulses
dynamicNode.physicsBody?.applyForce(SCNVector3(10, 0, 0), asImpulse: true)
swift
// 错误做法:会重置物理模拟
dynamicNode.position = SCNVector3(5, 0, 0)

// 正确做法:使用力或冲量
dynamicNode.physicsBody?.applyForce(SCNVector3(10, 0, 0), asImpulse: true)

Exceeding 8 lights per node

单个节点的光照数量超过8个

swift
// DON'T: 20 lights with no attenuation
for _ in 0..<20 {
    let light = SCNNode()
    light.light = SCNLight()
    light.light?.type = .omni
    scene.rootNode.addChildNode(light)
}

// DO: Set attenuationEndDistance so SceneKit skips distant lights
light.light?.attenuationEndDistance = 10
swift
// 错误做法:添加20个无衰减的光照
for _ in 0..<20 {
    let light = SCNNode()
    light.light = SCNLight()
    light.light?.type = .omni
    scene.rootNode.addChildNode(light)
}

// 正确做法:设置attenuationEndDistance,让SceneKit忽略远距离光照
light.light?.attenuationEndDistance = 10

Review Checklist

检查清单

  • Scene has at least one camera node set as
    pointOfView
  • Scene has appropriate lighting (or
    autoenablesDefaultLighting
    for prototyping)
  • Physics shapes use simplified geometry, not full mesh detail
  • contactTestBitMask
    set for bodies that need collision callbacks
  • SCNPhysicsContactDelegate
    assigned to
    scene.physicsWorld.contactDelegate
  • Dynamic body transforms changed via forces/impulses, not direct position
  • Lights limited to 8 per node;
    attenuationEndDistance
    set on point/spot lights
  • Materials use
    .physicallyBased
    lighting model for realistic rendering
  • 3D assets use
    .usdz
    format where possible
  • SCNReferenceNode
    used for large models to enable lazy loading
  • Particle
    birthRate
    and
    particleLifeSpan
    balanced to control particle count
  • categoryBitMask
    used to scope lights and cameras to relevant nodes
  • SwiftUI scenes use
    SceneView
    or
    UIViewRepresentable
    -wrapped
    SCNView
  • Deprecation acknowledged; RealityKit evaluated for new projects
  • 场景至少有一个相机节点被设置为
    pointOfView
  • 场景配置了合适的光照(或原型开发时启用
    autoenablesDefaultLighting
  • 物理形状使用简化几何,而非完整网格细节
  • 需触发碰撞回调的物理体已设置
    contactTestBitMask
  • scene.physicsWorld.contactDelegate
    已指定
    SCNPhysicsContactDelegate
  • 动态物理体的变换通过力/冲量修改,而非直接修改位置
  • 单个节点的光照数量不超过8个;点光源/聚光灯已设置
    attenuationEndDistance
  • 材质使用
    .physicallyBased
    光照模型以实现真实感渲染
  • 3D资源尽可能使用.usdz格式
  • 大型模型使用
    SCNReferenceNode
    实现懒加载
  • 粒子系统的
    birthRate
    particleLifeSpan
    已平衡设置,以控制粒子数量
  • 使用
    categoryBitMask
    限制光照和相机作用的节点范围
  • SwiftUI场景使用
    SceneView
    或包装了
    SCNView
    UIViewRepresentable
  • 已确认弃用通知;新项目已评估使用RealityKit

References

参考资料