Procedural texturing: Difference between revisions

From FlightGear wiki
Jump to navigation Jump to search
Line 91: Line 91:


For slope line noise, the terrain is segmented in patches, and based on the normal of the terrain, a line along the direction of steepest descent is drawn on the patch, with precise line position and thickness modulated by random numbers. This type of noise mixed into Perlin noise can give a good impression of snow distributions on steep slope or erosion patterns on terrain.
For slope line noise, the terrain is segmented in patches, and based on the normal of the terrain, a line along the direction of steepest descent is drawn on the patch, with precise line position and thickness modulated by random numbers. This type of noise mixed into Perlin noise can give a good impression of snow distributions on steep slope or erosion patterns on terrain.
=== Examples ===
To the left is an example for domain noise scaling the size of trees in patches of few hundred meters size, thus giving the impression of the different age of forest patches in a managed forest. On the right is an example of slopeline noise, emphasizing the vertical structure of the granite walls in Yosemite National Park.
[[File:Domains_forest.jpg|400px|Domain noise for forest]] [[File:Southwest08.jpg|375px|Slope line noise for Yosemite granite]]


=== Terrain probing functions ===
=== Terrain probing functions ===

Revision as of 10:43, 14 May 2014

Procedural texturing is a set of techniques where the color of a pixel is not determined by a texture image but fully or partially by evaluating a mathematical function. This article describes why procedural texturing is useful, what it can and can't do and how it is used and configured in FlightGear.

The terrain texturing challenge

The FlightGear terrain is segmented into different landclasses (Urban, Ocean, Glacier, ShrubCover,...) which describe what the terrain at a location is like. In the default texturing scheme, the file materials.xml is used to describe (among other things) what texture sheet is mapped onto a given landclass.

A typical terrain texture sheet has 512x512 pixels and is mapped onto a 2000m x 2000m sized area, which means that each pixel covers a 4m x 4m area in unsloped terrain. For the sake of the argument, assume the texture has a size of 300 kB in compressed form which expands to about 1 MB in the GPU memory after mipmapping.

The optimal distance to view the texture is when every texture pixel corresponds to a pixel on the screen, as then the maximum possible detail of the texture is exploited. Assuming a 60 deg field of view looking straight down onto the texture, that distance is about 2500 m.

If the texture is seem from a much larger distance, it does not large enough to cover the terrain, and hence it is automatically repeated. From about 3 times the optimal distance, this repetition creates a visually rather pronounced pattern which is referred to as tiling:

An example for texture tiling

If the texture is seen from a much smaller distance, a single texture pixel covers multiple screen pixels, which makes the terrain look coarse-grained and unrealistic:

An example for low texture resolution

A third problem occurs because the boundaries of the landclasses are vector data, i.e. they appear as unnaturally sharp division lines between different textures.

Thus, the main challenges for realistic terrain texturing can be summarized as the three areas

  • tiling
  • resolution
  • boundaries

Addressing the challenges

Tiling and resolution are correlated problems, i.e. many solution to one tend to make the other worse. For instance, resolution can easily be dealt with by mapping the texture sheet onto a smaller area, say 500m x 500 m, which gives a pixel size of 1m. However, then tiling becomes apparent from just 1500 m distance, i.e. almost inevitably.

Tiling is also reduced if the variation of contrast inside a texture is reduced, i.e. the texture becomes more 'dull' and the pattern repetition is less obvious:

An example for texture tiling Reduced contrast makes the problem less apparent

However, contrasts within a texture are the best way to distract the eye from the sharp landclass boundaries, so if one uses low contrast textures, the landclass boundary problem gets often worse.

Tiling can also be reduced at the expense of texture detail if a single texture sheet is mapped onto a larger area.

To some degree, all problems lessen with increased texture resolution - using 4096x4096 texture sheets allow 0.5m x 0.5m resolution if mapped onto the same areas - but memory consumption quickly becomes as issue as a single texture sheet of that size uses ~64 MB of graphic memory already.

Photo texturing

