swiftsprite-kitsktexture

Transparent pixel in SKMutableTexture


I am using a SKMutableTexture to render destructable ground in a game. For all intents and purposes, this works great, except for when I want to render a transparent pixel into a position in the MutableTexture where there is already a non-transparent pixel.

I have created a struct for RGBAColor like so:

struct RGBAColor {

    var r: UInt8
    var g: UInt8
    var b: UInt8
    var a: UInt8


    static var clear    = RGBAColor(r: 0, g: 0, b: 0, a: 0)
}

I then modify the SKMutableTexture like so:

self.texture.modifyPixelData({ (voidptr, len) in

            let bytes = voidptr!.assumingMemoryBound(to: RGBAColor.self)

            for (index, color) in self.modifiedPixels {
                bytes[index] = color
                self.pixels[index] = color
            }

        })

.. where "modifiedPixels" is a dictionary like [Int : RGBAColor] (in order not to update all pixels within the texture - only the modified ones.

It works as I intended when the color has an alpha (a) > 0 .. but not when it is 0 (it is like the pixel is rendered on top of the existing texture .. and not like you are modifying the actual pixel data).

Am I doing something wrong? Or is this the intended behaviour?


Solution

  • From the SKMutableTexture documentation,

    The color components you provide should have already been multiplied by the alpha value.

    Normally, if you wanted a color that is red with an alpha of 0.5, you would use RGBA components

    (red:1.0, green:0.0, blue:0.0, alpha:0.5)
    

    The equivalent for a premultiplied-alpha color is

    (red:0.5, green:0.0, blue:0.0, alpha:0.5)
    

    where the RGB components have been multiplied by the alpha value.

    For your specific case, you can add an initializer to the Color structure to perform the multiplication. Here's one way implement that...

    First, define an extension to scale a UInt8 by a Float value by

    extension UInt8 {
        func scaled(by value:Float) -> UInt8 {
            var scale = UInt(round(Float(self) * value))
            scale = Swift.min(scale, UInt(UInt8.max))
            return UInt8(scale)
        }
    }
    

    and then add an initializer that automatically multiples the components by the alpha value

    struct Color {
        var red:UInt8
        var green:UInt8
        var blue:UInt8
        var alpha:UInt8
    
        init(red:UInt8,green:UInt8,blue:UInt8,alpha:UInt8) {
            let alphaScale = Float(alpha) / Float(UInt8.max)
            self.red = red.scaled(by: alphaScale)
            self.blue = blue.scaled(by: alphaScale)
            self.green = green.scaled(by: alphaScale)
            self.alpha = alpha
        }
    }
    

    You can then create a color that is red with alpha = 0.5 by

    let color = Color(red:255, green:0, blue:0, alpha:127)
    

    and a completely transparent color with

    let color = Color(red:255, green:0, blue:0, alpha:0)