wcochran
wcochran

Reputation: 10896

How to animate custom material property in RealityKit?

I can successfully load a custom ShaderGraphMaterial material loaded from a .usda file created with Reality Composer

var shotTrailMaterial : ShaderGraphMaterial? = nil
do {
   ...
   let materialData = try Data(contentsOf: materialURL)
   shotTrailMaterial = try await ShaderGraphMaterial(named:"/Root/Material", 
                                                      from: materialData)
} catch { ... }

for which I can set the "fractionOpaque" custom property to a floating point value

try shotTrailMaterial.setParameter(name: "fractionOpaque", value: .float(0.9))

The above works well for the entities I create with this material. Now I would like to animate the material based on this parameter but the following fails:

let anim = FromToByAnimation<Float>(name: "foo",
                                    from: 0.0,
                                      to: 1.0,
                                duration: 3.0,
                              bindTarget: .parameter("fractionOpaque"))
if let animResource = try? AnimationResource.generate(with: anim) {
    entity.playAnimation(animResource, transitionDuration: 3.0, startsPaused: false)
}

I get the error

Cannot find a BindPoint for any bind path: "KeyValue.keyValueStore[fractionOpaque]"

Clearly my key path is wrong for the bindTarget: parameter. I want to set the "fractionOpaque" property on material 0, but I am not sure what the correct path for this is? This is a Vision OS app.

Upvotes: 3

Views: 659

Answers (1)

Andy Jazz
Andy Jazz

Reputation: 58553

Quite possible that someone will find another way to animate the MaterialX parameters (loaded from Reality Composer Pro) in visionOS RealityKit app, but this one is the only way I have found so far.

enter image description here

import SwiftUI
import RealityKit
import RealityKitContent

struct ContentView: View {
    @State var amount: CGFloat = 0.0
    @State var timer: Timer? = nil
    
    var body: some View {
        RealityView { content in
            if var material = try? await ShaderGraphMaterial(named: "/Root/MetallicGoldPULeather",
                                                              from: "Scene.usda",
                                                                in: realityKitContentBundle) {
                
                try! material.setParameter(name: "Basecolor_Tint", 
                                          value: .color(.red))
                
                let sphere = ModelEntity(mesh: .generateSphere(radius: 0.4), 
                                    materials: [material])
                
                content.add(sphere)
            }
        } update: { content in
            if let entity = content.entities.first as? ModelEntity,
               var material = entity.model?.materials.first as? ShaderGraphMaterial {
                Task {
                    self.timer = Timer.scheduledTimer(withTimeInterval: 0.02, 
                                                              repeats: true) { _ in
                        Task { @MainActor in
                            if amount <= 1.0 {
                                self.amount += 0.01
                                
                                try! material.setParameter(name: "Basecolor_Tint",
                                                          value: .color(.init(red: 1,
                                                                            green: amount,
                                                                             blue: amount,
                                                                            alpha: 1)))
                                
                                print(amount)
                                entity.model?.materials = [material]
                            }
                        }
                    }
                    self.timer?.fire()

                    // destroying
                    try await Task.sleep(nanoseconds: 3_000_000_000)
                    self.timer?.invalidate()
                    self.timer = nil
                    print("Timer is:", (self.timer?.isValid) as Any)
                }
            }
        }
    }
}

I load MaterialX from an RCP's cone scene and assign that material to a sphere primitive.

enter image description here


enter image description here

Upvotes: 4

Related Questions