Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Terrain3D.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,7 @@
<None Include="project\addons\terrain_3d\terrain.gdextension" />
<None Include="README.md" />
<None Include="SConstruct" />
<None Include="src\shaders\color_map.glsl" />
<None Include="src\shaders\debug_views.glsl" />
<None Include="src\shaders\displacement.glsl" />
<None Include="src\shaders\displacement_buffer.glsl" />
Expand Down
3 changes: 3 additions & 0 deletions Terrain3D.vcxproj.filters
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,9 @@
<None Include="doc\docs\displacement.md">
<Filter>2. Docs</Filter>
</None>
<None Include="src\shaders\color_map.glsl">
<Filter>4. Shaders</Filter>
</None>
</ItemGroup>
<ItemGroup>
<Text Include=".readthedocs.yaml">
Expand Down
6 changes: 6 additions & 0 deletions doc/doc_classes/Terrain3D.xml
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,12 @@
If enabled, heightmaps are saved as 16-bit half-precision to reduce file size. Files are always loaded in 32-bit for editing. Upon save, a copy of the heightmap is converted to 16-bit for writing. It does not change what is currently in memory.
This process is lossy. 16-bit precision gets increasingly worse with every power of 2. At a height of 256m, the precision interval is .25m. At 512m it is .5m. At 1024m it is 1m. Saving a height of 1024.4m will be rounded down to 1024m.
</member>
<member name="color_compression_mode" type="ColorCompressMode" setter="set_color_compression_mode" getter="get_color_compression_mode" default="None">
The selected compression mode will be used to compress the color maps, to be used during runtime. The uncompressed color map will be used during editing. Upon save, a copy of the color map is compressed to the selected compression mode. During runtime, the compressed color map will be used instead of the uncompressed color map.
</member>
<member name="free_uncompressed_color_maps" type="bool" setter="set_free_uncompressed_color_maps" getter="get_free_uncompressed_color_maps" default="true">
Frees the uncompressed color maps for the regions, if the application is not running in the editor, and [member color_compression_mode] is set to something other than None. The uncompressed color maps are also freed upon exporting the game.
</member>
<member name="show_autoshader" type="bool" setter="set_show_autoshader" getter="get_show_autoshader" default="false">
Displays the area designated for use by the autoshader, which shows materials based upon slope.
Alias for [member Terrain3DMaterial.show_autoshader].
Expand Down
4 changes: 3 additions & 1 deletion doc/doc_classes/Terrain3DData.xml
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@
<method name="get_color_maps_rid" qualifiers="const">
<return type="RID" />
<description>
Returns the resource ID of the generated height map Texture Array sent to the shader. You can use this RID with the RenderingServer to set it as a shader parameter for a sampler2DArray uniform in your own shader. See [url=https://terrain3d.readthedocs.io/en/stable/docs/tips.html#using-the-generated-height-map-in-other-shaders]Tips[/url] for an example.
Returns the resource ID of the generated color map Texture Array sent to the shader. You can use this RID with the RenderingServer to set it as a shader parameter for a sampler2DArray uniform in your own shader.
</description>
</method>
<method name="get_control" qualifiers="const">
Expand Down Expand Up @@ -431,10 +431,12 @@
<param index="0" name="region_location" type="Vector2i" />
<param index="1" name="directory" type="String" />
<param index="2" name="save_16_bit" type="bool" default="false" />
<param index="3" name="color_compression_mode" type="Image::CompressMode" default="Image::COMPRESS_MAX" />
<description>
Saves the specified active region to the directory. See [method Terrain3DRegion.save].
- region_location - the region to save.
- 16_bit - converts the edited 32-bit heightmap to 16-bit. This is a lossy operation.
- color_compression_mode - compresses the color map to selected compression mode.
</description>
</method>
<method name="set_color">
Expand Down
11 changes: 9 additions & 2 deletions doc/doc_classes/Terrain3DRegion.xml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
<return type="Image" />
<param index="0" name="map_type" type="int" enum="Terrain3DRegion.MapType" />
<description>
Returns the specified image map.
Returns the specified image map. Returns the compressed color map if a compression mode is selected and not in editor mode.
</description>
</method>
<method name="get_maps" qualifiers="const">
Expand All @@ -58,18 +58,22 @@
</method>
<method name="sanitize_maps">
<return type="void" />
<param index="0" name="free_uncompressed_color_maps" type="bool" />
<description>
Sanitizes all map types. See [method sanitize_map].
- free_uncompressed_color_maps - if true, the uncompressed color map will be freed instead of sanitized.
</description>
</method>
<method name="save">
<return type="int" enum="Error" />
<param index="0" name="path" type="String" default="&quot;&quot;" />
<param index="1" name="save_16_bit" type="bool" default="false" />
<param index="2" name="color_compression_mode" type="Image::CompressMode" default="Image::COMPRESS_MAX" />
<description>
Saves this region to the current file name.
- path - specifies a directory and file name to use from now on.
- 16-bit - save this region with 16-bit height map instead of 32-bit. This process is lossy. Does not change the bit depth in memory.
- 16-bit - saves this region with 16-bit height map instead of 32-bit. This process is lossy. Does not change the bit depth in memory.
- color_compression_mode - saves a compressed color map for this region if a compression mode is set.
</description>
</method>
<method name="set_data">
Expand Down Expand Up @@ -123,6 +127,9 @@
[b]RGB[/b] is used for color, which is multiplied by albedo in the shader. Multiply is a blend mode that only darkens.
[b]A[/b] is used for a roughness modifier. A value of 0.5 means no change to the existing texture roughness. Higher than this value increases roughness, lower decreases it.
</member>
<member name="compressed_color_map" type="Image" setter="set_compressed_color_map" getter="get_compressed_color_map">
A compressed version of the color map. Empty if [member Terrain3D.color_compression_mode] is set to None. Otherwise, this texture will be used instead of the uncompressed color map when not in editor mode.
</member>
<member name="control_map" type="Image" setter="set_control_map" getter="get_control_map">
This map tells the shader which textures to use where, how to blend, where to place holes, etc.
Image format: FORMAT_RF, 32-bit per pixel as full-precision floating-point.
Expand Down
1 change: 1 addition & 0 deletions doc/docs/data_format.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ The data format version is found as [Terrain3DData.version](../api/class_terrain

| Version | Description |
|---------|-------------------|
| 1.10 | Added a compressed color map [#842](https://github.com/TokisanGames/Terrain3D/pull/842)
| 0.93 | The monolithic storage file was split into one file per region [#374](https://github.com/TokisanGames/Terrain3D/pull/374), [#476](https://github.com/TokisanGames/Terrain3D/pull/476)
| 0.92 | Add `Terrain3DInstancer` data [#340](https://github.com/TokisanGames/Terrain3D/pull/340)
| 0.842 | Control map changed from FORMAT_RGB to 32-bit packed integer (encoded in FORMAT_RF) [#234](https://github.com/TokisanGames/Terrain3D/pull/234/)
Expand Down
2 changes: 2 additions & 0 deletions doc/docs/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,3 +80,5 @@ If upgrading from a very old version, you may need to go through multiple steps
| 0.8.0 | 0.8.4 - 0.9.0 |

* 0.9.3 - Data storage changed from a single .res file to one file per region saved in a directory.

Also see [Data Format Changelog](data_format.md).
4 changes: 4 additions & 0 deletions project/addons/terrain_3d/src/editor_plugin.gd
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ extends EditorPlugin
# Includes
const Terrain3DUI: Script = preload("res://addons/terrain_3d/src/ui.gd")
const RegionGizmo: Script = preload("res://addons/terrain_3d/src/region_gizmo.gd")
const ExportPlugin: Script = preload("res://addons/terrain_3d/src/export_plugin.gd")
const ASSET_DOCK: String = "res://addons/terrain_3d/src/asset_dock.tscn"

# Editor Plugin
Expand All @@ -21,6 +22,7 @@ var mouse_global_position: Vector3 = Vector3.ZERO
var godot_editor_window: Window # The Godot Editor window
var viewport: SubViewport # Viewport the mouse was last in
var mouse_in_main: bool = false # Helper to track when mouse is in the editor vp
var export_plugin = ExportPlugin.new()

# Terrain
var terrain: Terrain3D
Expand Down Expand Up @@ -64,6 +66,7 @@ func _enter_tree() -> void:

asset_dock = load(ASSET_DOCK).instantiate()
asset_dock.initialize(self)
add_export_plugin(export_plugin)


func _exit_tree() -> void:
Expand All @@ -76,6 +79,7 @@ func _exit_tree() -> void:

scene_changed.disconnect(_on_scene_changed)
godot_editor_window.focus_entered.disconnect(_on_godot_focus_entered)
remove_export_plugin(export_plugin)


func _on_godot_focus_entered() -> void:
Expand Down
46 changes: 46 additions & 0 deletions project/addons/terrain_3d/src/export_plugin.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Copyright © 2025 Cory Petkovsek, Roope Palmroos, and Contributors.
# Editor Export Plugin for Terrain3D
@tool
extends EditorExportPlugin


var _hash: String
var _free_uncompressed_color_maps: bool
var _color_compress_mode: Image.CompressMode


func _get_name() -> String:
return "Terrain3DExportPlugin"


func _begin_customize_scenes(platform: EditorExportPlatform, features: PackedStringArray) -> bool:
return true


func _customize_scene(scene: Node, path: String) -> Node:
var terrain: Terrain3D = scene.find_child("Terrain3D", true)
if terrain:
_free_uncompressed_color_maps = terrain.get_free_uncompressed_color_maps()
_color_compress_mode = terrain.get_color_image_compress_mode()
return null


func _begin_customize_resources(platform: EditorExportPlatform, features: PackedStringArray) -> bool:
_hash = ""
for feat: String in features:
_hash += feat
_hash += platform.to_string()
return true
Comment thread
stan4dbunny marked this conversation as resolved.


func _customize_resource(resource: Resource, path: String) -> Resource:
if resource is Terrain3DRegion:
var region: Terrain3DRegion = resource
if _color_compress_mode != Image.COMPRESS_MAX and _free_uncompressed_color_maps and region.compressed_color_map != null:
region.free_uncompressed_color_map()
return region
return null


func _get_customization_configuration_hash() -> int:
return hash(_hash)
1 change: 1 addition & 0 deletions project/addons/terrain_3d/src/export_plugin.gd.uid
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
uid://bfl6jfwv15i0f
24 changes: 24 additions & 0 deletions src/shaders/color_map.glsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Copyright © 2025 Cory Petkovsek, Roope Palmroos, and Contributors.

R"(
//INSERT: COLOR_MAP_BASE
color_map = region_uv.z > -1.0 ? textureLod(_color_maps, region_uv, region_mip) : COLOR_MAP_DEF;

//INSERT: COLOR_MAP_BILERP
// 4 lookups if linear filtering, else 1 lookup.
vec4 col_map[4];
col_map[3] = index[3].z > -1 ? texelFetch(_color_maps, index[3], 0) : COLOR_MAP_DEF;
#ifdef FILTER_LINEAR
col_map[0] = index[0].z > -1 ? texelFetch(_color_maps, index[0], 0) : COLOR_MAP_DEF;
col_map[1] = index[1].z > -1 ? texelFetch(_color_maps, index[1], 0) : COLOR_MAP_DEF;
col_map[2] = index[2].z > -1 ? texelFetch(_color_maps, index[2], 0) : COLOR_MAP_DEF;

color_map =
col_map[0] * weights[0] +
col_map[1] * weights[1] +
col_map[2] * weights[2] +
col_map[3] * weights[3] ;
#else
color_map = col_map[3];
#endif
)"
20 changes: 3 additions & 17 deletions src/shaders/main.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -481,26 +481,12 @@ void fragment() {
base_ddy *= bias;

// Color map
vec4 color_map = region_uv.z > -1.0 ? textureLod(_color_maps, region_uv, region_mip) : COLOR_MAP_DEF;
vec4 color_map = COLOR_MAP_DEF;
//INSERT: COLOR_MAP_BASE

// Branching smooth normals and manually interpolated color map - fixes cross region artifacts
if (bilerp) {
// 4 lookups if linear filtering, else 1 lookup.
vec4 col_map[4];
col_map[3] = index[3].z > -1 ? texelFetch(_color_maps, index[3], 0) : COLOR_MAP_DEF;
#ifdef FILTER_LINEAR
col_map[0] = index[0].z > -1 ? texelFetch(_color_maps, index[0], 0) : COLOR_MAP_DEF;
col_map[1] = index[1].z > -1 ? texelFetch(_color_maps, index[1], 0) : COLOR_MAP_DEF;
col_map[2] = index[2].z > -1 ? texelFetch(_color_maps, index[2], 0) : COLOR_MAP_DEF;

color_map =
col_map[0] * weights[0] +
col_map[1] * weights[1] +
col_map[2] * weights[2] +
col_map[3] * weights[3] ;
#else
color_map = col_map[3];
#endif
//INSERT: COLOR_MAP_BILERP

// 5 lookups
// Fetch the additional required height values for smooth normals
Expand Down
55 changes: 52 additions & 3 deletions src/terrain_3d.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -610,6 +610,30 @@ void Terrain3D::set_save_16_bit(const bool p_enabled) {
}
}

void Terrain3D::set_color_map_enabled(const bool p_enabled) {
if (_material.is_valid()) {
_material->set_color_map_enabled(p_enabled);
}
if (_data) {
_data->set_color_map_enabled(p_enabled);
}
}

void Terrain3D::set_color_map_mode(const ColorMapMode p_mode) {
SET_IF_DIFF(_color_map_mode, p_mode);
LOG(INFO, "Setting color map mode: ", _color_map_mode);
notify_property_list_changed();
}

void Terrain3D::set_color_compress_mode(const CompressMode p_mode) {
SET_IF_DIFF(_color_compress_mode, p_mode);
LOG(INFO, "Setting compression mode for color maps: ", _color_compress_mode);
TypedArray<Terrain3DRegion> regions = _data->get_regions_active();
for (Ref<Terrain3DRegion> region : regions) {
region->set_modified(true);
}
}

void Terrain3D::set_label_distance(const real_t p_distance) {
SET_IF_DIFF(_label_distance, CLAMP(p_distance, 0.f, 100000.f));
LOG(INFO, "Setting region label distance: ", _label_distance);
Expand Down Expand Up @@ -1141,7 +1165,12 @@ void Terrain3D::_validate_property(PropertyInfo &p_property) const {
p_property.name == StringName("displacement_sharpness") ||
p_property.name == StringName("buffer_shader_override_enabled") ||
p_property.name == StringName("buffer_shader_override")) {
p_property.usage = PROPERTY_USAGE_NO_EDITOR;
p_property.usage &= ~PROPERTY_USAGE_EDITOR;
}
}
if (_color_map_mode != COLOR_COMPRESSED) {
if (p_property.name == StringName("color_compress_mode")) {
p_property.usage &= ~PROPERTY_USAGE_EDITOR;
}
}
}
Expand All @@ -1159,6 +1188,11 @@ void Terrain3D::_bind_methods() {
BIND_ENUM_CONSTANT(SIZE_1024);
BIND_ENUM_CONSTANT(SIZE_2048);

BIND_ENUM_CONSTANT(COLOR_DISABLED);
BIND_ENUM_CONSTANT(COLOR_CLEARED);
BIND_ENUM_CONSTANT(COLOR_EDITABLE);
BIND_ENUM_CONSTANT(COLOR_COMPRESSED);

ClassDB::bind_method(D_METHOD("get_version"), &Terrain3D::get_version);
ClassDB::bind_method(D_METHOD("set_debug_level", "level"), &Terrain3D::set_debug_level);
ClassDB::bind_method(D_METHOD("get_debug_level"), &Terrain3D::get_debug_level);
Expand Down Expand Up @@ -1194,6 +1228,15 @@ void Terrain3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_region_size"), &Terrain3D::get_region_size);
ClassDB::bind_method(D_METHOD("set_save_16_bit", "enabled"), &Terrain3D::set_save_16_bit);
ClassDB::bind_method(D_METHOD("get_save_16_bit"), &Terrain3D::get_save_16_bit);
ClassDB::bind_method(D_METHOD("set_free_color_map"), &Terrain3D::set_free_color_map);
ClassDB::bind_method(D_METHOD("get_free_color_map"), &Terrain3D::get_free_color_map);
ClassDB::bind_method(D_METHOD("set_color_map_enabled", "enabled"), &Terrain3D::set_color_map_enabled);
ClassDB::bind_method(D_METHOD("get_color_map_enabled"), &Terrain3D::get_color_map_enabled);
ClassDB::bind_method(D_METHOD("set_color_map_mode", "mode"), &Terrain3D::set_color_map_mode);
ClassDB::bind_method(D_METHOD("get_color_map_mode"), &Terrain3D::get_color_map_mode);
ClassDB::bind_method(D_METHOD("set_color_compress_mode", "mode"), &Terrain3D::set_color_compress_mode);
ClassDB::bind_method(D_METHOD("get_color_compress_mode"), &Terrain3D::get_color_compress_mode);
ClassDB::bind_method(D_METHOD("get_color_image_compress_mode"), &Terrain3D::get_color_image_compress_mode);
ClassDB::bind_method(D_METHOD("set_label_distance", "distance"), &Terrain3D::set_label_distance);
ClassDB::bind_method(D_METHOD("get_label_distance"), &Terrain3D::get_label_distance);
ClassDB::bind_method(D_METHOD("set_label_size", "size"), &Terrain3D::set_label_size);
Expand Down Expand Up @@ -1244,7 +1287,7 @@ void Terrain3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_gi_mode"), &Terrain3D::get_gi_mode);
ClassDB::bind_method(D_METHOD("set_cull_margin", "margin"), &Terrain3D::set_cull_margin);
ClassDB::bind_method(D_METHOD("get_cull_margin"), &Terrain3D::get_cull_margin);
ClassDB::bind_method(D_METHOD("set_free_editor_textures"), &Terrain3D::set_free_editor_textures);
ClassDB::bind_method(D_METHOD("set_free_editor_textures", "enabled"), &Terrain3D::set_free_editor_textures);
ClassDB::bind_method(D_METHOD("get_free_editor_textures"), &Terrain3D::get_free_editor_textures);
ClassDB::bind_method(D_METHOD("set_instancer_mode", "mode"), &Terrain3D::set_instancer_mode);
ClassDB::bind_method(D_METHOD("get_instancer_mode"), &Terrain3D::get_instancer_mode);
Expand Down Expand Up @@ -1319,10 +1362,16 @@ void Terrain3D::_bind_methods() {

ADD_GROUP("Regions", "");
ADD_PROPERTY(PropertyInfo(Variant::INT, "region_size", PROPERTY_HINT_ENUM, "64:64,128:128,256:256,512:512,1024:1024,2048:2048", PROPERTY_USAGE_EDITOR), "change_region_size", "get_region_size");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "save_16_bit"), "set_save_16_bit", "get_save_16_bit");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "label_distance", PROPERTY_HINT_RANGE, "0.0,10000.0,0.5,or_greater"), "set_label_distance", "get_label_distance");
ADD_PROPERTY(PropertyInfo(Variant::INT, "label_size", PROPERTY_HINT_RANGE, "24,128,1"), "set_label_size", "get_label_size");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_grid"), "set_show_region_grid", "get_show_region_grid");
ADD_SUBGROUP("Advanced", "");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "save_16_bit"), "set_save_16_bit", "get_save_16_bit");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "free_color_map"), "set_free_color_map", "get_free_color_map");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "color_map_enabled"), "set_color_map_enabled", "get_color_map_enabled");
ADD_PROPERTY(PropertyInfo(Variant::INT, "color_map_mode", PROPERTY_HINT_ENUM, "Disabled,Cleared,Editable,Compressed"), "set_color_map_mode", "get_color_map_mode");
ADD_PROPERTY(PropertyInfo(Variant::INT, "color_compress_mode", PROPERTY_HINT_ENUM, "None,S3TC (LQ Desktop),BPTC (HQ Desktop),ETC1 (LQ Mobile),ETC2 (Mobile),ASTC (Mobile)"),
"set_color_compress_mode", "get_color_compress_mode");

ADD_GROUP("Collision", "");
ADD_PROPERTY(PropertyInfo(Variant::INT, "collision_mode", PROPERTY_HINT_ENUM, "Disabled,Dynamic / Game,Dynamic / Editor,Full / Game,Full / Editor"), "set_collision_mode", "get_collision_mode");
Expand Down
Loading