The tiling problem can be solved if each 2000m x 2000m area to be textured does not get the same texture sheet. If aerial imagery is used (see Photoscenery) then also the landclass boundary is solved. However, this inevitably creates a resolution problem. To see this, consider the following numbers: On a clear day, the visibility from airliner cruise altitude can easily be 120 km. This means that 45.000 square kilometers of terrain are visible at any given time. Using 2000mx2000m mapped texture sheets as in the example above, unique terrain texturing then needs 11.000 different texture sheets with a size of 1 MB each in graphic memory, or about 11 GB of graphic memory, quite exceeding the capabilities of modern graphic cards. Assuming that about 1 GB of graphic memory is available for terrain textures (clouds, models, the cockpit,... also need memory), the individual pixel must have a size of 12 m x 12 m or larger, which means that photoscenery can either not be used for large visibility, or has a terrible resolution from close-up, or uses a LOD scheme (the above photoscenery would still use 3 GB of harddisk space though).

Photo-texturing has other disadvantages - for instance the photographs are usually taken in a particular season in certain light, so the scene may easily seem unrealistic or wrong in a different season or at a different time of the day. FlightGear texturing also encodes meta-information (where on a texture to place trees or buildings) - such meta-information can not easily be encoded in photoscenery.

Procedural texturing

Procedural texturing is a solution to both the tiling and the resolution problem, and partially also for the boundary problem. A mathematical function, unlike a lookup table (which a texture in essence is) does not end at any boundary, i.e. it can simply be used across the whole scene without ever creating a repetition. Using a suitable noise function to mix various textures, a complete de-tiling effect can be achieved (this is really the same scene):

An example for texture tiling Procedural texturing removes the tiling

A function can also be evaluated at any scale resolution. Unlike a texture which needs to be stored regardless if it is used or not, a function only needs to be evaluated for every screen pixel, i.e. the workload and memory requirement do not grow quadratically with the needed resolution but only linear. This scaling allows to dynamically add details to the scene as one gets closer (this is really the same scene):

An example for low texture resolution Procedural texturing adds details at close range

Building blocks of procedural texturing

Mathematical functions can describe quite complex structures, but in order to be rendered in real time, the function has to evaluate fast enough. This means that 'simple' textures like snow, or possibly sand or rock can be completely procedural, while more complex shapes like agriculture which shows fields, crops, access paths and drains can remain a texture with some structure added procedurally. In general, a mixture of textures and functions is used for best effect.

The most important building blocks of procedural texturing are noise functions. A noise function is a function which takes the coordinates of a pixel as input and returns a pseudo-random number between 0 and 1 as output. Noise functions aren't uniquely defined, and there are several possibilities:

Perlin noise

Perlin noise is noise which exists at a certain scale (say 10 m). It is built by dividing the scene into a 10m x 10 m grid, creating random values at each grid point and interpolating smoothly between the grid points. The function created that way varies typically at a scale of 10 m, but not at much smaller scales and averages to 0.5 at much larger scales. Perlin noise isn't so useful at just a single scale, but noise at several scales added together can achieve a very realistic effect of roughness and structure. Finding the correct noise frequencies and amplitudes for blending Perlin noise to a realistic appearance is an art rather than a computational task.

Sparse dot noise

For sparse dot noise, the terrain is divided into a grid at certain scale, then random values at the grid edges are used to determine if a dot is placed into the cell, where into the cell it is placed and what radius it has. Based on the distance to the dot, the function returns 0 if the point probed is far from the dot and 1 at the dot position.

From the superposition of several sparse dot distributions, dense dot distributions can be created. Note that a sparse dot distribution does not average to 0.5 but, dependent on the details of the implementation, to a much lower value.

Stratified noise

Stratified noise uses the terrain mesh altitude information to draw bands of constant altitude which each have a random number associated. The bands are then distorted in width by an altitude-dependent random number and modulated with an (xy)-position dependent random number. Stratified noise is rather useful for some types of rock formations.

Domain noise

The idea of domain noise is to segment the terrain into discrete patches, each of which has a random number associated. Unlike for Perlin noise, the number is not interpolated, it suddenly jumps at the domain boundary to a different value.

