Every texture the engine loads gets mip levels, smaller copies of itself down to 1 by 1. A surface far away or seen at a slant samples a level whose pixels match the screen’s, so it stays even rather than breaking into moiré and shimmering as the camera moves. Anisotropic filtering keeps such surfaces (a floor, a road, a wall seen along) sharp.
A built game also gets compressed copies of its pictures, in formats the graphics card reads as they are: a quarter to an eighth of the video memory, and quicker to load, since nothing is decoded.
Nothing needs setting up; a project’s pictures are used as they are.
Mip levels
Mip levels are made when a texture is loaded, from what it holds:
| Kind | Which | Averaged |
|---|---|---|
| Colour | Albedo, emissive and interface pictures; anything not below | In linear light, as the eye sees it: a black and white checker shrinks to a mid grey, not a dark one |
| Data | A material’s metal / roughness and occlusion maps, lightmaps | As stored |
| Normal | A material’s normal map, a model’s normal images, and pictures named with “normal” (rock_normal.png) |
As directions, made unit length again |
--no-mipmaps loads textures without them, to compare (the Sandbox’s Scenes/Textures.tscene, a checker floor, shows the difference).
Compressed copies
File > Build (and tools/build_player.py) makes <picture>.ktx beside each picture: BC1 for opaque ones and BC3 for see-through ones, with all their mip levels, made by bgfx’s texturec (which the editor package ships). The engine loads the .ktx in place of the picture; the picture stays in the build for code that reads its pixels (colour grading lookups, the window icon).
Left alone:
- normal maps (these formats store colours as 5-6-5 bits, which bands directions badly);
- pictures under
UI/andFonts/(the project settingtextureCompressExclude = UI/*;Fonts/*inproject.tallow,;-separated patterns); - images inside glTF / GLB models;
- web builds (browsers do not all read the same compressed formats).
The Build window’s Compress textures turns it off for a project (compressTextures = 0), and --no-texture-compression for one build. python3 tools/compress_textures.py <folder> compresses a folder by hand. A running game’s log ends with how many textures it made, how many were compressed and their size on the graphics card. --no-compressed-textures ignores the .ktx files, to compare.
BC7, which looks better than BC1, is not used: bimg’s BC7 encoder takes most of a minute for one 512 by 512 picture.
Quality
Two quality settings (docs/quality.md) belong here:
| Setting | Low | Medium, High, Ultra |
|---|---|---|
Texture Detail (textureDetail) |
half (the largest level left out: a quarter of the memory) | full |
Anisotropic Filtering (anisotropic) |
off | on |
Texture Detail applies to textures loaded after it changes; the editor’s Window > Graphics Quality shows it, and how many textures there are and their size.
In code
let handle = Textures.Create(pixels, width, height, .Data); // RGBA8 pixels, with mip levels
Textures.Hint("Textures/mask.png", .Data); // before it is loaded
let kind = Textures.KindOf("Textures/rock_normal.png"); // .NormalNetworking
Nothing to send: textures are drawn by each machine for itself. A dedicated server loads none.
Compatibility
Projects, scenes and materials are unchanged. Distant textures look smoother than before. Built games are larger by the .ktx copies (about the size of the pictures again) and use far less video memory.
Limits
- Mip levels of large textures loaded in the background are made on the main thread when the texture is added.
- A colour texture whose kind cannot be told (a mask with an ordinary name) is averaged as colour;
Textures.Hintsays otherwise.
Tests
This guide is docs/textures.md in the engine’s repository. Spotted a mistake? Open an issue.