Tutorial: Multi-Pass Shaders

Tutorial: Multi-Pass Shaders

Another new feature in Corona’s updated graphics engine is the ability to define a graph of effects which can be applied to a display object. We have created a data-driven file format which allows you to extend Graphics 2.0 by connecting existing shaders to other shaders. Once configured, these will be executed as a multi-pass effect on a display object.

In fact, several of the effects we provide utilize this feature already. A simple example is the  “blurGaussian” effect which works in two phases: horizontal and vertical. This shows how a simple multi-pass shader works on a single texture to blur an image.

Creating a New Multi-Pass Effect

To create a new multi-pass effect that leverages existing shaders, we can create a separate Lua file that represents the new effect:


In this file, the first two lines of code are Corona-specific lines which instruct the SDK to treat this file as a shader. They should be set like this:

local kernel = {}
kernel.language = "glsl"

The next line added for the shader effect defines the shader category. We have defined the following categories for shaders: filter and composite.

kernel.category = "filter"

When defining a shader, the category should be set to either “filter” or “composite” based on whether the effect works off of a single texture or multiple textures.

  • “filter” — this corresponds to a single texture effect which uses 1 paint channel for input and produces 1 output.
  • “composite” — this corresponds to a multi-texture effect, using 2 paint channels for input while producing 1 output.

The next line defines the effect name. This must be a unique name:

kernel.name = "exampleBlurGaussian"

Next, we declare the graph definition of the effect — this is the substance of a multi-pass shader effect. This graph includes a nodes table and a final output.

kernel.graph =
nodes = {
horizontal = { effect="filter.blurHorizontal", input1="paint1" },
vertical = { effect="filter.blurVertical", input1="horizontal" },
output = "vertical",

The nodes table expects a set of tables with user-defined keys for each effect in the graph. In the first line, we tell Corona to load an existing shader effect called “filter.blurHorizontal”, internally name it “horizontal”, and set the input of that effect to “paint1”.

horizontal = { effect="filter.blurHorizontal", input1="paint1" },

For input1, “paint1” refers to the display object’s paint fill channel. For composite paint objects, “paint2” is also a valid input.

To tell Corona that this effect’s output should be the input to a vertical blur effect, we define the next line:

vertical = { effect="filter.blurVertical", input1="horizontal" },

In this line, notice that input1 is set to the key “horizontal”; this references the previous effect.

Finally, we set output to the last effect in the chain.

output = "vertical",

This tells Corona that the output from the vertical blur pass is the final output. It’s important to note that each effect can operate on an input1 and input2 channel for more complex multi-textured / multi-pass effects. For the sake of simplicity, this example uses just input1.

Loading the New Effect

From main.lua, we can load the effect and apply it to a display object via the following code. The first line simply requires the module like any standard Lua module. The second line loads this effect into the graphics engine.

local effect = require( "kernel_filter_example_blur_gaussian_gl" )
graphics.defineEffect( effect )

Once the filter has been defined, we can reference it by the name previously defined as kernel.name and apply it to an image:

local image1 = display.newImage( image_name )
image1.fill.effect = "filter.exampleBlurGaussian"

Accessing parameter values for each node in the shader is done by referencing the key name for the effect. In this sample, recall that we defined our vertical pass as “vertical” and horizontal as “horizontal”. Thus, to set the blurSize parameter on the blurGaussian horizontal and vertical component, code the following:

image1.fill.effect.horizontal.blurSize = 8
image1.fill.effect.vertical.blurSize = 8

And the final result, as you may imagine, is a standard gaussian blur on the image:

gaussian-before gaussian-after

Countless Possibilities

Other interesting filter effects can be achieved in the same manner, like a blur + sepia effect on a landscape photo. Feel free to experiment with the documented Effects — the new engine provides over 25 Photoshop-style composite effects and over 50 filter and generator effects. For more on this topic, please see the Fills and Strokes tutorial.

mtn-normal mtn-blur mtn-sepia

In Summary

As you can see, it’s possible to create complex and sophisticated filter effects with Graphics 2.0. Developers should, of course, understand potential performance implications of multi-pass effects. Also, please note this feature is still in beta and is subject to format/method changes.

  • Chris
    Posted at 00:42h, 18 October

    That’s fantastic!
    Thanks for the information!

  • Dave Baxter
    Posted at 01:15h, 18 October

    If I then saved that image, would it save with the effect or is it a visual thing ?


  • Bryan
    Posted at 10:53h, 18 October

    Yes, if you call display.capture, it currently saves the image with the effect applied.

  • Chris
    Posted at 01:15h, 08 November

    One question:
    Is there a way to mimic Photoshops Threshold-Effect?
    Using the Gaussian Blur on a Particle Group with small physical bodies and then using Threshold and a nice Texture, we could make some awesome Liquid/Fluid-Simulations 😉

  • scape
    Posted at 14:04h, 23 November

    any way to write custom shaders? or is it possible to use a mask to for shaders? i want to use shader as full screen effect (using snapshot) but apply shader only to part of the screen

  • Evgeniy
    Posted at 02:55h, 06 May

    for those who copy-paste this example and it doesn’t work, found on forum:
    image1.fill.effect = “filter.exampleBlurGaussian”
    image1.fill.effect = “filter.custom.exampleBlurGaussian”