Pass animatable custom parameters to metal shader in RealityKit 2

Hi, In SceneKit I pass custom parameters to a metal shader using a SCNAnimation, for example:

let revealAnimation = CABasicAnimation(keyPath: "revealage")
revealAnimation.duration =  duration
revealAnimation.toValue = toValue
let scnRevealAnimation = SCNAnimation(caAnimation: revealAnimation)
material.addAnimation(scnRevealAnimation, forKey: "Reveal")

How would I do similar to a metal shader in RealityKit?

I saw in the Octopus example: //int(params.uniforms().custom_parameter()[0])

But it's commented out and there is no example how to set the custom variable and animate it? (unless I missed it)

Great session BTW

Thanks

Replies

Hi! You are able to set a custom float4 and/or texture by setting material.custom.texture or material.custom.value on your custom material. If you plan to animate these properties, please make sure that you reassign the CustomMaterial to the ModelEntity you are rendering.

I'm not able to get my changes to material.custom.value to change the behavior of the shader. I'm able to set an initial value, and the shader uses that, but any changes are ignored. I reassigned the CustomMaterial to the ModelComponent as you suggested, and even reassigned the ModelComponent to the ModelEntity, but no effect. The callback changing the value is definitely getting called and setting the value. Help!

My Swift code:

import SwiftUI
import RealityKit
import Combine

// A class with a per-frame callback to change a custom uniform for the geometry shader
class TestData {
  var entity: ModelEntity? = nil
  var model: ModelComponent? = nil
  var material: CustomMaterial? = nil
  var theta = Float(0)
   
  func SetupCallback( _ arView: ARView) {
    _ = arView.scene.subscribe(to: SceneEvents.Update.self) { _ in
      // *** This value does get changed, but it's not reflected in the geometry shader
      self.material!.custom.value = simd_float4( 0.5 * sin( self.theta ), 0, 0, 0 )
      self.model?.materials = [self.material!]
      self.entity?.model = self.model!
//      print("x offset = ", self.material!.custom.value[0])
      self.theta += 0.05
    }
  }
}

struct ContentView : View {
  var body: some View {
    return ARViewContainer().edgesIgnoringSafeArea(.all)
  }
}

struct ARViewContainer: UIViewRepresentable {
   
  let testData = TestData()
   
  func makeUIView(context: Context) -> ARView {
    let arView = ARView(frame: .zero)

    // Create a simple box
    let box = MeshResource.generateBox(size: 0.3)
     
    // Create a custom materal with a Geometry shader -
    let mtlLibrary = MTLCreateSystemDefaultDevice()!.makeDefaultLibrary()!
    let geometryShader = CustomMaterial.GeometryModifier(
      named: "moveVertices", in: mtlLibrary
    )
    testData.material = try! CustomMaterial(
      from: SimpleMaterial(color: .orange, isMetallic: false),
      geometryModifier: geometryShader )
    // *** This initial value does make it to the Geometry shader
    testData.material!.custom.value = simd_float4(0.5, 0, 0, 0)
     
    // Create an entity with the box and custom material with geometry modifier
//    let entity = ModelEntity(mesh: box, materials: [testData.material!])
    testData.model = ModelComponent(mesh: box, materials: [testData.material!])
    testData.entity = ModelEntity()
    testData.entity!.model = testData.model!
    // Create an anchor in front of us and add the box to it
    let originAnchor = AnchorEntity(world: simd_float3( 0, -1, -1 ))
    originAnchor.addChild(testData.entity!)
    arView.scene.anchors.append(originAnchor)

    // Set up the per-frame callback subscription
    testData.SetupCallback( arView )

    return arView
  }
   
  func updateUIView(_ uiView: ARView, context: Context) {}
}

#if DEBUG
struct ContentView_Previews : PreviewProvider {
  static var previews: some View {
    ContentView()
  }
}
#endif

and my GeometryModifier:

#include <metal_stdlib>
#include <RealityKit/RealityKit.h>
using namespace metal;

[[visible]]
void moveVertices(realitykit::geometry_parameters params)
{
  // Interpret custom_parameter as a position offset
  float4 posOffset = float4( params.uniforms().custom_parameter());
  params.geometry().set_model_position_offset(float3(posOffset));
}