Converting a Stop Motion Animation to usdz

Hello everyone,

I've been trying for a few weeks now to convert a sequential series of meshes into a stop-motion animation in USDZ format. In Unreal Engine, I’ve already figured out how to transform the sequential series of individual meshes into a smooth animation using the node system and arrays. Unfortunately, the node system cannot be exported as a usdz animation logic in either Unreal or Blender. Because of this, I have tried several other methods to incorporate the animation logic. Here’s what I’ve tried so far:

  • I attempted to create the animation in Blender with Render-/Viewports and mapping it to keyframes. However, in my experience, Viewports are not supported in the conversion.
  • I tried aligning the vertices of individual objects and merging the frames using the Shrinkwrap modifier in Blender, then setting up a morph animation with keyframes. However, because the individual meshes are too different, this results in artifacts, and manually editing each mesh is too difficult for me to handle.
  • I placed all individual meshes at the same position and animated them sequentially by scaling them from 0 to 100 in keyframes (Frame 1 is visible for 10 frames, then scales down at frame 11, while Frame 2 becomes visible at frame 11, and so on). I also adjusted the keyframes so that the scaling happens in a "constant" manner rather than the default Bezier or linear interpolation. I then converted this animation to .abc, and the result initially looked good. However, some information is lost when converting it with OpenUSD. The animation does not maintain its intended jump-like behavior in USDZ format, and instead, the scaling of individual files is visible in the animation.
  • I tried using a Blender add-on (StepMotion), which allows the animation to be exported as .abc, but it can only be read in Blender or Unreal. Even in the preview, the animation is not displayed correctly, so converting the animation logic does not work either.


Unfortunately, I have no alternative way to create the animation, as the individual frames have been provided to me as meshes. So far, I haven’t found a way to implement this successfully. I would be very grateful for any tips or ideas, as I am running out of options on how to make this work.

Thanks in advance!

Answered by Vision Pro Engineer in 832834022

Hi @denis_wo

I'm a big fan of stop motion! I'll go over a code and no code way to use the meshes to create a stop motion-like effect. Both approaches involve displaying each frame's mesh on an interval.

Here's how I recommend writing code to solve the problem:

  • Create a single entity.
  • On an interval, replace the entity's mesh with the mesh for the next frame.

Here's a snippet:

import SwiftUI
import RealityKit
struct ImmersiveView: View {
private let meshNames = ["box", "sphere", "cylinder"]
@State private var updateSubscription: EventSubscription?
var body: some View {
RealityView { content in
let animationDurationInSeconds = 3.0
var frameIndex = 0
let entity = ModelEntity(mesh: loadMeshForIndex(frameIndex)!, materials: [SimpleMaterial(color: .red, isMetallic: false)])
var timeSinceLastUpdate:TimeInterval = 0
let updateEveryNSeconds:TimeInterval = animationDurationInSeconds / Double(meshNames.count)
entity.position = [0, 1.2, -1]
content.add(entity)
updateSubscription = content.subscribe(to: SceneEvents.Update.self) { event in
timeSinceLastUpdate += event.deltaTime
if timeSinceLastUpdate >= updateEveryNSeconds {
frameIndex += 1
timeSinceLastUpdate = 0
guard let nextFrame = loadMeshForIndex(frameIndex) else {
updateSubscription?.cancel()
return
}
entity.model?.mesh = nextFrame
}
}
}
}
// This is a placeholder.
// Load the real mesh.
func loadMeshForIndex(_ index: Int) -> MeshResource? {
guard index >= 0 && index < meshNames.count else {
return nil
}
if meshNames[index] == "box" {
return MeshResource.generateBox(size: 0.1)
}
if meshNames[index] == "sphere" {
return MeshResource.generateSphere(radius: 0.05)
}
if meshNames[index] == "cylinder" {
return MeshResource.generateCylinder(height: 0.1, radius: 0.05)
}
return nil
}
}

For a more modular approach consider refactoring the code to use ECS. Create a custom StopMotionComponent with an array of mesh names and a custom StopMotionSystem to iterate through the mesh names and update the entity's active mesh.

