HTerrain plugin documentation

Overview

This plugin allows to create heightmap-based terrains in Godot Engine. This kind of terrain uses 2D images, such as for heights or texturing information, which makes it cheap to implement while covering most use cases.

It is entirely built on top of the VisualServer scripting API, which means it should be expected to work on all platforms supported by Godot's GLES3 renderer.

Screenshot of the editor with the plugin enabled and arrows showing where UIs are

Video tutorials

This written doc should be the most up to date and precise information, but video tutorials exist for a quick start.

How to install

You will need to use Godot 4.1 or later.

To get the last version that supported Godot 3.x, checkout the godot3 branch in the Git repository.

Automatically

In Godot, go to the Asset Library tab, search for the terrain plugin, download it and then install it. Then you need to activate the plugin in your ProjectSettings.

Manually

The plugin can be found on the Asset Library website. The download will give you a .zip file. Decompress it at the root of your project. This should make it so the following hierarchy is respected:

addons/
    zylann.hterrain/
        <plugin files>

Then you need to activate the plugin in your ProjectSettings.

How to update

When a new version of the plugin comes up, you may want to update. If you re-run the same installation steps, it should work most of the time. However this is not a clean way to update, because files might have been renamed, moved or deleted, and they won't be cleaned up. This is an issue with Godot's plugin management in general (TODO: make a proposal).

So a cleaner way would be:

  • Turn off the plugin
  • Close all your scenes (or close Godot entirely)
  • Delete the addons/zylann.hterrain folder
  • Then install the new version and enable it

Development versions

The latest development version of the plugin can be found on Github. It is the most recently developed version, but might also have some bugs.

Creating a terrain

Creating a HTerrain node

Features of this plugin are mainly available from the HTerrain node. To create one, click the + icon at the top of the scene tree dock, and navigate to this node type to select it.

There is one last step until you can work on the terrain: you need to specify a folder in which all the data will be stored. The reason is that terrain data is very heavy, and it's a better idea to store it separately from the scene. Select the HTerrain node, and click on the folder icon to choose that folder.

Screenshot of the data dir property

Once the folder is set, a default terrain should show up, ready to be edited.

Screenshot of the default terrain

Note

If you don't have a default environment, it's possible that you won't see anything, so make sure you either have one, or add a light to the scene to see it. Also, because terrains are pretty large (513 units by default), it is handy to change the view distance of the editor camera so that you can see further: go to View, Options, and then increase far distance.

Terrain dimensions

By default, the terrain is a bit small, so if you want to make it bigger, there are two ways:

  • Modify map_scale, which will scale the ground without modifying the scale of all child nodes while using the same memory. As the scale cannot be equal or less than 0, the limit of 0.01 (1 cm per cell) was set as an arbitrary safety guard. This value is still high enough to not run into precision floating-point problems.
  • Use the resize tool in the Terrain menu, which will increase the resolution instead and take more memory.

Screenshot of the resize tool

If you use the resize tool, you can also choose to either stretch the existing terrain, or crop it by selecting an anchor point. Currently, this operation is permanent and cannot be undone, so if you want to go back, you should make a backup.

Note

The resolution of the terrain is limited to powers of two + 1, mainly because of the way LOD was implemented. The reason why there is an extra 1 is down to the fact that to make 1 quad, you need 2x2 vertices. If you need LOD, you must have an even number of quads that you can divide by 2, and so on. However there is a possibility to tweak that in the future because this might not play well with the way older graphics cards store textures.

Sculpting

Brush types

The default terrain is flat, but you may want to create hills and mountains. Because it uses a heightmap, editing this terrain is equivalent to editing an image. Because of this, the main tool is a brush with a configurable size and shape. You can see which area will be affected inside a 3D red circle appearing under your mouse, and you can choose how strong painting is by changing the Brush opacity slider.

Screenshot of the brush widget

To modify the heightmap, you can use the following brush modes, available at the top of the viewport:

Screenshot of the sculpting tools

  • Raise: raises the height of the terrain to produce hills
  • Lower: digs down to create crevices
  • Smooth: smoothes heights locally
  • Level: averages the heights within the radius of the brush until ground eventually becomes flat
  • Flatten: directly sets the height to a given value, which can be useful as an eraser or to make plateaux. It is also possible to pick a height from the viewport using the picking button.
  • Erode: smoothes the landscape by simulating erosion. When used on noisy terrain, it often produces characteristic shapes found in nature.

Note

Heightmaps work best for hills and large mountains. Making sharp cliffs or walls are not recommended because it stretches geometry too much, and might cause issues with collisions. To make cliffs it's a better idea to place actual meshes on top.

Normals

As you sculpt, the plugin automatically recomputes normals of the terrain, and saves it in a texture. This way, it can be used directly in ground shaders, grass shaders and previews at a smaller cost. Also, it allows to keep the same amount of details in the distance independently from geometry, which allows for levels of detail to work without affecting perceived quality too much.

Collisions

You can enable or disable collisions by checking the Collisions enabled property in the inspector.

Heightmap-based terrains usually implement collisions directly using the heightmap, which saves a lot of computations compared to a classic mesh collider.

Screenshot of the option to choose physics engines in project settings

Some editor tools rely on colliders to work, such as snapping to ground or plugins like Scatter or other prop placement utilities. To make sure the collider is up to date, you can force it to update after sculpting with the Terrain -> Update Editor Collider menu:

Screenshot of the menu to update the collider

Note: if you use Godot 3.3, you need to make sure to use the Bullet physics engine in your project settings.

Known issues

  • Updating the collider: In theory, Bullet allows us to specify a direct reference to the image data. This would allow the collider to automatically update for free. However, we still had to duplicate the heightmap for safety, to avoid potential crashes if it gets mis-used. Even if we didn't copy it, the link could be broken anytime because of internal Copy-on-Write behavior in Godot. This is why the collider update is manual, because copying the heightmap results in an expensive operation. It can't be threaded as well because in Godot physics engines are not thread-safe yet. It might be improved in the future, hopefully.

  • Misaligned collider in editor: At time of writing, the Bullet integration has an issue about colliders in the editor if the terrain is translated, which does not happen in game: Godot issue #37337

Holes

It is possible to cut holes in the terrain by using the Holes brush. Use it with draw holes checked to cut them, and uncheck it to erase them. This can be useful if you want to embed a cave mesh or a well on the ground. You can still use the brush because holes are also a texture covering the whole terrain, and the ground shader will basically discard pixels that are over an area where pixels have a value of zero.