The FG implementation of domain noise uses a regular grid at some wavelength, then distorts the grid points by random numbers and uses Voronoi tiling to determine the domains within the distorted grid. This creates domains with a controllable irregularity which are all about the same size.

Domain noise is very useful for man-made patterns, e.g. managed forest, agriculture or cities.

Slope line noise

For slope line noise, the terrain is segmented in patches, and based on the normal of the terrain, a line along the direction of steepest descent is drawn on the patch, with precise line position and thickness modulated by random numbers. This type of noise mixed into Perlin noise can give a good impression of snow distributions on steep slope or erosion patterns on terrain.

Examples

To the left is an example for domain noise scaling the size of trees in patches of few hundred meters size, thus giving the impression of the different age of forest patches in a managed forest. On the right is an example of slopeline noise, emphasizing the vertical structure of the granite walls in Yosemite National Park.

Domain noise for forest Slope line noise for Yosemite granite

Terrain probing functions

The underlying terrain can also be used as input for functions in a different way. Commonly used functions are for instance the altitude of a terrain pixel (for instance to determine the snow line) or its slope (to place rock textures on steep slopes). Since the underlying vector geometry of the terrain mesh leads to very sharp division lines, usually terrain probing functions are mixed with some noise for a more realistic appearance.

Pixel color postprocessing

Many environmental effects can be simulated at the simple expense of post-processing the color of a terrain pixel. For instance, mixing every pixel with a constant dust color can give the terrain a dusty appearance, mixing the final pixel color with a mossy green can give it an overgrown appearance. This is similar to the way pixel color is adjusted for fog, but unlike fog effects environment pixel color postprocessing does not involve distance computations between eye and pixel, rather the color is adjusted independent of the distance (but possibly dependent on terrain gradient or altitude).

Procedural terrain texturing in Flightgear

As of Flightgear 2.10+, procedural texturing of the terrain is done by a general terrain shader which is configurable from materials.xml. The creation of separate effects for various terrain types is not required (but still possible if so desired).

(Currently the following requires that Atmospheric Light Scattering is on! The full range of features described below will become available after the feature freeze for 2.10 is lifted.)

Basic texturing scheme

The texturing scheme is controlled on a per-landclass basis from materials.xml. The relevant sections for procedural textruring in each material declaration are <texture-set> and <parameters>. Here the <texture-set> block controls what textures are used whereas the <parameters> block controls how these textures are used.

Another important part are <xsize> and <ysize> (the sections which describes to which size in meters the base texture is mapped). The procedural texturing shader computes the size of all used textures relative to the base texture size, so if a base texture is declared to have a size of 500 m rather than the default 2000 m, the resolution of the hires overlay or the grain texture will automatically also be 5 times higher then default - which may or may not be desired.

The individual textures which can be used are:

Base texture

The base texture is used as in the default scheme to give the basic appearance of a landclass. The base texture is assumed to be opaque, and its alpha channel is hence not evaluated to transparent, but rather used to encode where the autumn color effect should be used. An alpha value of 0.5 is appropriate for deciduous forests which should get a strong red-orange appearance in autumn, a value of 0.8-0.9 is more appropriate for grass surfaces.

Base Texture

The base texture can take some additional parameters. The first is intrinsic wetness. If the quality level of the shader supports it, this will make the texture appear wet and add sparkling specular reflections to the terrain even if the surrounding terrain is dry. The dust resistance acts in the opposite way, it is intended for irrigated terrain in desert areas and is a multiplier for the dust effect, i.e. a value of 0.0 will make a landclass completely impervious to dust. Finally, rock strata represents an integer flag whether a stratified pattern will be drawn on vertical surfaces or not.

  • declaration:
<texture-set>
 <texture>base_texture.png</texture>
</texture-set>
  • parameters:
<parameters>
 <intrinsic_wetness>0.0</intrinsic_wetness>
 <dust_resistance>1.0</dust_resistance>
 <rock_strata>0</rock_strata>
</parameters>

The default value of intrinsic wetness is zero, the parameter can range from 0 (dry) to 1 (very wet, lots of open water). The defaul value of duse resistance is one, and the parameter can range from 1 (gets dusty like everything else) to 0 (impervious to dust).

