pixijs / layers

Separate the z-hierarchy of your scene tree from its canonical structure.
https://pixijs.io/layers/docs/
MIT License
222 stars 57 forks source link

Need way to handle mask as single layer #38

Open rkdrnf opened 5 years ago

rkdrnf commented 5 years ago

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!

ivanpopelyshev commented 5 years ago

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.

ivanpopelyshev commented 5 years ago

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

ivanpopelyshev commented 5 years ago

I'll see what I can do :)

ivanpopelyshev commented 5 years ago

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!

rkdrnf commented 5 years ago

I checked it out and it does not work properly now. https://github.com/gameofbombs/pixi-heaven/issues/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 :)

rkdrnf commented 5 years ago

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?

rkdrnf commented 5 years ago

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)

ivanpopelyshev commented 5 years ago

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

Dai696 commented 5 years ago

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

jonlepage commented 5 years ago

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: https://github.com/pixijs/pixi-layers/issues/37 The performance are very bad on my side. thanks

jonlepage commented 5 years ago

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

Dai696 commented 5 years ago

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.

jonlepage commented 5 years ago

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

jonlepage commented 5 years ago

it would help me a lot and also for others.

jonlepage commented 5 years ago

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;
ivanpopelyshev commented 5 years ago

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

Dai696 commented 5 years ago

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)

jonlepage commented 5 years ago

@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.

wpitallo commented 5 years ago

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

ivanpopelyshev commented 5 years ago

@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.

wpitallo commented 5 years ago

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.

jonlepage commented 5 years ago

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)
                    });
                }

            }
ivanpopelyshev commented 5 years ago

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.

ivanpopelyshev commented 5 years ago

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

jonlepage commented 5 years ago

I remember you talking about something like that long ago. I guess you dropped the idea of groupMask. https://github.com/pixijs/pixi-layers/issues/37#issuecomment-434418393

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.