Texture sets
This is an upcoming feature I'd like to expose before I merge it. Texture sets would be a new custom resource to setup terrain textures, replacing the old way.
Existing system
So far setting up textures for the ground was up to the user, as explained here: https://github.com/Zylann/godot_heightmap_plugin/blob/master/addons/zylann.hterrain/doc/main.md#texturing
If you used a classic shader, you had to make 4 pairs of textures that are each packed like this:
- Albedo in RGB, bump in Alpha
- Normal in RGB, roughness in Alpha
You had to do this packing yourself in an image editor, or using a separate plugin.
If you used an array-based shader, you had to not only do that, but also assemble all your textures in an atlas, and use the default TextureArray importer of Godot, which is impractical and time consuming (also you better have lots of RAM to spare).
Proposal
HTerrainTextureSet will be a new resource that can hold a collection of terrain textures. It lets you define source images (albedo, bump, normal, roughness), and allows to import them into the kind of texture the plugin expects. This way, you won't need to use an external tool anymore, and changing one of the source images is a lot simpler in the case of texture arrays.
Each HTerrain node will start with one of these resources by default, and once you choose the data directory, it will be saved next to the data.hterrain resource, as a simple .tres file. The generated textures will then be saved in the same place, either as .stex or .texarr.
In this workflow, source images may not be used by the game, so it will be possible to place them in a folder with a .gdignore file, so they won't bloat the exported build. This leads to the following folder organization:
terrain_test/
source_textures/
.gdignore
grass col.png
grass nrm.png
grass rgh.png
grass bmp.png
Rocks_Albedo.jpg
Rocks_Bump.jpg
Rocks_Normal.jpg
Rocks_Roughness.jpg
...
terrain_data/
color.png
data.hterrain
detail.png
detail2.png
global_albedo.png
height.res
normal.png
splat.png
textures.tres <-- TEXTURE SET
textures_t0_albedo_bump.stex <-- IMPORTED TEXTURES
textures_t1_albedo_bump.stex
...
textures_t0_normal_roughness.stex
textures_t1_normal_roughness.stex
...
terrain_test.tscn
Note: because it behaves like a regular resource, it is also possible to re-use the same pre-made set of textures for multiple terrains, by assigning it in the inspector.
Interface
There will be a custom interface to configure a HTerrainTextureSet, which can be shown either by inspecting the .tres resource, or with a shortcut from the terrain editor (double-click on any texture slot, or choose Edit...).
Work-in-progress:

Other options may also include default values for non-filled slots, normalmap strength to allow using DX convention from CC0Textures, and a mode to switch between texture arrays and individual textures, depending on the shader you are using.
Seems to be a nice workflow improvement. What are the drawbacks?
@RonanZe if you only want to use albedo textures without normals, without fancy blending, and only 4 textures, then you might find the process a bit redundant compared to assigning textures directly without import step (although Godot still imports from PNGs to STEXs in any case).
I was in the testing phase when I found the following blockers.
I was hoping that I would be able to generate StreamTextures or TextureArray from code, save them, assign them to the TextureSet resource, and finally save it. Simple? NOPE:
-
1: generating native Godot formats (.texarr, .stex) really sucks.
ResourceSaverdoes not allow to saveTextureArrayresources, andStreamTexturecan't even be made from scripts. So I ended up re-implementing the entire logic of texture importers in GDScript... sadly there is a bunch of things GDScript can't do because they are not exposed, in particular when dealing with image compression. -
2: it appears some VRAM compression formats may depend on feature tags sent to
EditorImportPlugin, depending on your project settings and the platforms you export for... which means to produce these imported formats, I would HAVE to write one. But the problem is, the import dock won't let me do anything close to the nice free UI I posted earlier. -
3: if I have to make an
EditorImportPlugin, it meansTextureSetcan't be the only file, because... to react to the importer dock, it has to be an "importable" format. So the idea would be to introduce a filetype and custom importer, which will import as a TextureSet.- There is a problem with importers to custom resources: https://github.com/godotengine/godot/issues/20496
- A variant would be to introduce a
PackedTextureImporterandPackedTextureArrayImporter, which would import textures from descriptor files listing the channel operations and other options. ThenTextureSetwould be able to reference the resulting textures. This would be very nice for re-usability and simplicity of each part, but unmanageable because of the next point...
-
4: if I rely on the import system, it means I have to:
- Be able to trigger import from script for specific files, which is not exposed.
- Be 100% sure that a file has been imported properly, so my editor can add the textures to the
TextureSetresource after the user clicks "import". Otherwise, doing something liketextures.add(load(path))would just fail because import either failed or did not happen yet. Right now I can trigger a full scan, but I have no guarantee as to when I'll be able to callloadto add the texture in the list... - If either of these isn't possible, then I have to write an importer that directly imports as a
TextureSet, which sucks for re-usability and API simplicity. Otherwise, it means I can't automate this step to users.
-
Bonus 1: I did the math, and if someone makes a game with terrains having 16 2K PBR textures on them (and it happened), the required space in the project can quickly go above 1 Gb, just from PNG files. Godot's
TextureArrayimporter holds all images im memory uncompressed, TWICE (once for the input, once for slicing that big atlas it wants), so you better have lots of RAM to spare while importing... unless I take the opportunity to implement the importer differently (what about the loading of that resource tho?? time will tell...). -
Bonus 2: the LOSSY compression mode (which just saves files with the WEBP format) is missing for texture arrays, and WEBP is missing from the script API, so I won't be able to make it available. A lost opportunity to lower this big amount of data...
The alternative is to not import, and just generate PNGs, then reverse-engineer .import files. It's also dirty, but not as bad, and I did this already with terrain data.
Downside is:
- It will bloat your project because then all those 2K PBR textures will now appear THREE times in your project (source PNGs, processed PNGs, and imported textures from Godot). This is going to be really heavy.
- It will force you to manually unfocus/refocus the editor each time you make a change to these textures, because scripts can't invoke the importer.
- I still can't automatically reference the resulting textures anyways, because there is no "transaction" in the import system to tell you when it's done or if it failed (see point 4).
Conclusion:
- I can make a monolithic importer that will look like the UI above, with internal limitations and bugs beyond my control
- Or I can make a tool to generate PNGs, separate from the UI because with this approach I can't integrate it reliably. In this approach you would also loose more disk space, and be limited by the builtin importer RAM usage.
I wasted two nights on this so I'll make a pause there... I need to figure out what to do from here, because it appears what I wanted to do is impossible, not with extra steps from the user (crazy, right)... I would really like to get the design from my previous post though... the "correct" way Godot is exposing feels very limiting.
Update: I've been working hard to overcome some of the issues I had earlier, and I managed to get this feature working in the end, with a few differences from the initial proposal.
The feature is available to test in the texture_sets branch, along with new documentation https://github.com/Zylann/godot_heightmap_plugin/blob/texture_sets/addons/zylann.hterrain/doc/main.md#texturing
This is now in master.