Screenshot with holes

At the moment, this brush uses the alpha channel of the color map to store where the holes are.

Note

This brush only produces holes visually. In order to have holes in the collider too, you have to do some tricks with collision layers because the collision shape this plugin uses (Bullet heightfield) cannot have holes. It might be added in the future, because it can be done by editing the C++ code and drop collision triangles in the main heightmap collision routine.

See issue 125

Level of detail

This terrain supports level of details on the geometry using a quad tree. It is divided in chunks of 32x32 (or 16x16 depending on your settings), which can be scaled by a power of two depending on the distance from the camera. If a group of 4 chunks are far enough, they will join into a single one. If a chunk is close enough, it will split in 4 smaller ones. Having chunks also improves culling because if you had a single big mesh for the whole terrain, that would be a lot of vertices for the GPU to go through. Care is also taken to make sure transitions between LODs are seamless, so if you toggle wireframe rendering in the editor you can see variants of the same meshes being used depending on which LOD their neighbors are using.

Screenshot of how LOD vertices decimate in the distance

LOD can be mainly tweaked in two ways:

  • lod scale: this is a factor determining at which distance chunks will split or join. The higher it is, the more details there will be, but the slower the game will be. The lower it is, the faster quality will decrease over distance, but will increase speed.
  • chunk size: this is the base size of a chunk. There aren't many values that it can be, and it has a similar relation as lod scale. The difference is, it affects how many geometry instances will need to be culled and drawn, so higher values will actually reduce the number of draw calls. But if it's too big, it will take more memory due to all chunk variants that are precalculated.

In the future, this technique could be improved by using GPU tessellation, once the Godot rendering engine supports it. GPU clipmaps are also a possibility, because at the moment the quad tree is updated on the CPU.

Note

Due to limitations of the Godot renderer's scripting API, LOD only works around one main camera, so it's not possible to have two cameras with split-screen for example. Also, in the editor, LOD only works while the HTerrain node is selected, because it's the only time the EditorPlugin is able to obtain camera information (but it should work regardless when you launch the game).

Texturing

Overview

Applying textures to terrains is a bit different than single models, because they are very large and a more optimal approach needs to be taken to keep memory and performance to an acceptable level. One very common way of doing it is by using a splatmap. A splatmap is another texture covering the whole terrain, whose role is to store which detail textures should be blended, and these textures may repeat seamlessly.

Screenshot showing splatmap and textured result

This magic is done with a single shader, i.e a single ShaderMaterial in Godot's terminology. This material is handled internally by the plugin, but you can customize it in several ways.

There are mainly 3 families of shaders this plugin supports:

  • CLASSIC4: simple shaders where each texture may be a separate resource. They are limited to 4 textures.
  • MULTISPLAT16: more advanced shader using more splatmaps and texture arrays. It's expensive but supports up to 16 textures.
  • ARRAY: experimental shader also using texture arrays, which comes with constraints, but allows to paint a lot more different textures.
  • Other shaders don't need textures, like LOW_POLY, which only uses colors.

On the HTerrain node, there is a property called shader_type, which lets you choose among built-in shaders. The one you choose will define which workflow to follow: textures, or texture arrays.

At time of writing, CLASSIC4 shaders are better supported, and are the default choice. Texture array shaders may be used more in the future.

Getting PBR textures

If you only plan to use simple color textures, you can skip to Texture Sets.

Before you can paint textures, you have to set them up. It is recommended to pick textures which can tile infinitely, and preferably use "organic" ones, because terrains are best-suited for exterior natural environments. For each texture, you may find the following types of images, common in PBR shading:

  • Albedo, color, or diffuse (required)
  • Bump, height, or displacement (optional)
  • Normal, or normalmap (optional)
  • Roughness (optional)

Screenshot of PBR textures

You can find some of these textures for free at cc0textures.com.

!!! note: Some shaders have a Lite and non-lite versions. One main difference between them is that Lite versions don't require normal maps, but the others require them. If you use a non-lite shader and forget to assign normal maps, shading will look wrong.

It is preferable to place those source images under a specific directory. Also, since the images will only serve as an input to generate the actual game resources, it is better to place a .gdignore file inside that directory. This way, Godot will not include those source files in the exported game:

terrain_test/
    terrain_data/
        height.res
        data.hterrain
        ...
    textures_src/
        .gdignore
        grass_albedo.png
        grass_bump.png
        grass_normal.png
        grass_roughness.png
        rocks_albedo.png
        rocks_bump.png
        rocks_normal.png
        rocks_roughness.png
        ...
    terrain_scene.tscn
    ...

Note

While bump might not be used often, this plugin actually uses it to achieve better blending effects.

Using the import tool

Ground textures are stored in a HTerrainTextureSet resource. All terrains come with a default one. However, it can be tedious to setup every texture and pack them, especially if you need PBR.

This plugin comes with an optional import tool. Select your HTerrain node, and in the bottom panel, click the Import... button:

Screenshot of bottom panel import button

This brings up the import tool dialog:

Screenshot of the texture set import tool

Import mode

One of the first important things is to decide which import mode you want to use:

  • Textures: For simple terrains with up to 4 textures (in use with CLASSIC4 shaders)
  • TextureArrays: For more complex terrains with up to 16 textures (in use with MULTISPLAT16 and ARRAY shaders)

This choice depends on the shader you will use for the terrain. Some shaders expect individual textures and others expect texture arrays.

Smart file pick

If you use PBR textures, there might be a lot of files to assign. If you use a naming convention, you can start loading an albedo texture, and the tool will attempt to find all the other maps automatically by recognizing other image file names. For example, using this convention may allow this shortcut to work:

  • grass_albedo.png
  • grass_bump.png
  • grass_normal.png
  • grass_roughness.png

Screenshot of texture types in import tool

Normal maps

As indicated in the Godot documentation, normal maps are expected to use OpenGL convention (X+, Y+, Z+). So it is possible that normalmaps you find online use a different convention.

To help with this, the import tool allows you to flip Y, in case the normalmap uses DirectX convention.

Examples of normalmap conventions

Importing

When importing, this tool will need to generate a few files representing intermediate Godot resources. You may have to choose the directory where those resources will be created, otherwise they will be placed at the root of your project.

Screenshot of directory option in import tool

