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)
}
}
}
}
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)
}
}
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)
.
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)
}
}