The overlay texture

The overlay texture is a texture at a similar resolution level which is mixed with the base texture to achieve de-tiling or gradient effects. By default, it covers about 50% of the available scene. As for the base texture, the alpha channel of the overlay texture encodes color rotations in autumn.

The overlay Texture

The overlay texture can be used in two different modes or a mixture of them. The control parameter is <transition_model> - if this parameter is set to 0, the transition between base and overlay texture is determined by a noise function. If the parameter is set to 1, the slope of the terrain determines the transition and the overlay texture is only used on steep slopes. A setting of 0.5 uses a mixture of noise and slope information to drive the transition.

  • declaration:
<texture-set>
 <texture>base_texture.png</texture>
 <texture n="12">overlay_texture.png</texture>
</texture-set>

The default texture selected is void.png which corresponds to no overlay texture.

  • parameters:
<parameters>
 <transition_model>0.5</transition_model>
</parameters>

The default value is 0.5, the parameter can range from 0 (random transition) to 1 (gradient-driven transition).

The hires texture

The hires texture adds patches of a texture at much higher resolution into the mix, which both add to de-tiling and lead to a better experience of the scenery from close-up. The hires textured patches will however inevitably appear featureless from large distance and will show tiling themselves, so the amount of hires texture as well as the hires texture sheet itself must be designed carefully.

The areas covered by the hires texture will (partially to mask tiling of the hires patches) receive a stronger bumpmap and some distortion than the rest of the terrain by the shader.

As for the base texture, the alpha channel of the hires texture encodes color rotations in autumn.

The hires Texture

The parameter <hires_overlay_bias> controls the fraction of terrain covered by the hires texture patches - by default about 40% of the scene will be covered. Positive or negative biases can make the fraction arbitrarily small or large.

  • declaration:
<texture-set>
 <texture>base_texture.png</texture>
 <texture n="11">hires_texture.png</texture>
 <texture n="12">overlay_texture.png</texture>
</texture-set>

The default texture selected is void.png which corresponds to no hires texture. The hires texture does not require the overlay texture to be declared.

  • parameters:
<parameters>
 <hires_overlay_bias>0.0</hires_overlay_bias>
</parameters>

The default value is 0.0, the parameter can range from -0.5 (strong suppression of the hires overlay texture) to +0.5 (strong enhancement of the hires overlay texture).

The gradient texture

The gradient texture is exclusively used for steep terrain gradients (significantly steeper than the point at which the overlay texture may be used) to account for the fact that beyond a certain slope, terrain can not have vegetation cover and hence landclass information needs to be corrected if necessary.

The default texture is rock.png which is hence used for all landclasses unless specifically declared otherwise. The default may be inappropriate in some regions (volcanic terrain with different rock hue, tropical terrain in which even steep slopes are covered) - the gradient texture then needs to be declared a different texture for all materials in the area. Declaring void.png will not use a gradient texture.

The gradient texture does not use any parameters.

  • declaration:
<texture-set>
 <texture>base_texture.png</texture>
 <texture n="11">hires_texture.png</texture>
 <texture n="12">overlay_texture.png</texture>
 <texture n="13">gradient_texture.png</texture>
</texture-set>

The grain texture

The grain texture adds a grainy pattern to the terrain at close distances, giving the appearance of better resolution. The grain texture parses the texture alpha channel as transparency (since it just superimposes a pattern), so here the alpha channel can not be used to encode vegetation effects, and the grain texture may not have a strong color.

The grain overlay texture

To allow fine-tuning of the grain effect, the texture can be multiplied with the <grain_strength> parameter which sets the overall alpha of the grain effect - a low value works best on bright base textures whereas dark base textures may require a stronger grain.

The default grain texture is grain_texture.png which adds a vegetation-like grain. This can be replaced with other grain patterns or void.png for no grain where appropriate.

  • declaration:
<texture-set>
 <texture>base_texture.png</texture>
 <texture n="11">hires_texture.png</texture>
 <texture n="12">overlay_texture.png</texture>
 <texture n="13">gradient_texture.png</texture>
 <texture n="14">grain_texture.png</texture>
