UI Mask Pixelated

Hi there,

I’m trying to make a progress bar. I’m using a mask because we want it to have round corners.

Structure looks like this:
image

And the settings on my mask are the following:
image

In game it looks like this:
image

As you can see it is very pixelated, how can I solve this?

Sadly I think you can’t do anything about that with the current masking implementation. Masking elements will use the alpha of the texture provided but only using 1-bit values (masked or not), fading isn’t supported.

Masks and Masking
Image Elements can be used to mask or hide elements that are descendents. To enable this feature set the mask property of an image Element to true.

If there is no texture asset assigned to an image Element used for masking the mask will be a rectangle defined by the width and height. If the image Element has a texture assigned the alpha-channel of the texture is used as the mask. Note, an image mask is 1-bit, i.e. there is no fading out of the mask using the alpha-channel of the texture.

https://developer.playcanvas.com/en/user-manual/user-interface/image-elements/

You could disable masking and try using multiple overlapping elements in place with both textures having alpha. Much like the UI progress bar example does:

https://developer.playcanvas.com/en/tutorials/ui-elements-progress/

Hmm ok, thanks for your answer Leonidas. I’ll try to find another way without masks then.

1 Like

Adding @will and @vaios for reference, in case this is a feature that can be added at some point.

Okay instead of using an image as a mask, I now use a rectangle and I tween its width instead of the Progress entity’s position.

The result is not exactly what I wanted but pretty close:
image

1 Like

I found an easier workaround to this.

UI Images can have a custom material assigned to them (they support a Texture, Sprite, or Material). So you can just create a new material for the UI & set the Opacity Texture of that material to be the shape you want the image to be masked as, and it works very smooth.

See the results here: https://github.com/playcanvas/engine/issues/2083

3 Likes

I also struggled with pixelated masks, so I wrote a simple shader material that works well for filling a sprite with two colors based on a normalised value. I use it with hp and stamina bars that have a base shape sprite, that is painted horizontally based on the value (leftColor on the left, rightColor on the right from the value)

class FillBar extends pc.ScriptType {
    initialize() {
        const vertexShader = `
            uniform mat4 matrix_viewProjection;
            uniform mat4 matrix_model;
            
            // Additional inputs
            attribute vec3 vertex_position;
            attribute vec2 vertex_texCoord0;
            
            // Additional shader outputs
            varying vec2 vUv0;
            
            void main(void) {
                // UV is simply passed along as varying
                vUv0 = vertex_texCoord0;
            
                // Position for screen-space
                gl_Position = matrix_model * vec4(vertex_position, 1.0);
                gl_Position.zw = vec2(0.0, 1.0);
            }
        `;
        const fragmentShader = `
            varying vec2 vUv0;
            uniform sampler2D uDiffuseMap;
            uniform float uAmount;
            uniform vec3 uLeftColor;
            uniform vec3 uRightColor;
            
            void main(void)
            {
                vec4 base = texture2D(uDiffuseMap, vUv0);
                if (base.a > 0.0) {
                    if (vUv0.x > uAmount) {
                        gl_FragColor = vec4(uRightColor, base.a);
                        return;
                    } else {
                        gl_FragColor = vec4(uLeftColor, base.a);
                        return;
                    }
                }
                
                gl_FragColor = base;
            }
        `

        
        const shader = pc.createShaderFromCode(
            this.app.graphicsDevice,
            vertexShader,
            fragmentShader,
            'playerBarShaderMaterial',
            {
                vertex_position: pc.SEMANTIC_POSITION,
                vertex_texCoord0: pc.SEMANTIC_TEXCOORD0
            }
        );
    
        const material = new pc.Material();
        material.shader = shader;
        material.blendType = pc.BLEND_NORMAL;
        material.depthWrite = true;
        material.setParameter('uDiffuseMap', this.entity.element.texture);
        material.setParameter('uAmount', 1);
        material.setParameter('uLeftColor', [this.leftColor.r, this.leftColor.g, this.leftColor.b]);
        material.setParameter('uRightColor', [this.rightColor.r, this.rightColor.g, this.rightColor.b]);
        material.update();
        this.material = material;
        this.entity.element.material = material;
    }
    
    /**
     * @param {number} value  - value from 0 to 1
     */
    setValue(value) {
        console.log(value);
        this.material.setParameter('uAmount', value); 
        this.material.update();
    }
}

pc.registerScript(FillBar, 'fillBar');

FillBar.attributes.add('leftColor', {
    type: 'rgb',
    default: [1, 1, 1]
});

FillBar.attributes.add('rightColor', {
    type: 'rgb',
    default: [1, 1, 1]
});