Using a shader as a blend mode

Flash Player 10 and later, Adobe AIR 1.5 and later

Using a shader as a blend mode is like using other blend modes. The shader defines the appearance resulting from two display objects being blended together visually. To use a shader as a blend mode, assign your Shader object to the blendShader property of the foreground display object. Assigning a value other than null to the blendShader property automatically sets the display object’s blendMode property to BlendMode.SHADER. The following listing demonstrates using a shader as a blend mode. Note that this example assumes that there is a display object named foreground contained in the same parent on the display list as other display content, with foreground overlapping the other content:

foreground.blendShader = myShader;

When you use a shader as a blend mode, the shader must be defined with at least two inputs. As the example shows, you do not set the input values in your code. Instead, the two blended images are automatically used as shader inputs. The foreground image is set as the second image. (This is the display object to which the blend mode is applied.) A background image is created by taking the composite of all the pixels behind the foreground image’s bounding box. This background image is set as the first input image. If you use a shader that expects more than two inputs, you provide a value for any input beyond the first two.

The following example demonstrates using a shader as a blend mode. This example uses a lighten blend mode based on luminosity. The result of the blend is that the lightest pixel value from either of the blended objects becomes the pixel that’s displayed.

Note: The code for this example was written by Mario Klingemann. Thank you Mario for sharing this example. You can see more of Mario’s work and read his writing at www.quasimondo.com/.

The important ActionScript code is in these two methods:

  • init(): The init() method is called when the application loads. In this method the code loads the shader bytecode file.

  • onLoadComplete(): In the onLoadComplete() method the code creates the Shader object named shader. It then draws three objects. The first, backdrop, is a dark gray background behind the blended objects. The second, backgroundShape, is a green gradient ellipse. The third object, foregroundShape, is an orange gradient ellipse.

    The foregroundShape ellipse is the foreground object of the blend. The background image of the blend is formed by the part of backdrop and the part of backgroundShape that are overlapped by the foregroundShape object’s bounding box. The foregroundShape object is the front-most object in the display list. It partially overlaps backgroundShape and completely overlaps backdrop. Because of this overlap, without a blend mode applied, the orange ellipse (foregroundShape) shows completely and part of the green ellipse (backgroundShape) is hidden by it:

    However, with the blend mode applied, the brighter part of the green ellipse “shows through” because it is lighter than the portion of foregroundShape that overlaps it:

The following is the ActionScript code for this example. Use this class as the main application class for an ActionScript-only project in Flash Builder, or as the document class for the FLA file in Flash Professional:

package 
{ 
    import flash.display.BlendMode; 
    import flash.display.GradientType; 
    import flash.display.Graphics; 
    import flash.display.Shader; 
    import flash.display.Shape; 
    import flash.display.Sprite; 
    import flash.events.Event; 
    import flash.geom.Matrix; 
    import flash.net.URLLoader; 
    import flash.net.URLLoaderDataFormat; 
    import flash.net.URLRequest; 
     
    public class LumaLighten extends Sprite 
    { 
        private var shader:Shader; 
        private var loader:URLLoader; 
         
        public function LumaLighten() 
        { 
            init(); 
        } 
         
        private function init():void 
        { 
            loader = new URLLoader(); 
            loader.dataFormat = URLLoaderDataFormat.BINARY; 
            loader.addEventListener(Event.COMPLETE, onLoadComplete); 
            loader.load(new URLRequest("LumaLighten.pbj")); 
        } 
         
         
        private function onLoadComplete(event:Event):void 
        { 
            shader = new Shader(loader.data); 
             
            var backdrop:Shape = new Shape(); 
            var g0:Graphics = backdrop.graphics; 
            g0.beginFill(0x303030); 
            g0.drawRect(0, 0, 400, 200); 
            g0.endFill(); 
            addChild(backdrop); 
             
            var backgroundShape:Shape = new Shape(); 
            var g1:Graphics = backgroundShape.graphics; 
            var c1:Array = [0x336600, 0x80ff00]; 
            var a1:Array = [255, 255]; 
            var r1:Array = [100, 255]; 
            var m1:Matrix = new Matrix(); 
            m1.createGradientBox(300, 200); 
            g1.beginGradientFill(GradientType.LINEAR, c1, a1, r1, m1); 
            g1.drawEllipse(0, 0, 300, 200); 
            g1.endFill(); 
            addChild(backgroundShape); 
             
            var foregroundShape:Shape = new Shape(); 
            var g2:Graphics = foregroundShape.graphics; 
            var c2:Array = [0xff8000, 0x663300]; 
            var a2:Array = [255, 255]; 
            var r2:Array = [100, 255]; 
            var m2:Matrix = new Matrix(); 
            m2.createGradientBox(300, 200); 
            g2.beginGradientFill(GradientType.LINEAR, c2, a2, r2, m2); 
            g2.drawEllipse(100, 0, 300, 200); 
            g2.endFill(); 
            addChild(foregroundShape); 
             
            foregroundShape.blendShader = shader; 
            foregroundShape.blendMode = BlendMode.SHADER; 
        } 
    } 
}

The following is the source code for the LumaLighten shader kernel, used to create the “LumaLighten.pbj” Pixel Bender bytecode file:

<languageVersion : 1.0;> 
kernel LumaLighten 
< 
    namespace : "com.quasimondo.blendModes"; 
    vendor : "Quasimondo.com"; 
    version : 1; 
    description : "Luminance based lighten blend mode"; 
> 
{ 
    input image4 background; 
    input image4 foreground; 
 
    output pixel4 dst; 
     
    const float3 LUMA = float3(0.212671, 0.715160, 0.072169); 
 
    void evaluatePixel() 
    { 
        float4 a = sampleNearest(foreground, outCoord()); 
        float4 b = sampleNearest(background, outCoord()); 
        float luma_a = a.r * LUMA.r + a.g * LUMA.g + a.b * LUMA.b; 
        float luma_b = b.r * LUMA.r + b.g * LUMA.g + b.b * LUMA.b; 
         
        dst = luma_a > luma_b ? a : b; 
    } 
}

For more information on using blend modes, see Applying blending modes.

Note: When a Pixel Bender shader program is run as a blend in Flash Player or AIR, the sampling and outCoord() functions behave differently than in other contexts.In a blend, a sampling function will always return the current pixel being evaluated by the shader. You cannot, for example, use add an offset to outCoord() in order to sample a neighboring pixel. Likewise, if you use the outCoord() function outside a sampling function, its coordinates always evaluate to 0. You cannot, for example, use the position of a pixel to influence how the blended images are combined.