</texture-set>
  • parameters:
<parameters>
 <grain_strength> 0.5</grain_strength>
</parameters>

The default value is 0.5 and the parameter range is 0 (completely transparent) to 1 (completely opaque).

The dot texture

The dot overlay uses sparse dot noise to create detail in the scene. Sparse dot noise textured with a rock texture can give the appearance of boulders scattered across the terrain, the same noise textured with a grass texture can be used to create round vegetation patches.

The dot overlay texture creating grass patches

Both the dot size and the dot density can be controlled by the user. The range out to which the dots are visible is automatically adjusted based on the dot size scale. By default, the dot texture is set to void.png, which means that no dot texture is used.

  • declaration:
<texture-set>
 <texture>base_texture.png</texture>
 <texture n="11">hires_texture.png</texture>
 <texture n="12">overlay_texture.png</texture>
 <texture n="13">gradient_texture.png</texture>
 <texture n="14">grain_texture.png</texture>
 <texture n="15">dot_texture.png</texture>
</texture-set>
  • parameters:
<parameters>
 <dot_density> 1.0</dot_density>
 <dot_size> 1.0</dot_size>
</parameters>

The default value for <dot_density> is 1.0, the parameter can be set in the range from 0.0 to 1.0 (values above 1.0 do not increase the dot density further). The default value for <dot_size> is also 1.0, which creates dots at size scales varying between 10 cm and few m. The parameter is a multiplier for these default values, i.e. at a value of 10 dots of ~10-20 m size are created.

Examples

The following declaration

 <texture-set>
  <texture>Terrain/marsh2.png</texture>
  <texture n="11">Terrain/grass_hires.png</texture>
  <texture n="12">Terrain/marsh4a.png</texture>
  <texture n="15">Terrain/airport_grass2.png</texture>
 </texture-set>
 <parameters>
  <grain_strength>0.9</grain_strength>
  <intrinsic_wetness>0.6</intrinsic_wetness>
 </parameters>

results in marshland drawn like this

Procedural marshland close-up Procedural marshland from the distance

The following declaration

  <texture-set>
   <texture>Terrain/shrub-hawaii.png</texture>
   <texture n="11">Terrain/sand_hires_red.png</texture>
   <texture n="12">Terrain/rock_red.png</texture>
   <texture n="13">Terrain/rock_red.png</texture>
   <texture n="14">Terrain/grain_texture.png</texture>
   <texture n="15">Terrain/airport_grass2.png</texture>
  </texture-set>
  <parameters>
   <rock_strata>1</rock_strata>
  </parameters>

results in stratified rock structures like the following:

Procedural rock Grand Canyon Procedural rock Zion National Park

Pixel color postprocessing

A number of environment effects are implemented under user control in the Environment menu. Let's consider the changes to the base scene:

Base scenery near Grenoble

Snow

Using terrain elevation and gradient information, the base location of a snow layer can be determined. The actual snow cover is a white base color, mixed with Perlin noise and normal-mapped with Perlin noise of multiple frequencies. A bias factor for the mixing allows to simulate snow layer of varying thickness.

A low layer thickness creates patches of snow on the terrain through which the basic terrain textures are visible:

Base scenery near Grenoble

A high layer thickness completely covers the terrain in snow. Subtle procedural color variations of the snow and bump-mapping change the base white snow pixel color into a realistic texture:

Base scenery near Grenoble

If the snowline is placed higher, snow is only simulated on the mountain tops - note also that steep gradients tend to remain clear of snow:

Base scenery near Grenoble

Dust

The dust effect mixes all pixels of the terrain with a basic dust color. A low dust effect setting gives the terrain a dry and parched appearance, as after a long summer period without rain.

Base scenery near Grenoble

A high dust effect setting changes the appearance to desert-like terrain (which is rather unrealistic for this scenery):

Base scenery near Grenoble

Wetness

Wet terrain is rendered overall darker. In flat areas, in addition patches of high specular reflection are added, leading to the appearance of glitterin light reflections on water puddles.

