Search code examples
actionscript-3flashshaderfragment-shaderstarling-framework

In Starling, how do you transform Filters to match the target Sprite's rotation & position?


Let's say your Starling display-list is as follows:

Stage
  |___MainApp
         |______Canvas (filter's target)

Then, you decide your MainApp should be rotated 90 degrees and offset a bit:

 mainApp.rotation = Math.PI * 0.5;
 mainApp.x = stage.stageWidth;

But all of a sudden, the filter keeps on applying itself to the target (canvas) in the angle it was originally (as if the MainApp was still at 0 degrees).

enter image description here

(notice in the GIF how the Blur's strong horizontal value continues to only apply horizontally although the parent object turned 90 degrees).

What would need to be changed to apply the filter to the target object before it gets it's parents transform? That way (I'm assuming) the filter's result would get transformed by the parent objects.

Any guess as to how this could be done?

https://github.com/bigp/StarlingShaderIssue

(PS: the filter I'm actually using is custom-made, but this BlurFilter example shows the same issue I'm having with the custom one. If there's any patching-up to do in the shader code, at least it wouldn't necessarily have to be done on the built-in BlurFilter specifically).


Solution

  • I solved this myself with numerous trial and error attempts over the course of several hours.

    Since I only needed the shader to run in either at 0 or 90 degrees (not actually tweened like the gif demo shown in the question), I created a shader with two specialized sets of AGAL instructions.

    Without going in too much details, the rotated version basically requires a few extra instructions to flip the x and y fields in the vertex and fragment shader (either by moving them with mov or directly calculating the mul or div result into the x or y field).

    For example, compare the 0 deg vertex shader...

    _vertexShader = [
        "m44 op, va0, vc0",     // 4x4 matrix transform to output space
        "mov posOriginal, va1",         // pass texture positions to fragment program
        "mul posScaled, va1, viewportScale",        // pass displacement positions (scaled)
    ].join("\n");
    

    ... with the 90 deg vertex shader:

    _vertexShader = [
        "m44 op, va0, vc0",     // 4x4 matrix transform to output space
        "mov posOriginal, va1",         // pass texture positions to fragment program
    
        //Calculate the rotated vertex "displacement" UVs
        "mov temp1, va1",
        "mov temp2, va1",
        "mul temp2.y, temp1.x, viewportScale.y", //Flip X to Y, and scale with viewport Y
        "mul temp2.x, temp1.y, viewportScale.x", //Flip Y to X, and scale with viewport X
        "sub temp2.y, 1.0, temp2.y", //Invert the UV for the Y axis.
        "mov posScaled, temp2",
    ].join("\n");
    

    You can ignore the special aliases in the AGAL example, they're essentially posOriginal = v0, posScaled = v1 variants and viewportScale = vc4constants, then I do a string-replace to change them back to their respective registers & fields ).

    Just a human-readable trick I use to avoid going insane. \☻/

    The part that I struggled with the most was calculating the correct scale to adjust the UV's scale (with proper detection to Stage / Viewport resize and render-texture size shifts).

    Eventually, this is what I came up with in the AS3 code:

    var pt:Texture = _passTexture,
        dt:RenderTexture = _displacement.texture,
        notReady:Boolean = pt == null,
        star:Starling = Starling.current;
    
    var finalScaleX:Number, viewRatioX:Number = star.viewPort.width / star.stage.stageWidth;
    var finalScaleY:Number, viewRatioY:Number = star.viewPort.height / star.stage.stageHeight;
    
    if (notReady) {
        finalScaleX = finalScaleY = 1.0;
    } else if (isRotated) {
        //NOTE: Notice how the native width is divided with height, instead of same side. Weird, but it works!
        finalScaleY = pt.nativeWidth / dt.nativeHeight / _imageRatio / paramScaleX / viewRatioX; //Eureka!
        finalScaleX = pt.nativeHeight / dt.nativeWidth / _imageRatio / paramScaleY / viewRatioY; //Eureka x2!
    } else {
        finalScaleX = pt.nativeWidth / dt.nativeWidth / _imageRatio / viewRatioX / paramScaleX;
        finalScaleY = pt.nativeHeight / dt.nativeHeight / _imageRatio / viewRatioY / paramScaleY;
    }
    

    Hopefully these extracted pieces of code can be helpful to others with similar shader issues.

    Good luck!