Once everything is ready, you can click Import. This can take a little while. If all goes well, a popup will tell you when it's done, and your terrain's texture set will be filled up with the imported textures.

Screenshot of import success

If importing goes wrong, most of the time an error will show up and the HTerrainTextureSet will not be modified. If it succeeded but you are unhappy with the result, it is possible to undo the changes done to the terrain using Ctrl+Z.

Note

  • If you need to change something after the first import, you can go back to the importing tool and change settings, then click Import again.
  • Importing with this tool will overwrite the whole set each time.
  • The tool does not store the settings anywhere, but it should fill them up as much as it can from existing sets so you shouldn't need to fill everything up again.
  • Custom importers are used as backend in order to support these features automatically, instead of default Godot importers. If you need more tinkering, you can take a look at packed texture importers.

Texture Sets

Description

HTerrainTextureSet is a custom resource which contains all textures a terrain can blend on the ground (grass, dirt, rocks, leaves, snow, sand...). All terrains come with an empty one assigned by default.

The import tool seen earlier is the quickest way to fill one up from base textures, but it is not mandatory if you prefer to do things manually.

You can inspect and edit the current set by selecting your HTerrain node, and in the bottom panel "Textures" section, click Edit...:

Screenshot of the bottom panel edit button

This opens the following dialog:

Screenshot of the texture set editor

Unlike the import tool, this dialog shows you the actual resources used by the terrain. They may be either pairs of two packed textures for each slot, or two TextureArray resources.

If you are using a CLASSIC4 shader, you should be able to add and remove slots using the + and - buttons, and directly load color textures in the Albedo channel. For using texture arrays or PBR textures, it might be better to use the import tool.

Actions done in this dialog behave like an extension of the inspector, and can be undone with Ctrl+Z.

Re-using a texture set

Texture sets are embedded in terrains by default, but it is possible to use the same set on another terrain. To do this, the HTerrainTextureSet must be saved as a .tres file.

Screenshot of texture set in the inspector

  • Select your HTerrain node
  • In the inspector, right-click on the value of the texture_set property
  • A HUGE menu will open (this is a Godot issue). Scroll all the way down with mouse wheel.
  • Click the Edit... menu item to edit the resource
  • On top of the inspector, a floppy disk icon should appear. You can click on it and choose Save As...

Screenshot of saving a texture set from inspector

  • A file dialog will prompt you for the location you want to put the resource file. Once you're done, click Save.

Once you have a .tres file, you will be able to pick it up in your other terrain, by clicking on the texture_set property, but choosing Load this time. You can also navigate to the .tres file in the FileSystem dock, then drag and drop to the property.

Shader types

Classic4

The CLASSIC4 shader is a simple splatmap technique, where R, G, B, A match the weight of 4 respective textures. Then are all blended together in every pixel of the ground. Here is how it looks when applied:

Screenshot showing splatmap and textured result

It comes in two variants:

  • CLASSIC4: full-featured shader, however it requires your textures to have normal maps. If you don't assign them, shading will look wrong.
  • CLASSIC4_LITE: simpler shader with less features. It only requires albedo textures.

MultiSplat16

the MULTISPLAT16 shader is an extension of the splatmap technique, but uses 4 splatmaps instead of 1. It also uses TextureArrays instead of individual textures. It allows to support up to 16 textures at once, and can blend up to 4 in the same pixel. It dynamically chooses the 4 most-representative textures to blend them.

It also comes in two variants:

  • MULTISPLAT16: full-featured shader, however it requires your texture arrays to have normal maps.
  • MULTISPLAT16_LITE: simpler shader with less features. It only requires albedo texture arrays.

It is the recommended choice if you need more than 4 textures, because it is much easier to use than the ARRAY shader and produces less artifacts.

One downside is performance: it is about twice slower than CLASSIC4 (on an nVidia 1060, a fullscreen CLASSIC4 is 0.8 ms, while MULTISPLAT16 is 1.8 ms). Although, considering objects placed on the terrain should usually occlude ground pixels, the cost might be lower in a real game scenario.

LowPoly

The LOWPOLY shader is the simplest shader. It produces a faceted look with only simple colors, and no textures. You will need to use the color brush to paint it.

Screenshot of the lowpoly shader

Note

If you need faceted visuals with other shaders using textures, you can obtain the same result by customizing the shader, and adding this line at the end of fragment(): NORMAL = normalize(cross(dFdx(VERTEX), dFdy(VERTEX)));

Array

WARNING: this shader is still experimental. It's not ideal and has known flaws, so it may change in the future.

The ARRAY shader uses a more advanced technique to render ground textures. Instead of one splatmap and many individual textures, it uses a weightmap, an index map, and a TextureArray.

The two maps are different from the classic one:

  • SPLAT_INDEX: this one stores the indexes of the textures to blend in every pixel of the ground. Indexes are stored respectively in R, G and B, and correspond to layers of the TextureArray.
  • SPLAT_WEIGHT: this one stores the weight of the 3 textures to blend on each pixel. It only has R and G channels, because the third one can be inferred (their sum must be 1).

This allows to paint up to 256 different textures, however it introduces an important constraint: you cannot blend more than 3 textures at a given pixel.

Painting the proper indexes and weights can be a challenge, so for now, the plugin comes with a compromise. Each texture is assigned a fixed color component, R, G or B. So for a given texture, all textures that have an index separated by a multiple of 3 from this texture will not always be able to blend with it. For example, texture 2 might not blend with texture 5, 8, 11, 14 etc. So choosing where you place textures in the TextureArray can be important.

Here is a close-up on an area where some textures are not totally blending, because they use the same color component:

Bad transition

In this situation, another workaround is to use a transition texture: if A and B cannot blend, use texture C which can blend with A and B:

Fixed transition

You may see this pop up quite often when using this shader, but it can often be worked around. The brush for this isn't perfect. This limitation can be smoothed out in the future, if a better algorithm is found which can work in real-time.

Creating a TextureArray manually

Note

It is now possible to use the import tool to set this up automatically. The following description explains how to do it manually.

Contrary to CLASSIC4 shaders, you cannot directly assign individual textures with a shader that requires TextureArray. Instead, you'll have to import one.

1) With an image editor, create an image, which you subdivide in square tiles, like an atlas. I each of them, place one ground texture, like so:

Example of an atlas for creating a Texture Array

