Progress bars
Part 1: Progress bars
Part 2: Counters
Part 3: (Coming soon)
Introduction
In this series of tutorials I’m going to show shaders for common user interface elements. Since every game has unique needs, the solutions explained here will probably be used as a template: Adding and removing features for a customized and optimized shader won’t be too hard.
This first part is about progress bars.
I built the shader in ShaderFX, but it should be pretty easy to create the same effects in MentalMill or Unreal.
The base idea
A “progress bar” sort of UI element has two main parts: the static background and the dynamic “bar". This bar changes in relation to a value.
In the shader, textures will define the basic look and behavior of the progress bar, while the visuals can be customized by other parameters.
On the left is the so called “diffuse” texture (although the shader itself is unlit). This defines the basic design of both the background (bottom half) and the foreground (top half). It is meant to be used on a quad with its UV covering only the bottom half of the UV space. The dynamic part will be shifted down and laid over the static background.
This is the “alpha” texture. (For the sake of clarity I have it as a separate image file throughout this tutorial. It is possible to have all data in a single image, I’ll discuss the options for that later.)
It defines the visibility of both the background and the foreground. For the static part the greyscale values mean different amounts of opacity.
For the foreground they define visibility in relation to the Amount parameter, which is the value we are measuring. Basically the bar will fills up from white to black as the Amount changes from 0 to 1.
Basic example
Before we take a look at the node tree, here is the list of parameters the shader has:
(Unfortunately the parameter order is automatic and therefore not very user friendly. Yet, instead of a more logical arrangement, I’ll list the parameters as they appear in the material editor.)
BG_FrameColor: The color of the background around the dynamic parts.
BG_InsideColor: The color of the background where the dynamic parts appear.
FG_Alpha: The alpha texture for the foreground. Same as BG_Alpha. (The reason for 4 texture parameters will be discussed later on.)
BG_Diffuse: The diffuse texture for the background. Same as FG_Diffuse.
FG_ColorMin: The color of the dynamic bar when the Amount parameter reaches, or go bellow the AmountLow value.
FG_ColorMax: The color of the dynamic bar when the Amount parameter reaches, or go above the AmountHigh value.
AmountLow: The value of the Amount parameter where all the *Min parameters have full effect.
AmountHigh: The value of the Amount parameter where all the *Max parameters have full effect.
Amount: The most important parameter, which controls everything. This is probably the only value here a programmer have to touch. Always stays in the [0,1] domain.
FG_Brightness: Brightness of the foreground. Can go above 1.
FG_PulseSpeedMin: The pulse speed of the dynamic bar when the Amount parameter reaches, or go bellow the AmountLow value.
FG_PulseSpeedMax: The pulse speed of the dynamic bar when the Amount parameter reaches, or go above the AmountHigh value.
FG_PulseStrMin: The pulse strength of the dynamic bar when the Amount parameter reaches, or go bellow the AmountLow value.
FG_PulseStrMax: The pulse strength of the dynamic bar when the Amount parameter reaches, or go above the AmountHigh value.
PanByTime: If true then foreground’s panning is based on time. Otherwise it’s driven by the Amount parameter.
PanSpeed: The speed of the panning.
FG_Tiling: The horizontal tiling of the foreground part.
FG_Diffuse: The diffuse texture for the foreground. Same as BG_Diffuse.
FG_Transition: How quickly the dynamic bar fades away at the end. Since it acts as a contrast adjustment, a higher number makes the bar’s end more sudden.
BG_Alpha: The alpha texture for the background. Same as FG_Alpha.
Here is an example:
I animated the Amount values to simulate a real progress bar. The material is using these textures and shader settings:
I also animated the FG_Transition value: the faster the Amount changes, the lower the transition contrast goes. The longer fade out of the bar simulates motion blur.
Foreword to the shader
In the node tree, most of the nodes are collapsed, so the network is less crowded. Sometimes nodes have more than one inputs, but their order is not indicated when collapsed. To work around this I used a convention:
The collapsed and original version of the same network
The order of the input lines indicate the order they are linked in. There are no crossing lines.
But if that wasn’t the case, then I didn’t collapsed the related node:
A typical case when I didn’t collapse a node.
The node tree
I trust that the comments above the nodes describe each step clearly, so here I’ll only discuss the higher level structure of the network.
At the far left, without colorization is the Amount parameter.
The yellow area contains the value remapping and all the effects depending on the AmountLow and AmountHigh: the foreground colorization and the pulse effect.
This remapping is used when we want to emphasize a certain range in the life of the progress bar. For example we want a health bar flashing and turning red when the player’s health goes bellow 20%. Or we only want to highlight the progress bar for the “Flaming Cows From Hell” spell when it’s recharged and can be cast. (We’ll see examples for both cases a bit later.)
The other thing worth mentioning is the clamping which is done in a CustomCode node. I used it instead of the Clamp node because this way I don’t have to create the two constant nodes for the limits.
The purple area is where the foreground is assembled. The only twist here is the FG_Alpha texture sampler: it must use point sampling instead of bilinear filtering. The reason for this is simple: we want our gradient exactly the way we made it. The bilinear filtering would smooth neighboring pixels together, creating unwanted gradients.
Here is a crop from the alpha texture above, using bilinear filtering.
As you can see, the values of the surroundings affect the value of every pixel.
In this case we want the progress bar to fill up from left to right and not “grow” in every direction.
As a matter of fact, bilinear filtering is only necessary on the rest of the texture samplers if you want to do one of the following:
- Scaling the UI element.
- Rotating it on any of the three axis.
If you simply map texels to screen pixels 1:1, then you can use point sampling everywhere.
The light blue part processes the background diffuse, while the green area is where all comes together: we blend the foreground and background and do the alpha blending for the whole thing.
More examples and downloads
The textures for all the examples, the 3DS Max 2010 scene and the ShaderFX file can be downloaded from here: ProgressBarAssets
The files are licensed under a Creative Commons Attribution-Share Alike 3.0 Unported License.
Similar to the first example, but here the panning of the bar texture is driven by the Amount value: PanByTime is false. After adjusting the PanSpeed now it looks like the stripes are glued to the bar.
Also, AmountLow is set to 0.99, so the FG_ColorMax, FG_PulseSpeedMax and FG_PulseStrMax parameters won’t affect the foreground until it’s filled up 99%.
The bar’s gradient has 15 shades of gray, so it fills up in 15 steps.
This foreground is panning as well, but not one pixel but a whole block at a time. Since the texture is 16 boxes wide, the PanStep parameter is set to 0.0625 (1/16). (Make sure you have at least 5 digits of precision set in Max!)
In the Max example file you’ll find another version of this: with a proper tiling and stepping setup, a crude animation player is possible.
The gradient representing the progress bar can have an arbitrary shape, like this spiral.
AmountLow is 0.25 and AmountHigh is 0.45 so the bar will flash in red from 0% to 25%, then it blends to green in the following 20% and stays plain green from 45% up.
The FG_Transition is fairly low, which gives a nicer looking, less aliased bar.
A simple angular gradient. In the alpha the text is 3% grey so it only appears at the end.
The foreground alpha mixes radial and angular gradients. A posterize effect was used to separate the rings.
A simple vertical, linear gradient was distorted using Paint Shop Pro’s “Mesh Warp Tool". In Photoshop the “Warp” and “Liquify” filters have similar functionality.
To make the alpha for the foreground, first I put the text lines next to each other in a straight line. I made a gradient matching that length, sliced it up properly and moved the pieces over the original text. (So the segments are under each other just like the text on the screen.) This arrangement makes the foreground appear from left to right and top to bottom.
Optimizations
- Although the shader isn’t particularly expensive, cut any aspect of the shader you don’t need: colorization, panning, pulse effect, etc.
- Regarding the textures, merging the diffuse and alpha information into one image is recommended. However, texture compression make things interesting. We have roughly the following options:
No compression: We should use uncompressed images if we can, because those look the best. Any loss of quality will be highly visible due to the way the images are presented to the player. Also, since UI elements are relatively small, the memory savings wouldn’t be great anyway.
G8: If our diffuse texture is greyscale then this palette based file format will provide top quality with small memory footprint. The downside is that it has no alpha channel, so the alpha image must be a separate texture, which may or may not be an issue.
DXT5: It might be a good choice if the diffuse has colors. The compression will degrade quality, but depending on the actual visuals of the UI element, it might be acceptable. (Should work pretty well for grungy stuff, less so for clean designs.)
While the alpha information is sensitive to compression, DXT5’s 4 bit interpolated alpha channel can maintain the gradients with good results.
DXT3: Only useful if we can live with the 16 levels of grey in the alpha channel, which makes the progress bar have only 15 states.
DXT1: The alpha channel is useless for us in this format, but if the diffuse data is greyscale then it can be put in the red channel, so the alpha info can go to the green one.
- By extending the shader we could use one, big texture containing many elements: by adding X and Y offsets, the texture area covered by the UI polygon can be pushed anywhere on the compound image.
- We should turn off MIP map generation for UI textures, as they have no need for it.
FAQ
Q : Why the pulse effect flashes randomly?
A : This weird pulse behavior happens when the FG_PulseSpeedMin is very different from FG_PulseSpeedMax and the Amount value smoothly and steadily changes.
In that case the frequency of the sin wave is constantly changing, producing some kind of “self interference".
You can make it less apparent by either decreasing the difference between the two pulse speeds, or using the setup in a situation where the changes of Amount are sudden. (Like a health bar where being shot takes away a chunk of the HP at once.)
Q : Is it necessary to have four texture parameters?
A : No. At this point the best you can have is three texture params: one for the background, one for the foreground diffuse and one for the foreground alpha. (All of them might use the same, merged diffuse+alpha texture.) The 3 textures have 3 different UV inputs, so they must be separate samplers. ShaderFX will create separate parameters for them in the material editor, instead of a single, shared one.
Q : I think you’re wrong when you say that the… *insert objectionable statement here*
A : If you find something in this article which is confusing, misspelled, dubious or just plain wrong, please don’t hesitate to drop me a mail to zoltan at zspline dot net.
