pixijs/layers

Need way to handle mask as single layer

rkdrnf opened this issue · 25 comments

I have code like below which creates many masked hearts.

public createHeart(index: number) {
        let container = new PIXI.Container();

        let heartFull = new PIXI.Sprite();
        heartFull.position = new PIXI.Point(index * 2.5, 0);
        heartFull.texture = PIXI.utils.TextureCache["healthGauge.png"]
        heart.parentGroup = uiGroup

        let heart = new PIXI.Sprite();
        heart.position = new PIXI.Point(index * 2.5, 0);
        heart.texture = PIXI.utils.TextureCache["heartMask.png"]
        heart.parentGroup = uiMaskGroup

       container.addChild(heartFull, heart);

       app.stage.addChild(container);

        heartFull.mask = heart
    }

which creates output as below.

2018-11-07 1 04 50

But when inspected from WebGL inspector extension of chrome,
I checked webGL useProgram() for masking operation is called n times for n hearts.

Is there any way to execute multiple mask operation as single operation?
Or should I merge masking sprites using renderTexture?
Thank you!

parentGroup does not affect masks, because the only thing we use from mask is their transform and texture.

let heartFull = new PIXI.Sprite();
        heartFull.position = new PIXI.Point(index * 2.5, 0);
        heartFull.texture = PIXI.utils.TextureCache["healthGauge.png"]
        heart.parentGroup = uiGroup

heartFull in last line.

You are right about multiple mask operation as single operation, its good idea and several people pointed me there.

I'll see what I can do :)

Oh, actually, there is spriteMask thingy in https://github.com/gameofbombs/pixi-heaven . If you use heaven sprites and set sprite.pluginName = 'spriteMasked' to all of them, they'll be batched!

I checked it out and it does not work properly now.
pixijs/pixi-heaven#7
Is there any workaround?

Below is what I think as a workaround.
I'm currently thinking of creating render texture which has 2 container.
one for sprites being masked,
one for masks.
and applying masking filter(shader) to whole mask container.
putting generated sprite of RenderTexture to main container which is directly rendered to screen.

If there is any better workaround or improvement, please let me know :)

I want to use certain layer as mask layer by setting layer.useRenderTexture = true and making sprite from this renderTexture, which would be used as a mask sprite.
Is it possible?

Successfully applied single mask layer by the method described above.
single drawback is that generated texture is too large than actual mask area. (it generates texture of same size as screen)

Congratulations! You mastered getRenderTexture function. Yes, I plan to add a field to specify an area, not whole screen.

I do hope we can get masks and layers working better

Having to mask each sprite added to layer separately slows things down a bunch

U either need to have all sprites in a container and that container added to a layer without it's children being in layers (thus losing sort)...then apply mask

Or mask each sprite separately...

This is especially difficult for stuff using sortPriority with a container being added to a specific layer

And it's children being rendered off in special light layers...

Being able to apply a mask direcly to the parent container and have that effect children would be amazing

I just wanted to bring the subject to the table again, since 1 years.
Nobody would have a concrete solution to share???

i also reOpen this: #37
The performance are very bad on my side.
thanks

i don't know if maybe a kind of mask shadders can be a solution?
example:

        const c = new PIXI.Container();
        const d = new PIXI.Sprite(texture);
        const n = new PIXI.Sprite(texture);
        d.parentGroup = PIXI.lights.diffuseGroup;
        n.parentGroup = PIXI.lights.normalGroup;
        c .addChild(d,n)

const shadderMaskfilters = ???; // need a magician here
c.filters = [shadderMaskfilters];

i found a codePen thats seem use a kind of mask shadders filters, but understand nothing here. :)
It can be a solution?
https://codepen.io/djmisterjon/pen/pXMqvd?editors=0010

So the solution is to put anything u need to mask into a single container, then mask the container.

If you need some sort of advanced sorting, its best to mask per sprite separately.

Another alternative is to render everything to a renderTexture, then mask the renderTexture. This is how i handle my complex volumetric fog stuff.

@Dairnon di you have a little example code to share and inspire me :)

it would help me a lot and also for others.

i know only 2 solution to perform this for now.
i need the 3er solution.

              //solution 1 poor performance: and very bad if we have more child stuff
      
              const mask = new PIXI.Sprite(PIXI.Texture.WHITE);
              const master = new PIXI.Container();
              const c = new PIXI.Container();
              const d = new PIXI.Sprite(textureName);
              const n = new PIXI.Sprite(textureName);
              d.parentGroup = PIXI.lights.diffuseGroup;
              n.parentGroup = PIXI.lights.normalGroup;
              c.addChild(d,n);
              master.addChild(c);

              d.mask = mask;
              n.mask = mask;


              
              //solution 2 ~more good performance but shitty code for interactions:
      
              const mask = new PIXI.Sprite(PIXI.Texture.WHITE);
              const master = new PIXI.Container();
              const cd = new PIXI.Container();
              const cn = new PIXI.Container();
              cd.parentGroup = PIXI.lights.diffuseGroup;
              cn.parentGroup = PIXI.lights.normalGroup;
              master.addChild(cd,cn);
              const d = new PIXI.Sprite(textureName);
              const n = new PIXI.Sprite(textureName);
              cd .addChild(d);
              cn .addChild(n);

              cd.mask = mask;
              cn.mask = mask;


            //solution 3 What we need plz

            const mask = new PIXI.Sprite(PIXI.Texture.WHITE);
            const master = new PIXI.Container();
            const c = new PIXI.Container();
            const d = new PIXI.Sprite(textureName);
            const n = new PIXI.Sprite(textureName);
            d.parentGroup = PIXI.lights.diffuseGroup;
            n.parentGroup = PIXI.lights.normalGroup;
            c.addChild(d,n);
            master.addChild(c);

            master.mask = mask;

