Sign in to follow this  
StephanieP

Use a whole Container() as a complex mask for an element

Recommended Posts

Hello everyone,

 

I’m quite new to PixiJS but I love it so far! However, I just came across a problem that I find surprisingly confusing and I’m not quite sure which option would be the most effective.

I have two stages, a background stage and a front stage, both of which are `PIXI.Container()`. The background stage has a transparent background but is filled with multiple elements (Sprites and Graphics) that are animated. The front stage only has a title in the form of a `PIXI.Text()`. I want that title to be revealed by the multiple elements from the background stage, meaning it should only be visible when there is an element from the background stage passing behind it.

Now, masking in PIXI seems to only work with `PIXI.Graphics()` and `PIXI.Sprite()`. My background stage isn’t a Graphics, and I can’t use `PIXI.Texture.fromCanvas()` because my background stage is in the same canvas. Also, it would probably be really costly to generate a texture every frame of the animation to update the mask.

 

Is there any clever trick that I can’t yet think of right now? What would be the most elegant way to do this, performance-wise? It feels like there should be, but again, I’m new and I can’t quite figure it out yet.

 

Thank you!

Share this post


Link to post
Share on other sites

Hi! Its possible if you use SpriteMaskFilter directly and pass there a sprite with texture from "pixi-display" plugin feature called "getRenderTexture()" .  https://github.com/pixijs/pixi-display

That requires understanding of both pixi-display examples and SpriteMaskFilter direct usage.

Share this post


Link to post
Share on other sites
Quote

Congratulations with first post!

Thank you! 😃

And thanks also for the quick reply! I’ll take a look at Pixi Display, it seems really interesting!

I continued fiddling around and indeed stumbled the Multiply blend mode trick, which works quite well in my case! However, I realized something that probably changes a lot of things when it comes to my understanding of the issue: in my very specific case, the fact that the basic masking relies on the pixel color to work is quite annoying. I actually don’t need that. Instead, the front stage should be fully visible at the areas where there is an element from the background stage, and fully invisible where there is nothing, regardless of the color of the element that is passing behind it. So, for instance, if there is a black square or a white square behind a letter from the title, that letter should be revealed in both cases. Which means there’s really only the alpha channel that should matter.

In that case, I’m really not sure which technique to use, or even if there are any. 😕

Share this post


Link to post
Share on other sites

Make your own filter based on SpriteMaskFilter, though you dont need those coords tricks with matrix because you are using fullScreen filters (container.filterArea=app.screen), it should map two layers 1:1.

Your case requires some time spent in those examples, modifying them, experimenting. Or you can wait for someone else to make a demo like yours, that will take much more time.

Also dont forget that colors are premultiplied everywhere ;)

Share this post


Link to post
Share on other sites

I followed your advice and went with a custom PIXI.Filter. I wrote a very small shader that receives a generated texture from the background stage that I use to determine whether the title should be visible or not :

precision mediump float;

varying vec2 vTextureCoord;
uniform vec2 res;
uniform sampler2D uSampler;
uniform sampler2D maskTexture;


void main() {
    vec4 texture = texture2D(uSampler, vTextureCoord);
    vec4 mask = texture2D(maskTexture, vTextureCoord);
	
	gl_FragColor = vec4(texture.r, texture.g, texture.b, mask.a * texture.a);
}

 

I generated the maskTexture with a PIXI.RenderTexture:

const maskTexture = PIXI.RenderTexture.create(app.screen.width, app.screen.height)

const filter = new PIXI.Filter(null, shader)
filter.uniforms.maskTexture = maskTexture

frontStage.filters = [filter]


app.ticker.add(() => {
    app.renderer.render(backgroundStage, maskTexture, true, null, false)
})

 

It works really well, but... Oddly enough, it seems like other DisplayObjects from containers outside the backgroundStage are included in the texture that is passed to the shader, which obviously causes the mask not to be what I want.

What I find weird is that, if I create a PIXI.Sprite from the generated texture and add it the stage, that sprite only includes the DisplayObjects from the backgroundStage. So...

const renderSprite = new PIXI.Sprite(maskTexture)
app.stage.addChild(renderSprite)

... will only display the elements from the backgroungStage, which is what I expect.

Why then does my shader pick up elements from containers outside the backgroundStage? What am I missing? 🤔

Thanks again!

Share this post


Link to post
Share on other sites

So, I realized ivan.popelyshev actually answered my question before I had even asked it:

Quote

Also dont forget that colors are premultiplied everywhere 

I think I didn’t realize what it truly meant when I first read it. But now I’ve updated my fiddle to reflect that. I tweaked the shader to take into account that premultiplication. Visually, it now works! Is there anything else I could improve in the way I built that scene?

Thanks again, Ivan!

 

Share this post


Link to post
Share on other sites
51 minutes ago, StephanieP said:

So, I realized ivan.popelyshev actually answered my question before I had even asked it:

I think I didn’t realize what it truly meant when I first read it. But now I’ve updated my fiddle to reflect that. I tweaked the shader to take into account that premultiplication. Visually, it now works! Is there anything else I could improve in the way I built that scene?

Thanks again, Ivan!

 

float alpha = mask.a * texture.a;
vec3 rgb = texture.rgb * alpha;

texture.rgb already multiplied

just "gl_FragColor = texture * mask.a" should work. Otherwise, colors on non-opaque parts of texture will be changed. Btw, that's a bug in SpriteMaskFilter, i know about it ;)

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
Sign in to follow this  

  • Recently Browsing   0 members

    No registered users viewing this page.