Search code examples
swiftuirealitykitvisionosreality-composer-pro

RealityKit - Change Material Color or other properties in RealityView


In a RealityView, I have scene loaded from Reality Composer Pro. The entity I'm interacting with has a PhysicallyBasedMaterial with a diffuse color. I want to change that color when on long press. I can get the entity and even get a reference to the material, but I can't seem to change anything about it. What is the best way to change the color of a material at runtime?

    var longPress: some Gesture {
        LongPressGesture(minimumDuration: 0.5)
            .targetedToAnyEntity()
            .onEnded { value in
                value.entity.position.y = value.entity.position.y + 0.01
                if var shadow = value.entity.components[GroundingShadowComponent.self] {
                    shadow.castsShadow = true
                    value.entity.components.set(shadow)
                }
                if let model = value.entity.components[ModelComponent.self] {
                    print("material", model)
                    if let mat = model.materials.first {
                        print("material", mat)
                        // I have a material here but I can't set any properties?
                        // mat.diffuseColor  does not exist
                    }
                }

            }
    }

Here is the full code

struct Lab5026: View {

    var body: some View {
        RealityView { content in

            if let root = try? await Entity(named: "GestureLab", in: realityKitContentBundle) {
                root.position = [0, -0.45, 0]

                if let subject = root.findEntity(named: "Cube") {
                    subject.components.set(HoverEffectComponent())
                    subject.components.set(GroundingShadowComponent(castsShadow: false))
                }

                content.add(root)
            }

        }
        .gesture(longPress.sequenced(before: dragGesture))

    }

    var longPress: some Gesture {
        LongPressGesture(minimumDuration: 0.5)
            .targetedToAnyEntity()
            .onEnded { value in
                value.entity.position.y = value.entity.position.y + 0.01
                if var shadow = value.entity.components[GroundingShadowComponent.self] {
                    shadow.castsShadow = true
                    value.entity.components.set(shadow)
                }
                if let model = value.entity.components[ModelComponent.self] {
                    print("material", model)
                    if let mat = model.materials.first {
                        print("material", mat)
                        // I have a material here but I can't set any properties?
                        // mat.diffuseColor  does not exist
//                        PhysicallyBasedMaterial
                    }
                }

            }
    }

    var dragGesture: some Gesture {
        DragGesture()
            .targetedToAnyEntity()
            .onChanged { value in

                let newPostion = value.convert(value.location3D, from: .global, to: value.entity.parent!)

                let limit: Float = 0.175
                value.entity.position.x = min(max(newPostion.x, -limit), limit)
                value.entity.position.z = min(max(newPostion.z, -limit), limit)

            }
            .onEnded { value in
                value.entity.position.y = value.entity.position.y - 0.01

                if var shadow = value.entity.components[GroundingShadowComponent.self] {
                    shadow.castsShadow = false
                     value.entity.components.set(shadow)
                }

            }
    }
}

Solution

  • Changing a Color of a PBR Material

    In order to reassign the color of a physically based material applied in Reality Composer Pro you'll need a type casting. Also, I've inserted a simple print() method in RealityView's update closure to update a state. You can use a boolean instead. Here is the code:

    import SwiftUI
    import RealityKit
    import RealityKitContent
    
    struct ContentView : View {
        @State var color: UIColor = .systemGreen
        
        var longPressGesture: some Gesture {
            LongPressGesture(minimumDuration: 1.0)
                .targetedToAnyEntity()
                .onEnded { _ in
                    self.color = .systemPurple
                    
                    Task { @MainActor in
                        try await Task.sleep(nanoseconds: UInt64(1.5e9))
                        self.color = .systemGreen
                    }
                }
        }
        var body: some View {
            RealityView { rvc in
                let scene = try! await Entity(named: "Scene", in: rkcb)
                let ball = scene.findEntity(named: "Sphere") as! ModelEntity
                ball.position = [0.0, 0.5,-2.0]
                ball.generateCollisionShapes(recursive: true)
                ball.components.set(InputTargetComponent())
                
                var mat = ball.model?.materials.first as! PhysicallyBasedMaterial
                mat.baseColor.tint = color
                ball.model?.materials[0] = mat
                rvc.add(scene)
                
            } update: { rvc in
                // Insert a simple print() method to help updating a state
                print(color.accessibilityName)
                
                if let sph = rvc.entities.first?.scene?.findEntity(named: "Sphere") as? ModelEntity {
                    var m = sph.model?.materials.first as! PhysicallyBasedMaterial
                    m.baseColor.tint = color
                    sph.model?.materials[0] = m
                }
            }
            .gesture(longPressGesture)
        }
    }
    

    enter image description here

    Changing a Color of a MaterialX

    In order to change the color of a model created in Reality Composer Pro with the MaterialX shader, you'll need to follow the same way. And remember that you need a control node in the ShaderGraph. In this case, that node controls the parameter of type Color3 (Float).

    enter image description here

    struct ContentView: View {
        @State var color: UIColor = .systemPink
        
        var longPressGesture: some Gesture {
            LongPressGesture(minimumDuration: 1.0)
                .targetedToAnyEntity()
                .onEnded { _ in
                    self.color = .systemCyan
                    
                    Task {
                        try await Task.sleep(nanoseconds: UInt64(1.5e9))
                        self.color = .systemPink
                    }
                }
        }
        var body: some View {
            RealityView { rvc in
                var matX = try! await ShaderGraphMaterial(named: "/Root/GridMaterial",
                                                           from: "Scene.usda",
                                                             in: rkcb)
                try! matX.setParameter(name: "Color", value: .color(color))
                
                let cone = ModelEntity(mesh: .generateCone(height: 1.0, radius: 0.2))
                cone.position = [0.0, 0.5,-2.0]
                cone.model?.materials = [matX]
                cone.generateCollisionShapes(recursive: false)
                cone.components.set(InputTargetComponent())
                rvc.add(cone)
                
            } update: { rvc in
                if let cone = rvc.entities.first as? ModelEntity {
                    var m = cone.model?.materials.first as? ShaderGraphMaterial
                    try! m?.setParameter(name: "Color", value: .color(color))
                    cone.model?.materials[0] = m!
                }
            }
            .gesture(longPressGesture)
        }
    }
    

    enter image description here