Table of Contents

World Text (In-Scene)

A gallery of everything WorldTextComponent can do, one setting per station: billboarding that stays upright, free billboarding, text fixed in place, text lying flat on the ground, world-unit sizing, depth-tested text hidden behind a wall next to text drawn through it, distance fading, and a glow behind the letters in a few colour combinations - a HUD blue, neon, a readability halo and a soft bloom. World text lives inside the scene - it shrinks with distance and geometry can hide it.

The Program.cs file shows how to:

  • Registering the text renderer once: AddWorldTextRenderer
  • Billboarding: KeepUpright versus facing the camera freely
  • Text fixed to a surface or lying flat with Billboard = false
  • Height in world units versus FontSize as sharpness
  • Depth-tested text hidden by geometry, and DepthTest = false to draw through
  • Distance fading with FadeStartDistance and MaxDistance
  • A glow behind the letters with GlowColor and GlowSize, from HUD halo to neon bloom

World Text (In-Scene)

View on GitHub.

using Stride.CommunityToolkit.Bepu;
using Stride.CommunityToolkit.Engine;
using Stride.CommunityToolkit.Rendering.ProceduralModels;
using Stride.CommunityToolkit.Rendering.Text;
using Stride.CommunityToolkit.Skyboxes;
using Stride.CommunityToolkit.Windows;
using Stride.Core.Mathematics;
using Stride.Engine;

// A gallery of everything WorldTextComponent can do. Each station on the ground demonstrates one
// setting, and the text itself names the setting that produced it.
//
// WorldTextComponent is IN-SCENE text: it is positioned by its entity's transform, shrinks with
// distance like everything else in the world, and - by default - is hidden by geometry in front of
// it. For text that must always be readable at the same size, such as a HUD, see
// E03_3D_EntityText.

WindowsDpiManager.EnablePerMonitorV2();

using var game = new Game();

game.Run(start: Start);