2) Place that atlas in your Godot project. The editor will attempt to import it a first time, it can take a while if it's big.

3) Select the atlas, and go to the Import dock. Change the import type to TextureArray.

Texture Array import dock

4) Make sure the Repeat mode is enabled. Then, change the tile counts below to match your grid. Once you're done, click Re-import. Godot will ask you to restart the editor, do that (I have no idea why).

5) Once the editor has restarted, select your terrain node, and make sure it uses the ARRAY shader type (or a similar custom shader). In the bottom panel, click on the Edit... button to edit the HTerrainTextureSet used by the terrain.

6) In the dialog, click on the Load Array... button under Albedo to load your texture array. You can do the same process with normal maps if needed.

7) The bottom panel should now update to show much more texture slots. They will appear in the same order they are in the atlas, from left-to-right. If the panel doesn't update, select another node and click the terrain again. You should now be able to paint.

Lots of textures blending

Packing textures manually

Note

It is now possible to use the import tool to set this up automatically. The following description explains how to do it manually.

The main ground shaders provided by the plugin should work fine with only regular albedo, but it supports a few features to make the ground look more realistic, such as normal maps, bump and roughness. To achieve this, shaders expects packed textures. The main reason is that more than one texture has to be sampled at a time, to allow them to blend. With a classic splatmap, it's 4 at once. If we want normalmaps, it becomes 8, and if we want roughness it becomes 12 etc, which is already a lot, in addition to internal textures Godot uses in the background. Not all GPUs allow that many textures in the shader, so a better approach is to combine them as much as possible into single images. This reduces the number of texture units, and reduces the number of fetches to do in the pixel shader.

Screenshot of the channel packer plugin

For this reason, the plugin uses the following convention in ground textures:

  • Albedo in RGB, Bump in A
  • Normal in RGB, Roughness in A

This operation can be done in an image editing program such as Gimp, or with a Godot plugin such as Channel Packer. It can also be done using packed texture importers, which are now included in the plugin.

Note

Normal maps must follow the OpenGL convention, where Y goes up. They are recognizable by being "brighter" on the top of bumpy features (because Y is green, which is the most energetic color to the human eye):

Examples of normalmap conventions

See also Godot's documentation notes about normal maps

Note

Because Godot would strip out the alpha channel if a packed texture was imported as a normal map, you should not make your texture import as "Normal Map" in the importer dock.

Depth blending

Bump textures holds a particular usage in this plugin: You may have noticed that when you paint multiple textures, the terrain blends them together to produce smooth transitions. Usually, a classic way is to do a "transparency" transition using the splatmap. However, this rarely gives realistic visuals, so an option is to enable depth blending under Shader Params.

Screenshot of depth blending VS alpha blending

This feature changes the way blending operates by taking the bump of the ground textures into account. For example, if you have sand blending with pebbles, at the transition you will see sand infiltrate between the pebbles because the pixels between pebbles have lower bump than the pebbles. You can see this technique illustrated in a Gamasutra article. It was tweaked a bit to work with 3 or 4 textures, and works best with fairly low brush opacity, around 10%.

Triplanar mapping

Making cliffs with a heightmap terrain is not recommended, because it stretches the geometry too much and makes textures look bad. Nevertheless, you can enable triplanar mapping on such texture in order for it to not look stretched. This option is in the shader section in the inspector.

Screenshot of triplanar mapping VS no triplanar

In the case of the CLASSIC4 shader, cliffs usually are made of the same ground texture, so it is only available for textures setup in the 4th slot, called cliff. It could be made to work on all slots, however it involves modifying the shader to add more options, which you may see in a later article.

The ARRAY shader does not have triplanar mapping yet, but it may be added in the future.

Tiling reduction

The fact repeating textures are used for the ground also means they will not look as good at medium to far distance, due to the pattern it produces:

Screenshot of tiling artifacts

On shaders supporting it, the tile_reduction parameter allows to break the patterns a bit to attenuate the effect:

Screenshot of reduced tiling artifacts

This option is present under the form of a vec4, where each component correspond to a texture, so you can enable it for some of them and not the others. Set a component to 1 to enable it, and 0 to disable it.

This algorithm makes the shader sample the texture a second time, at a different orientation and scale, at semi-random areas of the ground:

Screenshot of the warped checker pattern used to break repetitions

Here you can see where each of the two texture variants are being rendered. The pattern is a warped checker, which is simple enough to be procedural (avoiding the use of a noise texture), but organic enough so it shouldn't create artifacts itself. The result is made seamless by using depth blending (see Depth blending).

Although it's still possible to notice repetition over larger distances, this can be better covered by using a fade to global map (see Global map). In addition, many games don't present a naked terrain to players: there are usually many props on top of it, such as grass, vegetation, trees, rocks, buildings, fog etc. so overall tiling textures should not really be a big deal.

Painting only on slopes

The texture painting tool has a special option to limit the brush based on the slope of the terrain. This helps painting cliffs only or flat grounds only, without having to aim. It can even be used to paint a big area in one go, by increasing the brush size.

Screenshot of the slope limit tool

The control consists in a two-ways slider. You can drag two handles. The left handle controls minimum slope, the right handle controls maximum slope. The range between the two handles determines which slopes the brush will paint on.

Color tint

You can color the terrain using the Color brush. This is pretty much modulating the albedo, which can help adding a touch of variety to the landscape. If you make custom shader tweaks, color can also be used for your own purpose if you need to.

Screenshot with color painting

Depending on the shader, you may be able to choose which textures are affected by the colormap.

Global map

For shading purposes, it can be useful to bake a global map of the terrain. A global map takes the average albedo of the ground all over the terrain, which allows other elements of the scene to use that without having to recompute the full blending process that the ground shader goes through. The current use cases for a global map is to tint grass, and use it as a distance fade in order to hide texture tiling in the very far distance. Together with the terrain's normal map it could also be used to make minimap previews.

To bake a global map, select the HTerrain node, go to the Terrain menu and click Bake global map. This will produce a texture in the terrain data directory which will be used by the default shaders automatically, depending on your settings.

If you use a custom shader, you can define a specific one to use for the global map, by assigning the custom_globalmap_shader property. This is usually a stripped-down version of the main ground shader, where only ALBEDO is important.

Note

The globalmap is also used in the minimap to show the color of the terrain.

Terrain generator

