• Check out the results of the Techtree Contest #19!
  • Listen to a special audio message from Bill Roper to the Hive Workshop community (Bill is a former Vice President of Blizzard Entertainment, Producer, Designer, Musician, Voice Actor) 🔗Click here to hear his message!
  • Read Evilhog's interview with Gregory Alper, the original composer of the music for WarCraft: Orcs & Humans 🔗Click here to read the full interview.
  • Create a void inspired texture for Warcraft 3 and enter Hive's 34th Texturing Contest: Void! Click here to enter!
  • The Hive's 22nd Icon Contest: Creep Abilities is now concluded, time to vote for your favourite set of icons! Click here to vote!

How texttags are rendered (probably)

Texttags (also known as floating text) are those texts that appear in the 3d game world like "42!" (after a critical strike) or "miss" (after a missed attack), or hovering over a hero to read their name and level, or created from script using the CreateTextTag function. These texts have a 3d world position and they appear to always be facing the camera. I was curious how the game renders them, and at first I thought that the game draws the text into a texture/bitmap and then creates a single camera facing quad with it, or for each character in the text, it created a separate camera facing quad. After experimenting a bit I noticed that texttags have the same size regardless of how far the camera is from them, so I thought that they could be using an orthographic projection. It turns out it was much simpler than that. Their position is simply transformed from world space to screen space and then they are rendered like a text would be rendered by BJDebugMsg.

In some high level hand-wavy code it could look like:
JASS:
mat4 screen_from_world = ...
vec3 world_texttag_pos = GetTextTagPos(our_texttag)
vec4 screen_texttag_pos = screen_from_world * vec4(world_texttag_pos, 1.0)
vec2 screen_pos = vec2(screen_texttag_pos.x, screen_texttag_pos.y) * (1.0 / screen_texttag_pos.w) // perspective divide
ScreenDrawText(screen_pos, GetTextTagText(our_texttag))

We basically reduce the problem of rendering texttags, to that of rendering text on the screen, which unfortunately is a big rabbit hole of its own. Here are some pointers 1, 2, 3, 4, 5, 6, 7 though.

Its funny, Warcraft 3 seems pretty complicated (to me at least), a 3d multiplayer rts game, with single player campaign, save/loading, replays, modding support, custom scripting language, custom ui framework, custom model and texture formats, custom archive format, with many gameplay mechanics interacting in interesting ways, and yet it draws the line on text/font rendering by using a library (FreeType).
 
Their position is simply transformed from world space to screen space and then they are rendered like a text would be rendered by BJDebugMsg.
For what it's worth, I do the same thing in my open source remake of Warcraft 3. So it's nice to hear they do the same as me; perhaps great minds think alike.

I guess if you wanted to be crazy, you could use UI natives on Reforged to draw your own text tags on the screen if you bother to do your own world space to screen space conversions.
 
To draw the text to screen it usually involves one of these 2 approaches

The bitmap approach, where the principle used is to copy glyph art from a sheet onto the screen:
  • Rendering the required glyphs to a texture atlas. This involves caching rendered glyphs that are currently in use and managing space for glyphs at different typefaces such as bold/italic or sizes. The glyphs have to to be 1:1 with native pixels for optimum looks. Glyphs are usually rendered from a true type or similar vector font file.
  • Calculate glyph positions (kerning) for each string to be rendered to screen. In the old days this would likely be the form of a 2D (or even 3D) vector mesh. Nowadays it is probably just a 1D vector of offsets with the actual 2D mesh being calculated from it in a vertex shader. Some sub-pixel maths may be required to make sure each glyph is rendered out unstretched. This is repeated for all strings and stored in a single buffer, the text buffer.
  • Draw the text buffer using a single draw call with reference to the character glyph atlas. Vertex and pixel shaders might be used to process the text buffer into the final screen results. On older hardware where texture sizes were limited to being smaller than native screen resolutions it might require multiple draw calls, one for each texture atlas with each texture atlas having its own text buffer data. There is probably some quad optimisation that can be used to reduce overdraw, I am not familiar enough with modern graphics to know for sure.

Old games like Warcraft III legacy shortcut the first step by using a fixed size pre-rendered character sheet texture. This is a very bad approach to doing this because it does not scale correctly, why big or very small text looked ugly. It also limits character selection to a classic character set style rather than full unicode support, why Russian or Asian text looks garbled in English WC3 legacy. This approach technically has been used since the early days of video gaming such as on old consoles like the SNES and Genesis, but with scaling support coming later such as with the Saturn/N64/PS1 generation. True native resolution fonts were not really supported due to the processor and memory usage requirements.

This approach is extremely efficient since each character is produced by drawing effectively 1 quad to the screen. Further more it is typically possible to draw all characters with just a single draw call with a small input.

The vector approach, where the core principle is to generate a detailed text mesh and render it to the screen:
  • Calculate mesh data for each glyph and store it sequentially in a vertex buffer. This involves caching such data for all glyphs that are currently in use and managing space for glyphs at different typefaces such as bold/italic or sizes. Unique data for each size is needed rather than just scaling the data due to different render hints only applying at certain size ranges. Glyph mesh data is usually constructed from a true type or similar vector font file.
  • Calculate glyph positions (kerning) for each string to be rendered to screen. In the old days this would likely be the form of a 2D (or even 3D) vector mesh. Nowadays it is probably just a 1D vector of offsets with the actual 2D mesh being calculated from it in a vertex shader. Some sub-pixel maths may be required to make sure each glyph is rendered out correctly. This is repeated for all strings and stored in a single buffer, the text buffer.
  • Draw the text buffer using a single draw call with reference to the glyph mesh data. Vertex and pixel shaders are used to process the text buffer into the final screen results. On older hardware where texture sizes were limited to being smaller than native screen resolutions it might require multiple draw calls, one for each texture atlas with each texture atlas having its own text buffer data.

I do not think this approach is as widely used as the highly complex mesh with small triangles would be prone to a lot of overdraw. This principle was used to some extent by old vector displays, or where texture data is more costly than vertex data. Technically the total memory usage should be lower than the bitmap approach. The quality of this approach may be limited due to the inability to represent perfect curves, at least not without complex vertex and pixel shaders with additional metadata being bundled with the glyph mesh data.
 
I guess if you wanted to be crazy, you could use UI natives on Reforged to draw your own text tags on the screen if you bother to do your own world space to screen space conversions.
I reject your craziness and substitute my own. One could compute the inverse of the mat4 screen_from_world matrix (mat4 world_from_screen) and compute a world position such that when a texttag is placed there, the game would draw the text at the desired screen position. In other worlds, one could write their own BJDebugMsg function that could place text anywhere on the screen, although these texttags positions would have to be recomputed every time the camera changes.
Old games like Warcraft III legacy shortcut the first step by using a fixed size pre-rendered character sheet texture.
At least wc3 computes its glyph cache textures dynamically (people can use custom fonts and with custom font sizes). Even older games (like Quake 3 Arena) used prerendered glpyh textures, which results in tiny tiny text when using bigger and bigger game resolutions.
 
Even older games (like Quake 3 Arena) used prerendered glpyh textures, which results in tiny tiny text when using bigger and bigger game resolutions.
Warcraft III, at least in legacy builds, just stretches the character sheet glyph textures. It also did not align the vector mesh or UV correctly when using OpenGL render resulting in artefacts around text glyphs in that graphic mode.
 
Back
Top