void Start(Scene scene)
{
    game.Window.AllowUserResizing = true;

    game.SetupBase3DScene();
    game.AddSkybox();

    // One call, however much text the scene has. Without the renderer, world text simply never
    // appears - there is no error for a missing renderer.
    game.AddWorldTextRenderer();

    // --- Row 1: orientation --------------------------------------------------------------------

    AddText(scene, new Vector3(-4f, 1.2f, -2), text =>
    {
        // The default: turns to face the camera, but swivels about the world Y axis only, so it
        // stays standing like a signpost and never rolls when the camera tilts
        text.Text = "Billboard (default)\nfly around me!";
    });

    AddText(scene, new Vector3(0f, 1.2f, -2), text =>
    {
        // Faces the camera squarely from any angle, including from directly above
        text.Text = "KeepUpright = false";
        text.KeepUpright = false;
    });

    AddText(scene, new Vector3(4f, 1.2f, -2), text =>
    {
        // No billboarding: the text keeps its entity's rotation, so it foreshortens and disappears
        // edge-on like any other surface in the scene
        text.Text = "Billboard = false\n(fixed in place)";
        text.Billboard = false;
    });

    // Lying flat on the ground, like a road marking: no billboard, rotated face-up
    var floor = AddText(scene, new Vector3(0f, 0.02f, 1.5f), text =>
    {
        text.Text = "FLAT ON THE GROUND";
        text.Billboard = false;
        text.Height = 0.6f;
        text.TextColor = Color.Cyan;
    });

    floor.Transform.Rotation = Quaternion.RotationX(MathUtil.DegreesToRadians(-90));

    // --- Row 2: size ---------------------------------------------------------------------------

    AddText(scene, new Vector3(-4f, 0.6f, 4), text =>
    {
        // Height is world units - this text is a quarter metre tall and always will be. FontSize is
        // only sharpness: raise it if text viewed close up looks soft.
        text.Text = "Height = 0.25";
        text.Height = 0.25f;
    });

    AddText(scene, new Vector3(0f, 1.0f, 4), text =>
    {
        text.Text = "Height = 1";
        text.Height = 1f;
        text.TextColor = Color.Orange;
    });

    // --- Row 3: depth --------------------------------------------------------------------------

    // A wall with text behind it, twice: depth-tested text is hidden by the wall until you fly
    // around it, while DepthTest = false text shows through everything
    var wall = game.Create3DPrimitive(PrimitiveModelType.Cube, new Primitive3DEntityOptions
    {
        EntityName = "Wall",
        Size = new Vector3(3f, 2f, 0.2f),
    });

    wall.Transform.Position = new Vector3(6f, 1f, 4f);
    wall.Scene = scene;

    AddText(scene, new Vector3(6f, 1.3f, 6f), text =>
    {
        text.Text = "DepthTest = true\n(hidden by the wall)";
        text.Height = 0.35f;
    });

    AddText(scene, new Vector3(6f, 0.5f, 6f), text =>
    {
        text.Text = "DepthTest = false\n(drawn through the wall)";
        text.Height = 0.35f;
        text.TextColor = Color.Lime;
        text.DepthTest = false;
    });

    // --- Row 4: distance -----------------------------------------------------------------------

    AddText(scene, new Vector3(-4f, 1.0f, 8), text =>
    {
        text.Text = "Fades from 10, gone at 18\n(walk backwards!)";
        text.Height = 0.4f;
        text.FadeStartDistance = 10;
        text.MaxDistance = 18;
    });

    // --- Row 5: glow (behind row 1, on the far side of the ground, in the default camera view) --

    // A glow is the text drawn again in GlowColor, offset in a ring behind the letters, so it
    // follows every glyph and scales with the text. GlowSize is in font pixels at FontSize, so the
    // same value looks the same at any Height; the glow colour's alpha is its strength.

    AddText(scene, new Vector3(-5.5f, 1.1f, -5), text =>
    {
        // The HUD look: light text over a deeper glow of the same hue reads as lit, not painted
        text.Text = "GlowColor = DeepSkyBlue\nGlowSize = 4";
        text.Height = 0.4f;
        text.TextColor = new Color(170, 225, 255);
        text.GlowColor = Color.DeepSkyBlue;
        text.GlowSize = 4;
    });

    AddText(scene, new Vector3(-2.5f, 1.1f, -5), text =>
    {
        // Neon: a contrasting glow, wide enough to bleed well past the letters
        text.Text = "GlowColor = OrangeRed\nGlowSize = 8";
        text.Height = 0.4f;
        text.TextColor = Color.Yellow;
        text.GlowColor = Color.OrangeRed;
        text.GlowSize = 8;
    });

    AddText(scene, new Vector3(0.5f, 1.1f, -5), text =>
    {
        // Readability rather than looks: dark text with a tight light halo stays legible over a
        // busy or dark background, the way a subtitle does
        text.Text = "GlowColor = White\nGlowSize = 2";
        text.Height = 0.4f;
        text.TextColor = Color.Black;
        text.GlowColor = Color.White;
        text.GlowSize = 2;
    });

    AddText(scene, new Vector3(3.5f, 1.1f, -5), text =>
    {
        // Bloom: the same colour as the text, wide and half strength - the alpha sets how strong
        // the glow is at the letters before it fades
        text.Text = "GlowColor = Magenta, alpha 128\nGlowSize = 12";
        text.Height = 0.4f;
        text.TextColor = Color.Magenta;
        text.GlowColor = new Color(255, 0, 255, 128);
        text.GlowSize = 12;
    });
}

// Creates one gallery station: an entity carrying a world text, handed over for its own settings.
Entity AddText(Scene scene, Vector3 position, Action<WorldTextComponent> configure)
{
    var component = new WorldTextComponent
    {
        Text = string.Empty,
        Height = 0.35f,
        Alignment = Stride.Graphics.TextAlignment.Center,
    };

    configure(component);

    var entity = new Entity("WorldText") { component };

    entity.Transform.Position = position;
    entity.Scene = scene;

    return entity;
}