Creating high-quality game planets
Crafting planets for a space game is harder than it sounds. Years ago, when Helium Rain was in development, I realized a fun fact about planets - when you orbit them at a low altitude, you can only see a really tiny part of the surface. For example, France, a country that's about 1,000 km (600 mi) wide, barely fits in an astronaut's field of view while on the International Space Station. The Earth's perimeter is 40,000 km (25,000 mi) wide. This has surprising implications for game development!
Indeed, if you were building a realistic space game orbiting Earth, you would want to display France with 4K resolution - about 4,000 pixels for those 1,000 km, or 4 pixels per kilometer (6.5 per mile). Since planets are usually mapped to textures the same way we create maps of the Earth - using a rectangular Mercator projection - this means our 40,000 km planet now needs a 160,000 pixel wide texture, that is also 80,000 pixels high. These numbers are absurd for game development - we're looking at 50GB of uncompressed texture space, and the tooling required for such work is prohibitive. Even the game-compressed texture format is over 6GB - it won't fit entirely into most GPU's video memory. And that is before you realize a unique texture won't cut it:
- the main texture with the planet's color is called the albedo map;
- you also need a normal map to add lighting details;
- you also need a roughness map to add more surface detail.
Clearly a better approach is needed.
Detail texturing
Astral Shipwright uses an Earth-sized planet and allows low orbits - lower than the Earth's ISS. The calculation above still stands, but we need to drastically cut down on texture space to make it viable at a high quality. The first obvious technique is called detail texturing, and works by subtly blending a detail texture, a tileable grain texture, with the main maps. Its purpose is not to enhance the image detail, but to add a higher-resolution pattern so that the pixels are less noticeable.
This detail texture is injected both into albedo (color) and into roughness (surface detail). A secondary detail texture is used for the normal map - adding the same detail grain to the surface lighting.
On top of this common technique, no dedicated roughness map is used. Instead, the planet's material - the shader outputting the planet's characteristics into the engine - derives roughness from the planet's albedo, using a simple set of rules. This is essentially invisible and allows for one entire texture less, which at these resolutions is nothing to laugh at. We are left with one albedo map and one normal map. The normal map itself is procedurally generated from the albedo to ensure everything lines up well, though it is done locally, not in-engine - the normal map still exists as a full resolution texture.
The equatorial band
Detail texturing visually allows for a much lighter surface resolution - 65,635 x 32,384, or 64k x 32k. But we can very easily remove half of that with a simple trick: rectangular projections of a planet's surface stretch polar areas by absurd amounts. Greenland famously appears incredibly larger on maps than it actually is!
On top of that, Astral Shipwright is strictly contained to the ecliptic plane, the plane that intersects the equator. This means the polar areas aren't just smaller than they appear on the planetary textures - they actually never appear at all. Clearly we need to optimize this.
The solution for this problem is to make our planetary textures half as high. The central half around the equator is covered by high-resolution data, while the poles use a much smaller resolution. The final planetary resolution is down to an almost pedestrian size of 64k x 16k. This central band texture is where most of the work occured with multiple days of painting high resolution detail.
Virtual textures
This is where Unreal Engine kicks in. Helium Rain could only rely on traditional textures that are always loaded entirely into memory. Unreal Engine introduced virtual texturing a few years ago - allowing textures to be loaded at full resolution for some parts, while keeping other parts almost entirely unloaded. This is extraordinarily useful for planets - obviously you never see more than half of a planet, and usually even less than that. In low orbit, you would only need 5% to 10% of the entire space; and in high orbit, you need a much lower resolution. Virtual textures are a total win!
The only problem left is authoring and importing such a texture. As you might guess, very few file formats allow for such large files, and even less can actually be opened in Unreal Engine. As a result, an intermediary format needs to be used: source textures are cut into 8192px sized tiles, named using the UDIM convention, and bulk-imported into Unreal Engine.
The final textures are reasonably sized - they take less than 1.5GB on disk together after compression, and the quality is good.
Atmosphere and clouds
Final touches to our planet involve the atmosphere. Again, Unreal Engine proved invaluable by providing an easy-to-use atmosphere simulation that works out of the box and very efficiently.
Clouds were done in the planet material directly. They are made with flow maps - additional textures that control the speed and direction of movement across the planet. Though they don't look as good as the Unreal Engine volumetric cloud system, they are however really fast to render - essentially zero cost - and look acceptable. This might be a future area of work.
Thank you for your interest in this first devblog entry!