Search code examples
swiftrealitykitreality-composer

RealityKit – How to make an Entity and its PBR materials transparent?


I have an Entity called "Table" that represents a table with a sticker on it. This Entity has Model Entities as children (leg1, leg2, leg3, leg4, plank and sticker). Each ModelEntity has a PBR texture applied to its materials ('lightWood' for the legs, darkWood for the plank and an image for the sticker placed on top of the table).

I would like to create a 'ghost' / transparent version of the Table entity to show before the user places it in the scene.

Is there a simple way to set the opacity of materials before or after they are created?

Is what I am trying possible without having to create a Metal shader (which I don't know how to do)?

Material for the Sticker Model Entity

    // Setup image material
    var pictureMaterial = PhysicallyBasedMaterial()
    var imageTexture: TextureResource? = nil

    if let cgImage = image?.cgImage,
        let resource = try? TextureResource.generate(from: cgImage, options: TextureResource.CreateOptions.init(semantic: nil)) {
        imageTexture = resource
    }
        
    let baseColor = MaterialParameters.Texture(imageTexture ?? StickerEntity.defaultImage)
    pictureMaterial.baseColor = PhysicallyBasedMaterial.BaseColor(texture:baseColor)


self.stickerEntity.components[ModelComponent.self] =
        ModelComponent(mesh: .generateBox(width: width!,
                                          height: self.pictureDepth,
                                          depth: height!,
                                          cornerRadius: 0,
                                          splitFaces: true),
                       materials: [canvasMaterial,
                                       pictureMaterial,
                                       canvasMaterial,
                                       canvasMaterial,
                                       canvasMaterial,
                                       canvasMaterial
                                      ])

Function I use to create PBR materials for the legs and plank

static func generateMaterialFromImages(uiColor: UIColor? = nil,
                                           baseColor: String? = nil,
                                           normal: String? = nil,
                                           roughness: String? = nil,
                                           ambientOcclusion: String? = nil,
                                           metallic: String? = nil,
                                           clearcoat: String? = nil,
                                           emissiveColor: String? = nil) -> PhysicallyBasedMaterial {
        
        var material = PhysicallyBasedMaterial()
        
        if let color = uiColor {
            material.baseColor = PhysicallyBasedMaterial.BaseColor(tint: color)
            
        } else if let resourceFileName = baseColor, let resource = try? TextureResource.load(named: resourceFileName) {
            let baseColor = MaterialParameters.Texture(resource)
            material.baseColor = PhysicallyBasedMaterial.BaseColor(texture: baseColor)
        }
        
        if let resourceFileName = normal, let resource = try? TextureResource.load(named: resourceFileName) {
            let normal = MaterialParameters.Texture(resource)
            material.normal = PhysicallyBasedMaterial.Normal(texture:normal)
        }
        if let resourceFileName = roughness, let resource = try? TextureResource.load(named: resourceFileName) {
            let roughness = MaterialParameters.Texture(resource)
            material.roughness = PhysicallyBasedMaterial.Roughness(texture:roughness)
        }
        if let resourceFileName = ambientOcclusion, let resource = try? TextureResource.load(named: resourceFileName) {
            let ambientOcclusion = MaterialParameters.Texture(resource)
            material.ambientOcclusion = PhysicallyBasedMaterial.AmbientOcclusion(texture:ambientOcclusion)
        }
        if let resourceFileName = metallic, let resource = try? TextureResource.load(named: resourceFileName) {
            let metallic = MaterialParameters.Texture(resource)
            material.metallic = PhysicallyBasedMaterial.Metallic(texture:metallic)
        }
        if let resourceFileName = clearcoat, let resource = try? TextureResource.load(named: resourceFileName) {
            let clearcoat = MaterialParameters.Texture(resource)
            material.clearcoat = PhysicallyBasedMaterial.Clearcoat(texture:clearcoat)
        }
        if let resourceFileName = emissiveColor, let resource = try? TextureResource.load(named: resourceFileName) {
            let emissiveColor = MaterialParameters.Texture(resource)
            material.emissiveColor = PhysicallyBasedMaterial.EmissiveColor(texture:emissiveColor)
        }
        
        return material
    }  

I have looked for documentation or functions that would allow to change the opacity of materials but couldn't find any documentation on the matter.


Solution

  • Setting opacity of PhysicallyBasedMaterial

    I used usdz table model found in Reality Composer's Furnishing section (iconic look).

    Here's how I changed an opacity of PBR material.

    import UIKit
    import RealityKit
    
    class ViewController : UIViewController {
        
        @IBOutlet var arView: ARView!
        
        override func viewDidLoad() {
            super.viewDidLoad()
            
            let tableScene = try! Experience.loadTable()
            
            if let table = tableScene.findEntity(named: "table_end_base_iconic_lod0") 
                                                                  as? ModelEntity {
    
                table.model?.materials[0] = semiTransparentShader(0.5)
    
                table.setPosition([0,-0.4, 0], relativeTo: nil)
            }
    
            arView.scene.anchors.append(tableScene)
        }
        
        func semiTransparentShader(_ value: Float) -> Material {
    
            var material = PhysicallyBasedMaterial()
            material.baseColor.texture = try! .init(.load(named: "image", in: nil))
            material.blending = .transparent(opacity: .init(floatLiteral: value))
    
            return material
        }
    }
    

    enter image description here


    P. S.

    In visionOS, to create a semi-transparent material has become even easier than before:

    table.components[OpacityComponent.self] = .init(opacity: 0.5)