BlendMode & GradientFill problems on native

I’m trying to make basic lights work in my top-down 2D game (Mac, native, OpenFL 4.3.1). I have following layers (Sprites) where I draw things:

  • FxLayer:Sprite (BlendMode.MULTIPLY)
  • Light:Shape (BlendMode.NORMAL)
  • Shade:Shape (BlendMode.NORMAL)
  • other layers (BlendMode.NORMAL)

Expected result:

Result I’ve got:

When using BlendMode.NORMAL on all layers, results look identical:

Code:

   ...
        shade.graphics.beginFill(0x888888);
        shade.graphics.drawRect(0, 0, 500, 500);
        shade.graphics.endFill();
        shade.blendMode = BlendMode.NORMAL;

        var mtx:Matrix = new Matrix();
        mtx.createGradientBox(100, 100, 0, 0, 0);

        light.graphics.beginGradientFill(GradientType.RADIAL, [0xffffff, 0xffffff], [1,0], [0, 255], mtx);
        light.graphics.drawRect(0, 0, 100, 100);
        light.graphics.endFill();
        light.blendMode = BlendMode.NORMAL;

        fxLayer.addChild(shade);
        fxLayer.addChild(light);
        fxLayer.blendMode = BlendMode.MULTIPLY;
   ...

If you have any ideas what am I doing wrong, please let me know. Thanks.

EDIT:
I kind of fixed it by reversing the gradient from

light.graphics.beginGradientFill(GradientType.RADIAL, [0xffffff, 0xffffff], [1,0], [0, 255], mtx);

to

light.graphics.beginGradientFill(GradientType.RADIAL, [0xffffff, 0xffffff], [0,1], [0, 255], mtx);

which looks better, but it still looks like it first multiplies with shade and then with light:

I need to merge shade and light and multiply other layers with this merged layer, i.e. centre of the light should have same lightness as left side of the image (without shade).

We are using OpenGL, so things may not be exact.

I noticed that despite my intention to use premultiplied alpha when blending, this was not used as recently as 4.3.1 (surfaces were premultiplied, but the blend mode and shader “un”-premultiplied them)

You may have better results on OpenFL 4.4.1 and Lime 3.4.1

Also, I’d be happy if you wanted to look into our blending equation for MULTIPLY, we’re in GL, so there may not be a perfect substitute, but here’s our current code:

https://github.com/openfl/openfl/blob/develop/openfl/_internal/renderer/opengl/GLBlendModeManager.hx#L43-L44

I’m reading here that perhaps we want gl.DST_COLOR and gl.ZERO

https://www.opengl.org/discussion_boards/showthread.php/136221-in-dx-its-called-multiply-blend-mode-and-in-ogl

Worth the experimentation?

Thanks for info, I’ll experiment a bit and post results.

Well now it works like it is supposed to, but I still cannot figure out how to get the result I need.

When I have a parent sprite with two child sprites – first is shade (dark colour) and second light (white colour) and then use BlendMode.MULTIPLY on parent sprite, the result looks like it first multiplies scene with first child and then with second child. What I need is to multiply scene with merged child 1 and child 2, i.e. merge shade with lights in normal mode and only after that multiply scene with this merged result.

When I draw everything to graphics in single sprite, it works as I need it to. But as I need to redraw it every frame, because lights are moving, I’ve got 30 FPS drop which is unusable.

What changes did you make?

It sounds like we still need cacheAsBitmap with an intermediate OpenGL framebuffer supported, that will allow us to flatten objects, but without the cost of going through a software render

None, the code, which is there now is probably right (
gl.blendFunc (gl.DST_COLOR, gl.ONE_MINUS_SRC_ALPHA); ), or at least I assume so. I don’t see any noticeable difference between that and gl.DST_COLOR, gl.ZERO in my scenario.

Yeah, some hardware flattening would be really, really helpful. I’m not sure how difficult it would be to implement it for OpenFl / Lime, I should probably start looking into the internals so I’m able to actually help with something.

Right now I’m using software workaround – using bitmapData.draw() with downscaled vector shadow map to Bitmap object on stage, which I then upscale to the screen. I haven’t finished it yet, but it seems it could probably work reasonably well with small enough shadow maps, which in my case is not too much hassle, as I have pixelart graphics. On larger shadow maps using draw() on every frame will of course be major issue, hardware flattening would help here hugely.

Just a quick question. Is there a way to have custom blend modes, i. e. switch BlendModeManager in renderer, so I don’t have to directly modify GLBlendModeManager.hx and lose changes with every OpenFL update?

What kind of blend modes are you using?

For now

	case OVERLAY:

		gl.blendEquation (gl.FUNC_ADD);
		gl.blendFunc (gl.DST_COLOR, gl.SRC_COLOR);

but I might need others in the future. It doesn’t seem to be exact overlay blend in mathematical sense, but looks close enough and works better for what I need it for anyway.

I found a nice cheatsheet about GL blends:

<img src=’/uploads/default/original/2X/e/eb8ec4f2cd656c389535f932998195db9b9563af.jpg’ width=“600” ">

and also GLSL implementations, if it could be done using shaders and we want exact Photoshop / Flash equivalents:

But as I said, even if we had exact overlay using GLSL, I’d still prefer to use stated GL blend equation. The idea was to replace renderSession.blendModeManager with my modified one, but I don’t know if it is possible to access renderSession which renders the stage from application code (also GLRenderContext which is needed by constructor).