Search code examples
swiftuikitscenekitcore-imagecifilter

CIFilter can't be applied to SCNMaterial


Any CIFilter works fine when it's applied to UIImageView.

import UIKit
import CoreImage

@IBOutlet var imageView: UIImageView!
let ciBlurFilter = CIFilter(name: "CIGaussianBlur")!


func gaussianBlur() -> UIImage? {        
    let uiImage = UIImage(named: "texture.png")!
    let ciImage = CIImage(image: uiImage)
    ciBlurFilter.setValue(ciImage, forKey: "inputImage")
    let resultedImage = ciBlurFilter.value(forKey: "outputImage") as! CIImage
    let blurredImage = UIImage(ciImage: resultedImage)
    return blurredImage
}

override func viewDidLoad() {
    super.viewDidLoad()        
    imageView.image = self.gaussianBlur()
}

But it doesn't work if it's applied to SceneKit's material:

import SceneKit

@IBOutlet var sceneView: SCNView!
let ciBlurFilter = CIFilter(name: "CIGaussianBlur")!


func gaussianBlur() -> UIImage? {        
    let uiImage = UIImage(named: "texture.png")!
    let ciImage = CIImage(image: uiImage)
    ciBlurFilter.setValue(ciImage, forKey: "inputImage")
    let resultedImage = ciBlurFilter.value(forKey: "outputImage") as! CIImage
    let blurredImage = UIImage(ciImage: resultedImage)
    return blurredImage
}

override func viewDidLoad() {
    super.viewDidLoad()
    sceneView.scene = SCNScene()       
    let sphereNode = SCNNode(geometry: SCNSphere(radius: 0.1))
    sphereNode.geometry?.firstMaterial?.diffuse.contents = self.gaussianBlur()
    sceneView.scene?.rootNode.addChildNode(sphereNode)
}

Why SCNMaterial with CIFilter is invisible (although it supports UIImages)?

What's the matter?


Solution

  • The UIImage you create with that constructor is not actually rendered at that moment. The receiver of the image needs to know that the image needs to be rendered before use, which is seemingly not handled by SceneKit.

    Please see my answer here for details.

    Here's how you render the CIImage in Swift:

    // ideally you create this once and re-use it;
    // you should not create a new context for every draw call
    let ciContext = CIContext()
    
    let cgImage = ciContext.createCGImage(ciImage, from: ciImage.extent)
    let uiImage = cgImage.flatMap({ UIImage.init(cgImage: $0) })
    

    You can pass the CGImage to the material or wrap it into a UIImage, both should work.