Basic sculpting tools can be useful to get started or tweaking, but it's cumbersome to make a whole terrain only using them. For larger scale terrain modeling, procedural techniques are often preferred, and then adjusted later on.

This plugin provides a simple procedural generator. To open it, click on the HTerrain node to see the Terrain menu, in which you select generate.... Note that you should have a properly setup terrain node before you can use it.

Screenshot of the terrain generator

The generator is quite simple and combines a few common techniques to produce a heightmap. You can see a 3D preview which can be zoomed in with the mouse wheel and rotated by dragging holding middle click.

Height range

height range and base height define which is the minimum and maximum heights of the terrain. The result might not be exactly reaching these boundaries, but it is useful to determine in which region the generator has to work in.

Perlin noise

Perlin noise is very common in terrain generation, and this one is no exception. Multiple octaves (or layers) of noise are added together at varying strength, forming a good base that already looks like a good environment.

The usual parameters are available:

  • seed: this chooses the random seed the perlin noise will be based on. Same number gives the same landscape.
  • offset: this chooses where in the landscape the terrain will be cropped into. You can also change that setting by panning the preview with the right mouse button held.
  • scale: expands or shrinks the length of the patterns. Higher scale gives lower-frequency relief.
  • octaves: how many layers of noise to use. The more octaves, the more details there will be.
  • roughness: this controls the strength of each octave relatively to the previous. The more you increase it, the more rough the terrain will be, as high-frequency octaves get a higher weight.

Try to tweak each of them to get an idea of how they affect the final shape.

Erosion

The generator features morphological erosion. Behind this barbaric name hides a simple image processing algorithm, described here. In the context of terrains, what it does is to quickly fake real-life erosion, where rocks might slide along the slopes of the mountains over time, giving them a particular appearance. Perlin noise alone is nice, but with erosion it makes the result look much more realistic.

Screenshot with the effect of erosion

It's also possible to use dilation, which gives a mesa-like appearance.

Screenshot with the effect of dilation

There is also a slope direction parameter, this one is experimental but it has a tendency to simulate wind, kind of "pushing" the ground in the specified direction. It can be tricky to find a good value for this one but I left it because it can give interesting results, like sand-like ripples, which are an emergent behavior.

Screenshot of slope erosion

Note

Contrary to previous options, erosion is calculated over a bunch of shader passes. In Godot 3, it is only possible to wait for one frame to be rendered every 16 milliseconds, so the more erosion steps you have, the slower the preview will be. In the future it would be nice if Godot allowed multiple frames to be rendered on demand so the full power of the GPU could be used.

Applying

Once you are happy with the result, you can click "Apply", which will calculate the generated terrain at full scale on your scene. This operation currently can't be undone, so if you want to go back you should make a backup.

Import an existing terrain

Besides using built-in tools to make your landscape, it can be convenient to import an existing one, which you might have made in specialized software such as WorldMachine, Scape or Lithosphere.

Import dialog

To do this, select the HTerrain node, click on the Terrain menu and chose Import. This window allows you to import several kinds of data, such as heightmap but also splatmap or color map.

Screenshot of the importer

There are a few things to check before you can successfully import a terrain though:

  • The resolution should be power of two + 1, and square. If it isn't, the plugin will attempt to crop it, which might be OK or not if you can deal with map borders that this will produce.
  • If you import a RAW heightmap, it has to be encoded using either 16-bit or 32-bit unsigned integer format. Upon selecting a file via the file chooser dialog, the importer will attempt to deduce the bit depth.
  • If you import a PNG heightmap, Godot can only load it as 8-bit depth, so it is not recommended for high-range terrains because it doesn't have enough height precision.

This feature also can't be undone when executed, as all terrain data will be overwritten with the new one. If anything isn't correct, the tool will warn you before to prevent data loss.

It is possible that the height range you specify doesn't works out that well after you see the result, so for now it is possible to just re-open the importer window, change the height scale and apply again.

4-channel splatmaps caveat

Importing a 4-channel splatmap requires an RGBA image, where each channel will be used to represent the weight of a texture. However, if you are creating a splatmap by going through an image editor, you must make sure the color data is preserved.

Most image editors assume you create images to be seen. When you save a PNG, they assume fully-transparent areas don't need to store any color data, because they are invisible. The RGB channels are then compressed away, which can cause blocky artifacts when imported as a splatmap.

To deal with this, make sure your editor has an option to turn this off. In Gimp, for example, this option is here:

Screenshot of the importer

Detail layers

Once you have textured ground, you may want to add small detail objects to it, such as grass and small rocks.

Screenshot of two grass layers under the terrain node

Painting details

Grass is supported through HTerrainDetailLayer node. They can be created as children of the HTerrain node. Each layer represents one kind of detail, so you may have one layer for grass, and another for flowers, for example.

Detail layers come in two parts:

  • A 8-bit density texture covering the whole terrain, also called a "detail map" at the moment. You can see how many maps the terrain has in the bottom panel after selecting the terrain.
  • A HTerrainDetailLayer node, which uses one of the detail maps to render instanced models based on the density.

You can paint detail maps just like you paint anything else, using the same brush system. It uses opacity to either add more density, or act as an eraser with an opacity of zero. HTerrainDetailLayer nodes will then update in realtime, rendering more or less instances in places you painted.

Note

A detail map can be used by more than one node (by setting the same index in their layer_index property), so you can have one for grass, another for flowers, and paint on the shared map to see both nodes update at the same time.

Shading options

At the moment, detail layers only come with a single shader type, which is made for grass. More may be added in the future.

You can choose which texture will be used, and it will be rendered using alpha-scissor. It is done that way because it allows drawing grass in the opaque render pass, which is cheaper than treating every single quad like a transparent object which would have to be depth-sorted to render properly. Alpha-to-coverage would look better, but isn't supported in Godot 3.

Like the ground, detail layers use a custom shader that takes advantage of the heightmap to displace each instanced object at a proper position. Also, hardware instancing is used under the hood to allow for a very high number of items with low cost. Multimeshes are generated in chunks, and then instances are hidden from the vertex shader depending on density. For grass, it also uses the normal of the ground so there is no need to provide it. There are also shader options to tint objects with the global map, which can help a lot making grass to blend better with the environment.

Finally, the shader fades in the distance by increasing the threshold of alpha scissor. This works better with a transparent texture. An alternative is to make it sink in the ground, but that's left to customization.

For writing custom shaders, see Custom detail shaders.

Meshes

