简体   繁体   中英

How to use alpha component in gradient colors with GKNoise and SKTexture?

I'm using GKNoise with a gradient map to generate color noise, getting a CGImage via SKTexture, on Mac OS. In particular I'm using a GKPerlinNoiseSource and setting two gradient colors, at values -1.0 and 1.0.

It works as expected if the two colors are opaque. If one or both colors has an alpha component less than 1.0, I expect the output to have transparency. However, it looks to me like the alpha is completely ignored in GKNoise's gradient color input (treated as if it's always 1.0) - or, if not ignored there, ignored in the SKTexture rendering of the image output.

I have found a couple of SO questions which reference limitations with SKTexture in other uses, including a possible workaround with SKView rendering, however that doesn't apply here because I'm only using an SKTexture instance to get a CGImage (not using SKView or otherwise using anything from SpriteKit) - and FWIW those questions focus on iOS. For reference:

I'm looking for ideas on how to make alpha components in the gradient colors work using GKNoise/SKTexture.

Below is the test output image, and the code that reproduces it. In the view, both CGImages draw identically; I expect the one drawn with the red alpha=0.5 to be darker in the red parts when the background is black, lighter when it's white, etc.

文本代码图像输出截图

import Foundation
import GameplayKit

class GKNoiseGradientIssue {
    
    var redColor_opaque = NSColor(red: 1.0, green: 0.0, blue: 0.0, alpha: 1.0)
    var redColor_halfAlpha = NSColor(red: 1.0, green: 0.0, blue: 0.0, alpha: 0.5)
    
    var blueColor_opaque = NSColor(red: 0.0, green: 0.0, blue: 1.0, alpha: 1.0)
    
    var opaqueImage: CGImage
    var halfAlphaImage: CGImage
    
    init() {
        let noiseSource = GKPerlinNoiseSource(frequency: 0.15, 
                                               octaveCount: 7, 
                                               persistence: 1.25, 
                                               lacunarity: 0.5, 
                                               seed: 12345)
    
        let opaqueGradient: [NSNumber: NSColor] = [-1.0: redColor_opaque, 1.0: blueColor_opaque]
        let opaqueNoise = GKNoise(noiseSource, gradientColors: opaqueGradient)
        let opaqueNoiseMap = GKNoiseMap(opaqueNoise,
                                         size: [200.0, 200.0],
                                         origin: [0.0, 0.0],
                                         sampleCount: [200, 200],
                                         seamless: true)
        let opaqueTexture = SKTexture(noiseMap: opaqueNoiseMap)
        self.opaqueImage = opaqueTexture.cgImage()
        
        
        let halfAlphaGradient: [NSNumber: NSColor] = [-1.0: redColor_halfAlpha, 1.0: blueColor_opaque]
        let halfAlphaNoise = GKNoise(noiseSource, gradientColors: halfAlphaGradient)
        let halfAlphaNoiseMap = GKNoiseMap(halfAlphaNoise,
                                                size: [200.0, 200.0],
                                                origin: [0.0, 0.0],
                                                sampleCount: [200, 200],
                                                seamless: true)
        let halfAlphaTexture = SKTexture(noiseMap: halfAlphaNoiseMap)
        self.halfAlphaImage = halfAlphaTexture.cgImage()
    }
}

class GradientIssueView: NSView {
    var issue: GKNoiseGradientIssue?
    
    override func awakeFromNib() {
        self.issue = GKNoiseGradientIssue()
    }
    
    override func draw(_ dirtyRect: NSRect) {
        NSColor.black.setFill()
        self.bounds.fill()
        
        if let cgc = NSGraphicsContext.current?.cgContext {
            cgc.draw(self.issue!.opaqueImage, 
                     in: CGRect(origin: CGPoint(x: 10.0, y: 10.0), 
                                size: CGSize(width: 200.0, height: 200.0)))
            
            cgc.draw(self.issue!.halfAlphaImage, 
            in: CGRect(origin: CGPoint(x: 10.0, y: 220.0), 
                       size: CGSize(width: 200.0, height: 200.0)))
        }
    }
}

Just following up here, for anyone else who might encounter this... While I never got a definitive reply from Apple Developer forums I did come to the conclusion that SKTexture + GKNoise only supports opaque colors, so the question as asked has no answer: one can't do this with SKTexture. Instead, I updated my approach to just use GKNoiseMap directly and calculate my own gradients.

An advantage of this approach is that I can also now create gradients from a noise source using more than two colors. I also have more options for creatively skewing or clamping the noise values before they're converted to colors.

To use GKNoiseMap directly instead of with the two-color gradients and SKTexture approach, create the noise with no reference to gradient colors, and create the GKNoiseMap the same way:

let myNoise = GKNoise(noiseSource)
let noiseMap = GKNoiseMap(myNoise, 
                          size: [myModelWidth, myModelHeight],
                          origin: [myX, myY],
                          sampleCount: [myPointWidth, myPointHeight],
                          seamless: true)

Then when rendering, just call the noiseMap value() method to get the noise value for each point in your view or image output, and then calculate your own color gradient for that point including the alpha (if desired). Note that voronoi noise will be between 0.0 and 1.0 while other perlin-based noise will be between -1.0 and 1.0; to simplify here I'll assume one just wants a gradient between two colors at 0.0 and 1.0.

Given two colors in.deviceRGB color space (rgbColor1 and rgbColor2), and the noise value unitNoiseValue as a CGFloat,

let r_range = rgbColor2.redComponent - rgbColor1.redComponent
let g_range = rgbColor2.greenComponent - rgbColor1.greenComponent
let b_range = rgbColor2.blueComponent - rgbColor1.blueComponent
let a_range = rgbColor2.alphaComponent - rgbColor1.alphaComponent
        
let r = noiseUnitValue * r_range + rgbColor1.redComponent
let g = noiseUnitValue * g_range + rgbColor1.greenComponent
let b = noiseUnitValue * b_range + rgbColor1.blueComponent
let a = noiseUnitValue * a_range + rgbColor2.alphaComponent
        
return NSColor(red: r, green: g, blue: b, alpha: a)

In order to make this performant it may be helpful to limit the discrete levels of each gradient (eg 256 levels), and to cache calculated colors for large images and store in a hash that can be looked up quickly, and similar techniques. Also, because the conversion here just maps a unit value, you can have other skewing of your noise value if desired before calculation of the gradient color, such as additional fade or normalization or other 'shaping' of the input value.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM