How dynamic update the material texture in ShaderGraphMaterial?

I know that CustomMaterial in RealityKit can update texture by use DrawableQueue, but in new VisionOS, CustomMaterial doesn't work anymore. How i can do the same thing,does ShaderGraphMaterial can do?I can't find example about how to do that. Looking forward your repley, thank you!

Answered by DTS Engineer in 773726022

Hello,

Yes, you can update input texture resources of a ShaderGraphMaterial using a DrawableQueue, below is a self-contained snippet that demonstrates this.

Important: The particulars of when/where/how you request and render to the nextDrawable depend on the exact use-case in your app. This snippet uses a Timer and CIContext for convenience of demonstration only, it is not intended to demonstrate a best practice for the functionality that it demonstrates.

import SwiftUI
import RealityKit
import RealityKitContent
import Combine

struct ContentView: View {
    
    let drawableQueue = try! TextureResource.DrawableQueue(.init(pixelFormat: .bgra8Unorm, width: 256, height: 256, usage: [.renderTarget, .shaderRead, .shaderWrite], mipmapsMode: .none))
    
    let context = CIContext()
    
    @State private var cancellables = Set<AnyCancellable>()
    
    var body: some View {
        RealityView { content in
            
            do {
                var dynamicMaterial = try await ShaderGraphMaterial(named: "/Root/DynamicMaterial", from: "Scene.usda", in: realityKitContentBundle)
                
                let color = CIImage(color: .red).cropped(to: CGRect(origin: .zero, size: .init(width: 256, height: 256)))
                let image = context.createCGImage(color, from: color.extent)!
                
                let resource = try await TextureResource.generate(from: image, options: .init(semantic: .color))
                                
                resource.replace(withDrawables: drawableQueue)
                
                try dynamicMaterial.setParameter(name: "DiffuseColorImageInput", value: .textureResource(resource))
                
                let box = Entity()
                box.name = "box"
                
                box.components.set(ModelComponent(mesh: .generateBox(size: 0.1), materials: [dynamicMaterial]))
                
                content.add(box)
                
            } catch {
                fatalError(error.localizedDescription)
            }
        }.task {
            
            Timer.publish(every: 0.5, on: .main, in: .common).autoconnect().sink { output in
                let t = output.timeIntervalSince1970
                let red = abs(sin(t))
                
                let color = CIImage(color: .init(red: red, green: 0, blue: 0)).cropped(to: CGRect(origin: .zero, size: .init(width: 256, height: 256)))
                
                do {
                    let nextDrawable = try drawableQueue.nextDrawable()
                    
                    context.render(color, to: nextDrawable.texture, commandBuffer: nil, bounds: color.extent, colorSpace: CGColorSpace(name: CGColorSpace.displayP3)!)
                    
                    nextDrawable.present()
                } catch {
                    print(error.localizedDescription)
                }
                
            }.store(in: &cancellables)
        }
    }
}

And here is a screenshot of the Shader Graph in Reality Composer Pro:

Accepted Answer

Hello,

Yes, you can update input texture resources of a ShaderGraphMaterial using a DrawableQueue, below is a self-contained snippet that demonstrates this.

Important: The particulars of when/where/how you request and render to the nextDrawable depend on the exact use-case in your app. This snippet uses a Timer and CIContext for convenience of demonstration only, it is not intended to demonstrate a best practice for the functionality that it demonstrates.

import SwiftUI
import RealityKit
import RealityKitContent
import Combine

struct ContentView: View {
    
    let drawableQueue = try! TextureResource.DrawableQueue(.init(pixelFormat: .bgra8Unorm, width: 256, height: 256, usage: [.renderTarget, .shaderRead, .shaderWrite], mipmapsMode: .none))
    
    let context = CIContext()
    
    @State private var cancellables = Set<AnyCancellable>()
    
    var body: some View {
        RealityView { content in
            
            do {
                var dynamicMaterial = try await ShaderGraphMaterial(named: "/Root/DynamicMaterial", from: "Scene.usda", in: realityKitContentBundle)
                
                let color = CIImage(color: .red).cropped(to: CGRect(origin: .zero, size: .init(width: 256, height: 256)))
                let image = context.createCGImage(color, from: color.extent)!
                
                let resource = try await TextureResource.generate(from: image, options: .init(semantic: .color))
                                
                resource.replace(withDrawables: drawableQueue)
                
                try dynamicMaterial.setParameter(name: "DiffuseColorImageInput", value: .textureResource(resource))
                
                let box = Entity()
                box.name = "box"
                
                box.components.set(ModelComponent(mesh: .generateBox(size: 0.1), materials: [dynamicMaterial]))
                
                content.add(box)
                
            } catch {
                fatalError(error.localizedDescription)
            }
        }.task {
            
            Timer.publish(every: 0.5, on: .main, in: .common).autoconnect().sink { output in
                let t = output.timeIntervalSince1970
                let red = abs(sin(t))
                
                let color = CIImage(color: .init(red: red, green: 0, blue: 0)).cropped(to: CGRect(origin: .zero, size: .init(width: 256, height: 256)))
                
                do {
                    let nextDrawable = try drawableQueue.nextDrawable()
                    
                    context.render(color, to: nextDrawable.texture, commandBuffer: nil, bounds: color.extent, colorSpace: CGColorSpace(name: CGColorSpace.displayP3)!)
                    
                    nextDrawable.present()
                } catch {
                    print(error.localizedDescription)
                }
                
            }.store(in: &cancellables)
        }
    }
}

And here is a screenshot of the Shader Graph in Reality Composer Pro:

Thank you very much! I will try it.

if a video as ShaderGraphMaterial texture?

How dynamic update the material texture in ShaderGraphMaterial?
 
 
Q