7
\$\begingroup\$

My attempt: I make a texture (could be a single colour or otherwise), and then stretch that texture from a minimum 0 up to a maximum max value. This works, but leaves me rather dissatisfied for a number of reasons, one of which being:

  • If I want rounded corners as the bar progresses, the edges are squashed or stretched.

My question: What is the best practice to achieve a progress bar result.

Thoughts: Is there a way to stop openGL from drawing anything from a certain x value onwards of a texture? That way I could draw the texture as it should appear, but specify the value of x.

I have found this potentially useful link.

Update

I get the impression that using the stencil buffer might be the way to go. The stencil buffer allows to define any region you like through which other rendered geometry can be clipped. Apparently there are many other uses for the stencil buffer, e.g. reflections.

Possible Solution

I ultimately achieved what I wanted using the stencil buffer combined with my own shader, since OpenGL ES 2.0 does not support glAplhaTest and related functions. Here's an image of the result:

Progress bars

The texture drawn to the stencil buffer was an elongated rectangle with rounded corners. The texture drawn to the image buffer was just a stretched red rectangle, which as you can see is clipped to the stencil contents. You need to write your own shader to deal with the alpha channel since OpenGL ES 2.0 does not support the glAplhaTest and related functions.

Note - perhaps this isn't the best image example. In fact the presented solution allows you to progressively reveal the left and right caps, which I unfortunately didn't demonstrate in the above image.

\$\endgroup\$
5
  • \$\begingroup\$ Did you mean u in texture or x in world coordinates? \$\endgroup\$
    – wondra
    Oct 21, 2015 at 21:20
  • \$\begingroup\$ I'm not sure to be honest - it was just an idea, but I'm not sure how to achieve that yet. \$\endgroup\$
    – user24493
    Oct 21, 2015 at 21:33
  • \$\begingroup\$ You probably want to pass separate mask texture and a uniform to you shader to allow un-stretch the mask. Or even easier would be to create quads for both ends and the body separately. \$\endgroup\$
    – wondra
    Oct 21, 2015 at 21:53
  • \$\begingroup\$ Why can't you just render only part of the progress bar you want to render? If it's at 50%, then render the left half, using the left half of the texture. \$\endgroup\$
    – user253751
    May 4, 2016 at 22:50
  • \$\begingroup\$ @immibis thanks... At the time I had no idea you could map any part of a texture to vertices, and I was just rendering each sprite individually using its very own texture. Having been using sprite atlases for some time now, I understand how I would implement your suggestion. It's much more efficient than my original idea ! Thank you. \$\endgroup\$
    – user24493
    Dec 14, 2018 at 17:22

3 Answers 3

3
\$\begingroup\$

No need to bring in additional entities, KISS

Just render the progress bar out of three parts - left cap, right cap and the body. Schematic:

 {left cap] [body] [right cap}

Left and Right caps are usually always the same - half-circles with transparency. The body texture needs to be stretchable without noticeable effects.

Left and Right caps usually get rendered irregardless, and the required percentage is shown with body width. So e.g. 0% could be an empty bar, 1% is caps with body of 0 width.

Further reading for stretching without distortions in both axis: google for "nine-patch button".

\$\endgroup\$
3
  • \$\begingroup\$ I like the principle. However, I want a fluid look to these bars, and the described method does not allow for the (left cap] or [right cap) to gradually reveal itself. My current implementation does :-D In addition I am now able to achieve many other effects given my knowledge of stencil buffering. I will definitely keep KISS in mind though ! \$\endgroup\$
    – user24493
    Oct 4, 2016 at 7:50
  • \$\begingroup\$ Stencil often could be replaced with a texture mask. Just in case it is not available or an overkill for a single progress bar ) \$\endgroup\$
    – Kromster
    Oct 4, 2016 at 8:02
  • \$\begingroup\$ I'll take a look. I don't know anything about texture masks ! \$\endgroup\$
    – user24493
    Oct 4, 2016 at 8:04
1
\$\begingroup\$

There are a few ways to do this.

This is the solution described by poirot:

I ultimately achieved what I wanted using the stencil buffer combined with my own shader... The texture drawn to the stencil buffer was an elongated rectangle with rounded corners. The texture drawn to the image buffer was just a stretched red rectangle, which as you can see is clipped to the stencil contents.

You could instead use three instances of geometry; one half circle for each end and one rectangle stitched in between. Note how this does not require custom shader or stenciling. (This solution appears to be the same as suggested in comments by wondra.) This solution is my favorite for a number of reasons.

One note about stencil buffer it can be multisampled yielding anti-aliasing for a full frame without application intervention. Alpha testing (shader or not) however is a fragment operation which is not multisampled. If you combine the two stencil buffer never truly multisamples; all samples are treated equally, thus no anti-aliasing.

\$\endgroup\$
2
  • \$\begingroup\$ But using a half circle, a rectangle, and a final half circle how do you nicely show the progress of the half circles? E.g. supposing the half circles represented 10% each and the rectangle a remaining 80%. How would I nicely draw 5% ? The stencil approach permits to show progress before or after the start/end of the rectangle, i.e. anywhere between the width of either half circle. \$\endgroup\$
    – user24493
    May 5, 2016 at 6:52
  • \$\begingroup\$ @poirot Yes I know. It was not clear from your example how you wanted it to look like when the bar is empty or close to empty. A variant would be to render the bar shape to stencil (write ones) and then fill the masked area (where there are ones) with a rectangle with length based on %. When the bar is almost empty the rectangle does not fully cover the masked area. Does that make any sense to you? It is similar but does not require any addition shader programming and multisampling may still work. \$\endgroup\$
    – Andreas
    May 5, 2016 at 7:58
0
\$\begingroup\$

In the end I just made use of the fact that OpenGL lets you map any part of a texture to your vertices. So, suppose you have a texture with three hearts in a row, of width 300 and height 80 say, and you only want to show two-thirds on screen. Then you would map texture (0, 0, 200, 80) to your vertices, but make sure your vertices are also reduced accordingly. Saves messing around with the stencil buffer and is much quicker and elegant. Seems obvious, but back when I thought each sprite had to use a separate texture I couldn't see it !

\$\endgroup\$

You must log in to answer this question.