UI Element stencil

Hello there, good people of the Internet.
I was wondering if it’s possible to create sort of a “reverse mask” on UI elements to cut out a text (using text element) out of an image element. Something along these lines, but with text https://puu.sh/D5ceU/ed6ceee32a.png . One way I know of doing this would be to draw text on a canvas and use it as an alpha texture, however that’s really not ideal since the final game should support multiple localisations/fonts/font sizes, and switching between those/generating them on the fly seems like a pain in the butt to manage.
I’ve been trying to do this using stencil buffers for a couple of hours, but found the resources on the topic to be…lacking. Here’s a simple project that shows where I’m at and illustrates what I’m trying to achieve https://playcanvas.com/editor/scene/734120 .
I would really appreciate any guidance on how to do this, be it with the stencil buffer or otherwise.

Thanks,
Adam

From what i can see you have gotten one of the text elements to do what you wanted but what is the portal.js for?

Hey, the one on the right is actually a text mesh I created in blender, rather than an actual Elementcomponent (I reaaally, really don’t want to generate meshes on the fly). It’s there just to show what I’m trying to achieve. The portal and outsidePortal scripts are there to set the stencil properties on the foreground and background elements respectively.

Try text opacity check out https://developer.playcanvas.com/en/api/pc.ElementComponent.html#text to kinda see what the text component is used for and work from there if you have anymore questions or i missed one let me know

Text opacity doesn’t allow me to “cut a hole” through the next component. If I placed a Text component with 0 opacity on top of an Image component that has a button texture, I would then see a full button texture with no text in it, rather than one with a text-shaped hole. https://puu.sh/D5EMs/4460e96a6c.jpg
Another way I could think of doing this would be to create a shader that would discard font pixels for the specific pc layer, however WebGL and I are not really friends.

Hmmm i will have to look at the project again and see what i can accomplish (If anything) I will let you know what i can figure out. Also could u post an overview to the project so i can fork it?

Thanks Nathan, here’s the overview https://playcanvas.com/project/609611/overview/element-stencil-test (you can also get to a project’s overview through the home button in the editor.

1 Like

So far i seem not to be able to do this @dave is this possible in PC i have been looking for a while but cant seem to find out how i am to accomplish what he is asking if you know if this is possible please let me/CeLLko know

@devMidgard Can you help out with your shader knowledge please? :slight_smile:

Uh, I literally have no idea no how to approach this.

You could possibly look at how someone else does this (if engines like Unity support stuff like this), and deconstructing how it works there, try to apply on PlayCanvas.

The only idea I got right now is setting up all the texts etc on a separate layer that isn’t rendered by the main camera, then rendering it into a texture that you can apply to a UI Element that’s rendered by the main camera, and write a simple custom fragment shader that basically discards each pixel colored with red, blue, or whatever, that way it’d show what’s behind the quad.

It’d be essentially like grabbing your Elements, taking a “screenshot” of them, so you turn it into a single texture that you can mask.

2 Likes

Yes i cannot find nothing either man i have looked and looked all i can say is try to look through Unity as @devMidgard suggested if you have anymore questions ask

That’s not a bad approach. Shame the material doesn’t have an invert option for the opacity which would make this possible without a custom shader.

Well I managed to do it using stencil buffer and custom shader chunk that discards the invisible background around the text mesh.

Text:

mat = this.entity.element._text._material.clone();
this.entity.element._text._setMaterial(mat);
mat.chunks.endPS = `gl_FragColor.a = dAlpha;
                    gl_FragColor = applyMsdf(gl_FragColor);
                    if(gl_FragColor.a < 0.1)
                        discard;`;
mat.depthWrite = false;
mat.redWrite = mat.greenWrite = mat.blueWrite = mat.alphaWrite = false;
mat.stencilBack = mat.stencilFront = new pc.StencilParameters({
     zpass: pc.STENCILOP_INCREMENT
});

Background:

    mat = this.entity.element._image._material.clone();
    this.entity.element._image._material = mat;
    this.entity.element._image._updateMaterial(true);
    mat.stencilBack = mat.stencilFront = new pc.StencilParameters({
        func: pc.FUNC_EQUAL,
        ref: 0
    });
    mat.update();

Thanks for the help everyone, and I hope this helps someone in the future.

4 Likes

Nicely done and thanks for sharing the solution!

Yes very nice :wink: keep up your good work!