scenekit
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseSceneKit
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)allowsCameraControlswift
import SceneKit
let sceneView = SCNView(frame: view.bounds)
sceneView.scene = SCNScene()
sceneView.allowsCameraControl = true
sceneView.autoenablesDefaultLighting = true
sceneView.backgroundColor = .black
view.addSubview(sceneView)allowsCameraControlCreating 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 . 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.
rootNodeswift
let parentNode = SCNNode()
scene.rootNode.addChildNode(parentNode)
let childNode = SCNNode()
childNode.position = SCNVector3(0, 1, 0) // 1 unit above parent
parentNode.addChildNode(childNode)每个场景都有一个,所有内容都作为子节点存在。节点定义了自身在父节点坐标系中的位置、方向和缩放比例。SceneKit采用右手坐标系:+X为右,+Y为上,+Z指向相机方向。
rootNodeswift
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 performanceswift
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
内置几何基元
SCNBoxSCNSphereSCNCylinderSCNConeSCNTorusSCNCapsuleSCNTubeSCNPlaneSCNFloorSCNTextSCNShapeswift
let node = SCNNode(geometry: SCNSphere(radius: 0.5))SCNBoxSCNSphereSCNCylinderSCNConeSCNTorusSCNCapsuleSCNTubeSCNPlaneSCNFloorSCNTextSCNShapeswift
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
材质
SCNMaterialfirstMaterialmaterialsSCNMaterialfirstMaterialmaterialsColor 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 = materialswift
let material = SCNMaterial()
material.diffuse.contents = UIColor.systemBlue // 纯色
material.diffuse.contents = UIImage(named: "brick") // 纹理
material.normal.contents = UIImage(named: "brick_normal")
sphere.firstMaterial = materialPhysically 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.blinn.phong.lambert.constant.shadowOnlyEach material property is an accepting ,
, scalar, , , or .
SCNMaterialPropertyUIColorUIImageCGFloatSKTextureCALayerAVPlayer.physicallyBased.blinn.phong.lambert.constant.shadowOnly每个材质属性都是,可接受、、标量、、或作为内容。
SCNMaterialPropertyUIColorUIImageCGFloatSKTextureCALayerAVPlayerTransparency
透明度
swift
material.transparency = 0.5
material.transparencyMode = .dualLayer
material.isDoubleSided = trueswift
material.transparency = 0.5
material.transparencyMode = .dualLayer
material.isDoubleSided = trueLighting
光照
Attach an to a node. The light's direction follows the node's
negative Z-axis.
SCNLight将附加到节点上,光照方向遵循节点的负Z轴方向。
SCNLightLight 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 = 60Attach 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 lightsSceneKit renders a maximum of 8 lights per node. Use
on point/spot lights so SceneKit skips them for distant nodes.
attenuationEndDistanceswift
light.categoryBitMask = 1 << 1 // 类别2
node.categoryBitMask = 1 << 1 // 仅受类别2的光照影响SceneKit每个节点最多支持8个光照。对点光源/聚光灯设置,可让SceneKit忽略距离过远的光源。
attenuationEndDistanceCameras
相机
Attach an to a node to define a viewpoint.
SCNCameraswift
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.position = SCNVector3(0, 5, 15)
cameraNode.look(at: SCNVector3Zero)
scene.rootNode.addChildNode(cameraNode)
sceneView.pointOfView = cameraNode将附加到节点上,以此定义视角。
SCNCameraswift
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.position = SCNVector3(0, 5, 15)
cameraNode.look(at: SCNVector3Zero)
scene.rootNode.addChildNode(cameraNode)
sceneView.pointOfView = cameraNodeConfiguration
配置
swift
camera.fieldOfView = 60 // Degrees
camera.zNear = 0.1
camera.zFar = 500
camera.automaticallyAdjustsZRange = true
// Orthographic
camera.usesOrthographicProjection = true
camera.orthographicScale = 10Depth-of-field (, , ) and HDR effects
(, , , )
are configured directly on .
wantsDepthOfFieldfocusDistancefStopwantsHDRbloomIntensitybloomThresholdscreenSpaceAmbientOcclusionIntensitySCNCameraswift
camera.fieldOfView = 60 // 角度
camera.zNear = 0.1
camera.zFar = 500
camera.automaticallyAdjustsZRange = true
// 正交投影
camera.usesOrthographicProjection = true
camera.orthographicScale = 10景深(、、)和HDR效果(、、、)可直接在上配置。
wantsDepthOfFieldfocusDistancefStopwantsHDRbloomIntensitybloomThresholdscreenSpaceAmbientOcclusionIntensitySCNCameraAnimation
动画
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-drivenWhen is , SceneKit derives it from geometry. For performance, use
simplified shapes:
shapenilswift
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.3swift
node.physicsBody = SCNPhysicsBody(type: .dynamic, shape: nil) // 受外力影响并参与碰撞
floor.physicsBody = SCNPhysicsBody(type: .static, shape: nil) // 不可移动
platform.physicsBody = SCNPhysicsBody(type: .kinematic, shape: nil) // 由代码驱动移动当为时,SceneKit会根据几何形状自动生成物理形状。为提升性能,建议使用简化的形状:
shapenilswift
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.3Applying 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 = falseswift
scene.physicsWorld.gravity = SCNVector3(0, -9.8, 0)
node.physicsBody?.isAffectedByGravity = falseParticle Systems
粒子系统
SCNParticleSystemswift
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
. Particles can
collide with geometry via .
SCNParticleSystem(named: "fire.scnp", inDirectory: nil)colliderNodesSCNParticleSystemswift
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)可通过从Xcode粒子编辑器加载粒子系统。粒子可通过与几何形状发生碰撞。
SCNParticleSystem(named: "fire.scnp", inDirectory: nil)colliderNodesLoading Models
加载模型
SceneKit loads , , , , and . Prefer .
.usdz.scn.dae.obj.abc.usdzswift
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 with loading policy for large models.
Use to inspect or selectively load entries from a file.
SCNReferenceNode.onDemandSCNSceneSourceSceneKit支持加载.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.onDemandSCNSceneSourceSwiftUI Integration
SwiftUI集成
SceneViewswift
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.temporalAntialiasingEnabledFor render loop control, wrap in with an
coordinator. See references/scenekit-patterns.md.
SCNViewUIViewRepresentableSCNSceneRendererDelegateSceneViewswift
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如需控制渲染循环,可将包装在中,并搭配协调器使用。详情可查看references/scenekit-patterns.md。
SCNViewUIViewRepresentableSCNSceneRendererDelegateCommon 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 = trueswift
// 错误做法:场景渲染空白或全黑——缺少相机和光照
sceneView.scene = scene
// 正确做法:添加相机和光照,或使用便捷配置项
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.position = SCNVector3(0, 5, 15)
scene.rootNode.addChildNode(cameraNode)
sceneView.pointOfView = cameraNode
sceneView.autoenablesDefaultLighting = trueUsing 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 = 10swift
// 错误做法:添加20个无衰减的光照
for _ in 0..<20 {
let light = SCNNode()
light.light = SCNLight()
light.light?.type = .omni
scene.rootNode.addChildNode(light)
}
// 正确做法:设置attenuationEndDistance,让SceneKit忽略远距离光照
light.light?.attenuationEndDistance = 10Review Checklist
检查清单
- Scene has at least one camera node set as
pointOfView - Scene has appropriate lighting (or for prototyping)
autoenablesDefaultLighting - Physics shapes use simplified geometry, not full mesh detail
- set for bodies that need collision callbacks
contactTestBitMask - assigned to
SCNPhysicsContactDelegatescene.physicsWorld.contactDelegate - Dynamic body transforms changed via forces/impulses, not direct position
- Lights limited to 8 per node; set on point/spot lights
attenuationEndDistance - Materials use lighting model for realistic rendering
.physicallyBased - 3D assets use format where possible
.usdz - used for large models to enable lazy loading
SCNReferenceNode - Particle and
birthRatebalanced to control particle countparticleLifeSpan - used to scope lights and cameras to relevant nodes
categoryBitMask - SwiftUI scenes use or
SceneView-wrappedUIViewRepresentableSCNView - Deprecation acknowledged; RealityKit evaluated for new projects
- 场景至少有一个相机节点被设置为
pointOfView - 场景配置了合适的光照(或原型开发时启用)
autoenablesDefaultLighting - 物理形状使用简化几何,而非完整网格细节
- 需触发碰撞回调的物理体已设置
contactTestBitMask - 已指定
scene.physicsWorld.contactDelegateSCNPhysicsContactDelegate - 动态物理体的变换通过力/冲量修改,而非直接修改位置
- 单个节点的光照数量不超过8个;点光源/聚光灯已设置
attenuationEndDistance - 材质使用光照模型以实现真实感渲染
.physicallyBased - 3D资源尽可能使用.usdz格式
- 大型模型使用实现懒加载
SCNReferenceNode - 粒子系统的和
birthRate已平衡设置,以控制粒子数量particleLifeSpan - 使用限制光照和相机作用的节点范围
categoryBitMask - SwiftUI场景使用或包装了
SceneView的SCNViewUIViewRepresentable - 已确认弃用通知;新项目已评估使用RealityKit
References
参考资料
- See references/scenekit-patterns.md for custom geometry, shader modifiers, node constraints, morph targets, hit testing, scene serialization, render loop delegates, performance optimization, SpriteKit overlay, LOD, and Metal shaders.
- SceneKit documentation
- SCNScene
- SCNNode
- SCNView
- SceneView (SwiftUI)
- SCNGeometry
- SCNMaterial
- SCNLight
- SCNCamera
- SCNAction
- SCNPhysicsBody
- SCNParticleSystem
- WWDC 2025 session 288: Bring your SceneKit project to RealityKit
- 查看references/scenekit-patterns.md了解自定义几何、着色器修改器、节点约束、变形目标、点击测试、场景序列化、渲染循环代理、性能优化、SpriteKit叠加、LOD及Metal着色器相关内容。
- SceneKit官方文档
- SCNScene文档
- SCNNode文档
- SCNView文档
- SceneView(SwiftUI)文档
- SCNGeometry文档
- SCNMaterial文档
- SCNLight文档
- SCNCamera文档
- SCNAction文档
- SCNPhysicsBody文档
- SCNParticleSystem文档
- WWDC 2025第288场:将SceneKit项目迁移至RealityKit ",