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!

Accepted Reply

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:

Add a Comment

Replies

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:

Add a Comment

Thank you very much! I will try it.