By default, detail layers draw simple quads on top of the ground. But it is possible to choose another kind of geometry, by assigning the instance_mesh property. Several meshes are bundled with the plugin, which you can find in res://addons/zylann.hterrain/models/.

Bundled grass models

They are all thought for grass rendering. You can make your own for things that aren't grass, however there is no built-in shader for conventional objects at the moment (rocks, bits and bobs). So if you want normal shading you need to write a custom shader. That may be bundled too in the future.

Note

Detail meshes must be Mesh resources, so the easiest way is to use the OBJ format. If you use GLTF or FBX, Godot will import it as a scene by default, so you may have to configure it to import as single mesh if possible.

Custom shaders

This plugin comes with default shaders, but you are allowed to modify them and change things to match your needs. The plugin does not expose materials directly because it needs to set built-in parameters that are always necessary, and some of them cannot be properly saved as material properties, if at all.

Ground shaders

In order to write your own ground shader, select the HTerrain node, and change the shader type to Custom. Then, select the custom shader property and choose New Shader. This will create a new shader which is pre-filled with the same source code as the last built-in shader you had selected. Doing it this way can help seeing how every feature is done and find your own way into implementing customizations.

The plugin does not actually hardcode its features based on its built-in shaders. Instead, it looks at which uniform parameters your shader defines, and adapts in consequence. A list of uniform parameters are recognized, some of which are required for heightmap rendering to work:

Parameter name Type Format Description
u_terrain_heightmap sampler2D RH The heightmap, a 32-bit float texture which can be sampled in the red channel. Like the other following maps, you have to access it using cell coordinates, which can be computed as seen in the built-in shader.
u_terrain_normalmap sampler2D RGB8 The precalculated normalmap of the terrain, which you can use instead of computing it from the heightmap
u_terrain_colormap sampler2D RGBA8 The color map, which is the one modified by the color brush. The alpha channel is used for holes.
u_terrain_splatmap sampler2D RGBA8 The classic 4-component splatmap, where each channel determines the weight of a given texture. The sum of each channel across all splatmaps must be 1.0.
u_terrain_splatmap_1 sampler2D RGBA8 Additional splatmap
u_terrain_splatmap_2 sampler2D RGBA8 Additional splatmap
u_terrain_splatmap_3 sampler2D RGBA8 Additional splatmap
u_terrain_globalmap sampler2D RGB8 The global albedo map.
u_terrain_splat_index_map sampler2D RGB8 An index map, used for texturing based on a TextureArray. the R, G and B components multiplied by 255.0 will provide the index of the texture.
u_terrain_splat_weight_map sampler2D RG8 A 2-component weight map where a 3rd component can be obtained with 1.0 - r - g, used for texturing based on a TextureArray. The sum of R and G must be 1.0.
u_ground_albedo_bump_0...3 sampler2D RGBA8 These are up to 4 albedo textures for the ground, which you have to blend using the splatmap. Their alpha channel can contain bump.
u_ground_normal_roughness_0...3 sampler2D RGBA8 Similar to albedo, these are up to 4 normal textures to blend using the splatmap. Their alpha channel can contain roughness.
u_ground_albedo_bump_array sampler2DArray RGBA8 Equivalent of the previous individual albedo textures, as an array. The plugin knows you use this texturing technique by checking the existence of this parameter.
u_ground_normal_roughness_array sampler2DArray RGBA8 Equivalent of the previous individual normalmap textures, as an array.
u_terrain_inverse_transform mat4x4 A 4x4 matrix containing the inverse transform of the terrain. This is useful if you need to calculate the position of the current vertex in world coordinates in the vertex shader, as seen in the builtin shader.
u_terrain_normal_basis mat3x3 A 3x3 matrix containing the basis used for transforming normals. It is not always needed, but if you use map scale it is required to keep them correct.

You don't have to declare them all. It's fine if you omit some of them, which is good because it frees a slot in the limited amount of uniforms, especially for texture units. Other parameters are not used by the plugin, and are shown procedurally under the Shader params section of the HTerrain node.

Grass shaders

Detail layers follow the same design as ground shaders. In order to make your own, select the custom shader property and assign it a new empty shader. This will also fork the built-in shader, which at the moment is specialized into rendering grass quads.

They share the following parameters with ground shaders:

  • u_terrain_heightmap
  • u_terrain_normalmap
  • u_terrain_globalmap
  • u_terrain_inverse_transform

And there also have specific parameters which you can use:

Parameter name Type Format Description
u_terrain_detailmap sampler2D R8 This one contains the grass density, from 0 to 1. Depending on this, you may hide instances by outputting degenerate triangles, or let them pass through. The builtin shader contains an example.
u_albedo_alpha sampler2D RGBA8 This is the texture applied to the whole model, typically transparent grass.
u_view_distance float How far details are supposed to render. Beyond this range, the plugin will cull chunks away, so it is a good idea to use this in the shader to smoothly fade pixels in the distance to hide this process.
u_ambient_wind vec2 Combined vec2 parameter for ambient wind. x is the amplitude, and y is a time value. It is better to use it instead of directly TIME because it allows to animate speed without causing stutters.

Lookdev

The plugin features an experimental debugging feature in the Terrain menu called "Lookdev". It temporarily replaces the ground shader with a simpler one which displays the raw value of a specific map. For example, you can see the actual values taken by a detail map by choosing one of them in the menu:

Screenshot of detail map seen with lookdev shader

It is very simple at the moment but it can also be used to display data maps which are not necessarily used for rendering. So you could also use it to paint them, even if they don't translate into a visual element in the game.

To turn it off, select Disabled in the menu.

Screenshot of detail map seen with lookdev shader

Note

The heightmap cannot be seen with this feature because its values extend beyond usual color ranges.

Scripting

Overview

Scripts relevant to in-game functionality are located under the plugin's root folder, res://addons/zylann.hterrain/.

res://
- addons/
    - zylann.hterrain/
        - doc/
        - models/                    <-- Models used for grass
        - native/                    <-- GDNative library
        - shaders/                   
        - tools/                     <-- Editor-specific stuff, don't use in game
        - util/                      <-- Various helper scripts

        - hterrain.gd                <-- The HTerrain node
        - hterrain_data.gd           <-- The HTerrainData resource
        - hterrain_detail_layer.gd   <-- The HTerrainDetailLayer node

        - (other stuff used internally)