Base scenery near Grenoble

Vegetation

A high setting of the vegetation cover is most appropriate for tropical settings, but it can also be used to show seasonal effects, for instance the desert a few days after rainfall. It covers the whole terrain with mossy overgrowth, which in the case of the Grenoble scenery used here blurs the boundaries of the fields and gives the appearance of abandoned agriculture.

Base scenery near Grenoble

Autumn colors

The autumn color effect is unique in the pixel color postprocessing effects in that it needs modifications to the base texture to work. Since the basic terrain texture is never transparent, the information of where vegetation changes color in autumn and where it remains unchanged is hence encoded in the terrain texture alpha channel. A value of 1 means no change of color, a value of 0.5 is appropriate for high changes to bright red-orange. In order to work with the autumn effect, all textures need to be edited manually to indicate where the effect should be computed.

In early autumn, vegetation just tends to pale a little:

Base scenery near Grenoble

Later, fields get bright golden-yellow whereas deciduous forests go to orange-red (mixed forest cover rotates locally where indicated by the alpha channel):

Base scenery near Grenoble

Finally, in late autumn all vegetation cover is turned into a dull brown:

Base scenery near Grenoble


Limitations of procedural texturing

Despite its obvious benefits, there are some problems which are not easily addressed by procedural terrain texturing.

  • Landclass boundaries: Procedural texturing can de-emphasize landclass boundaries. This happens for instance if a landclass uses both overlay and detail texture, and only the base texture is changed in the adjacent landclass. Since the noise is continuous across landclass boundaries, the change in base texture is masked by the continuous overlay components. However, procedural texturing can not remove the landclass seams, as a different texture mix is evaluated for every declared landclass.


  • Tiling for non-natural landclasses: The de-tiling strategies described above are based on mixing texture components using noise functions. This works fine for natural landclasses, but human-influenced landclasses like agriculture or urban terrain are not well described by Perlin noise, and de-tiling crop fields using noise functions does not yield plausible results. A suitable function producing for instance plausible agriculture pattern has yet to be found.
  • Detail averaging: A problem arises when details are generated which are smaller than a pixel. In this case, the rendering engine does not average over many sub-pixels to determine the final color of the pixel but just takes one (few) samples. This means that a high contrast detailed structure of black and white is not averaged to a grey pixel but, dependent on the viewer position, either results in a black or a white pixel. The net result is a flickering Moire pattern which is visually very unpleasant (the same effect occurs when models get smaller than a pixel). In textures, the problem is solved by mipmapping - every 4 pixels are averaged to a single pixel on a coarse-grained version of the texture at texture loading time, and only the appropriate resolution level of the texture is referenced. To do the same thing in procedural texturing is very expensive (two resolution levels up one ends with 16 times the calculational effort for a pixel), so a viable strategy to remove the flickering patterns involves replacing noise by its global average (i.e. the 'all pixel' rather than '4 pixel' average, which is usually known for the noise function). The net result is that procedurally generated details tend to looks less grainy than true textures as soon as details get smaller than a single pixel, which is s subtle, but noticeable effect. In essence, this is similar to an antialiasing problem and could also be dealt with by generically sampling multiple sub-pixels at the expense of the increased performance cost.
  • Computational cost: Especially on older graphics cards, multiple texture lookups combined with evaluating noise and other functions is significantly slower than just texturing the terrain with a single texture. Most optimization strategies used in other 3D games (such as filling the z-buffer early and discarding occluded pixels) do not work well for a flight simulator where most of the terrain is visible at any given time.

Some points have found a solution:

  • Terrain tile boundaries: Terrain is loaded in discrete chunks, so-called tiles. For technical reasons, the coordinate system used to address positions changes for each tile. In practice, this unfortunately implies that noise functions are discontinuous across tile boundaries, and this shows in more or less pronounced seams in the computed textures -> however this can be addressed by going from tile-based to FG world coordinates and using 3-d noise. Due to the large distances involved, small-scale noise becomes then numerically unstable, but noise wavelength above 250 m can safely be done in world coordinates, leading to appealing visuals both at large and small distance.

Further reading