Counters
Part 1: Progress bars
Part 2: Counters
Part 3: (Coming soon)
Introduction
This part is about a counter shader. It mimics the look of a mechanical counter but can do plain digital displays as well. It supports arbitrary numeric systems and any number of digits.
This shader was also created in ShaderFX.
The base idea
The idea is very simple:
- Take a texture containing the digits, in a vertical arrangement.
- Scale it up so one digit fills the 0,0-1,1 UV space.
- Start tiling horizontally to show more than one digit.
- Shift each column of digits up or down properly.
To make the rolling animation nicer, we’ll apply motion blur to the whole thing. To do that we’ll need a slightly blurred version of the original texture.
Parameters
Now let’s see what shader parameters can be adjusted (still in ShaderFX’s pseudo-random order):
Digits: Defines how many digits to show.
BlurOffset: Only used when the CheapBlur flag is off, so a more accurate motion blur implementation is used. This value sets the maximum length of the blur.
NumSysBase: The numeric system’s base, which is the number of symbols on the diffuse texture.
Since the symbols can be anything, it is relatively easy to set up the shader to show strings:
The text is displayed on four different shaders next to each other, all of them are four digits wide and using base 32 numeral system. (See the FAQ section for the reason why it wasn’t done in one shader.)
RollExponent: Adjusts the way a digit animates. The more it goes bellow zero, the earlier a digit starts to roll. The more it goes above zero, the more a digit will wait before moving. In other words, a low value means more fluid animation, a high value means more snappy digit changes.
Here is a comparison (with fixed BlurAmount):
There are a few things to note: First, try to have negative RollExponents only when moving between values. When arriving a number which carries actual information, set the RollExponent to at least 0, otherwise the viewer will be confused by the state of the counter.
A good example is the very last frame: all counters are in sync at 1000, but at 1001, the tens in the left most one started to move already. It was just “1″ past the closest round number, but the very low exponent makes it very sensitive.
The effect can produce very confusing results. In the animation there is a point where the right three counters show 989.
At that point it seems that the left most counter shows 1099. The thousands are moving right from the start and although they have not yet arrived technically, visually they seem to be already at a valid digit.
So for best results, animate this parameter in game.
There is another thing: in the middle of the animation, the counter with the exponent of 2 looks/feels almost identical to the “full digital” one on its right. The reason for this is that at high speeds there might not be enough frames displayed on screen to catch when a new digit snaps into place. If it bothers you then just decrease the “RollExponent” temporarily, so the digits move more often and for longer, therefore the motion blur will be present on more rendered frames.
(By the way, all animations on this page run at 60 fps.)
Amount: The number we want to display. It is always a decimal number, and it will be converted to the chosen numeric system inside the shader.
VerticalTiling: Sets how much of the neighboring digits is visible.
BlurAmount: The amount of motion blur to be applied. It should be adjusted on the fly depending how fast the Amount parameter changes: the faster it does the more motion blur is expected.
BlurMax: The motion blur gets weaker from right to left: the smaller place value digits receive more. If this value is 1, then only the rightmost digit will get full blur, all others get less and less. If this value goes above 1, then more and more digits on the right will get maxed out blur.
BlurMap: A vertically motion blurred version of the DiffuseMap.
DiffuseMap: The texture containing the symbols to display the Amount value.
CheapBlur: If true, a faster but less accurate motion blur method will be used. It might look weird when the digits are moving slowly and fluidly (low speed, small RollExponent), but on snappy counters (fast changes, high RollExponent) it is indistinguishable from the more expensive method.
Here is a side by side comparison of the two methods, using a bit overdone motion blur to clearly show the effect. (CheapBlur is true on the left)
The expensive motion blur’s length varies and has a distinct direction, while the cheap blur only changes opacity.
EdgeFixWidth: There are interpolation artifacts between the shifted digit columns, which we can hide with static pixels covering those areas.
The width of the cover up is set here. Its optimal value depends on the size of the counter on screen: the artifact is always two screen pixels wide, regardless of texture resolution/density.
OverlayMap: This texture is laid over the whole counter, using one of two blending modes. It is used for adding lighting effects or fake reflections to the counter.
Blending mode 1 : Blacks darken, whites lighten the texture, 50% grays are invisible. Same as the “Overlay” blending mode in Photoshop.
Blending mode 2 : In this case 50% grays are invisible as well, but as pixels getting closer to black or white, they become more and more opaque. Like using a mask on a layer with “Normal” blending mode.
OverlayStrength: The visibility of the overlay texture.
BlendMode2: If true, the second blending mode will be used.
Here is a typical use of the shader (with a bit exaggerated motion blur):
I animated the RollExponent and Amount parameter by hand, while the BlurAmount is driven by a scripted controller: it checks the speed of the Amount’s change, and sets the BlurAmount accordingly.
Foreword to the shader
As before, I only collapsed nodes where the order of incoming connections is obvious.
There are nodes with the label “RELAY” above them. They don’t actually do anything, their purpose is to make the layout cleaner.
Unfortunately the “multiply by 1″ operations are not discarded by ShaderFX during export, so the nodes should be removed sooner or later. However, if you’re building the shader in Unreal, then you can leave them in, because the shader compiler will optimize the low level code, and remove unnecessary operations.
On several occasions, I used the custom code node to implement more complex math functions. This way the tree is much simpler, and those functions are isolated, so they can be more easily debugged. I won’t really get into details about these custom codes, only describe what they do. If you want to dissect them, I recommend using an application for visualizing functions, so the way they work becomes more apparent.
I used Ivan Johansen’s excellent Graph. I couldn’t have finished the shader without visualization and step by step breakdowns of the math expressions.
The node tree
Hopefully the node descriptions are helpful enough, so here we just step through the main parts of the node tree.
The yellow area is where we generate the base UVs for the counter, based on a few parameters. The computed UV coordinates are linked to the texture map node in the orange box. The cyan part is where we hide the interpolation artifacts. That’s the core mechanic of the shader, everything else is fancy stuff which can be easily removed/replaced.
The first extra is the motion blur. The purple/white box is where the blur factor is computed. Its value will be used in both motion blur implementation. The purple/yellow section is the cheap blur. It doesn’t care about the direction of the movement, only it’s magnitude. The custom node mixes the diffuse and blur textures. When using this method, a more blurred blur texture is recommended, especially if long, fast counting is a common event.
The more accurate but more expensive motion blur implementation is in the purple/red area. It takes the blur texture sampler (in this case the image should only be slightly blurred) and makes four instances of it with different offsets, relative to the original location of the digits. The last step is to average the four blur samples and the original texture.
You can remove the whole motion blur branch if you only need a digital display like this:
At the very end of the tree, in the green box is where the overlay texture is applied, using one of the two blending modes.
Downloads
The textures for all the examples, the 3DS Max 2010 scene and the ShaderFX file can be downloaded from here: CounterAssets
The files are licensed under a Creative Commons Attribution-Share Alike 3.0 Unported License.
FAQ
Q : Why do some digits become ugly after a while?
A : In the string display example above, I hinted that I used four shaders with four digits each instead of one shader with 16 digits. It’s not very convenient, but unfortunately splitting the string into smaller pieces is unavoidable. The reason is simple: at high Amount values, the limited precision of UV operations starts to show.
The effect is visible in the animation, but here is a bigger image showing the issue:
As you can see the letter “D” looks crappy. That digit has the lowest place value, so that column was shifted the most vertically. As a matter of fact, it was shifted 615428 times the normal UV space.
The comma character looks just fine, because it was only shifted 55484 times.
Aside of this visual anomaly, there is one more factor that limits the highest value the shader can display: the resolution of the parameter variable. Max2010 limits the shader parameters to the [-999999, 999999] range. That, combined with a 32 based numeric system, will won’t even allow you to show all characters on four digits, you only get free hand when using three. In theory, it should not be a problem in a game engine where a IEEE 754 Standard floating point variable can handle values up to about 10^37, which is 23 effective places in a 32 based numeral system.
But of course the graphical glitches will ruin the whole thing well before reaching the limits of the floating point variable.
I haven’t found an elegant workaround for the problem yet. However, by sacrificing the roll animation, one can create an LCD kind of display which is immune to the issue. Using the Frac operation, it is possible to make the UVs “wrap around” and jump back to the origin after showing all symbols in a column, instead of moving further and further away.
Q : If all three textures used by the shader are grayscale, then they can be combined into one colored texture, yeah?
A : Yeah.