This plugin does not use global class names, so to use or hint one of these types, you may want to "const-import" them on top of your script, like so:

const HTerrain = preload("res://addons/zylann.hterrain/hterrain.gd")

There is no API documentation yet, so if you want to see which functions and properties are available, take a look at the source code in the editor. Functions and properties beginning with a _ are private and should not be used directly.

Creating the terrain from script

You can decide to create the terrain from a script. Here is an example:

extends Node

const HTerrain = preload("res://addons/zylann.hterrain/hterrain.gd")
const HTerrainData = preload("res://addons/zylann.hterrain/hterrain_data.gd")

func _ready():
    var data = HTerrainData.new()
    data.resize(513)

    var terrain = HTerrain.new()
    terrain.set_data(data)
    add_child(terrain)

Modifying terrain from script

The terrain is described by several types of large textures, such as heightmap, normal map, grass maps, color map and so on. Modifying the terrain boils down to modifying them using the Image API.

For example, this code will tint the ground red at a specific position (in pixels, not world space):

const HTerrainData = preload("res://addons/zylann.hterrain/hterrain_data.gd")

@onready var _terrain = $Path/To/Terrain

func test():
    # Get the image
    var data : HTerrainData = _terrain.get_data()
    var colormap : Image = data.get_image(HTerrainData.CHANNEL_COLOR)

    # Modify the image
    var position = Vector2(42, 36)
    colormap.set_pixel(position, Color(1, 0, 0))

    # Notify the terrain of our change
    data.notify_region_changed(Rect2(position.x, position.y, 1, 1), HTerrainData.CHANNEL_COLOR)

The same goes for the heightmap and grass maps, however at time of writing, there are several issues with editing it in game:

  • Normals of the terrain don't automatically update, you have to calculate them yourself by also modifying the normalmap. This is a bit tedious and expensive, however it may be improved in the future. Alternatively you could compute them in shader, but it makes rendering a bit more expensive.
  • The collider won't update either, for the same reason mentioned in the section about collisions in the editor. You can force it to update by calling update_collider() but it can cause a hiccup.

Procedural generation

It is possible to generate the terrain data entirely from script. It may be quite slow if you don't take advantage of GPU techniques (such as using a compute viewport), but it's still useful to copy results to the terrain or editing it like the plugin does in the editor.

Again, we can use the Image resource to modify pixels. Here is a full GDScript example generating a terrain from noise and 3 textures:

extends Node

# Import classes
const HTerrain = preload("res://addons/zylann.hterrain/hterrain.gd")
const HTerrainData = preload("res://addons/zylann.hterrain/hterrain_data.gd")
const HTerrainTextureSet = preload("res://addons/zylann.hterrain/hterrain_texture_set.gd")

# You may want to change paths to your own textures
var grass_texture = load("res://addons/zylann.hterrain_demo/textures/ground/grass_albedo_bump.png")
var sand_texture = load("res://addons/zylann.hterrain_demo/textures/ground/sand_albedo_bump.png")
var leaves_texture = load("res://addons/zylann.hterrain_demo/textures/ground/leaves_albedo_bump.png")


func _ready():
    # Create terrain resource and give it a size.
    # It must be either 513, 1025, 2049 or 4097.
    var terrain_data = HTerrainData.new()
    terrain_data.resize(513)

    var noise = FastNoiseLite.new()
    var noise_multiplier = 50.0

    # Get access to terrain maps
    var heightmap: Image = terrain_data.get_image(HTerrainData.CHANNEL_HEIGHT)
    var normalmap: Image = terrain_data.get_image(HTerrainData.CHANNEL_NORMAL)
    var splatmap: Image = terrain_data.get_image(HTerrainData.CHANNEL_SPLAT)

    # Generate terrain maps
    # Note: this is an example with some arbitrary formulas,
    # you may want to come up with your owns
    for z in heightmap.get_height():
        for x in heightmap.get_width():
            # Generate height
            var h = noise_multiplier * noise.get_noise_2d(x, z)

            # Getting normal by generating extra heights directly from noise,
            # so map borders won't have seams in case you stitch them
            var h_right = noise_multiplier * noise.get_noise_2d(x + 0.1, z)
            var h_forward = noise_multiplier * noise.get_noise_2d(x, z + 0.1)
            var normal = Vector3(h - h_right, 0.1, h_forward - h).normalized()

            # Generate texture amounts
            var splat = splatmap.get_pixel(x, z)
            var slope = 4.0 * normal.dot(Vector3.UP) - 2.0
            # Sand on the slopes
            var sand_amount = clamp(1.0 - slope, 0.0, 1.0)
            # Leaves below sea level
            var leaves_amount = clamp(0.0 - h, 0.0, 1.0)
            splat = splat.linear_interpolate(Color(0,1,0,0), sand_amount)
            splat = splat.linear_interpolate(Color(0,0,1,0), leaves_amount)

            heightmap.set_pixel(x, z, Color(h, 0, 0))
            normalmap.set_pixel(x, z, HTerrainData.encode_normal(normal))
            splatmap.set_pixel(x, z, splat)

    # Commit modifications so they get uploaded to the graphics card
    var modified_region = Rect2(Vector2(), heightmap.get_size())
    terrain_data.notify_region_change(modified_region, HTerrainData.CHANNEL_HEIGHT)
    terrain_data.notify_region_change(modified_region, HTerrainData.CHANNEL_NORMAL)
    terrain_data.notify_region_change(modified_region, HTerrainData.CHANNEL_SPLAT)

    # Create texture set
    # NOTE: usually this is not made from script, it can be built with editor tools
    var texture_set = HTerrainTextureSet.new()
    texture_set.set_mode(HTerrainTextureSet.MODE_TEXTURES)
    texture_set.insert_slot(-1)
    texture_set.set_texture(0, HTerrainTextureSet.TYPE_ALBEDO_BUMP, grass_texture)
    texture_set.insert_slot(-1)
    texture_set.set_texture(1, HTerrainTextureSet.TYPE_ALBEDO_BUMP, sand_texture)
    texture_set.insert_slot(-1)
    texture_set.set_texture(2, HTerrainTextureSet.TYPE_ALBEDO_BUMP, leaves_texture)

    # Create terrain node
    var terrain = HTerrain.new()
    terrain.set_shader_type(HTerrain.SHADER_CLASSIC4_LITE)
    terrain.set_data(terrain_data)
    terrain.set_texture_set(texture_set)
    add_child(terrain)

    # No need to call this, but you may need to if you edit the terrain later on
    #terrain.update_collider()