guys, please, give me live demos. I dont understand it anymore.

well what u can try is

u can render each sprite separately to a renderTexture in a loop

I think its like renderer.render(sprite, renderTexture)

@ivanpopelyshev
sorry for late.
I was made you a great demo on playground, but i get bug that's make me lose all, unfortunately I did not have the courage to start again.

So i made new fast one, here is a lazy demo, it's your demo with only one adds, it does not present a great case of use , but it presents the general idea.
If you can uncomment the line 68.
https://codepen.io/djmisterjon/pen/mNbxbK

also @Dairnon give me a new way and good example to do this with app.renderer.
https://www.pixiplayground.com/#/edit/myxIVFlAu7y2jxiqw3FEk

it works well for a simple case, but does not work fine in a complex case because this will rendered all at locally positions and it just too complicated to manage containers and interactions with multiples childs, except if I follow misUnderstand how is work!?

hope you will understand me and demo will help.

Is there a way to apply a mask to group of sprites? (Parent Container) This works but not with layers :(

@wpitallo that's what we are discussing. Due to our architecture its not possible, you really need to know how rendering in pixi-layers work to understand why.

So it looks like I somehow got this working, I created a parent container and then added a mask and child containers that each themselves contained an animated sprite. I then created one global group and set the parentGroup of the container that contained the animated sprite and this seems have done the trick.

thats was so hard to get good performance.
I converted my menu to use custom renderer but am not big fan
The code is complex to manage, but I absolutely wish to obtain this rendering.
So here the result
https://youtu.be/8abhxoKBrTg

here the code
I add a extraRenderTo to my class itemContainer, thats disable diffuse or normal from context.

        //!rendererMask
        const itX = -460;
        const itY = -285;
        const mask = new PIXI.Sprite(PIXI.Texture.from('DATA2/testmask.png')) //new PIXI.Sprite(PIXI.Texture.WHITE);
        mask.width = 970;
        mask.height = 515;

        const renderTextureD = PIXI.RenderTexture.create(970, 515);
        const renderTextureN = PIXI.RenderTexture.create(970, 515);
        const renderSpriteD = new PIXI.Sprite(renderTextureD);
        const renderSpriteN = new PIXI.Sprite(renderTextureN);
        renderSpriteD.parentGroup = PIXI.lights.diffuseGroup;
        renderSpriteN.parentGroup = PIXI.lights.normalGroup;
        renderSpriteD.position.set(itX,itY);
        renderSpriteN.position.set(itX,itY);
        mask.position.set(itX,itY);
        renderSpriteD.mask = mask;
        renderSpriteN.mask = mask;

        //!ContainerItems
        //TODO: Eventuelement, ajouter un options pour generer des textures diffuses, avec mask pour cool FX (pas important)
        const ItemsContainer = new PIXI.Container();
        ItemsContainer.position.set(itX,itY);
        ItemsContainer.mask = mask;
        for (let i=0,xx=0,yy=0,marge=325, l=$items.list_items.length; i<l; i++) {
            const itemBase = $items.list_items[i];
            const itemslot = this.itemSlots[i] = new Menue_items.SlotItem(itemBase);
            itemslot.position.set(xx+10,yy+70);// +10,+70 because rendered are from local positions
            itemslot.renderable = false;
            ((i+1)%3)? xx+=marge : (yy+=126, xx=0);
        };
        ItemsContainer.addChild(...this.itemSlots);
        $app.ticker.add(()=> {
            for (let i=0,yy=60, l=this.itemSlots.length; i<l; i++) {
                const itemslot = this.itemSlots[i];
                itemslot.renderable = true;
                itemslot.extraRenderTo(renderTextureD,'d',!i);
                itemslot.extraRenderTo(renderTextureN,'n',!i);
                itemslot.pivot.y = this._currentLine*126;
                itemslot.renderable = false;
            };
        });
        //!End
        this.addChild(Frame,ExtraInformations,ItemsContainer,renderSpriteD,renderSpriteN,mask,SliderBar); // renderSpriteD,renderSpriteN,mask
        this.child = this.childrenToName();
        
        //DELETEME: TODO: TEST SCROLL
        document.addEventListener('wheel', (e) => { // zoom
            const _currentLine = this.child.SliderBar.scroll(e);
            TweenLite.to(this, 1, { _currentLine: _currentLine, ease: Power4.easeOut });
        });
            extraRenderTo(renderTexture,mapper,clear){
                if(mapper==='d'){
                    this.children.forEach(c => {
                        c.n && (c.n.renderable = false)
                    });
                    $app.renderer.render(this, renderTexture, clear, null, false);
                    this.children.forEach(c => {
                        c.n && (c.n.renderable = true)
                    });
                }
                if(mapper==='n'){
                    this.children.forEach(c => {
                        c.d && (c.d.renderable = false)
                    });
                    $app.renderer.render(this, renderTexture, clear, null, false);
                    this.children.forEach(c => {
                        c.d && (c.d.renderable = true)
                    });
                }
                
            }

ok, now i get your use-case. But I dont know how to solve it API-wise, need a mask that is taken into account in layers.

Maybe need special flag for objects with that mask, or extra field, like container.maskLayers = true

I remember you talking about something like that long ago.
I guess you dropped the idea of groupMask.
#37 (comment)

for now I'll will leave that with this Render solution , my performance is ok
But if one day you have a magick idea that made it all simpler, I'll be happy to test it. ;)
A, also don't know how solve it sorry, i can't help.