This will be a large post, so first I’ll dilute the wall of text with a screenshots of new landforms, recently added to the engine. They are based on Rodrigo’s mods, so far I added the rivers and rifts (faults) for desert planets. Parameters for rifts are also added to the editor and scripts. This is not a direct copy of Rodrigo’s code, but a modified, optimized and adapted to the new engine.

Flying above new terrain. I am still not satisfied with color/material transitions.



I’m continue to work on the updated terrain engine. The quadtree bypass logic is completely redesigned, the geometry generation is separated from the texture generation (in some sense), the priority system is introduced, the error metric is changed. All this resulted in faster terrain loading with less memory consumption and higher visual quality (level of detail).

Terrain in SE is based on quadtree. To cover the sphere, and for convenient working with textures, the so-called “cube-sphere” is used – a cube, each face of which is represented by a quadtree, “folded” to a sphere by a simple transformation of vertices:

Thus, each planet in the SE consists of six separate quadtrees. The resolution of the textures of the upper (zero) level is 256 * 256 pixels, the resolution of the geometry grid is 33 * 33 vertices. If the camera is close enough to the planet, so that the texture is too stretched (see about the error metric below), then the corresponding quadtree node is subdivided into 4 children nodes (the level 1): each of them has its own textures and geometry, but the same resolution. The process continues until one no longer need to split nodes (textures of all nodes is less than 256 pixels on the screen). Textures of nodes are generated or loaded from the disk on demand, i.e. only those that are really visible at the moment. Thus, theoretically it is possible to obtain infinite level of details of planets. But in practice, it is limited by the existing set of textures (for real planets), or by the accuracy of a floating-point numbers (for procedural ones). So, for an Earth-sized planet, the maximum level of quadtree is 12: when trying to generate textures for the level 13, artifacts appear. The resolution of one “face of the cube” (one quadtree) is 256 * 2 ^ 12 = 1048576 (1 terapixel), and in the meters along the equator or meridian this corresponds to 9.5 meters per pixel. Because resolution of geometry (33 * 33 vertices) is 8 times smaller than the resolution of textures (256 * 256 pixels), one can create 3 more “virtual” levels to bring the geometry to maximum detail. The textures for these virtual levels are generated somewhat trickier: the heights and normal map is used from the last, 12th level, i.e. not have additional details, but the color map can be generated as usual, because the floating-point accuracy problem for the color map is not so severe. Resolution of color maps is obtained is 8 times higher, i.e. about 1 meter per pixel, but they are somewhat “cheat”.

Thus, the level of detail of 1 meter per pixel is the fundamental limitation of the SE engine. In other engines it is overcome differently, for example, the generation of textures for the last levels using double-precision numbers. However, they are very slow on most GPUs. Instead, simulation of double-precision math can be used, implemented on single-precision numbers. In SE, I acted differently – just added a little procedural noise when rendering a planet. Now this noise is replaced by a new detail texturing system, which enables a resolution down to 1 millimeter per pixel, and achieving more realistic result than a completely procedural textures. Because procedural generation of realistic textures of rock, pebbles, grass, sand and others is way too complex and expensive.

Next is the task to improve the geometry resolution: after all, 10 meter-sized triangles are way too large, this is especially evident in virtual reality. I plan to do this with tessellation, but this is a work for the next SE version. For now, lets talk about recent changes in the terrain engine:

Geometry for the terrain nodes is created from the height map, as before. But since the geometry resolution is 8 times smaller than the texture resolution (256 * 256 pixels), there is no need to generate a height map for each quadtree level, one can “jump” over 3 levels (2 ^ 3 = 8). Since engine has an ability to render terrain node using a textures of its ancestor (which is used for three last “virtual” levels), then one can try to generate textures only for every 3rd level. This leads to a very fast, almost instant loading of the terrain, although in some places its textures turn out to be very blurry. But geometry (silhouettes of mountains) immediately appears in full detail.

Normal maps are created from the height maps for lighting. In addition, a color (albedo) maps also needed, and, if the planet has cities or volcanoes, the emission map. For visual perception, the normal map is much more important than the color map, so one can introduce a generation order priority – generate the normal map first, and only after that generate the color and emission maps.

After this fast initial steps, one can generate textures for the skipped nodes. And, by the way, only for those nodes that are actually rendered (i.e. if some node is split up into 4 descendants, it will not be rendered – its descendants will be rendered instead). This further reduces the list of textures that need to be generated, so speeds up loading. One can use the priority system on this stage as well: first, generate textures of the lower (more detailed) nodes – they are closer to the camera. Another priority level is the distance from the node to the camera (because nodes of the same level can have different distance, so the priority by level only is not enough). This was the closer areas of the terrain are loaded first.

In addition to the indicated textures, for the last levels of quadtree are generated still so-called. splat textures indicating any materials and how to impose in this paragraph (see the previous terrain blog post).

The “error metric” is a condition in which the terrain node should be subdivided into 4 children. Previously, this condition was “the size of the texture on the screen in pixels”: if it is greater than the resolution of the texture (256 pixels), for example 300 pixels, then the texture starts “washing out”, and the node should be split into 4 children. But calculation of the “size on the screen” is tricky, a terrain node is not a cube and even not a quadrilateral. Therefore, the error metric code was very catchy, with different formulas for nodes of different levels. Now it is replaced by the distance to the edge of the node, divided by the size of the node, which is calculated in the “unwarped” coordinates (as if the sphere of the planet was unwarped back to a cube). This automatically gives the correct result for nodes of any level and any position, including those near the corners of the “cube”, which are about half as large as in the center of the faces of the “cube”. It needs only to add a factor that scales distances depending on the camera field of view and the terrain LOD setting (the larger the LOD – the lesser the distance of division, the sharper the textures, but more video memory and GPU performance is needed).

Also, I removed the code, which allowed the engine to draw only a part of the ancestor node. After the node was split into 4 children, an error metric was calculated for each of them, and if one of them was smaller than 256 pixels, then it was not drawn, and instead of it a piece of the ancestor node was drawn. This reduced the number of polygons, which increased performance on a weak or old hardware. But, as a consequence, a piece of ancestor node with “washed” textures was seen – after all, by its own error metric it should be already split up. This reduced the visual quality of the landscape. Another unfortunate consequence is the need to generate/load the ancestor’s textures, which increased the requirements for video memory and the terrain loading time. But now is the year 2017, GPUs are fast enough, so one can get rid of such optimization. This increases terrain fidelity and allows memory optimization as described in the point 3 above (no need to load texture of a node that is split up).

For the detail textures presets, I’ve implemented a system that can simplify modding, by removing requirement of describing all textures for each surface type. For each planet class (airless terra, desert terra, etc) one have a “default” preset script with all carefully configured textures for all biomes/materials and their colors (palette). Other “alternate” scripts for the same planet classes may contain only needed changes, for example description of alternate texture for one biome, or alternate colors. All missing data is loaded from the default preset, link to which is specified in the alternate preset script. Material preset editor automatically saves the script file in that way: writes there only difference from the default preset. This should simplify “modding in the notepad”, for example converting old planets palette file to the new system.

You can see the terrain loading speed on this video. It was captured using external software (GeForce Experience), not by the built-in video tool, so all lags and loading latency is visible. In the second part, I toggled the debug overlay showing the octree nodes edges. Different colors represents nodes of different levels, while white splashes indicated nodes which are currently generating textures.

Discuss this post on the forum.