1
votes

I have hard time removing this banding from SceneKit.

Diffuse image is ok (added black background here to make contrast) (if you see a bit of banding here is because of the compression post upload)

enter image description here

has no banding, but this is the result in arkit (I occluded the camera to have a dark background)

enter image description here

Code is:

var bloomBackground             = UIImage(named: "diffuse_map_02")!.withRenderingMode(.alwaysTemplate)
bloomBackground                 = bloomBackground.maskWithColor(color: UIColor(hex: baseColorFullOpacity))
bNode.geometry?.firstMaterial?.diffuse.contents = bloomBackground

Am I missing any flag to be set to remove this banding problem?

1

1 Answers

0
votes

Solution I

Banding artifacts on gradients is quite common issue in computer graphics. To eliminate banding you usually need to use blur. Here's a code that helps you do it for SceneKit diffuse material:

import SceneKit

class ViewController: UIViewController {
    
    @IBOutlet var sceneView: SCNView!
    let ciContext = CIContext()
    
    fileprivate func gaussianBlur() -> UIImage? {
        
        let uiImage = UIImage(named: "art.scnassets/banding.png")!
        let ciImage = CIImage(image: uiImage)

        guard let ciBlurFilter = CIFilter(name: "CIGaussianBlur")
        else { return nil }

        ciBlurFilter.setValue(ciImage, forKey: "inputImage")

        let resultedImage = ciBlurFilter.value(forKey: "outputImage") as! CIImage
        var blurredImage = UIImage(ciImage: resultedImage)

        let cgImage = ciContext.createCGImage(resultedImage, 
                                        from: resultedImage.extent)
        blurredImage = cgImage.flatMap { UIImage(cgImage: $0) }!            
        return blurredImage
    }

    override func viewDidLoad() {
        super.viewDidLoad()        
        sceneView.scene = SCNScene()

        let sphereNode = SCNNode(geometry: SCNSphere(radius: 0.2))
        sphereNode.geometry?.firstMaterial?.diffuse.contents = gaussianBlur()
        sceneView.scene?.rootNode.addChildNode(sphereNode)
    }
}


Solution II

Banding artifacts isn't possible in generated source as 16-bit and 32-bit images (for instance .psd, .hdr, .tiff or .exr file formats). Regular .png or .jpg are 8-bit per channel.

Increasing a size of 8-bit image doesn't bring a positive result. That's because you still have 256 grey half-tones per channel. But if you use 16-bit .tiff you get 65536 steps of grey color per channel. It's 256 times more than in an 8-bit image.

However, let's see what Apple documentation says about it.

Although image objects support all platform-native image formats, it is recommended that you use PNG or JPEG files for most images in your app. Image objects are optimized for reading and displaying both formats, and those formats offer better performance than most other image formats. Because the PNG format is lossless, it is especially recommended for the images you use in your app’s interface.

So Apple tries to say us that using 16-bit and 32-bit files is possible but it smells like non-optimized way of development. In case you're planning to render too many 32-bit textures in SCNScene – be ready to get a freezed (unresponsive) view.

I personally tried using .hdr, .tiff and .exr file formats and it looks OK about them. Not 100% sure, but I think you could exploit 16-bit and 32-bit .psd files, however I suppose they must be flattened (to be a-single-layer) before importing them into Xcode project.


Solution III

You can build a CIFilter's CISmoothLinearGradient programmatically. This filter has four parameters:

  • inputPoint0 (CIVector)
  • inputPoint1 (CIVector)
  • inputColor0 (CIColor)
  • inputColor1 (CIColor)