You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SceneKit场景背景纹理动画实现失败,求正确方案

SceneKit场景背景循环动画实现问题

我需要在SceneKit中实现场景背景(scene.background.contents)的循环动画,而非物体上的纹理动画。当前项目使用单个EXR文件作为背景,尝试用3张测试用EXR序列图(background_0、1、2)创建CAKeyframeAnimation,再通过SCNMaterialProperty、SCNMaterial赋值给场景背景,但应用加载后仅显示半秒背景就变黑,单帧单独使用正常。

原实现代码

// load the image sequences: .exr
var imageArray: [UIImage] = []
for i in 0...2 { // afor now 3 frames long, 0, 1 and 2
  if let image = UIImage(named: "background_\(i).exr") { // we assume their names: "background_0.exr", "background_1.exr", etc.
      imageArray.append(image)
      print(image)
  }
}
print(imageArray)

// Create texture animation
let bg_animation = CAKeyframeAnimation(keyPath: "contents")
bg_animation.calculationMode = .discrete
bg_animation.duration = 5 // animation's duration
bg_animation.values = imageArray.map { $0.cgImage as Any }
bg_animation.repeatCount = .infinity // animation repeats forever.
//bg_animation.autoreverses = true // the animation reverses

print("hi, this is the bg animation: ")
print(bg_animation)

// Create the animation using the array
backgroundAnimation = SCNMaterialProperty(contents: bg_animation)

print("hi, this is the SCNMaterial property animation: ")
print(backgroundAnimation)

// Create the material and asign the animation as its content
let backgroundMaterial = SCNMaterial()
backgroundMaterial.setValue(backgroundAnimation, forKey: "contents")
backgroundMaterial.locksAmbientWithDiffuse = true

print("This is background Material")
print(backgroundMaterial)
print(backgroundMaterial.animationKeys)
print(backgroundMaterial.diffuse)

// Assign the material to the scene background content
scene.background.contents = backgroundMaterial

print("Asign material as background")
print(scene.background.contents)

问题原因

原实现的核心错误是错误地通过SCNMaterial中转动画。SceneKit的scene.background本身就是一个SCNMaterialProperty,不需要将动画封装到SCNMaterial里再赋值,这种方式会导致动画的生命周期管理异常,进而出现黑屏。

正确实现方式

方式一:直接给背景属性添加关键帧动画

这是最贴合需求的轻量方案,直接对scene.background这个SCNMaterialProperty添加动画:

// 1. 加载序列图为CGImage数组(确保图片有效)
var cgImageSequence: [CGImage] = []
for index in 0...2 {
    guard let uiImage = UIImage(named: "background_\(index).exr"),
          let cgImage = uiImage.cgImage else { continue }
    cgImageSequence.append(cgImage)
}

// 2. 创建关键帧动画
let bgAnimation = CAKeyframeAnimation(keyPath: "contents")
bgAnimation.values = cgImageSequence
bgAnimation.duration = 5
bgAnimation.calculationMode = .discrete // 离散切换,无插值
bgAnimation.repeatCount = .infinity

// 3. 设置初始帧(避免动画启动前黑屏)
scene.background.contents = cgImageSequence.first

// 4. 给背景属性添加动画
scene.background.addAnimation(bgAnimation, forKey: "backgroundLoop")

方式二:用AVPlayer播放序列帧视频(适合大量帧的场景)

如果后续序列帧数量较多,建议将EXR序列转成循环视频(如MOV格式),用AVPlayer实现更高效的循环播放:

// 加载本地循环视频
guard let videoURL = Bundle.main.url(forResource: "background_loop", withExtension: "mov") else { return }

let player = AVPlayer(url: videoURL)
player.actionAtItemEnd = .none // 播放结束后不暂停

// 设置循环播放逻辑
NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime,
                                       object: player.currentItem,
                                       queue: .main) { [weak player] _ in
    player?.seek(to: .zero)
    player?.play()
}

// 启动播放并赋值给背景
player.play()
scene.background.contents = player

内容的提问来源于stack exchange,提问作者sar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 02:58:10