Reload while the game is running

If your want to reload a terrain without restarting the game, you can do the following with a script:

# Reload terrain data from files, disregarding cached resources
terrain.data.reload()

# Update the collider, as it won't update automatically
terrain.update_collider()

So the following workflow is possible:

  • While the game runs, do some changes to the terrain in the editor
  • Save the scene containing the terrain
  • Optional: tab in/out of Godot to make sure terrain textures get re-imported (if you don't do it, only the heightmap will update and shading might look wrong in changed areas)
  • Press a hotkey in the game calling the code that reloads the terrain.

Export

The plugin should work normally in exported games, but there are some files you should be able to remove because they are editor-specific. This allows to reduce the size from the executable a little.

Everything under res://addons/zylann.hterrain/tools/ folder is required for the plugin to work in the editor, but it can be removed in exported games. You can specify this folder in your export presets:

Screenshot of the export window with tools folder ignored

The documentation in res://addons/zylann.hterrain/doc/ can also be removed, but this one contains a .gdignore file so hopefully Godot will automatically ignore it even in the editor.

GDNative

This plugin contains an optional native component, which speeds up some operations such as sculpting the terrain. However, at time of writing, a prebuilt binary is built-in only on Windows and Linux, I'm not yet able to build for other platforms so you may need to do it yourself, until I can provide an official one.

Before doing this, it's preferable to close the Godot editor so it won't lock the library files. Note that these steps are very similar to GDNative C++ development, which repeats parts of Godot's documentation.

Building instructions

To build the library, you will need to install the following:

  • Python 3.6 or later
  • The SCons build system
  • A C++ compiler
  • The Git version control system

If you got the plugin from the asset library

You will need to download C++ bindings for Godot. Go to res://addons/zylann.hterrain/native, open a command prompt, and run the following commands:

git clone https://github.com/GodotNativeTools/godot-cpp
cd godot-cpp
git submodule update --init --recursive

If you cloned the plugin using Git

In this case the C++ bindings submodule will already be there, and will need to be updated. Go to res://addons/zylann.hterrain/native, open a command prompt, and run the following commands:

git submodule update --init --recursive target=release

Build C++ bindings

Now go to res://addons/zylann.hterrain/native/cpp-bindings, open a command prompt (or re-use the one you have already), and run this command:

scons platform=<yourplatform> generate_bindings=yes target=release

yourplatform must match the platform you want to build for. It should be one of the following:

  • windows
  • linux
  • osx

Build the HTerrain library

Go back to res://addons/zylann.hterrain/native, and run this command, which has similar options as the one we saw before:

scons platform=<yourplatform> target=release

This will produce a library file under the bin/ folder.

Register the library

Now the last step is to tell the plugin the library is available. In the native/ folder, open the hterrain.gdnlib resource in a text editor, and add the path to the library under the [entry] category. Here is an example of how it should look like for several platforms:

[general]

singleton=false
load_once=true
symbol_prefix="godot_"
reloadable=false

[entry]

OSX.64 = "res://addons/zylann.hterrain/native/bin/osx64/libhterrain_native.dylib"
OSX.32 = "res://addons/zylann.hterrain/native/bin/osx32/libhterrain_native.dylib"
Windows.64 = "res://addons/zylann.hterrain/native/bin/win64/libhterrain_native.dll"
X11.64 = "res://addons/zylann.hterrain/native/bin/linux/libhterrain_native.so"

[dependencies]

Windows.64=[  ]
X11.64=[  ]

Finally, open the factory.gd script, and add an OS entry for your platform. The plugin should now be ready to use the native library.

Debugging

If you get a crash or misbehavior, check logs first to make sure Godot was able to load the library. If you want to use a C++ debugger, you can repeat this setup, only replacing release with debug when running SCons. This will then allow you to attach to Godot and place breakpoints (which works best if you also use a debug Godot version).

Troubleshooting

We do the best we can on our free time to make this plugin usable, but it's possible bugs appear. Some of them are known issues. If you have a problem, please refer to the issue tracker.

Before reporting any bug

  • Make sure you have the latest version of the plugin
  • Make sure it hasn't been reported already (including closed issues)
  • Check your Godot version. This plugin only works starting from Godot 3.1, and does not support 4.x yet. It is also possible that some issues exist in Godot 3.1 but could only be fixed in later versions.
  • Make sure you are using the GLES3 renderer. GLES2 is not supported.
  • Make sure your addons folder is located at res://addons, and does not contain uppercase letters. This might work on Windows but it will break after export.

If you report a new bug

If none of the initial checks help and you want to post a new issue, do the following:

  • Check the console for messages, warnings and errors. These are helpful to diagnose the issue.
  • Try to reproduce the bug with precise reproduction steps, and indicate them
  • Provide a test project with those steps (unless it's reproducible from an empty project), so that we can reproduce the bug and fix it more easily. Github allows you to drag-and-drop zip files.
  • Indicate your OS, Godot version and graphics card model. Those are present in logs as well.

Terrain not saving / not up to date / not showing

This issue happened a few times and had various causes so if the checks mentioned before don't help:

  • Check the contents of your terrain's data folder. It must contain a .hterrain file and a few textures.
  • If they are present, make sure Godot has imported those textures. If it didn't, unfocus the editor, and focus it back (you should see a short progress bar as it does it)
  • Check if you used Ctrl+Z (undo) after a non-undoable action, like described in issue #101
  • Make sure your res://addons folder is named addons exactly lowercase. It should not be named Addons. Plugins can fail if this convention is not respected.
  • If your problem relates to collisions in editor, update the collider using Terrain -> Update Editor Collider, because this one does not update automatically yet
  • Godot seems to randomly forget where the terrain saver is, but I need help to find out why because I could never reproduce it. See issue #120

Temporary files

The plugin creates temporary files to avoid cluttering memory. They are necessary for some functionalities to work. Those files should be cleaned up automatically when you close the editor or if you turn off the plugin. However, if a crash occurs or something else goes wrong, they might not get removed. If you want to check them out, they are located in user://hterrain_image_cache.

On Windows, that directory corresponds to C:\Users\Username\AppData\Roaming\Godot\app_userdata\ProjectName\hterrain_image_cache.

See Godot's documentation for other platforms.