Next, let's review the no code option. It's a bit cumbersome and ideal if your animation is only a few frames. If you have lots of mesh data you may run into performance issues since the approach requires you to keep lots of mesh data loaded.

  • In Reality Composer Pro, create an entity for each frame using the corresponding mesh.
  • Position the entities in the same place.
  • At the beginning of the timeline use a "Disable Entity" action to disable each of the entities. Then overtime, for each frame, enable the active frame's entity using an "Enable Entity" action. In parallel disable the previously enabled entity using a "Disable Entity" action. My timeline had 2 lanes, one for "Enable Entity" actions and another for "Disable Entity" actions.

To learn more about Timelines in Reality Composer Pro, watch Compose interactive 3D content in Reality Composer Pro.

Please accept this answer if it answers your question. Otherwise, follow up and I'll do my best to help you achieve stop motion!

Accepted Answer

Hi @denis_wo

I'm a big fan of stop motion! I'll go over a code and no code way to use the meshes to create a stop motion-like effect. Both approaches involve displaying each frame's mesh on an interval.

Here's how I recommend writing code to solve the problem:

  • Create a single entity.
  • On an interval, replace the entity's mesh with the mesh for the next frame.

Here's a snippet:

import SwiftUI
import RealityKit
struct ImmersiveView: View {
private let meshNames = ["box", "sphere", "cylinder"]
@State private var updateSubscription: EventSubscription?
var body: some View {
RealityView { content in
let animationDurationInSeconds = 3.0
var frameIndex = 0
let entity = ModelEntity(mesh: loadMeshForIndex(frameIndex)!, materials: [SimpleMaterial(color: .red, isMetallic: false)])
var timeSinceLastUpdate:TimeInterval = 0
let updateEveryNSeconds:TimeInterval = animationDurationInSeconds / Double(meshNames.count)
entity.position = [0, 1.2, -1]
content.add(entity)
updateSubscription = content.subscribe(to: SceneEvents.Update.self) { event in
timeSinceLastUpdate += event.deltaTime
if timeSinceLastUpdate >= updateEveryNSeconds {
frameIndex += 1
timeSinceLastUpdate = 0
guard let nextFrame = loadMeshForIndex(frameIndex) else {
updateSubscription?.cancel()
return
}
entity.model?.mesh = nextFrame
}
}
}
}
// This is a placeholder.
// Load the real mesh.
func loadMeshForIndex(_ index: Int) -> MeshResource? {
guard index >= 0 && index < meshNames.count else {
return nil
}
if meshNames[index] == "box" {
return MeshResource.generateBox(size: 0.1)
}
if meshNames[index] == "sphere" {
return MeshResource.generateSphere(radius: 0.05)
}
if meshNames[index] == "cylinder" {
return MeshResource.generateCylinder(height: 0.1, radius: 0.05)
}
return nil
}
}

For a more modular approach consider refactoring the code to use ECS. Create a custom StopMotionComponent with an array of mesh names and a custom StopMotionSystem to iterate through the mesh names and update the entity's active mesh.

Next, let's review the no code option. It's a bit cumbersome and ideal if your animation is only a few frames. If you have lots of mesh data you may run into performance issues since the approach requires you to keep lots of mesh data loaded.

  • In Reality Composer Pro, create an entity for each frame using the corresponding mesh.
  • Position the entities in the same place.
  • At the beginning of the timeline use a "Disable Entity" action to disable each of the entities. Then overtime, for each frame, enable the active frame's entity using an "Enable Entity" action. In parallel disable the previously enabled entity using a "Disable Entity" action. My timeline had 2 lanes, one for "Enable Entity" actions and another for "Disable Entity" actions.

To learn more about Timelines in Reality Composer Pro, watch Compose interactive 3D content in Reality Composer Pro.

Please accept this answer if it answers your question. Otherwise, follow up and I'll do my best to help you achieve stop motion!

Hello,

Thanks so much for the detailed explanation! I'm pretty new to Swift and have just started developing my first app for the Vision Pro, so I really appreciate your code snippet and the effort you put into explaining the options I have. I'll definitely try out the code approach since I have quite a bit of mesh data and performance issues are likely.

Hope you have a great day! Best, Denis

Converting a Stop Motion Animation to usdz
 
 
Q