diff --git a/Terrain3D.vcxproj b/Terrain3D.vcxproj
index abec7f2d8..66ee340b3 100644
--- a/Terrain3D.vcxproj
+++ b/Terrain3D.vcxproj
@@ -224,11 +224,13 @@
+
Document
+
diff --git a/Terrain3D.vcxproj.filters b/Terrain3D.vcxproj.filters
index 026e09887..67d5b5d8b 100644
--- a/Terrain3D.vcxproj.filters
+++ b/Terrain3D.vcxproj.filters
@@ -283,6 +283,12 @@
2. Docs
+
+ 4. Shaders
+
+
+ 4. Shaders
+
diff --git a/doc/api/class_terrain3dassets.rst b/doc/api/class_terrain3dassets.rst
index 02b8c77d4..d19c45059 100644
--- a/doc/api/class_terrain3dassets.rst
+++ b/doc/api/class_terrain3dassets.rst
@@ -74,8 +74,6 @@ Methods
+-----------------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| ``PackedFloat32Array`` | :ref:`get_texture_uv_scales`\ (\ ) |const| |
+-----------------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
- | ``int`` | :ref:`get_texture_vertical_projections`\ (\ ) |const| |
- +-----------------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Error | :ref:`save`\ (\ path\: ``String`` = ""\ ) |
+-----------------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| |void| | :ref:`set_mesh_asset`\ (\ id\: ``int``, mesh\: :ref:`Terrain3DMeshAsset`\ ) |
@@ -416,18 +414,6 @@ Returns the array of uv scale values for each texture asset, indexed by asset id
----
-.. _class_Terrain3DAssets_method_get_texture_vertical_projections:
-
-.. rst-class:: classref-method
-
-``int`` **get_texture_vertical_projections**\ (\ ) |const| :ref:`🔗`
-
-Returns a 32-bit int that identifies which textures are to be projected vertically.
-
-.. rst-class:: classref-item-separator
-
-----
-
.. _class_Terrain3DAssets_method_save:
.. rst-class:: classref-method
diff --git a/doc/api/class_terrain3dmaterial.rst b/doc/api/class_terrain3dmaterial.rst
index b099afcb6..0b555bb79 100644
--- a/doc/api/class_terrain3dmaterial.rst
+++ b/doc/api/class_terrain3dmaterial.rst
@@ -23,7 +23,9 @@ This class handles options for both the built-in shader and any custom override
It is a savable resource, so you can save it to disk and use the same material settings in multiple scenes that use Terrain3D. The amount of data is small, assuming you have saved your shader parameter textures to disk, so it can be saved as a git-friendly, text based .tres file or left within the scene file.
-While it does mimic some of the functionality of ShaderMaterial, it does not derive from any of the Godot Material classes. It will not pass any ``is Material`` checks. It is a ``Resource``.
+While it does mimic some of the functionality of ShaderMaterial, it does not derive from any of the Godot Material classes. It will fail any ``is Material`` checks. It is a ``Resource``.
+
+Inspector settings above `Custom Shader` and :ref:`shader_override` are used to determine what code is used in the current shader. Inspector settings in `Shader Uniforms` are the public uniforms (not prefaced with `\_`) available in the current shader.
.. rst-class:: classref-reftable-group
@@ -48,6 +50,18 @@ Properties
+------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------+-----------+
| ``bool`` | :ref:`dual_scaling_enabled` | ``false`` |
+------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------+-----------+
+ | ``bool`` | :ref:`macro_variation_enabled` | ``false`` |
+ +------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------+-----------+
+ | ``bool`` | :ref:`output_albedo` | ``true`` |
+ +------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------+-----------+
+ | ``bool`` | :ref:`output_ambient_occlusion` | ``true`` |
+ +------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------+-----------+
+ | ``bool`` | :ref:`output_normal_map` | ``true`` |
+ +------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------+-----------+
+ | ``bool`` | :ref:`output_roughness` | ``true`` |
+ +------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------+-----------+
+ | ``bool`` | :ref:`projection_enabled` | ``false`` |
+ +------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------+-----------+
| ``Shader`` | :ref:`shader_override` | |
+------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------+-----------+
| ``bool`` | :ref:`shader_override_enabled` | ``false`` |
@@ -124,7 +138,7 @@ Methods
+-------------+----------------------------------------------------------------------------------------------------------------------------+
| |void| | :ref:`set_shader_param`\ (\ name\: ``StringName``, value\: ``Variant``\ ) |
+-------------+----------------------------------------------------------------------------------------------------------------------------+
- | |void| | :ref:`update`\ (\ full\: ``bool`` = false\ ) |
+ | |void| | :ref:`update`\ (\ flags\: ``int`` = 0\ ) |
+-------------+----------------------------------------------------------------------------------------------------------------------------+
.. rst-class:: classref-section-separator
@@ -192,6 +206,56 @@ Textures are filtered using a blend of 4 adjacent pixels. Use this for most case
Textures are filtered using a the nearest pixel only. It is faster than LINEAR, but the texture will look pixelated. Use this for a low-poly look, with a very low uv_scale.
+.. rst-class:: classref-item-separator
+
+----
+
+.. _enum_Terrain3DMaterial_UpdateFlags:
+
+.. rst-class:: classref-enumeration
+
+enum **UpdateFlags**: :ref:`🔗`
+
+.. _class_Terrain3DMaterial_constant_UNIFORMS_ONLY:
+
+.. rst-class:: classref-enumeration-constant
+
+:ref:`UpdateFlags` **UNIFORMS_ONLY** = ``0``
+
+Non-texture array values are assigned to the shader. This is the default and is always done.
+
+.. _class_Terrain3DMaterial_constant_TEXTURE_ARRAYS:
+
+.. rst-class:: classref-enumeration-constant
+
+:ref:`UpdateFlags` **TEXTURE_ARRAYS** = ``1``
+
+The ground texture arrays are assigned to the shader, along with the values in `UNIFORMS_ONLY`.
+
+.. _class_Terrain3DMaterial_constant_REGION_ARRAYS:
+
+.. rst-class:: classref-enumeration-constant
+
+:ref:`UpdateFlags` **REGION_ARRAYS** = ``2``
+
+The region data texture arrays are assigned to the shader, along with the values in `UNIFORMS_ONLY`.
+
+.. _class_Terrain3DMaterial_constant_UPDATE_ARRAYS:
+
+.. rst-class:: classref-enumeration-constant
+
+:ref:`UpdateFlags` **UPDATE_ARRAYS** = ``3``
+
+Values in `TEXTURE_ARRAYS` and `REGION_ARRAYS` are assigned to the shader.
+
+.. _class_Terrain3DMaterial_constant_FULL_REBUILD:
+
+.. rst-class:: classref-enumeration-constant
+
+:ref:`UpdateFlags` **FULL_REBUILD** = ``7``
+
+The shader is rebuilt, then all values in `UPDATE_ARRAYS` are assigned to the shader.
+
.. rst-class:: classref-section-separator
----
@@ -221,8 +285,8 @@ This private dictionary stores all of the shader parameters in the resource. It
.. rst-class:: classref-property-setget
-- |void| **set_auto_shader**\ (\ value\: ``bool``\ )
-- ``bool`` **get_auto_shader**\ (\ )
+- |void| **set_auto_shader_enabled**\ (\ value\: ``bool``\ )
+- ``bool`` **get_auto_shader_enabled**\ (\ )
Enables selecting two texture IDs that will automatically be applied to the terrain based upon slope.
@@ -306,8 +370,8 @@ Adjusts the transition between textures. When set at `1.0`, the blending of disp
.. rst-class:: classref-property-setget
-- |void| **set_dual_scaling**\ (\ value\: ``bool``\ )
-- ``bool`` **get_dual_scaling**\ (\ )
+- |void| **set_dual_scaling_enabled**\ (\ value\: ``bool``\ )
+- ``bool`` **get_dual_scaling_enabled**\ (\ )
Enables selecting one texture ID that will have multiple scales applied based upon camera distance. Use it for something like a rock texture so up close it will be nicely detailed, and far away mountains can be covered in the same rock texture without looking tiled. The two blend together at a specified distance.
@@ -315,6 +379,108 @@ Enables selecting one texture ID that will have multiple scales applied based up
----
+.. _class_Terrain3DMaterial_property_macro_variation_enabled:
+
+.. rst-class:: classref-property
+
+``bool`` **macro_variation_enabled** = ``false`` :ref:`🔗`
+
+.. rst-class:: classref-property-setget
+
+- |void| **set_macro_variation_enabled**\ (\ value\: ``bool``\ )
+- ``bool`` **get_macro_variation_enabled**\ (\ )
+
+Allows you to add a couple of noise patterns at different scales and colors to add variation to your terrain to avoid tiled textures.
+
+.. rst-class:: classref-item-separator
+
+----
+
+.. _class_Terrain3DMaterial_property_output_albedo:
+
+.. rst-class:: classref-property
+
+``bool`` **output_albedo** = ``true`` :ref:`🔗`
+
+.. rst-class:: classref-property-setget
+
+- |void| **set_output_albedo_enabled**\ (\ value\: ``bool``\ )
+- ``bool`` **get_output_albedo_enabled**\ (\ )
+
+Enables the Albedo, aka Base Color or Diffuse, output channel in the shader.
+
+.. rst-class:: classref-item-separator
+
+----
+
+.. _class_Terrain3DMaterial_property_output_ambient_occlusion:
+
+.. rst-class:: classref-property
+
+``bool`` **output_ambient_occlusion** = ``true`` :ref:`🔗`
+
+.. rst-class:: classref-property-setget
+
+- |void| **set_output_ambient_occlusion_enabled**\ (\ value\: ``bool``\ )
+- ``bool`` **get_output_ambient_occlusion_enabled**\ (\ )
+
+Enables the Ambient Occlusion output channel in the shader.
+
+.. rst-class:: classref-item-separator
+
+----
+
+.. _class_Terrain3DMaterial_property_output_normal_map:
+
+.. rst-class:: classref-property
+
+``bool`` **output_normal_map** = ``true`` :ref:`🔗`
+
+.. rst-class:: classref-property-setget
+
+- |void| **set_output_normal_map_enabled**\ (\ value\: ``bool``\ )
+- ``bool`` **get_output_normal_map_enabled**\ (\ )
+
+Enables the Normal Map output channel in the shader.
+
+.. rst-class:: classref-item-separator
+
+----
+
+.. _class_Terrain3DMaterial_property_output_roughness:
+
+.. rst-class:: classref-property
+
+``bool`` **output_roughness** = ``true`` :ref:`🔗`
+
+.. rst-class:: classref-property-setget
+
+- |void| **set_output_roughness_enabled**\ (\ value\: ``bool``\ )
+- ``bool`` **get_output_roughness_enabled**\ (\ )
+
+Enables the Roughness output channel in the shader.
+
+.. rst-class:: classref-item-separator
+
+----
+
+.. _class_Terrain3DMaterial_property_projection_enabled:
+
+.. rst-class:: classref-property
+
+``bool`` **projection_enabled** = ``false`` :ref:`🔗`
+
+.. rst-class:: classref-property-setget
+
+- |void| **set_projection_enabled**\ (\ value\: ``bool``\ )
+- ``bool`` **get_projection_enabled**\ (\ )
+
+Enables textures to be projected vertically when placed on slopes above 45 degrees. This is useful for mapping textures on cliff faces without stretching, even though the polygons are stretched.
+
+.. rst-class:: classref-item-separator
+
+----
+
.. _class_Terrain3DMaterial_property_shader_override:
.. rst-class:: classref-property
@@ -343,7 +509,9 @@ If shader_override_enabled is true and this Shader is valid, the material will u
- |void| **set_shader_override_enabled**\ (\ value\: ``bool``\ )
- ``bool`` **is_shader_override_enabled**\ (\ )
-Enables use of the :ref:`shader_override` shader code. Generates default code if shader_override is blank.
+Enables using the :ref:`shader_override` shader. An editable shader is generated from the current one if shader_override is blank.
+
+The inspector settings above this group determine the code that is used in the current shader. The settings below are uniforms for the current shader.
.. rst-class:: classref-item-separator
@@ -854,11 +1022,9 @@ Set a parameter in the active shader (built-in or override shader).
.. rst-class:: classref-method
-|void| **update**\ (\ full\: ``bool`` = false\ ) :ref:`🔗`
-
-Sends all uniform values to the shader again.
+|void| **update**\ (\ flags\: ``int`` = 0\ ) :ref:`🔗`
-full - recompiles the shader first.
+Sends uniform values to the shader. See :ref:`UpdateFlags` for options.
.. |virtual| replace:: :abbr:`virtual (This method should typically be overridden by the user to have any effect.)`
.. |const| replace:: :abbr:`const (This method has no side effects. It doesn't modify any of the instance's member variables.)`
diff --git a/doc/api/class_terrain3dregion.rst b/doc/api/class_terrain3dregion.rst
index 456c2aa53..35ccc9f97 100644
--- a/doc/api/class_terrain3dregion.rst
+++ b/doc/api/class_terrain3dregion.rst
@@ -64,6 +64,8 @@ Methods
+-----------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+
| |void| | :ref:`calc_height_range`\ (\ ) |
+-----------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+
+ | |void| | :ref:`clear`\ (\ ) |
+ +-----------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+
| |void| | :ref:`dump`\ (\ verbose\: ``bool`` = false\ ) |const| |
+-----------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+
| :ref:`Terrain3DRegion` | :ref:`duplicate`\ (\ deep\: ``bool`` = false\ ) |
@@ -402,6 +404,18 @@ Recalculates the height range for this region by looking at every pixel in the h
----
+.. _class_Terrain3DRegion_method_clear:
+
+.. rst-class:: classref-method
+
+|void| **clear**\ (\ ) :ref:`🔗`
+
+Unreferences the maps and resets all of the variables to default values.
+
+.. rst-class:: classref-item-separator
+
+----
+
.. _class_Terrain3DRegion_method_dump:
.. rst-class:: classref-method
diff --git a/doc/api/class_terrain3dtextureasset.rst b/doc/api/class_terrain3dtextureasset.rst
index 59f4a7658..a7b41524b 100644
--- a/doc/api/class_terrain3dtextureasset.rst
+++ b/doc/api/class_terrain3dtextureasset.rst
@@ -56,8 +56,6 @@ Properties
+---------------+--------------------------------------------------------------------------------------+-----------------------+
| ``float`` | :ref:`uv_scale` | ``0.1`` |
+---------------+--------------------------------------------------------------------------------------+-----------------------+
- | ``bool`` | :ref:`vertical_projection` | ``false`` |
- +---------------+--------------------------------------------------------------------------------------+-----------------------+
.. rst-class:: classref-reftable-group
@@ -365,23 +363,6 @@ Increases or decreases the roughness texture values.
The scale of the textures.
-.. rst-class:: classref-item-separator
-
-----
-
-.. _class_Terrain3DTextureAsset_property_vertical_projection:
-
-.. rst-class:: classref-property
-
-``bool`` **vertical_projection** = ``false`` :ref:`🔗`
-
-.. rst-class:: classref-property-setget
-
-- |void| **set_vertical_projection**\ (\ value\: ``bool``\ )
-- ``bool`` **get_vertical_projection**\ (\ )
-
-Projects this texture vertically so it is suitable for the stretched polygons on cliff faces. Turn this on for textures you'll use on cliffs. It can still work on horizontal areas with care and blending. Enable Vertical Projection and adjust the settings in the material.
-
.. rst-class:: classref-section-separator
----
diff --git a/doc/doc_classes/Terrain3DAssets.xml b/doc/doc_classes/Terrain3DAssets.xml
index c37537fc4..3ce3cfe81 100644
--- a/doc/doc_classes/Terrain3DAssets.xml
+++ b/doc/doc_classes/Terrain3DAssets.xml
@@ -112,12 +112,6 @@
Returns the array of uv scale values for each texture asset, indexed by asset id.
-
-
-
- Returns a 32-bit int that identifies which textures are to be projected vertically.
-
-
diff --git a/doc/doc_classes/Terrain3DMaterial.xml b/doc/doc_classes/Terrain3DMaterial.xml
index fdb2b498a..50eaffe9f 100644
--- a/doc/doc_classes/Terrain3DMaterial.xml
+++ b/doc/doc_classes/Terrain3DMaterial.xml
@@ -6,7 +6,8 @@
This class handles options for both the built-in shader and any custom override shader. It collects compiled texture data from the other classes and sends all of it to the shader via the RenderingServer.
It is a savable resource, so you can save it to disk and use the same material settings in multiple scenes that use Terrain3D. The amount of data is small, assuming you have saved your shader parameter textures to disk, so it can be saved as a git-friendly, text based .tres file or left within the scene file.
- While it does mimic some of the functionality of ShaderMaterial, it does not derive from any of the Godot Material classes. It will not pass any [code skip-lint]is Material[/code] checks. It is a [code skip-lint]Resource[/code].
+ While it does mimic some of the functionality of ShaderMaterial, it does not derive from any of the Godot Material classes. It will fail any [code skip-lint]is Material[/code] checks. It is a [code skip-lint]Resource[/code].
+ Inspector settings above `Custom Shader` and [member shader_override] are used to determine what code is used in the current shader. Inspector settings in `Shader Uniforms` are the public uniforms (not prefaced with `_`) available in the current shader.
@@ -60,10 +61,9 @@
-
+
- Sends all uniform values to the shader again.
- full - recompiles the shader first.
+ Sends uniform values to the shader. See [enum UpdateFlags] for options.
@@ -71,7 +71,7 @@
This private dictionary stores all of the shader parameters in the resource. It is not a cache.
-
+
Enables selecting two texture IDs that will automatically be applied to the terrain based upon slope.
@@ -86,14 +86,33 @@
Adjusts the transition between textures. When set at `1.0`, the blending of displacment between textures will match the aldebo/normal blend sharpness exactly. Lower values will have a softer transition, avoiding harsh shapes, without compromising the abldeo and normal blend sharpness. If set at or very near to `0.0`, it is possible that more displaced textures can affect less displaced textures at low blend values even if not visible.
-
+
Enables selecting one texture ID that will have multiple scales applied based upon camera distance. Use it for something like a rock texture so up close it will be nicely detailed, and far away mountains can be covered in the same rock texture without looking tiled. The two blend together at a specified distance.
+
+ Allows you to add a couple of noise patterns at different scales and colors to add variation to your terrain to avoid tiled textures.
+
+
+ Enables the Albedo, aka Base Color or Diffuse, output channel in the shader.
+
+
+ Enables the Ambient Occlusion output channel in the shader.
+
+
+ Enables the Normal Map output channel in the shader.
+
+
+ Enables the Roughness output channel in the shader.
+
+
+ Enables textures to be projected vertically when placed on slopes above 45 degrees. This is useful for mapping textures on cliff faces without stretching, even though the polygons are stretched.
+
If shader_override_enabled is true and this Shader is valid, the material will use this custom shader code. If this is blank when you enable the override, the system will generate a shader with the current settings. So if you have a debug view enabled, the generated shader will have all of that code. A visual shader will also work here. However we only generate a text based shader so currently a visual shader needs to be constructed with the base code before it can work.
- Enables use of the [member shader_override] shader code. Generates default code if shader_override is blank.
+ Enables using the [member shader_override] shader. An editable shader is generated from the current one if shader_override is blank.
+ The inspector settings above this group determine the code that is used in the current shader. The settings below are uniforms for the current shader.
Displays the area designated for use by the autoshader, which shows materials based upon slope.
@@ -185,5 +204,20 @@
Textures are filtered using a the nearest pixel only. It is faster than LINEAR, but the texture will look pixelated. Use this for a low-poly look, with a very low uv_scale.
+
+ Non-texture array values are assigned to the shader. This is the default and is always done.
+
+
+ The ground texture arrays are assigned to the shader, along with the values in `UNIFORMS_ONLY`.
+
+
+ The region data texture arrays are assigned to the shader, along with the values in `UNIFORMS_ONLY`.
+
+
+ Values in `TEXTURE_ARRAYS` and `REGION_ARRAYS` are assigned to the shader.
+
+
+ The shader is rebuilt, then all values in `UPDATE_ARRAYS` are assigned to the shader.
+
diff --git a/doc/doc_classes/Terrain3DRegion.xml b/doc/doc_classes/Terrain3DRegion.xml
index eb4c1f705..c8e6cac96 100644
--- a/doc/doc_classes/Terrain3DRegion.xml
+++ b/doc/doc_classes/Terrain3DRegion.xml
@@ -14,6 +14,12 @@
Recalculates the height range for this region by looking at every pixel in the heightmap.
+
+
+
+ Unreferences the maps and resets all of the variables to default values.
+
+
diff --git a/doc/doc_classes/Terrain3DTextureAsset.xml b/doc/doc_classes/Terrain3DTextureAsset.xml
index 6bc7f6d62..aabcf01d7 100644
--- a/doc/doc_classes/Terrain3DTextureAsset.xml
+++ b/doc/doc_classes/Terrain3DTextureAsset.xml
@@ -84,9 +84,6 @@
The scale of the textures.
-
- Projects this texture vertically so it is suitable for the stretched polygons on cliff faces. Turn this on for textures you'll use on cliffs. It can still work on horizontal areas with care and blending. Enable Vertical Projection and adjust the settings in the material.
-
diff --git a/doc/docs/double_precision.md b/doc/docs/double_precision.md
index 2fa2171a2..ff7ac7cef 100644
--- a/doc/docs/double_precision.md
+++ b/doc/docs/double_precision.md
@@ -21,9 +21,11 @@ For a more detailed explanation, see [Large World Coordinates](https://docs.godo
To get Terrain3D and Godot working with double precision floats, you must:
-1. [Build Godot from source](https://docs.godotengine.org/en/latest/contributing/development/compiling/index.html) with `scons precision=double` along with your other parameters like target, platform, etc.
-2. [Generate custom godot-cpp bindings](https://docs.godotengine.org/en/latest/tutorials/scripting/gdextension/gdextension_cpp_example.html#building-the-c-bindings) using this new executable. This will generate a JSON file you will use in the next step.
-3. [Build Terrain3D from source](building_from_source.md) (which includes godot-cpp) using `scons precision=double custom_api_file=YOUR_CUSTOM_JSON_FILE`
+1. [Build Godot from source](https://docs.godotengine.org/en/latest/engine_details/development/compiling/index.html) with `scons precision=double` along with your other parameters like target, platform, etc.
+2. Generate custom godot-cpp bindings using this new executable. This will generate a JSON file you will use in the next step.
+ - `godot --dump-extension-api`, which gives you an extension_api.json
+ - `scons custom_api_file=path_to/extension_api.json`
+3. [Build Terrain3D from source](building_from_source.md) (which includes godot-cpp) using `scons precision=double custom_api_file=path_to/extension_api.json`
After that, you can run the double version of Godot with the double version of Terrain3D.
diff --git a/doc/docs/import_export.md b/doc/docs/import_export.md
index e43e1ca65..4986ca388 100644
--- a/doc/docs/import_export.md
+++ b/doc/docs/import_export.md
@@ -18,41 +18,40 @@ Terrain3D has three map types you can import or export: [Height](../api/class_te
We can import any supported image format that Godot can read, however we have recomendations below for specific formats to use for the different maps.
-### Import Formats
-
-* [Godot supported Image file formats](https://docs.godotengine.org/en/stable/tutorials/assets_pipeline/importing_images.html#supported-image-formats): `bmp`, `dds`, `exr`, `hdr`, `jpg`, `jpeg`, `png`, `tga`, `svg`, `webp`
-* [Godot resource files](https://docs.godotengine.org/en/stable/classes/class_image.html#enum-image-format): Any data format listed at the link stored as a `tres` or `res`.
-* `exr`: Should be RGB not greyscale, 16 or 32-bit float, no transparency. Older versions of Photoshop can use [exr-io](https://www.exr-io.com/).
-* `png`: Godot only supports 8-bit PNGs. This works fine for the colormap, but not for heightmaps.
-* `r16`: for heightmaps only. Values are scaled based on min and max heights and stored as 16-bit unsigned int. Can be read/written by Krita. Min/max heights and image dimensions are not stored in the file, so you must keep track of them elsewhere, such as in the filename.
-* `raw`: This is not a format specification! It just means the file contains a dump of values. But what format are the values? They could be 8, 16, or 32-bit, signed or unsigned ints or floats, little or big endian byte order (aka Windows or macOS, Intel or Arm/Motorola). In order to read this file you need to know all of these, *and* the dimensions. `Photoshop Raw` only supports 8/16/32-bit float, little or big endian. **Terrain3D interprets raw as r16**, as does Unity, Unreal Engine, and various commercial software.
-
-### Export Formats
+Godot can read these file formats:
+* [Godot supported Image file formats](https://docs.godotengine.org/en/stable/tutorials/assets_pipeline/importing_images.html#supported-image-formats): `bmp`, `dds`, `exr`, `hdr`, `jpg`, `jpeg`, `png` 8-bit, `tga`, `svg`, `webp`
+* [Godot resource files](https://docs.godotengine.org/en/stable/classes/class_image.html#enum-image-format): Any data format listed at the link stored as a `tres` or `res`.
+Godot can write these file formats:
* [Godot supported Image save functions](https://docs.godotengine.org/en/stable/classes/class_image.html#class-image-method-save-exr): `exr`, `png`, `jpg`, `webp`
-* `r16`: for heightmaps only.
* `res`: Godot binary resource file with `ResourceSaver::FLAG_COMPRESS` enabled. The format contained inside is defined in our API linked at the top of this section for each map type.
* `tres`: Godot text resource file. Not recommended.
-
+
### Height Map Recommendations
-* Use `exr` or `r16/raw`.
-* If you have a 16-bit `png`, convert it.
-* Only use 16 or 32-bit height data. If your data is only 8-bit, it will look terraced and require a lot of smoothing to be useable. You could convert the image to 16-bit and blur it a bit in Photoshop.
+Use `exr` or `r16/raw` for import / export.
+
+* `exr`: Values should be real heights, not normalized (0.0 - 1.0). RGB, not greyscale. 16 or 32-bit float, no transparency. Older versions of Photoshop can use [exr-io](https://www.exr-io.com/).
+* `r16`: Values are normalized to 0 - 65,535 (maximum 16-bit unsigned int value). Can be read/written by Krita. Min/max heights and image dimensions are not stored in the file, so you must keep track of them elsewhere, such as in the filename.
+* `raw`: This is not a format specification! It just means the file contains a dump of values. But what format are the values? They could be 8, 16, or 32-bit, signed or unsigned ints or floats, little or big endian byte order (aka Windows or macOS, Intel or Arm/Motorola). In order to read this file you need to know all of these, *and* the dimensions. `Photoshop Raw` only supports 8/16/32-bit float, little or big endian. **Terrain3D interprets a .raw extension as r16**, as does Unity, Unreal Engine, and various commercial software.
+
+Other notes:
+* `png`: Godot only supports 8-bit PNGs. This works fine for the colormap, but not for heightmaps. If you have a 16-bit `png`, convert it to `exr` with an image editor.
+* Only use 16 or 32-bit height data. If your data is 8-bit, it will look terraced and require a lot of smoothing to be useable. You could convert the image to 16-bit and blur it in Photoshop.
* [Zylann's HTerrain](https://github.com/Zylann/godot_heightmap_plugin/) stores height data in a `res` file which we can import directly. No need to export it first, though his tool also exports `exr` and `r16`.
### Control Map Recommendations
-* Our control maps use a [proprietary format](controlmap_format.md). We currently only import our own format. Use `exr` to export and reimport only from this tool. This is only for transferring the data to another Terrain3D data file.
+Our control maps use a [proprietary format](controlmap_format.md). We currently only import our own format. Use `exr` to export and reimport only from this tool. This is only for transferring the data to another Terrain3D data file.
### Color Map Recommendations
* Any regular color format is fine.
* `png` or `webp` are recommended as they are lossless, unlike `jpg`.
-* The alpha channel is interpretted as a [roughness modifier](../api/class_terrain3ddata.rst#class-terrain3ddata-property-color-maps) for wetness. So if you wish to edit the color map in an external program, you may need to disable the alpha channel first.
+* The alpha channel is interpretted as a [roughness modifier](../api/class_terrain3ddata.rst#class-terrain3ddata-property-color-maps) for wetness. So if you wish to edit the color map in an external program, you may need to disable or separate the alpha channel first.
## Importing Data
diff --git a/project/addons/terrain_3d/csharp/Terrain3D.cs b/project/addons/terrain_3d/csharp/Terrain3D.cs
index 52dad6621..ef6dbabb7 100644
--- a/project/addons/terrain_3d/csharp/Terrain3D.cs
+++ b/project/addons/terrain_3d/csharp/Terrain3D.cs
@@ -29,10 +29,9 @@ protected Terrain3D() { }
/// The existing or a new instance of the wrapper script attached to the supplied .
public new static Terrain3D Bind(GodotObject godotObject)
{
-#if DEBUG
if (!IsInstanceValid(godotObject))
- throw new InvalidOperationException("The supplied GodotObject instance is not valid.");
-#endif
+ return null;
+
if (godotObject is Terrain3D wrapperScriptInstance)
return wrapperScriptInstance;
@@ -161,7 +160,6 @@ public event AssetsChangedSignalHandler AssetsChangedSignal
public new static readonly StringName MeshSize = "mesh_size";
public new static readonly StringName VertexSpacing = "vertex_spacing";
public new static readonly StringName TessellationLevel = "tessellation_level";
- public new static readonly StringName Displacement = "Displacement";
public new static readonly StringName DisplacementScale = "displacement_scale";
public new static readonly StringName DisplacementSharpness = "displacement_sharpness";
public new static readonly StringName BufferShaderOverrideEnabled = "buffer_shader_override_enabled";
@@ -190,7 +188,6 @@ public event AssetsChangedSignalHandler AssetsChangedSignal
public new static readonly StringName ShowColormap = "show_colormap";
public new static readonly StringName ShowRoughmap = "show_roughmap";
public new static readonly StringName ShowDisplacementBuffer = "show_displacement_buffer";
- public new static readonly StringName Pbr = "PBR";
public new static readonly StringName ShowTextureAlbedo = "show_texture_albedo";
public new static readonly StringName ShowTextureHeight = "show_texture_height";
public new static readonly StringName ShowTextureNormal = "show_texture_normal";
@@ -386,9 +383,9 @@ public event AssetsChangedSignalHandler AssetsChangedSignal
set => Set(GDExtensionPropertyName.MouseLayer, value);
}
- public new Variant CastShadows
+ public new long CastShadows
{
- get => Get(GDExtensionPropertyName.CastShadows).As();
+ get => Get(GDExtensionPropertyName.CastShadows).As();
set => Set(GDExtensionPropertyName.CastShadows, value);
}
diff --git a/project/addons/terrain_3d/csharp/Terrain3DAssets.cs b/project/addons/terrain_3d/csharp/Terrain3DAssets.cs
index bf43bde63..731c0f0ca 100644
--- a/project/addons/terrain_3d/csharp/Terrain3DAssets.cs
+++ b/project/addons/terrain_3d/csharp/Terrain3DAssets.cs
@@ -29,10 +29,9 @@ protected Terrain3DAssets() { }
/// The existing or a new instance of the wrapper script attached to the supplied .
public new static Terrain3DAssets Bind(GodotObject godotObject)
{
-#if DEBUG
if (!IsInstanceValid(godotObject))
- throw new InvalidOperationException("The supplied GodotObject instance is not valid.");
-#endif
+ return null;
+
if (godotObject is Terrain3DAssets wrapperScriptInstance)
return wrapperScriptInstance;
@@ -152,7 +151,6 @@ public event TexturesChangedSignalHandler TexturesChangedSignal
public new static readonly StringName GetTextureAoLightAffects = "get_texture_ao_light_affects";
public new static readonly StringName GetTextureRoughnessMods = "get_texture_roughness_mods";
public new static readonly StringName GetTextureUvScales = "get_texture_uv_scales";
- public new static readonly StringName GetTextureVerticalProjections = "get_texture_vertical_projections";
public new static readonly StringName GetTextureDetiles = "get_texture_detiles";
public new static readonly StringName GetTextureDisplacements = "get_texture_displacements";
public new static readonly StringName ClearTextures = "clear_textures";
@@ -198,9 +196,6 @@ public event TexturesChangedSignalHandler TexturesChangedSignal
public new float[] GetTextureUvScales() =>
Call(GDExtensionMethodName.GetTextureUvScales, []).As();
- public new long GetTextureVerticalProjections() =>
- Call(GDExtensionMethodName.GetTextureVerticalProjections, []).As();
-
public new Vector2[] GetTextureDetiles() =>
Call(GDExtensionMethodName.GetTextureDetiles, []).As();
diff --git a/project/addons/terrain_3d/csharp/Terrain3DCollision.cs b/project/addons/terrain_3d/csharp/Terrain3DCollision.cs
index 8cb64210a..66a196fa4 100644
--- a/project/addons/terrain_3d/csharp/Terrain3DCollision.cs
+++ b/project/addons/terrain_3d/csharp/Terrain3DCollision.cs
@@ -29,10 +29,9 @@ protected Terrain3DCollision() { }
/// The existing or a new instance of the wrapper script attached to the supplied .
public new static Terrain3DCollision Bind(GodotObject godotObject)
{
-#if DEBUG
if (!IsInstanceValid(godotObject))
- throw new InvalidOperationException("The supplied GodotObject instance is not valid.");
-#endif
+ return null;
+
if (godotObject is Terrain3DCollision wrapperScriptInstance)
return wrapperScriptInstance;
diff --git a/project/addons/terrain_3d/csharp/Terrain3DData.cs b/project/addons/terrain_3d/csharp/Terrain3DData.cs
index 93b31da61..c49b625b5 100644
--- a/project/addons/terrain_3d/csharp/Terrain3DData.cs
+++ b/project/addons/terrain_3d/csharp/Terrain3DData.cs
@@ -29,10 +29,9 @@ protected Terrain3DData() { }
/// The existing or a new instance of the wrapper script attached to the supplied .
public new static Terrain3DData Bind(GodotObject godotObject)
{
-#if DEBUG
if (!IsInstanceValid(godotObject))
- throw new InvalidOperationException("The supplied GodotObject instance is not valid.");
-#endif
+ return null;
+
if (godotObject is Terrain3DData wrapperScriptInstance)
return wrapperScriptInstance;
diff --git a/project/addons/terrain_3d/csharp/Terrain3DEditor.cs b/project/addons/terrain_3d/csharp/Terrain3DEditor.cs
index 0356f365c..34e40d860 100644
--- a/project/addons/terrain_3d/csharp/Terrain3DEditor.cs
+++ b/project/addons/terrain_3d/csharp/Terrain3DEditor.cs
@@ -29,10 +29,9 @@ protected Terrain3DEditor() { }
/// The existing or a new instance of the wrapper script attached to the supplied .
public new static Terrain3DEditor Bind(GodotObject godotObject)
{
-#if DEBUG
if (!IsInstanceValid(godotObject))
- throw new InvalidOperationException("The supplied GodotObject instance is not valid.");
-#endif
+ return null;
+
if (godotObject is Terrain3DEditor wrapperScriptInstance)
return wrapperScriptInstance;
diff --git a/project/addons/terrain_3d/csharp/Terrain3DInstancer.cs b/project/addons/terrain_3d/csharp/Terrain3DInstancer.cs
index 3685e2870..87232484f 100644
--- a/project/addons/terrain_3d/csharp/Terrain3DInstancer.cs
+++ b/project/addons/terrain_3d/csharp/Terrain3DInstancer.cs
@@ -29,10 +29,9 @@ protected Terrain3DInstancer() { }
/// The existing or a new instance of the wrapper script attached to the supplied .
public new static Terrain3DInstancer Bind(GodotObject godotObject)
{
-#if DEBUG
if (!IsInstanceValid(godotObject))
- throw new InvalidOperationException("The supplied GodotObject instance is not valid.");
-#endif
+ return null;
+
if (godotObject is Terrain3DInstancer wrapperScriptInstance)
return wrapperScriptInstance;
diff --git a/project/addons/terrain_3d/csharp/Terrain3DMaterial.cs b/project/addons/terrain_3d/csharp/Terrain3DMaterial.cs
index fdfb96389..78b961419 100644
--- a/project/addons/terrain_3d/csharp/Terrain3DMaterial.cs
+++ b/project/addons/terrain_3d/csharp/Terrain3DMaterial.cs
@@ -29,10 +29,9 @@ protected Terrain3DMaterial() { }
/// The existing or a new instance of the wrapper script attached to the supplied .
public new static Terrain3DMaterial Bind(GodotObject godotObject)
{
-#if DEBUG
if (!IsInstanceValid(godotObject))
- throw new InvalidOperationException("The supplied GodotObject instance is not valid.");
-#endif
+ return null;
+
if (godotObject is Terrain3DMaterial wrapperScriptInstance)
return wrapperScriptInstance;
@@ -74,13 +73,27 @@ public enum TextureFilteringEnum
Nearest = 1,
}
+ public enum UpdateFlags
+ {
+ UniformsOnly = 0,
+ TextureArrays = 1,
+ RegionArrays = 2,
+ Arrays = 3,
+ FullRebuild = 7,
+ }
+
public new static class GDExtensionPropertyName
{
- public new static readonly StringName ShaderParameters = "_shader_parameters";
public new static readonly StringName WorldBackground = "world_background";
public new static readonly StringName TextureFiltering = "texture_filtering";
public new static readonly StringName AutoShaderEnabled = "auto_shader_enabled";
public new static readonly StringName DualScalingEnabled = "dual_scaling_enabled";
+ public new static readonly StringName MacroVariationEnabled = "macro_variation_enabled";
+ public new static readonly StringName ProjectionEnabled = "projection_enabled";
+ public new static readonly StringName OutputAlbedo = "output_albedo";
+ public new static readonly StringName OutputRoughness = "output_roughness";
+ public new static readonly StringName OutputNormalMap = "output_normal_map";
+ public new static readonly StringName OutputAmbientOcclusion = "output_ambient_occlusion";
public new static readonly StringName ShaderOverrideEnabled = "shader_override_enabled";
public new static readonly StringName ShaderOverride = "shader_override";
public new static readonly StringName ShowRegionGrid = "show_region_grid";
@@ -111,12 +124,6 @@ public enum TextureFilteringEnum
public new static readonly StringName ShowDisplacementBuffer = "show_displacement_buffer";
}
- public new Godot.Collections.Dictionary ShaderParameters
- {
- get => Get(GDExtensionPropertyName.ShaderParameters).As();
- set => Set(GDExtensionPropertyName.ShaderParameters, value);
- }
-
public new Terrain3DMaterial.WorldBackgroundEnum WorldBackground
{
get => Get(GDExtensionPropertyName.WorldBackground).As();
@@ -141,6 +148,42 @@ public enum TextureFilteringEnum
set => Set(GDExtensionPropertyName.DualScalingEnabled, value);
}
+ public new bool MacroVariationEnabled
+ {
+ get => Get(GDExtensionPropertyName.MacroVariationEnabled).As();
+ set => Set(GDExtensionPropertyName.MacroVariationEnabled, value);
+ }
+
+ public new bool ProjectionEnabled
+ {
+ get => Get(GDExtensionPropertyName.ProjectionEnabled).As();
+ set => Set(GDExtensionPropertyName.ProjectionEnabled, value);
+ }
+
+ public new bool OutputAlbedo
+ {
+ get => Get(GDExtensionPropertyName.OutputAlbedo).As();
+ set => Set(GDExtensionPropertyName.OutputAlbedo, value);
+ }
+
+ public new bool OutputRoughness
+ {
+ get => Get(GDExtensionPropertyName.OutputRoughness).As();
+ set => Set(GDExtensionPropertyName.OutputRoughness, value);
+ }
+
+ public new bool OutputNormalMap
+ {
+ get => Get(GDExtensionPropertyName.OutputNormalMap).As();
+ set => Set(GDExtensionPropertyName.OutputNormalMap, value);
+ }
+
+ public new bool OutputAmbientOcclusion
+ {
+ get => Get(GDExtensionPropertyName.OutputAmbientOcclusion).As();
+ set => Set(GDExtensionPropertyName.OutputAmbientOcclusion, value);
+ }
+
public new bool ShaderOverrideEnabled
{
get => Get(GDExtensionPropertyName.ShaderOverrideEnabled).As();
@@ -321,8 +364,8 @@ public enum TextureFilteringEnum
public new static readonly StringName Save = "save";
}
- public new void Update(bool full = false) =>
- Call(GDExtensionMethodName.Update, [full]);
+ public new void Update(long flags = 0) =>
+ Call(GDExtensionMethodName.Update, [flags]);
public new Rid GetMaterialRid() =>
Call(GDExtensionMethodName.GetMaterialRid, []).As();
diff --git a/project/addons/terrain_3d/csharp/Terrain3DMeshAsset.cs b/project/addons/terrain_3d/csharp/Terrain3DMeshAsset.cs
index 910f7bc05..02dd9d61a 100644
--- a/project/addons/terrain_3d/csharp/Terrain3DMeshAsset.cs
+++ b/project/addons/terrain_3d/csharp/Terrain3DMeshAsset.cs
@@ -29,10 +29,9 @@ protected Terrain3DMeshAsset() { }
/// The existing or a new instance of the wrapper script attached to the supplied .
public new static Terrain3DMeshAsset Bind(GodotObject godotObject)
{
-#if DEBUG
if (!IsInstanceValid(godotObject))
- throw new InvalidOperationException("The supplied GodotObject instance is not valid.");
-#endif
+ return null;
+
if (godotObject is Terrain3DMeshAsset wrapperScriptInstance)
return wrapperScriptInstance;
@@ -246,9 +245,9 @@ public event InstanceCountChangedSignalHandler InstanceCountChangedSignal
set => Set(GDExtensionPropertyName.Density, value);
}
- public new Variant CastShadows
+ public new long CastShadows
{
- get => Get(GDExtensionPropertyName.CastShadows).As();
+ get => Get(GDExtensionPropertyName.CastShadows).As();
set => Set(GDExtensionPropertyName.CastShadows, value);
}
diff --git a/project/addons/terrain_3d/csharp/Terrain3DRegion.cs b/project/addons/terrain_3d/csharp/Terrain3DRegion.cs
index 37357a691..9e7af4f29 100644
--- a/project/addons/terrain_3d/csharp/Terrain3DRegion.cs
+++ b/project/addons/terrain_3d/csharp/Terrain3DRegion.cs
@@ -29,10 +29,9 @@ protected Terrain3DRegion() { }
/// The existing or a new instance of the wrapper script attached to the supplied .
public new static Terrain3DRegion Bind(GodotObject godotObject)
{
-#if DEBUG
if (!IsInstanceValid(godotObject))
- throw new InvalidOperationException("The supplied GodotObject instance is not valid.");
-#endif
+ return null;
+
if (godotObject is Terrain3DRegion wrapperScriptInstance)
return wrapperScriptInstance;
diff --git a/project/addons/terrain_3d/csharp/Terrain3DTextureAsset.cs b/project/addons/terrain_3d/csharp/Terrain3DTextureAsset.cs
index fd2e748a7..63d68e81c 100644
--- a/project/addons/terrain_3d/csharp/Terrain3DTextureAsset.cs
+++ b/project/addons/terrain_3d/csharp/Terrain3DTextureAsset.cs
@@ -29,10 +29,9 @@ protected Terrain3DTextureAsset() { }
/// The existing or a new instance of the wrapper script attached to the supplied .
public new static Terrain3DTextureAsset Bind(GodotObject godotObject)
{
-#if DEBUG
if (!IsInstanceValid(godotObject))
- throw new InvalidOperationException("The supplied GodotObject instance is not valid.");
-#endif
+ return null;
+
if (godotObject is Terrain3DTextureAsset wrapperScriptInstance)
return wrapperScriptInstance;
@@ -154,7 +153,6 @@ public event SettingChangedSignalHandler SettingChangedSignal
public new static readonly StringName DisplacementScale = "displacement_scale";
public new static readonly StringName DisplacementOffset = "displacement_offset";
public new static readonly StringName UvScale = "uv_scale";
- public new static readonly StringName VerticalProjection = "vertical_projection";
public new static readonly StringName DetilingRotation = "detiling_rotation";
public new static readonly StringName DetilingShift = "detiling_shift";
}
@@ -231,12 +229,6 @@ public event SettingChangedSignalHandler SettingChangedSignal
set => Set(GDExtensionPropertyName.UvScale, value);
}
- public new bool VerticalProjection
- {
- get => Get(GDExtensionPropertyName.VerticalProjection).As();
- set => Set(GDExtensionPropertyName.VerticalProjection, value);
- }
-
public new double DetilingRotation
{
get => Get(GDExtensionPropertyName.DetilingRotation).As();
diff --git a/project/addons/terrain_3d/csharp/Terrain3DUtil.cs b/project/addons/terrain_3d/csharp/Terrain3DUtil.cs
index dce0afd79..b68305be2 100644
--- a/project/addons/terrain_3d/csharp/Terrain3DUtil.cs
+++ b/project/addons/terrain_3d/csharp/Terrain3DUtil.cs
@@ -29,10 +29,9 @@ protected Terrain3DUtil() { }
/// The existing or a new instance of the wrapper script attached to the supplied .
public new static Terrain3DUtil Bind(GodotObject godotObject)
{
-#if DEBUG
if (!IsInstanceValid(godotObject))
- throw new InvalidOperationException("The supplied GodotObject instance is not valid.");
-#endif
+ return null;
+
if (godotObject is Terrain3DUtil wrapperScriptInstance)
return wrapperScriptInstance;
diff --git a/project/addons/terrain_3d/extras/shaders/lightweight.gdshader b/project/addons/terrain_3d/extras/shaders/lightweight.gdshader
index 7fb346eb8..ed2eb64b8 100644
--- a/project/addons/terrain_3d/extras/shaders/lightweight.gdshader
+++ b/project/addons/terrain_3d/extras/shaders/lightweight.gdshader
@@ -1,3 +1,6 @@
+// Copyright © 2025 Cory Petkovsek, Roope Palmroos, and Contributors.
+// This shader is the minimum needed to allow the terrain to function, without any texturing.
+
shader_type spatial;
render_mode blend_mix,depth_draw_opaque,cull_back,diffuse_burley,specular_schlick_ggx,skip_vertex_transform;
@@ -13,9 +16,6 @@ render_mode blend_mix,depth_draw_opaque,cull_back,diffuse_burley,specular_schlic
*/
// Defined Constants
-#define SKIP_PASS 0
-#define VERTEX_PASS 1
-#define FRAGMENT_PASS 2
#define COLOR_MAP vec4(1.0, 1.0, 1.0, 0.5)
#define DIV_255 0.003921568627450 // 1. / 255.
@@ -33,6 +33,7 @@ render_mode blend_mix,depth_draw_opaque,cull_back,diffuse_burley,specular_schlic
#endif
// Private uniforms
+group_uniforms shader_uniforms;
uniform vec3 _target_pos = vec3(0.f);
uniform float _mesh_size = 48.f;
uniform uint _background_mode = 1u; // NONE = 0, FLAT = 1, NOISE = 2
@@ -56,24 +57,25 @@ uniform highp sampler2DArray _control_maps : repeat_disable;
uniform highp sampler2DArray _color_maps : source_color, filter_linear_mipmap_anisotropic, repeat_disable;
uniform highp sampler2DArray _texture_array_albedo : source_color, filter_linear_mipmap_anisotropic, repeat_enable;
uniform highp sampler2DArray _texture_array_normal : hint_normal, filter_linear_mipmap_anisotropic, repeat_enable;
+group_uniforms;
// Public uniforms
-group_uniforms general;
+group_uniforms shader_uniforms.general;
uniform float ground_level : hint_range(-1000., 1000.) = 0.0;
uniform float region_blend : hint_range(.001, 1., 0.001) = 0.25;
uniform bool flat_terrain_normals = false;
-uniform bool enable_textures = true;
+uniform bool textures_enabled = true;
uniform float blend_sharpness : hint_range(0, 1) = 0.5;
group_uniforms;
-group_uniforms auto_shader;
+group_uniforms shader_uniforms.auto_shader;
uniform float auto_slope : hint_range(0, 10) = 1.0;
uniform float auto_height_reduction : hint_range(0, 1) = 0.1;
uniform int auto_base_texture : hint_range(0, 31) = 0;
uniform int auto_overlay_texture : hint_range(0, 31) = 1;
group_uniforms;
-group_uniforms macro_variation;
+group_uniforms shader_uniforms.macro_variation;
uniform bool macro_variation_enabled = true;
uniform vec3 macro_variation1 : source_color = vec3(1.);
uniform vec3 macro_variation2 : source_color = vec3(1.);
@@ -95,24 +97,16 @@ varying mat3 TBN;
// Vertex
////////////////////////
-// Takes in world space XZ (UV) coordinates & search depth (only applicable for background mode none)
+// Takes in world space XZ (UV) coordinates
// Returns ivec3 with:
// XY: (0 to _region_size - 1) coordinates within a region
// Z: layer index used for texturearrays, -1 if not in a region
-ivec3 get_index_coord(const vec2 uv, const int search) {
+ivec3 get_index_coord(const vec2 uv) {
vec2 r_uv = round(uv);
- vec2 o_uv = mod(r_uv,_region_size);
- ivec2 pos;
- int bounds, layer_index = -1;
- for (int i = -1; i < clamp(search, SKIP_PASS, FRAGMENT_PASS); i++) {
- if ((layer_index == -1 && _background_mode == 0u ) || i < 0) {
- r_uv -= i == -1 ? vec2(0.0) : vec2(float(o_uv.x <= o_uv.y), float(o_uv.y <= o_uv.x));
- pos = ivec2(floor((r_uv) * _region_texel_size)) + (_region_map_size / 2);
- bounds = int(uint(pos.x | pos.y) < uint(_region_map_size));
- layer_index = (_region_map[ pos.y * _region_map_size + pos.x ] * bounds - 1);
- }
- }
- return ivec3(ivec2(mod(r_uv,_region_size)), layer_index);
+ ivec2 pos = ivec2(floor(r_uv * _region_texel_size)) + (_region_map_size / 2);
+ int bounds = int(uint(pos.x | pos.y) < uint(_region_map_size));
+ int layer_index = _region_map[pos.y * _region_map_size + pos.x] * bounds - 1;
+ return ivec3(ivec2(mod(r_uv, _region_size)), layer_index);
}
// Takes in descaled (world_space / region_size) world to region space XZ (UV2) coordinates, returns vec3 with:
@@ -125,6 +119,16 @@ vec3 get_index_uv(const vec2 uv2) {
return vec3(uv2 - _region_locations[layer_index], float(layer_index));
}
+// Takes in world space XZ (UV) and returns if the coordinate should be part of the NONE background.
+bool is_none_bg(const vec2 uv) {
+ ivec4 regions = ivec4(
+ get_index_coord(uv - vec2(0.5, 0.0)).z,
+ get_index_coord(uv - vec2(0.0, 0.5)).z,
+ get_index_coord(uv + vec2(1.0, 0.0)).z,
+ get_index_coord(uv + vec2(0.0, 1.0)).z);
+ return any(equal(regions, ivec4(-1)));
+}
+
// Takes in UV2 region space coordinates, returns 1.0 or 0.0 if a region is present or not.
float check_region(const vec2 uv2) {
ivec2 pos = ivec2(floor(uv2)) + (_region_map_size / 2);
@@ -137,7 +141,7 @@ float check_region(const vec2 uv2) {
// Takes in UV2 region space coordinates, returns a blend value (0 - 1 range) between empty, and valid regions
float get_region_blend(vec2 uv2) {
- uv2 -= 0.4989; // Offset from - 0.5 to account for FP errors highlighted by Tessellation.
+ uv2 -= 0.5;
const vec2 offset = vec2(0.0, 1.0);
float a = check_region(uv2 + offset.xy);
float b = check_region(uv2 + offset.yy);
@@ -152,10 +156,10 @@ float interpolated_height(vec2 pos) {
const vec2 offsets = vec2(0, 1);
vec2 index_id = floor(pos);
ivec3 index[4];
- index[0] = get_index_coord(index_id + offsets.xy, VERTEX_PASS);
- index[1] = get_index_coord(index_id + offsets.yy, VERTEX_PASS);
- index[2] = get_index_coord(index_id + offsets.yx, VERTEX_PASS);
- index[3] = get_index_coord(index_id + offsets.xx, VERTEX_PASS);
+ index[0] = get_index_coord(index_id + offsets.xy);
+ index[1] = get_index_coord(index_id + offsets.yy);
+ index[2] = get_index_coord(index_id + offsets.yx);
+ index[3] = get_index_coord(index_id + offsets.xx);
float h0 = texelFetch(_height_maps, index[0], 0).r;
float h1 = texelFetch(_height_maps, index[1], 0).r;
float h2 = texelFetch(_height_maps, index[2], 0).r;
@@ -202,13 +206,13 @@ void vertex() {
UV2 = fma(UV, vec2(_region_texel_size), vec2(0.5 * _region_texel_size));
// Discard vertices for Holes. 1 lookup
- ivec3 v_region = get_index_coord(start_pos, VERTEX_PASS);
+ ivec3 v_region = get_index_coord(start_pos);
uint control = floatBitsToUint(texelFetch(_control_maps, v_region, 0)).r;
bool hole = DECODE_HOLE(control);
// Show holes to all cameras except mouse camera (on exactly 1 layer)
if ( !(CAMERA_VISIBLE_LAYERS == _mouse_layer) &&
- (hole || (_background_mode == 0u && v_region.z == -1))) {
+ (hole || (_background_mode == 0u && is_none_bg(UV)))) {
v_vertex.x = 0. / 0.;
} else {
// Set final vertex height, and vertex normal.
@@ -216,26 +220,27 @@ void vertex() {
// This branch is static for each of the clipmap segments
// Interpolated reads only occur where sub-texel values are required.
if (scale < _vertex_spacing) {
- h = mix(interpolated_height(start_pos), interpolated_height(end_pos), vertex_lerp);
- u = mix(interpolated_height(start_pos + vec2(1, 0)), interpolated_height(end_pos + vec2(1, 0)), vertex_lerp);
- v = mix(interpolated_height(start_pos + vec2(0, 1)), interpolated_height(end_pos + vec2(0, 1)), vertex_lerp);
+ h = interpolated_height(UV);
+ u = interpolated_height(UV + vec2(1, 0));
+ v = interpolated_height(UV + vec2(0, 1));
} else {
- ivec3 coord_a = get_index_coord(start_pos, VERTEX_PASS);
- ivec3 coord_b = get_index_coord(end_pos, VERTEX_PASS);
- ivec3 coord_ua = get_index_coord(start_pos + vec2(1, 0), VERTEX_PASS);
- ivec3 coord_ub = get_index_coord(end_pos + vec2(1, 0), VERTEX_PASS);
- ivec3 coord_va = get_index_coord(start_pos + vec2(0, 1), VERTEX_PASS);
- ivec3 coord_vb = get_index_coord(end_pos + vec2(0, 1), VERTEX_PASS);
+ ivec3 coord_a = get_index_coord(start_pos);
+ ivec3 coord_b = get_index_coord(end_pos);
+ ivec3 coord_ua = get_index_coord(start_pos + vec2(1, 0));
+ ivec3 coord_ub = get_index_coord(end_pos + vec2(1, 0));
+ ivec3 coord_va = get_index_coord(start_pos + vec2(0, 1));
+ ivec3 coord_vb = get_index_coord(end_pos + vec2(0, 1));
h = mix(texelFetch(_height_maps, coord_a, 0).r, texelFetch(_height_maps, coord_b, 0).r, vertex_lerp);
u = mix(texelFetch(_height_maps, coord_ua, 0).r, texelFetch(_height_maps, coord_ub, 0).r, vertex_lerp);
v = mix(texelFetch(_height_maps, coord_va, 0).r, texelFetch(_height_maps, coord_vb, 0).r, vertex_lerp);
}
-
+
// Apply background ground level and region blend
- h += ground_level * smoothstep(1.0 - region_blend, 1.0, get_region_blend(UV2));
- u += ground_level * smoothstep(1.0 - region_blend, 1.0, get_region_blend(UV2 + vec2(_region_texel_size, 0.)));
- v += ground_level * smoothstep(1.0 - region_blend, 1.0, get_region_blend(UV2 + vec2(0., _region_texel_size)));
-
+ vec2 ruv = UV * _region_texel_size;
+ h += ground_level * smoothstep(1.0 - region_blend, 1.0, get_region_blend(ruv));
+ u += ground_level * smoothstep(1.0 - region_blend, 1.0, get_region_blend(ruv + vec2(_region_texel_size, 0.)));
+ v += ground_level * smoothstep(1.0 - region_blend, 1.0, get_region_blend(ruv + vec2(0., _region_texel_size)));
+
v_vertex.y = h;
v_normal = normalize(vec3(h - u, _vertex_spacing, h - v));
}
@@ -282,10 +287,10 @@ void fragment() {
ivec3 index[4];
// control map lookups, used for some normal lookups as well
- index[0] = get_index_coord(index_id + offsets.xy, FRAGMENT_PASS);
- index[1] = get_index_coord(index_id + offsets.yy, FRAGMENT_PASS);
- index[2] = get_index_coord(index_id + offsets.yx, FRAGMENT_PASS);
- index[3] = get_index_coord(index_id + offsets.xx, FRAGMENT_PASS);
+ index[0] = get_index_coord(index_id + offsets.xy);
+ index[1] = get_index_coord(index_id + offsets.yy);
+ index[2] = get_index_coord(index_id + offsets.yx);
+ index[3] = get_index_coord(index_id + offsets.xx);
vec3 base_ddx = dFdxCoarse(v_vertex);
vec3 base_ddy = dFdyCoarse(v_vertex);
@@ -309,7 +314,7 @@ void fragment() {
float ao = 1.0;
float ao_affect = 0.;
- if (enable_textures) {
+ if (textures_enabled) {
// set to zero before accumulation
albedo_height = vec4(0.);
normal_rough = vec4(0.);
@@ -331,7 +336,8 @@ void fragment() {
float auto_blend = clamp(fma(auto_slope * 2.0, (v_normal.y - 1.0), 1.0)
- auto_height_reduction * 0.01 * v_vertex.y, 0.0, 1.0);
// Enable Autoshader if outside regions or painted in regions, otherwise manual painted
- uvec4 is_auto = (control & uvec4(0x1u)) | uvec4(uint(region_uv.z < 0.0));
+ uvec4 is_auto = (control & uvec4(0x1u)) |
+ uvec4(lessThan(ivec4(index[0].z, index[1].z, index[2].z, index[3].z), ivec4(0)));
uint u_auto =
((uint(auto_base_texture) & 0x1Fu) << 27u) |
((uint(auto_overlay_texture) & 0x1Fu) << 22u) |
diff --git a/project/addons/terrain_3d/extras/shaders/minimum.gdshader b/project/addons/terrain_3d/extras/shaders/minimum.gdshader
index 28a293242..46e6ca1c6 100644
--- a/project/addons/terrain_3d/extras/shaders/minimum.gdshader
+++ b/project/addons/terrain_3d/extras/shaders/minimum.gdshader
@@ -4,11 +4,6 @@
shader_type spatial;
render_mode blend_mix,depth_draw_opaque,cull_back,diffuse_burley,specular_schlick_ggx,skip_vertex_transform;
-// Defined Constants
-#define SKIP_PASS 0
-#define VERTEX_PASS 1
-#define FRAGMENT_PASS 2
-
#if CURRENT_RENDERER == RENDERER_COMPATIBILITY
#define fma(a, b, c) ((a) * (b) + (c))
#define dFdxCoarse(a) dFdx(a)
@@ -17,15 +12,16 @@ render_mode blend_mix,depth_draw_opaque,cull_back,diffuse_burley,specular_schlic
// Private uniforms
// Commented uniforms aren't needed for this shader, but are available for your own needs.
+group_uniforms shader_uniforms;
uniform vec3 _target_pos = vec3(0.f);
uniform float _mesh_size = 48.f;
uniform float _subdiv = 1.f;
uniform uint _background_mode = 1u; // NONE = 0, FLAT = 1, NOISE = 2
uniform uint _mouse_layer = 0x80000000u; // Layer 32
uniform float _vertex_spacing = 1.0;
-uniform float _vertex_density = 1.0; // = 1/_vertex_spacing
+uniform float _vertex_density = 1.0; // = 1./_vertex_spacing
uniform float _region_size = 1024.0;
-uniform float _region_texel_size = 0.0009765625; // = 1/1024
+uniform float _region_texel_size = 0.0009765625; // = 1./region_size
uniform int _region_map_size = 32;
uniform int _region_map[1024];
//uniform vec2 _region_locations[1024];
@@ -41,9 +37,12 @@ uniform highp sampler2DArray _control_maps : repeat_disable;
//uniform highp sampler2DArray _color_maps : source_color, filter_linear_mipmap_anisotropic, repeat_disable;
//uniform highp sampler2DArray _texture_array_albedo : source_color, filter_linear_mipmap_anisotropic, repeat_enable;
//uniform highp sampler2DArray _texture_array_normal : hint_normal, filter_linear_mipmap_anisotropic, repeat_enable;
+group_uniforms;
-// Public Uniforms
+// Public uniforms
+group_uniforms shader_uniforms.general;
uniform bool flat_terrain_normals = false;
+group_uniforms;
// Varyings & Types
// Some are required for editor functions
@@ -55,34 +54,36 @@ varying vec3 v_camera_pos;
// Vertex
////////////////////////
-// Takes in world space XZ (UV) coordinates & search depth (only applicable for background mode none)
+// Takes in world space XZ (UV) coordinates
// Returns ivec3 with:
// XY: (0 to _region_size - 1) coordinates within a region
// Z: layer index used for texturearrays, -1 if not in a region
-ivec3 get_index_coord(const vec2 uv, const int search) {
+ivec3 get_index_coord(const vec2 uv) {
vec2 r_uv = round(uv);
- vec2 o_uv = mod(r_uv,_region_size);
- ivec2 pos;
- int bounds, layer_index = -1;
- for (int i = -1; i < clamp(search, SKIP_PASS, FRAGMENT_PASS); i++) {
- if ((layer_index == -1 && _background_mode == 0u ) || i < 0) {
- r_uv -= i == -1 ? vec2(0.0) : vec2(float(o_uv.x <= o_uv.y), float(o_uv.y <= o_uv.x));
- pos = ivec2(floor((r_uv) * _region_texel_size)) + (_region_map_size / 2);
- bounds = int(uint(pos.x | pos.y) < uint(_region_map_size));
- layer_index = (_region_map[ pos.y * _region_map_size + pos.x ] * bounds - 1);
- }
- }
- return ivec3(ivec2(mod(r_uv,_region_size)), layer_index);
+ ivec2 pos = ivec2(floor(r_uv * _region_texel_size)) + (_region_map_size / 2);
+ int bounds = int(uint(pos.x | pos.y) < uint(_region_map_size));
+ int layer_index = _region_map[pos.y * _region_map_size + pos.x] * bounds - 1;
+ return ivec3(ivec2(mod(r_uv, _region_size)), layer_index);
+}
+
+// Takes in world space XZ (UV) and returns if the coordinate should be part of the NONE background.
+bool is_none_bg(const vec2 uv) {
+ ivec4 regions = ivec4(
+ get_index_coord(uv - vec2(0.5, 0.0)).z,
+ get_index_coord(uv - vec2(0.0, 0.5)).z,
+ get_index_coord(uv + vec2(1.0, 0.0)).z,
+ get_index_coord(uv + vec2(0.0, 1.0)).z);
+ return any(equal(regions, ivec4(-1)));
}
float interpolated_height(vec2 pos) {
const vec2 offsets = vec2(0, 1);
vec2 index_id = floor(pos);
ivec3 index[4];
- index[0] = get_index_coord(index_id + offsets.xy, VERTEX_PASS);
- index[1] = get_index_coord(index_id + offsets.yy, VERTEX_PASS);
- index[2] = get_index_coord(index_id + offsets.yx, VERTEX_PASS);
- index[3] = get_index_coord(index_id + offsets.xx, VERTEX_PASS);
+ index[0] = get_index_coord(index_id + offsets.xy);
+ index[1] = get_index_coord(index_id + offsets.yy);
+ index[2] = get_index_coord(index_id + offsets.yx);
+ index[3] = get_index_coord(index_id + offsets.xx);
float h0 = texelFetch(_height_maps, index[0], 0).r;
float h1 = texelFetch(_height_maps, index[1], 0).r;
float h2 = texelFetch(_height_maps, index[2], 0).r;
@@ -129,13 +130,13 @@ void vertex() {
UV2 = fma(UV, vec2(_region_texel_size), vec2(0.5 * _region_texel_size));
// Discard vertices for Holes. 1 lookup
- ivec3 v_region = get_index_coord(start_pos, VERTEX_PASS);
+ ivec3 v_region = get_index_coord(start_pos);
uint control = floatBitsToUint(texelFetch(_control_maps, v_region, 0)).r;
bool hole = bool(control >>2u & 0x1u);
// Show holes to all cameras except mouse camera (on exactly 1 layer)
if ( !(CAMERA_VISIBLE_LAYERS == _mouse_layer) &&
- (hole || (_background_mode == 0u && v_region.z < 0))) {
+ (hole || (_background_mode == 0u && is_none_bg(UV)))) {
v_vertex.x = 0. / 0.;
} else {
// Set final vertex height.
@@ -143,10 +144,10 @@ void vertex() {
// This branch is static for each of the clipmap segments
// Interpolated reads only occur where sub-texel values are required.
if (scale < _vertex_spacing) {
- h = mix(interpolated_height(start_pos), interpolated_height(end_pos), vertex_lerp);
+ h = interpolated_height(UV);
} else {
- ivec3 coord_a = get_index_coord(start_pos, VERTEX_PASS);
- ivec3 coord_b = get_index_coord(end_pos, VERTEX_PASS);
+ ivec3 coord_a = get_index_coord(start_pos);
+ ivec3 coord_b = get_index_coord(end_pos);
h = mix(texelFetch(_height_maps, coord_a, 0).r,texelFetch(_height_maps, coord_b, 0).r,vertex_lerp);
}
v_vertex.y = h;
@@ -186,14 +187,14 @@ void fragment() {
// Calculate the effective mipmap for regionspace, and if less than 0,
// skip all extra lookups required for bilinear blend.
float region_mip = log2(max(length(base_ddx.xz), length(base_ddy.xz)) * _vertex_density);
- bool bilerp = region_mip < 0.0;
+ bool bilerp = region_mip < 4.0;
ivec3 index[4];
// control map lookups, used for some normal lookups as well
- index[0] = get_index_coord(index_id + offsets.xy, FRAGMENT_PASS);
- index[1] = get_index_coord(index_id + offsets.yy, FRAGMENT_PASS);
- index[2] = get_index_coord(index_id + offsets.yx, FRAGMENT_PASS);
- index[3] = get_index_coord(index_id + offsets.xx, FRAGMENT_PASS);
+ index[0] = get_index_coord(index_id + offsets.xy);
+ index[1] = get_index_coord(index_id + offsets.yy);
+ index[2] = get_index_coord(index_id + offsets.yx);
+ index[3] = get_index_coord(index_id + offsets.xx);
// Terrain normals
vec3 index_normal[4];
@@ -216,10 +217,10 @@ void fragment() {
// 5 lookups
// Fetch the additional required height values for smooth normals
h[1] = texelFetch(_height_maps, index[1], 0).r; // 3 (1,1)
- float h_4 = texelFetch(_height_maps, get_index_coord(index_id + offsets.yz, FRAGMENT_PASS), 0).r; // 4 (1,2)
- float h_5 = texelFetch(_height_maps, get_index_coord(index_id + offsets.zy, FRAGMENT_PASS), 0).r; // 5 (2,1)
- float h_6 = texelFetch(_height_maps, get_index_coord(index_id + offsets.zx, FRAGMENT_PASS), 0).r; // 6 (2,0)
- float h_7 = texelFetch(_height_maps, get_index_coord(index_id + offsets.xz, FRAGMENT_PASS), 0).r; // 7 (0,2)
+ float h_4 = texelFetch(_height_maps, get_index_coord(index_id + offsets.yz), 0).r; // 4 (1,2)
+ float h_5 = texelFetch(_height_maps, get_index_coord(index_id + offsets.zy), 0).r; // 5 (2,1)
+ float h_6 = texelFetch(_height_maps, get_index_coord(index_id + offsets.zx), 0).r; // 6 (2,0)
+ float h_7 = texelFetch(_height_maps, get_index_coord(index_id + offsets.xz), 0).r; // 7 (0,2)
// Calculate the normal for the remaining index ids.
index_normal[0] = normalize(vec3(h[0] - h[1] + u, _vertex_spacing, h[0] - h_7 + v));
diff --git a/project/addons/terrain_3d/src/editor_plugin.gd b/project/addons/terrain_3d/src/editor_plugin.gd
index 51196ca7b..bb0e12905 100644
--- a/project/addons/terrain_3d/src/editor_plugin.gd
+++ b/project/addons/terrain_3d/src/editor_plugin.gd
@@ -6,7 +6,6 @@ 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 ASSET_DOCK: String = "res://addons/terrain_3d/src/asset_dock.tscn"
# Editor Plugin
@@ -15,7 +14,6 @@ var editor: Terrain3DEditor
var editor_settings: EditorSettings
var ui: Node # Terrain3DUI see Godot #75388
var asset_dock: PanelContainer
-var region_gizmo: RegionGizmo
var current_region_position: Vector2
var mouse_global_position: Vector3 = Vector3.ZERO
var godot_editor_window: Window # The Godot Editor window
@@ -58,8 +56,6 @@ func _enter_tree() -> void:
ui.plugin = self
add_child(ui)
- region_gizmo = RegionGizmo.new()
-
scene_changed.connect(_on_scene_changed)
asset_dock = load(ASSET_DOCK).instantiate()
@@ -131,19 +127,12 @@ func _edit(p_object: Object) -> void:
terrain.set_editor(editor)
debug = terrain.debug_level
editor.set_terrain(terrain)
- region_gizmo.set_node_3d(terrain)
- terrain.add_gizmo(region_gizmo)
- ui.set_visible(true)
terrain.set_meta("_edit_lock_", true)
# Get alerted when a new asset list is loaded
if not terrain.assets_changed.is_connected(asset_dock.update_assets):
terrain.assets_changed.connect(asset_dock.update_assets)
asset_dock.update_assets()
- # Get alerted when the region map changes
- if not terrain.data.region_map_changed.is_connected(update_region_grid):
- terrain.data.region_map_changed.connect(update_region_grid)
- update_region_grid()
else:
_clear()
@@ -157,16 +146,10 @@ func _edit(p_object: Object) -> void:
func _clear() -> void:
if is_terrain_valid():
- if terrain.data.region_map_changed.is_connected(update_region_grid):
- terrain.data.region_map_changed.disconnect(update_region_grid)
-
- terrain.clear_gizmos()
terrain = null
editor.set_terrain(null)
ui.clear_picking()
-
- region_gizmo.clear()
func _forward_3d_gui_input(p_viewport_camera: Camera3D, p_event: InputEvent) -> AfterGUIInput:
@@ -218,9 +201,6 @@ func _forward_3d_gui_input(p_viewport_camera: Camera3D, p_event: InputEvent) ->
## Update region highlight
var region_position: Vector2 = ( Vector2(mouse_global_position.x, mouse_global_position.z) \
/ (terrain.get_region_size() * terrain.get_vertex_spacing()) ).floor()
- if current_region_position != region_position:
- current_region_position = region_position
- update_region_grid()
if _input_mode > 0 and editor.is_operating():
# Inject pressure - Relies on C++ set_brush_data() using same dictionary instance
@@ -385,26 +365,6 @@ func consume_hotkey(keycode: int) -> bool:
return true
-func update_region_grid() -> void:
- if not region_gizmo:
- return
- region_gizmo.set_hidden(not ui.visible)
-
- if is_terrain_valid():
- region_gizmo.show_rect = editor.get_tool() == Terrain3DEditor.REGION
- region_gizmo.use_secondary_color = editor.get_operation() == Terrain3DEditor.SUBTRACT
- region_gizmo.region_position = current_region_position
- region_gizmo.region_size = terrain.get_region_size() * terrain.get_vertex_spacing()
- region_gizmo.grid = terrain.get_data().get_region_locations()
-
- terrain.update_gizmos()
- return
-
- region_gizmo.show_rect = false
- region_gizmo.region_size = 1024
- region_gizmo.grid = [Vector2i.ZERO]
-
-
func _on_scene_changed(scene_root: Node) -> void:
if debug:
print("Terrain3DEditorPlugin: _on_scene_changed: ", scene_root)
diff --git a/project/addons/terrain_3d/src/region_gizmo.gd b/project/addons/terrain_3d/src/region_gizmo.gd
deleted file mode 100644
index c74c8f503..000000000
--- a/project/addons/terrain_3d/src/region_gizmo.gd
+++ /dev/null
@@ -1,68 +0,0 @@
-# Copyright © 2025 Cory Petkovsek, Roope Palmroos, and Contributors.
-# Editor Region Gizmos for Terrain3D
-extends EditorNode3DGizmo
-
-var material: StandardMaterial3D
-var selection_material: StandardMaterial3D
-var region_position: Vector2
-var region_size: float
-var grid: Array[Vector2i]
-var use_secondary_color: bool = false
-var show_rect: bool = true
-
-var main_color: Color = Color.GREEN_YELLOW
-var secondary_color: Color = Color.RED
-var grid_color: Color = Color.WHITE
-var border_color: Color = Color.BLUE
-
-
-func _init() -> void:
- material = StandardMaterial3D.new()
- material.set_flag(BaseMaterial3D.FLAG_DISABLE_DEPTH_TEST, true)
- material.set_flag(BaseMaterial3D.FLAG_ALBEDO_FROM_VERTEX_COLOR, true)
- material.set_shading_mode(BaseMaterial3D.SHADING_MODE_UNSHADED)
- material.set_albedo(Color.WHITE)
-
- selection_material = material.duplicate()
- selection_material.set_render_priority(0)
-
-
-func _redraw() -> void:
- clear()
-
- var rect_position = region_position * region_size
-
- if show_rect:
- var modulate: Color = main_color if !use_secondary_color else secondary_color
- if abs(region_position.x) > Terrain3DData.REGION_MAP_SIZE*.5 or abs(region_position.y) > Terrain3DData.REGION_MAP_SIZE*.5:
- modulate = Color.GRAY
- draw_rect(Vector2(region_size,region_size)*.5 + rect_position, region_size, selection_material, modulate)
-
- for pos in grid:
- var grid_tile_position = Vector2(pos) * region_size
- if show_rect and grid_tile_position == rect_position:
- # Skip this one, otherwise focused region borders are not always visible due to draw order
- continue
-
- draw_rect(Vector2(region_size,region_size)*.5 + grid_tile_position, region_size, material, grid_color)
-
- draw_rect(Vector2.ZERO, region_size * Terrain3DData.REGION_MAP_SIZE, material, border_color)
-
-
-func draw_rect(p_pos: Vector2, p_size: float, p_material: StandardMaterial3D, p_modulate: Color) -> void:
- var lines: PackedVector3Array = [
- Vector3(-1, 0, -1),
- Vector3(-1, 0, 1),
- Vector3(1, 0, 1),
- Vector3(1, 0, -1),
- Vector3(-1, 0, 1),
- Vector3(1, 0, 1),
- Vector3(1, 0, -1),
- Vector3(-1, 0, -1),
- ]
-
- for i in lines.size():
- lines[i] = ((lines[i] / 2.0) * p_size) + Vector3(p_pos.x, 0, p_pos.y)
-
- add_lines(lines, p_material, false, p_modulate)
-
diff --git a/project/addons/terrain_3d/src/region_gizmo.gd.uid b/project/addons/terrain_3d/src/region_gizmo.gd.uid
deleted file mode 100644
index 346a40407..000000000
--- a/project/addons/terrain_3d/src/region_gizmo.gd.uid
+++ /dev/null
@@ -1 +0,0 @@
-uid://bh6qwe1ok4cx3
diff --git a/project/addons/terrain_3d/src/ui.gd b/project/addons/terrain_3d/src/ui.gd
index e11437b50..d60dc32a7 100644
--- a/project/addons/terrain_3d/src/ui.gd
+++ b/project/addons/terrain_3d/src/ui.gd
@@ -143,6 +143,8 @@ func set_visible(p_visible: bool, p_menu_only: bool = false) -> void:
if plugin.debug:
print("Terrain3DUI: set_visible: calling _on_tool_changed()")
_on_tool_changed(_selected_tool, _selected_operation)
+ if _selected_tool in [ Terrain3DEditor.REGION, Terrain3DEditor.NAVIGATION ]:
+ plugin.terrain.material.update(Terrain3DMaterial.FULL_REBUILD)
else:
plugin.editor.set_tool(Terrain3DEditor.TOOL_MAX)
plugin.editor.set_operation(Terrain3DEditor.OP_MAX)
@@ -279,7 +281,6 @@ func _on_tool_changed(p_tool: Terrain3DEditor.Tool, p_operation: Terrain3DEditor
if plugin.debug:
print("Terrain3DUI: _on_tool_changed: calling _on_setting_changed()")
_on_setting_changed()
- plugin.update_region_grid()
func _on_setting_changed(p_setting: Variant = null) -> void:
diff --git a/project/demo/Demo.tscn b/project/demo/Demo.tscn
index 49b922cbe..fc814d4f4 100644
--- a/project/demo/Demo.tscn
+++ b/project/demo/Demo.tscn
@@ -36,36 +36,35 @@ _shader_parameters = {
"auto_base_texture": 0,
"auto_height_reduction": 0.05,
"auto_overlay_texture": 1,
-&"auto_shader": null,
"auto_slope": 0.45,
"bias_distance": 512.0,
"blend_sharpness": 0.5,
"depth_blur": 0.0,
-&"displacement": null,
"dual_scale_far": 170.0,
"dual_scale_near": 100.0,
"dual_scale_reduction": 0.3,
"dual_scale_texture": 0,
-&"dual_scaling": null,
&"flat_terrain_normals": false,
-&"general": null,
-&"macro_variation": true,
+&"ground_level": 0.0,
"macro_variation1": Color(0.855, 0.8625, 0.9, 1),
"macro_variation2": Color(0.9, 0.885, 0.81, 1),
-&"macro_variation_enabled": true,
"macro_variation_slope": 0.333,
"mipmap_bias": 1.0,
-&"mipmaps": null,
"noise1_angle": 0.1,
"noise1_offset": Vector2(0.5, 0.5),
"noise1_scale": 0.04,
"noise2_scale": 0.076,
"noise_texture": SubResource("NoiseTexture2D_bov7h"),
-"projection_threshold": 0.87,
&"region_blend": 0.25,
+&"shader_uniforms": null,
+&"shader_uniforms::auto_shader": null,
+&"shader_uniforms::displacement": null,
+&"shader_uniforms::dual_scaling": null,
+&"shader_uniforms::general": null,
+&"shader_uniforms::macro_variation": null,
+&"shader_uniforms::mipmaps": null,
+&"shader_uniforms::world_background_noise": null,
"tri_scale_reduction": 0.3,
-&"vertical_projection": true,
-&"world_background_noise": null,
"world_noise_fragment_normals": false,
"world_noise_height": 34.0,
"world_noise_lod_distance": 7500.0,
@@ -77,6 +76,8 @@ _shader_parameters = {
world_background = 2
auto_shader_enabled = true
dual_scaling_enabled = true
+macro_variation_enabled = true
+projection_enabled = true
displacement_sharpness = 0.25
[node name="Demo" type="Node"]
diff --git a/project/demo/data/assets.tres b/project/demo/data/assets.tres
index 005b31715..09c3296cb 100644
--- a/project/demo/data/assets.tres
+++ b/project/demo/data/assets.tres
@@ -45,7 +45,6 @@ ao_strength = 1.0
ao_light_affect = 0.8
roughness = -0.05
displacement_scale = 0.65
-vertical_projection = true
[sub_resource type="Terrain3DTextureAsset" id="Terrain3DTextureAsset_od0q7"]
name = "Grass"
diff --git a/src/constants.h b/src/constants.h
index b1cc474a3..545fa685f 100644
--- a/src/constants.h
+++ b/src/constants.h
@@ -26,9 +26,6 @@ using namespace godot;
#ifndef FLT_MAX
#define FLT_MAX __FLT_MAX__
#endif
-#ifndef FLT_MIN
-#define FLT_MIN __FLT_MIN__
-#endif
// Terrain3D::_warnings is uint8_t
#define WARN_MISMATCHED_SIZE 0x01
diff --git a/src/shaders/auto_shader.glsl b/src/shaders/auto_shader.glsl
index 493a5717a..c26e03627 100644
--- a/src/shaders/auto_shader.glsl
+++ b/src/shaders/auto_shader.glsl
@@ -4,7 +4,7 @@ R"(
//INSERT: AUTO_SHADER_UNIFORMS
#define AUTO_SHADER
-group_uniforms auto_shader;
+group_uniforms shader_uniforms.auto_shader;
uniform float auto_slope : hint_range(0, 10) = 1.0;
uniform float auto_height_reduction : hint_range(0, 1) = 0.1;
uniform int auto_base_texture : hint_range(0, 31) = 0;
diff --git a/src/shaders/backgrounds.glsl b/src/shaders/backgrounds.glsl
index d6e35c7c3..4ad8c83c0 100644
--- a/src/shaders/backgrounds.glsl
+++ b/src/shaders/backgrounds.glsl
@@ -1,10 +1,22 @@
// Copyright © 2025 Cory Petkovsek, Roope Palmroos, and Contributors.
R"(
+//INSERT: NONE_FUNCTIONS
+// Takes in world space XZ (UV) and returns if the coordinate should be part of the NONE background.
+bool is_none_bg(const vec2 uv) {
+ ivec4 regions = ivec4(
+ get_index_coord(uv - vec2(0.5, 0.0)).z,
+ get_index_coord(uv - vec2(0.0, 0.5)).z,
+ get_index_coord(uv + vec2(1.0, 0.0)).z,
+ get_index_coord(uv + vec2(0.0, 1.0)).z);
+ return any(equal(regions, ivec4(-1)));
+}
+
+//INSERT: NONE_CHECK
+ || (_background_mode == 0u && is_none_bg(UV))
//INSERT: FLAT_UNIFORMS
uniform float ground_level : hint_range(-1000., 1000.) = 0.0;
uniform float region_blend : hint_range(.001, 1., 0.001) = 0.25;
-varying vec2 bg_ddxy;
//INSERT: FLAT_FUNCTIONS
// Takes in UV2 region space coordinates, returns 1.0 or 0.0 if a region is present or not.
@@ -19,32 +31,31 @@ float check_region(const vec2 uv2) {
// Takes in UV2 region space coordinates, returns a blend value (0 - 1 range) between empty, and valid regions
float get_region_blend(vec2 uv2) {
- uv2 -= 0.5;
+ uv2 -= 0.5011; // correct for floating point error
const vec2 offset = vec2(0.0, 1.0);
float a = check_region(uv2 + offset.xy);
float b = check_region(uv2 + offset.yy);
float c = check_region(uv2 + offset.yx);
float d = check_region(uv2 + offset.xx);
- vec2 w = smoothstep(vec2(0.0), vec2(1.0), fract(uv2));
+ vec2 blend_factor = vec2(2.0 + 126.0 * (1.0 - region_blend));
+ vec2 f = fract(uv2);
+ vec2 w = 1.0 / (1.0 + exp(blend_factor * log((1.0 - f) / f)));
float blend = mix(mix(d, c, w.x), mix(a, b, w.x), w.y);
- return 1.0 - blend;
+ return (1.0 - blend) * 2.0;
}
//INSERT: FLAT_VERTEX
- // Apply background ground level and region blend
- float ground_h = ground_level * smoothstep(1.0 - region_blend, 1.0, get_region_blend(UV2));
- h += ground_h;
- bg_ddxy.x = ground_h - ground_level * smoothstep(1.0 - region_blend, 1.0, get_region_blend(UV2 + vec2(_region_texel_size, 0.)));
- bg_ddxy.y = ground_h - ground_level * smoothstep(1.0 - region_blend, 1.0, get_region_blend(UV2 + vec2(0., _region_texel_size)));
+ // Apply background ground level and region blend, dont include texel offset
+ h = mix(h, ground_level, smoothstep(0.0, 1.0, get_region_blend(UV * _region_texel_size)));
//INSERT: FLAT_FRAGMENT
- // Apply background normal
- u += bg_ddxy.x;
- v += bg_ddxy.y;
+ if (_background_mode != 0u) {
+ float blend = get_region_blend(index_id * _region_texel_size + offset * _region_texel_size);
+ height = mix(height, ground_level, smoothstep(0., 1., blend));
+ }
//INSERT: WORLD_NOISE_UNIFORMS
-group_uniforms world_background_noise;
-uniform float region_blend : hint_range(.001, 1., 0.001) = 0.25;
+group_uniforms shader_uniforms.world_background_noise;
uniform bool world_noise_fragment_normals = false;
uniform int world_noise_max_octaves : hint_range(0, 15) = 4;
uniform int world_noise_min_octaves : hint_range(0, 15) = 2;
@@ -58,19 +69,9 @@ varying vec2 world_noise_ddxy;
//INSERT: WORLD_NOISE_FUNCTIONS
// World Noise Functions Start
-// Takes in UV2 region space coordinates, returns 1.0 or 0.0 if a region is present or not.
-float check_region(const vec2 uv2) {
- ivec2 pos = ivec2(floor(uv2)) + (_region_map_size / 2);
- int layer_index = 0;
- if (uint(pos.x | pos.y) < uint(_region_map_size)) {
- layer_index = clamp(_region_map[ pos.y * _region_map_size + pos.x ] - 1, -1, 0) + 1;
- }
- return float(layer_index);
-}
-
// Takes in UV2 region space coordinates, returns a blend value (0 - 1 range) between empty, and valid regions
-float get_region_blend(vec2 uv2) {
- uv2 -= 0.4989; // Offset from - 0.5 to account for FP errors highlighted by Tessellation.
+float get_noise_region_blend(vec2 uv2) {
+ uv2 -= 0.5011; // correct for floating point error
const vec2 offset = vec2(0.0, 1.0);
float a = check_region(uv2 + offset.xy);
float b = check_region(uv2 + offset.yy);
@@ -78,7 +79,7 @@ float get_region_blend(vec2 uv2) {
float d = check_region(uv2 + offset.xx);
vec2 w = smoothstep(vec2(0.0), vec2(1.0), fract(uv2));
float blend = mix(mix(d, c, w.x), mix(a, b, w.x), w.y);
- return 1.0 - blend;
+ return 1.0 - blend * 2.0;
}
float hashf(float f) {
@@ -135,15 +136,15 @@ float world_noise(vec2 p) {
}
float get_noise_height(const vec2 uv) {
- float weight = get_region_blend(uv);
+ float weight = get_noise_region_blend(uv);
// Only calculate world noise when it would be visible.
- if (weight <= 1.0 - region_blend) {
+ if (weight <= 0.5) {
return 0.0;
}
//TODO: Offset/scale UVs are semi-dependent upon region size 1024. Base on v_vertex.xz instead
float noise = world_noise((uv + world_noise_offset.xz * 1024. / _region_size) * world_noise_scale * _region_size / 1024. * .1) *
world_noise_height * 10. + world_noise_offset.y * 100.;
- weight = smoothstep(1.0 - region_blend, 1.0, weight);
+ weight = smoothstep(0.5, 1.0, weight);
return mix(0.0, noise, weight);
}
@@ -152,8 +153,8 @@ float get_noise_height(const vec2 uv) {
//INSERT: WORLD_NOISE_VERTEX
// World Noise
if (_background_mode == 2u) {
- vec2 nuv_a = fma(start_pos, vec2(_region_texel_size), vec2(0.5 * _region_texel_size));
- vec2 nuv_b = fma(end_pos, vec2(_region_texel_size), vec2(0.5 * _region_texel_size));
+ vec2 nuv_a = start_pos * _region_texel_size;
+ vec2 nuv_b = end_pos * _region_texel_size;
float nh = mix(get_noise_height(nuv_a), get_noise_height(nuv_b), vertex_lerp);
float nu = mix(get_noise_height(nuv_a + vec2(_region_texel_size, 0.0)),
get_noise_height(nuv_b + vec2(_region_texel_size, 0.0)), vertex_lerp);
diff --git a/src/shaders/debug_views.glsl b/src/shaders/debug_views.glsl
index 8971a3490..4fefa3674 100644
--- a/src/shaders/debug_views.glsl
+++ b/src/shaders/debug_views.glsl
@@ -30,7 +30,7 @@ R"(
}
//INSERT: DEBUG_HEIGHTMAP_SETUP
-group_uniforms debug_heightmap;
+group_uniforms shader_uniforms.debug_heightmap;
uniform float heightmap_black_height: hint_range(-2048.,2048.,.5) = -100.0;
uniform float heightmap_white_height: hint_range(-2048.,2048.,.5) = 300.0;
group_uniforms;
diff --git a/src/shaders/displacement_buffer.glsl b/src/shaders/displacement_buffer.glsl
index ddc8d3208..05c44014e 100644
--- a/src/shaders/displacement_buffer.glsl
+++ b/src/shaders/displacement_buffer.glsl
@@ -7,6 +7,8 @@ R"(shader_type canvas_item;
// the buffer directly via RID (avoids GPU > CPU > GPU copies) as alpha
// is premultiplied by the renderer.
+#define IS_DISPLACEMENT_BUFFER
+
// Defined Constants
#define SKIP_PASS 0
#define VERTEX_PASS 1
@@ -47,26 +49,28 @@ uniform int _region_map_size = 32;
uniform int _region_map[1024];
uniform vec2 _region_locations[1024];
uniform float _texture_uv_scale_array[32];
-uniform uint _texture_vertical_projections;
uniform vec2 _texture_detile_array[32];
uniform vec2 _texture_displacement_array[32];
uniform highp sampler2DArray _height_maps : repeat_disable;
uniform highp sampler2DArray _control_maps : repeat_disable;
//INSERT: TEXTURE_SAMPLERS_NEAREST
//INSERT: TEXTURE_SAMPLERS_LINEAR
+uniform highp sampler2DArray _texture_array_albedo : source_color, FILTER_METHOD, repeat_enable;
+uniform highp sampler2DArray _texture_array_normal : hint_normal, FILTER_METHOD, repeat_enable;
// Public uniforms
-group_uniforms general;
+group_uniforms shader_uniforms.general;
uniform float blend_sharpness : hint_range(0, 1) = 0.5;
-uniform bool vertical_projection = true;
-uniform float projection_threshold : hint_range(0.0, 0.99, 0.01) = 0.8;
group_uniforms;
//INSERT: AUTO_SHADER_UNIFORMS
+//INSERT: FLAT_UNIFORMS
+//INSERT: FLAT_FUNCTIONS
+
// Uniquely named displacement uniforms should be in this group.
// Uniforms that are shared with the main shader are automatically synchronised.
// Subgroups should work as expected.
-group_uniforms displacement;
+group_uniforms shader_uniforms.displacement;
uniform float _displacement_sharpness : hint_range(0.0, 1.0, 0.01) = 0.25;
group_uniforms;
@@ -82,24 +86,16 @@ struct material {
// Vertex
////////////////////////
-// Takes in world space XZ (UV) coordinates & search depth (only applicable for background mode none)
+// Takes in world space XZ (UV) coordinates
// Returns ivec3 with:
// XY: (0 to _region_size - 1) coordinates within a region
// Z: layer index used for texturearrays, -1 if not in a region
-ivec3 get_index_coord(const vec2 uv, const int search) {
+ivec3 get_index_coord(const vec2 uv) {
vec2 r_uv = round(uv);
- vec2 o_uv = mod(r_uv,_region_size);
- ivec2 pos;
- int bounds, layer_index = -1;
- for (int i = -1; i < clamp(search, SKIP_PASS, FRAGMENT_PASS); i++) {
- if ((layer_index == -1 && _background_mode == 0u ) || i < 0) {
- r_uv -= i == -1 ? vec2(0.0) : vec2(float(o_uv.x <= o_uv.y), float(o_uv.y <= o_uv.x));
- pos = ivec2(floor((r_uv) * _region_texel_size)) + (_region_map_size / 2);
- bounds = int(uint(pos.x | pos.y) < uint(_region_map_size));
- layer_index = (_region_map[ pos.y * _region_map_size + pos.x ] * bounds - 1);
- }
- }
- return ivec3(ivec2(mod(r_uv,_region_size)), layer_index);
+ ivec2 pos = ivec2(floor(r_uv * _region_texel_size)) + (_region_map_size / 2);
+ int bounds = int(uint(pos.x | pos.y) < uint(_region_map_size));
+ int layer_index = _region_map[pos.y * _region_map_size + pos.x] * bounds - 1;
+ return ivec3(ivec2(mod(r_uv, _region_size)), layer_index);
}
// Takes in descaled (world_space / region_size) world to region space XZ (UV2) coordinates, returns vec3 with:
@@ -144,26 +140,13 @@ void accumulate_material(const mat3 TNB, const float weight, const ivec3 index,
// Projection
vec2 i_uv = i_vertex.xz;
mat2 p_align = mat2(1.);
- vec2 p_uv = i_uv;
- vec2 p_pos = i_pos;
- if (i_normal.y <= projection_threshold && vertical_projection) {
- // Projected normal map alignment matrix
- p_align = mat2(vec2(i_normal.z, -i_normal.x), vec2(i_normal.x, i_normal.z));
- // Fast 45 degree snapping https://iquilezles.org/articles/noatan/
- vec2 xz = round(normalize(-i_normal.xz) * 1.3065629648763765); // sqrt(1.0 + sqrt(0.5))
- xz *= abs(xz.x) + abs(xz.y) > 1.5 ? 0.7071067811865475 : 1.0; // sqrt(0.5)
- xz = vec2(-xz.y, xz.x);
- p_pos = floor(vec2(dot(i_pos, xz), -h));
- p_uv = vec2(dot(i_vertex.xz, xz), -i_vertex.y);
- }
+//INSERT: PROJECTION
// Control map rotation. Must be applied seperatley from detiling to maintain UV continuity.
float c_angle = DECODE_ANGLE(control);
vec2 c_cs_angle = vec2(cos(c_angle), sin(c_angle));
i_uv = rotate_vec2(i_uv, c_cs_angle);
i_pos = rotate_vec2(i_pos, c_cs_angle);
- p_uv = rotate_vec2(p_uv, c_cs_angle);
- p_pos = rotate_vec2(p_pos, c_cs_angle);
// Blend adjustment of Higher ID from Lower ID normal map in world space.
float world_normal = 1.;
@@ -176,18 +159,15 @@ void accumulate_material(const mat3 TNB, const float weight, const ivec3 index,
// 1st Texture Asset ID
if (blend < 1.0) {
int id = texture_id[0];
- bool projected = TEXTURE_ID_PROJECTED(id);
float id_w = texture_weight[0];
float id_scale = _texture_uv_scale_array[id];
// Detiling and Control map rotation
- vec2 id_pos = fma(p_pos, vec2(float(projected)), i_pos * vec2(float(!projected)));
- vec2 uv_center = floor(fma(id_pos, vec2(id_scale), vec2(0.5)));
+ vec2 uv_center = floor(fma(i_pos, vec2(id_scale), vec2(0.5)));
vec2 id_detile = fma(random(uv_center), 2.0, -1.0) * _texture_detile_array[id] * TAU;
vec2 id_cs_angle = vec2(cos(id_detile.x), sin(id_detile.x));
// Apply UV rotation and shift around pivot.
- vec2 id_uv = fma(p_uv, vec2(float(projected)), i_uv * vec2(float(!projected)));
- id_uv = rotate_vec2(fma(id_uv, vec2(id_scale), -uv_center), id_cs_angle) + uv_center + id_detile.y - 0.5;
+ vec2 id_uv = rotate_vec2(fma(i_uv, vec2(id_scale), -uv_center), id_cs_angle) + uv_center + id_detile.y - 0.5;
// Manual transpose to rotate derivatives and normals counter to uv rotation whilst also
// including control map rotation. avoids extra matrix op, and sin/cos calls.
id_cs_angle = vec2(
@@ -198,8 +178,7 @@ void accumulate_material(const mat3 TNB, const float weight, const ivec3 index,
vec4 nrm = textureLod(_texture_array_normal, vec3(id_uv, float(id)), 0.);
// Unpack and rotate normal map.
nrm.xyz = fma(nrm.xzy, vec3(2.0), vec3(-1.0));
- nrm.xz = rotate_vec2(nrm.xz, id_cs_angle);
- nrm.xz = fma((nrm.xz * p_align), vec2(float(projected)), nrm.xz * vec2(float(!projected)));
+ nrm.xz = rotate_vec2(nrm.xz, id_cs_angle) * p_align;
world_normal = FAST_WORLD_NORMAL(nrm).y;
@@ -217,18 +196,15 @@ void accumulate_material(const mat3 TNB, const float weight, const ivec3 index,
// 2nd Texture Asset ID
if (blend > 0.0 && texture_id[1] != texture_id[0]) {
int id = texture_id[1];
- bool projected = TEXTURE_ID_PROJECTED(id);
float id_w = texture_weight[1];
float id_scale = _texture_uv_scale_array[id];
// Detiling and Control map rotation
- vec2 id_pos = fma(p_pos, vec2(float(projected)), i_pos * vec2(float(!projected)));
- vec2 uv_center = floor(fma(id_pos, vec2(id_scale), vec2(0.5)));
+ vec2 uv_center = floor(fma(i_pos, vec2(id_scale), vec2(0.5)));
vec2 id_detile = fma(random(uv_center), 2.0, -1.0) * _texture_detile_array[id] * TAU;
vec2 id_cs_angle = vec2(cos(id_detile.x), sin(id_detile.x));
// Apply UV rotation and shift around pivot.
- vec2 id_uv = fma(p_uv, vec2(float(projected)), i_uv * vec2(float(!projected)));
- id_uv = rotate_vec2(fma(id_uv, vec2(id_scale), -uv_center), id_cs_angle) + uv_center + id_detile.y - 0.5;
+ vec2 id_uv = rotate_vec2(fma(i_uv, vec2(id_scale), -uv_center), id_cs_angle) + uv_center + id_detile.y - 0.5;
// Manual transpose to rotate derivatives and normals counter to uv rotation whilst also
// including control map rotation. avoids extra matrix op, and sin/cos calls.
id_cs_angle = vec2(
@@ -249,6 +225,13 @@ void accumulate_material(const mat3 TNB, const float weight, const ivec3 index,
mat.total_weight += id_weight;
}
}
+
+float get_height(vec2 index_id, vec2 offset) {
+ float height = texelFetch(_height_maps, get_index_coord(index_id + offset), 0).r;
+//INSERT: FLAT_FRAGMENT
+ return height;
+}
+
)"
R"(
@@ -275,10 +258,10 @@ void fragment() {
ivec3 index[4];
// control map lookups, used for some normal lookups as well
- index[0] = get_index_coord(index_id + offsets.xy, FRAGMENT_PASS);
- index[1] = get_index_coord(index_id + offsets.yy, FRAGMENT_PASS);
- index[2] = get_index_coord(index_id + offsets.yx, FRAGMENT_PASS);
- index[3] = get_index_coord(index_id + offsets.xx, FRAGMENT_PASS);
+ index[0] = get_index_coord(index_id + offsets.xy);
+ index[1] = get_index_coord(index_id + offsets.yy);
+ index[2] = get_index_coord(index_id + offsets.yx);
+ index[3] = get_index_coord(index_id + offsets.xx);
// Terrain normals
vec3 index_normal[4];
@@ -288,18 +271,18 @@ void fragment() {
float v = 0.0;
// Re-use index[] for the first lookups, skipping some math. 3 lookups
- h[3] = texelFetch(_height_maps, index[3], 0).r; // 0 (0,0)
- h[2] = texelFetch(_height_maps, index[2], 0).r; // 1 (1,0)
- h[0] = texelFetch(_height_maps, index[0], 0).r; // 2 (0,1)
+ h[3] = get_height(index_id, offsets.xx); // 0 (0, 0)
+ h[2] = get_height(index_id, offsets.yx); // 1 (1, 0)
+ h[0] = get_height(index_id, offsets.xy); // 2 (0, 1)
index_normal[3] = normalize(vec3(h[3] - h[2] + u, _vertex_spacing, h[3] - h[0] + v));
// 5 lookups
// Fetch the additional required height values for smooth normals
- h[1] = texelFetch(_height_maps, index[1], 0).r; // 3 (1,1)
- float h_4 = texelFetch(_height_maps, get_index_coord(index_id + offsets.yz, FRAGMENT_PASS), 0).r; // 4 (1,2)
- float h_5 = texelFetch(_height_maps, get_index_coord(index_id + offsets.zy, FRAGMENT_PASS), 0).r; // 5 (2,1)
- float h_6 = texelFetch(_height_maps, get_index_coord(index_id + offsets.zx, FRAGMENT_PASS), 0).r; // 6 (2,0)
- float h_7 = texelFetch(_height_maps, get_index_coord(index_id + offsets.xz, FRAGMENT_PASS), 0).r; // 7 (0,2)
+ h[1] = get_height(index_id, offsets.yy); // 3 (1, 1)
+ float h_4 = get_height(index_id, offsets.yz); // 4 (1, 2)
+ float h_5 = get_height(index_id, offsets.zy); // 5 (2, 1)
+ float h_6 = get_height(index_id, offsets.zx); // 6 (2, 0)
+ float h_7 = get_height(index_id, offsets.xz); // 7 (0, 2)
// Calculate the normal for the remaining index ids.
index_normal[0] = normalize(vec3(h[0] - h[1] + u, _vertex_spacing, h[0] - h_7 + v));
@@ -350,7 +333,6 @@ void fragment() {
vec2(weights_id_0[3], weights_id_1[3]));
// interpolated weights
- #if CURRENT_RENDERER == RENDERER_FORWARD_PLUS
t_weights = {vec2(0), vec2(0), vec2(0), vec2(0)};
weights_id_0 *= weights;
weights_id_1 *= weights;
@@ -365,7 +347,6 @@ void fragment() {
t_weights[3] += fma(w_0, vec2(equal(texture_ids[3], id_0)), w_1 * vec2(equal(texture_ids[3], id_1)));
}
- #endif
// Struct to accumulate all texture data.
material mat = material(0., 0.);
diff --git a/src/shaders/dual_scaling.glsl b/src/shaders/dual_scaling.glsl
index bb2e2285b..66dc61131 100644
--- a/src/shaders/dual_scaling.glsl
+++ b/src/shaders/dual_scaling.glsl
@@ -3,7 +3,7 @@
R"(
//INSERT: DUAL_SCALING_UNIFORMS
-group_uniforms dual_scaling;
+group_uniforms shader_uniforms.dual_scaling;
uniform int dual_scale_texture : hint_range(0,31) = 0;
uniform float dual_scale_reduction : hint_range(0.001,1) = 0.3;
uniform float tri_scale_reduction : hint_range(0.001,1) = 0.3;
@@ -18,21 +18,18 @@ group_uniforms;
vec4 far_nrm = vec4(0.);
float far_ao = 1.0;
if (far_factor > 0. && any(equal(texture_id, ivec2(dual_scale_texture)))) {
- bool projected = TEXTURE_ID_PROJECTED(dual_scale_texture);
float far_scale = _texture_uv_scale_array[dual_scale_texture] * dual_scale_reduction;
if (index.z < 0) {
far_scale *= tri_scale_reduction;
}
- vec4 far_dd = fma(p_dd, vec4(float(projected)), i_dd * vec4(float(!projected))) * far_scale;
+ vec4 far_dd = i_dd * far_scale;
// Detiling and Control map rotation
- vec2 id_pos = fma(p_pos, vec2(float(projected)), i_pos * vec2(float(!projected)));
- vec2 uv_center = floor(fma(id_pos, vec2(far_scale), vec2(0.5)));
+ vec2 uv_center = floor(fma(i_pos, vec2(far_scale), vec2(0.5)));
vec2 far_detile = fma(random(uv_center), 2.0, -1.0) * _texture_detile_array[dual_scale_texture] * TAU;
vec2 far_cs_angle = vec2(cos(far_detile.x), sin(far_detile.x));
// Apply UV rotation and shift around pivot.
- vec2 far_uv = fma(p_uv, vec2(float(projected)), i_uv * vec2(float(!projected)));
- far_uv = rotate_vec2(fma(far_uv, vec2(far_scale), -uv_center), far_cs_angle) + uv_center + far_detile.y - 0.5;
+ vec2 far_uv = rotate_vec2(fma(i_uv, vec2(far_scale), -uv_center), far_cs_angle) + uv_center + far_detile.y - 0.5;
// Manual transpose to rotate derivatives and normals counter to uv rotation whilst also
// including control map rotation. avoids extra matrix op, and sin/cos calls.
far_cs_angle = vec2(
@@ -51,8 +48,7 @@ group_uniforms;
far_ao = length(far_nrm.xyz) * 2.0 - 1.0;
far_ao = mix(far_ao * far_ao * _texture_ao_strength_array[dual_scale_texture] + 1.0 - _texture_ao_strength_array[dual_scale_texture], 1.0, far_alb.a * far_alb.a);
far_nrm.xyz = normalize(far_nrm.xyz);
- far_nrm.xz = rotate_vec2(far_nrm.xz, far_cs_angle);
- far_nrm.xz = fma((far_nrm.xz * p_align), vec2(float(projected)), far_nrm.xz * vec2(float(!projected)));
+ far_nrm.xz = rotate_vec2(far_nrm.xz, far_cs_angle) * p_align;
// apply weighting when far_factor == 1.0 as the later lookup will be skipped.
if (far_factor == 1.0) {
diff --git a/src/shaders/editor_functions.glsl b/src/shaders/editor_functions.glsl
index b15c8f165..36fc01065 100644
--- a/src/shaders/editor_functions.glsl
+++ b/src/shaders/editor_functions.glsl
@@ -6,11 +6,36 @@ R"(
//INSERT: EDITOR_NAVIGATION
// Show navigation
{
- if(bool(floatBitsToUint(texelFetch(_control_maps, get_index_coord(floor(uv + 0.5), SKIP_PASS), 0)).r >>1u & 0x1u)) {
+ if(bool(floatBitsToUint(texelFetch(_control_maps, get_index_coord(floor(uv + 0.5)), 0)).r >>1u & 0x1u)) {
ALBEDO *= vec3(.5, .0, .85);
}
}
+//INSERT: EDITOR_REGION_GRID
+ // Show region grid
+ {
+ vec3 __boundary_color = pow(vec3(0.095, 0.328, 0.56), vec3(2.2)); // Medium dark blue, hue 210, converted to linear
+ vec3 __active_color = vec3(1.0);
+ vec3 __inactive_color = vec3(0.1);
+ float __line_thickness = 0.05 * sqrt(-VERTEX.z);
+ vec3 __pixel_pos = (INV_VIEW_MATRIX * vec4(VERTEX, 1.0)).xyz * _vertex_density;
+ vec2 __p = __pixel_pos.xz;
+ // Region Grid
+ vec2 __g = abs(fract((__p + _region_size * 0.5) / _region_size) - 0.5) * _region_size;
+ float __grid_d = min(__g.x, __g.y);
+ float __grid_mask = 1.0 - smoothstep(__line_thickness - fwidth(__grid_d), __line_thickness + fwidth(__grid_d), __grid_d);
+ // Region Map Boundry
+ float __hmap = _region_size * 16.0;
+ vec2 __bp = abs(__p) - __hmap;
+ float __box_d = abs(max(__bp.x, __bp.y));
+ float __box_mask = 1.0 - smoothstep( __line_thickness - fwidth(__box_d), __line_thickness + fwidth(__box_d), __box_d);
+ // Clip Grid at Boundary
+ float __b_line = __hmap - __line_thickness - fwidth(__box_d);
+ __grid_mask *= step(abs(__p.x), __b_line) * step(abs(__p.y), __b_line);
+ vec3 __grid_color = mix(__inactive_color, __active_color, float(clamp(get_index_coord(__pixel_pos.xz - 0.5).z + 1, 0, 1)));
+ ALBEDO = mix(ALBEDO, __grid_mask * __grid_color + __box_mask * __boundary_color, max(__grid_mask, __box_mask));
+ }
+
//INSERT: EDITOR_DECAL_SETUP
uniform highp sampler2D _editor_brush_texture : source_color, filter_linear, repeat_disable;
uniform vec2 _editor_decal_position[3];
diff --git a/src/shaders/macro_variation.glsl b/src/shaders/macro_variation.glsl
new file mode 100644
index 000000000..b19c33f1d
--- /dev/null
+++ b/src/shaders/macro_variation.glsl
@@ -0,0 +1,27 @@
+// Copyright © 2025 Cory Petkovsek, Roope Palmroos, and Contributors.
+
+R"(
+
+//INSERT: MACRO_VARIATION_UNIFORMS
+group_uniforms shader_uniforms.macro_variation;
+uniform vec3 macro_variation1 : source_color = vec3(1.);
+uniform vec3 macro_variation2 : source_color = vec3(1.);
+uniform float macro_variation_slope : hint_range(0., 1.) = 0.333;
+uniform highp sampler2D noise_texture : source_color, FILTER_METHOD, repeat_enable;
+uniform float noise1_scale : hint_range(0.001, 1.) = 0.04; // Used for macro variation 1. Scaled up 10x
+uniform float noise1_angle : hint_range(0, 6.283) = 0.;
+uniform vec2 noise1_offset = vec2(0.5);
+uniform float noise2_scale : hint_range(0.001, 1.) = 0.076; // Used for macro variation 2. Scaled up 10x
+group_uniforms;
+
+//INSERT: MACRO_VARIATION
+ // Macro variation. 2 lookups
+ {
+ float noise1 = texture(noise_texture, rotate_vec2(fma(uv, vec2(noise1_scale * .1), noise1_offset) , vec2(cos(noise1_angle), sin(noise1_angle)))).r;
+ float noise2 = texture(noise_texture, uv * noise2_scale * .1).r;
+ vec3 macrov = mix(macro_variation1, vec3(1.), noise1);
+ macrov *= mix(macro_variation2, vec3(1.), noise2);
+ mat.albedo_height.rgb *= mix(vec3(1.0), macrov, clamp(w_normal.y + macro_variation_slope, 0., 1.));
+ }
+
+)"
diff --git a/src/shaders/main.glsl b/src/shaders/main.glsl
index 65f5d2de8..0067208f1 100644
--- a/src/shaders/main.glsl
+++ b/src/shaders/main.glsl
@@ -20,9 +20,6 @@ render_mode blend_mix, depth_draw_opaque, cull_back, diffuse_burley, specular_sc
*/
// Defined Constants
-#define SKIP_PASS 0
-#define VERTEX_PASS 1
-#define FRAGMENT_PASS 2
#define COLOR_MAP_DEF vec4(1.0, 1.0, 1.0, 0.5)
#define DIV_255 0.003921568627450 // 1. / 255.
#define DIV_1024 0.0009765625 // 1. / 1024.
@@ -38,8 +35,6 @@ render_mode blend_mix, depth_draw_opaque, cull_back, diffuse_burley, specular_sc
#define DECODE_SCALE(control) (0.9 - float(((control >>7u & 0x7u) + 3u) % 8u + 1u) * 0.1)
#define DECODE_HOLE(control) bool(control >>2u & 0x1u)
-#define TEXTURE_ID_PROJECTED(id) bool((_texture_vertical_projections >> uint(id)) & 0x1u)
-
#if CURRENT_RENDERER == RENDERER_COMPATIBILITY
#define fma(a, b, c) ((a) * (b) + (c))
#define dFdxCoarse(a) dFdx(a)
@@ -47,9 +42,11 @@ render_mode blend_mix, depth_draw_opaque, cull_back, diffuse_burley, specular_sc
#endif
// Private uniforms
+group_uniforms shader_uniforms;
uniform vec3 _target_pos = vec3(0.f);
uniform float _mesh_size = 48.f;
uniform float _subdiv = 1.f;
+uniform float _tessellation_level = 0.f;
uniform uint _background_mode = 1u; // NONE = 0, FLAT = 1, NOISE = 2
uniform uint _mouse_layer = 0x80000000u; // Layer 32
uniform float _vertex_spacing = 1.0;
@@ -64,40 +61,31 @@ uniform float _texture_ao_strength_array[32];
uniform float _texture_ao_affect_array[32];
uniform float _texture_roughness_mod_array[32];
uniform float _texture_uv_scale_array[32];
-uniform uint _texture_vertical_projections;
uniform vec2 _texture_detile_array[32];
uniform vec4 _texture_color_array[32];
uniform highp sampler2DArray _height_maps : repeat_disable;
uniform highp sampler2DArray _control_maps : repeat_disable;
//INSERT: TEXTURE_SAMPLERS_NEAREST
//INSERT: TEXTURE_SAMPLERS_LINEAR
-// Public uniforms
+uniform highp sampler2DArray _color_maps : source_color, FILTER_METHOD, repeat_disable;
+uniform highp sampler2DArray _texture_array_albedo : source_color, FILTER_METHOD, repeat_enable;
+uniform highp sampler2DArray _texture_array_normal : hint_normal, FILTER_METHOD, repeat_enable;
+group_uniforms;
-group_uniforms general;
+// Public uniforms
+group_uniforms shader_uniforms.general;
//INSERT: FLAT_UNIFORMS
uniform bool flat_terrain_normals = false;
uniform float blend_sharpness : hint_range(0, 1) = 0.5;
-uniform bool vertical_projection = true;
-uniform float projection_threshold : hint_range(0.0, 0.99, 0.01) = 0.8;
+//INSERT: PROJECTION_UNIFORMS
group_uniforms;
//INSERT: AUTO_SHADER_UNIFORMS
//INSERT: DISPLACEMENT_UNIFORMS
//INSERT: DUAL_SCALING_UNIFORMS
-group_uniforms macro_variation;
-uniform bool macro_variation_enabled = true;
-uniform vec3 macro_variation1 : source_color = vec3(1.);
-uniform vec3 macro_variation2 : source_color = vec3(1.);
-uniform float macro_variation_slope : hint_range(0., 1.) = 0.333;
-//INSERT: NOISE_SAMPLER_NEAREST
-//INSERT: NOISE_SAMPLER_LINEAR
-uniform float noise1_scale : hint_range(0.001, 1.) = 0.04; // Used for macro variation 1. Scaled up 10x
-uniform float noise1_angle : hint_range(0, 6.283) = 0.;
-uniform vec2 noise1_offset = vec2(0.5);
-uniform float noise2_scale : hint_range(0.001, 1.) = 0.076; // Used for macro variation 2. Scaled up 10x
-group_uniforms;
+//INSERT: MACRO_VARIATION_UNIFORMS
-group_uniforms mipmaps;
+group_uniforms shader_uniforms.mipmaps;
uniform float bias_distance : hint_range(0.0, 16384.0, 0.1) = 512.0;
uniform float mipmap_bias : hint_range(0.5, 1.5, 0.01) = 1.0;
uniform float depth_blur : hint_range(0.0, 35.0, 0.1) = 0.0;
@@ -126,23 +114,15 @@ varying vec3 v_camera_pos;
// Vertex
////////////////////////
-// Takes in world space XZ (UV) coordinates & search depth (only applicable for background mode none)
+// Takes in world space XZ (UV) coordinates
// Returns ivec3 with:
// XY: (0 to _region_size - 1) coordinates within a region
// Z: layer index used for texturearrays, -1 if not in a region
-ivec3 get_index_coord(const vec2 uv, const int search) {
+ivec3 get_index_coord(const vec2 uv) {
vec2 r_uv = round(uv);
- vec2 o_uv = mod(r_uv, _region_size);
- ivec2 pos;
- int bounds, layer_index = -1;
- for (int i = -1; i < clamp(search, SKIP_PASS, FRAGMENT_PASS); i++) {
- if ((layer_index == -1 && _background_mode == 0u ) || i < 0) {
- r_uv -= i == -1 ? vec2(0.0) : vec2(float(o_uv.x <= o_uv.y), float(o_uv.y <= o_uv.x));
- pos = ivec2(floor((r_uv) * _region_texel_size)) + (_region_map_size / 2);
- bounds = int(uint(pos.x | pos.y) < uint(_region_map_size));
- layer_index = (_region_map[ pos.y * _region_map_size + pos.x ] * bounds - 1);
- }
- }
+ ivec2 pos = ivec2(floor(r_uv * _region_texel_size)) + (_region_map_size / 2);
+ int bounds = int(uint(pos.x | pos.y) < uint(_region_map_size));
+ int layer_index = _region_map[pos.y * _region_map_size + pos.x] * bounds - 1;
return ivec3(ivec2(mod(r_uv, _region_size)), layer_index);
}
@@ -160,10 +140,10 @@ float interpolated_height(vec2 pos) {
const vec2 offsets = vec2(0, 1);
vec2 index_id = floor(pos);
ivec3 index[4];
- index[0] = get_index_coord(index_id + offsets.xy, VERTEX_PASS);
- index[1] = get_index_coord(index_id + offsets.yy, VERTEX_PASS);
- index[2] = get_index_coord(index_id + offsets.yx, VERTEX_PASS);
- index[3] = get_index_coord(index_id + offsets.xx, VERTEX_PASS);
+ index[0] = get_index_coord(index_id + offsets.xy);
+ index[1] = get_index_coord(index_id + offsets.yy);
+ index[2] = get_index_coord(index_id + offsets.yx);
+ index[3] = get_index_coord(index_id + offsets.xx);
float h0 = texelFetch(_height_maps, index[0], 0).r;
float h1 = texelFetch(_height_maps, index[1], 0).r;
float h2 = texelFetch(_height_maps, index[2], 0).r;
@@ -176,6 +156,7 @@ float interpolated_height(vec2 pos) {
}
//INSERT: DISPLACEMENT_FUNCTIONS
+//INSERT: NONE_FUNCTIONS
//INSERT: FLAT_FUNCTIONS
//INSERT: WORLD_NOISE_FUNCTIONS
@@ -214,14 +195,15 @@ void vertex() {
UV2 = fma(UV, vec2(_region_texel_size), vec2(0.5 * _region_texel_size));
// Discard vertices for Holes. 1 lookup
- ivec3 v_region = get_index_coord(start_pos, VERTEX_PASS);
+ ivec3 v_region = get_index_coord(start_pos);
uint control = floatBitsToUint(texelFetch(_control_maps, v_region, 0)).r;
bool hole = DECODE_HOLE(control);
vec3 displacement = vec3(0.);
// Show holes to all cameras except mouse camera (on exactly 1 layer)
- if ( !(CAMERA_VISIBLE_LAYERS == _mouse_layer) &&
- (hole || (_background_mode == 0u && v_region.z == -1))) {
+ if ( !(CAMERA_VISIBLE_LAYERS == _mouse_layer) && (hole
+//INSERT: NONE_CHECK
+ )){
v_vertex.x = 0. / 0.;
} else {
// Set final vertex height.
@@ -229,14 +211,15 @@ void vertex() {
// This branch is static for each of the clipmap segments
// Interpolated reads only occur where sub-texel values are required.
if (scale < _vertex_spacing) {
- h = mix(interpolated_height(start_pos), interpolated_height(end_pos), vertex_lerp);
+ h = interpolated_height(UV);
} else {
- ivec3 coord_a = get_index_coord(start_pos, VERTEX_PASS);
- ivec3 coord_b = get_index_coord(end_pos, VERTEX_PASS);
+ ivec3 coord_a = get_index_coord(start_pos);
+ ivec3 coord_b = get_index_coord(end_pos);
h = mix(texelFetch(_height_maps, coord_a, 0).r, texelFetch(_height_maps, coord_b, 0).r, vertex_lerp);
}
-//INSERT: WORLD_NOISE_VERTEX
+
//INSERT: FLAT_VERTEX
+//INSERT: WORLD_NOISE_VERTEX
//INSERT: DISPLACEMENT_VERTEX
v_vertex.y = h;
}
@@ -286,29 +269,13 @@ void accumulate_material(vec3 base_ddx, vec3 base_ddy, const mat3 TNB, const flo
vec2 i_uv = i_vertex.xz;
vec4 i_dd = vec4(base_ddx.xz, base_ddy.xz);
mat2 p_align = mat2(1.);
- vec2 p_uv = i_uv;
- vec4 p_dd = i_dd;
- vec2 p_pos = i_pos;
- if (i_normal.y <= projection_threshold && vertical_projection) {
- // Projected normal map alignment matrix
- p_align = mat2(vec2(i_normal.z, -i_normal.x), vec2(i_normal.x, i_normal.z));
- // Fast 45 degree snapping https://iquilezles.org/articles/noatan/
- vec2 xz = round(normalize(-i_normal.xz) * 1.3065629648763765); // sqrt(1.0 + sqrt(0.5))
- xz *= abs(xz.x) + abs(xz.y) > 1.5 ? 0.7071067811865475 : 1.0; // sqrt(0.5)
- xz = vec2(-xz.y, xz.x);
- p_pos = floor(vec2(dot(i_pos, xz), -h));
- p_uv = vec2(dot(i_vertex.xz, xz), -i_vertex.y);
- p_dd.xy = vec2(dot(base_ddx.xz, xz), -base_ddx.y);
- p_dd.zw = vec2(dot(base_ddy.xz, xz), -base_ddy.y);
- }
+//INSERT: PROJECTION
// Control map rotation. Must be applied seperatley from detiling to maintain UV continuity.
float c_angle = DECODE_ANGLE(control);
vec2 c_cs_angle = vec2(cos(c_angle), sin(c_angle));
i_uv = rotate_vec2(i_uv, c_cs_angle);
i_pos = rotate_vec2(i_pos, c_cs_angle);
- p_uv = rotate_vec2(p_uv, c_cs_angle);
- p_pos = rotate_vec2(p_pos, c_cs_angle);
// Blend adjustment of Higher ID from Lower ID normal map in world space.
float world_normal = 1.;
@@ -325,19 +292,16 @@ void accumulate_material(vec3 base_ddx, vec3 base_ddy, const mat3 TNB, const flo
//INSERT: DUAL_SCALING_CONDITION_0
) {
int id = texture_id[0];
- bool projected = TEXTURE_ID_PROJECTED(id);
float id_w = texture_weight[0];
float id_scale = _texture_uv_scale_array[id];
- vec4 id_dd = fma(p_dd, vec4(float(projected)), i_dd * vec4(float(!projected))) * id_scale;
+ vec4 id_dd = i_dd * id_scale;
// Detiling and Control map rotation
- vec2 id_pos = fma(p_pos, vec2(float(projected)), i_pos * vec2(float(!projected)));
- vec2 uv_center = floor(fma(id_pos, vec2(id_scale), vec2(0.5)));
+ vec2 uv_center = floor(fma(i_pos, vec2(id_scale), vec2(0.5)));
vec2 id_detile = fma(random(uv_center), 2.0, -1.0) * _texture_detile_array[id] * TAU;
vec2 id_cs_angle = vec2(cos(id_detile.x), sin(id_detile.x));
// Apply UV rotation and shift around pivot.
- vec2 id_uv = fma(p_uv, vec2(float(projected)), i_uv * vec2(float(!projected)));
- id_uv = rotate_vec2(fma(id_uv, vec2(id_scale), -uv_center), id_cs_angle) + uv_center + id_detile.y - 0.5;
+ vec2 id_uv = rotate_vec2(fma(i_uv, vec2(id_scale), -uv_center), id_cs_angle) + uv_center + id_detile.y - 0.5;
// Manual transpose to rotate derivatives and normals counter to uv rotation whilst also
// including control map rotation. avoids extra matrix op, and sin/cos calls.
id_cs_angle = vec2(
@@ -356,8 +320,7 @@ void accumulate_material(vec3 base_ddx, vec3 base_ddy, const mat3 TNB, const flo
float ao = length(nrm.xyz) * 2.0 - 1.0;
ao = mix(ao * ao * _texture_ao_strength_array[id] + 1.0 - _texture_ao_strength_array[id], 1.0, alb.a * alb.a);
nrm.xyz = normalize(nrm.xyz);
- nrm.xz = rotate_vec2(nrm.xz, id_cs_angle);
- nrm.xz = fma((nrm.xz * p_align), vec2(float(projected)), nrm.xz * vec2(float(!projected)));
+ nrm.xz = rotate_vec2(nrm.xz, id_cs_angle) * p_align;
//INSERT: DUAL_SCALING_MIX
world_normal = FAST_WORLD_NORMAL(nrm).y;
@@ -376,19 +339,16 @@ void accumulate_material(vec3 base_ddx, vec3 base_ddy, const mat3 TNB, const flo
//INSERT: DUAL_SCALING_CONDITION_1
) {
int id = texture_id[1];
- bool projected = TEXTURE_ID_PROJECTED(id);
float id_w = texture_weight[1];
float id_scale = _texture_uv_scale_array[id];
- vec4 id_dd = fma(p_dd, vec4(float(projected)), i_dd * vec4(float(!projected))) * id_scale;
+ vec4 id_dd = i_dd * id_scale;
// Detiling and Control map rotation
- vec2 id_pos = fma(p_pos, vec2(float(projected)), i_pos * vec2(float(!projected)));
- vec2 uv_center = floor(fma(id_pos, vec2(id_scale), vec2(0.5)));
+ vec2 uv_center = floor(fma(i_pos, vec2(id_scale), vec2(0.5)));
vec2 id_detile = fma(random(uv_center), 2.0, -1.0) * _texture_detile_array[id] * TAU;
vec2 id_cs_angle = vec2(cos(id_detile.x), sin(id_detile.x));
// Apply UV rotation and shift around pivot.
- vec2 id_uv = fma(p_uv, vec2(float(projected)), i_uv * vec2(float(!projected)));
- id_uv = rotate_vec2(fma(id_uv, vec2(id_scale), -uv_center), id_cs_angle) + uv_center + id_detile.y - 0.5;
+ vec2 id_uv = rotate_vec2(fma(i_uv, vec2(id_scale), -uv_center), id_cs_angle) + uv_center + id_detile.y - 0.5;
// Manual transpose to rotate derivatives and normals counter to uv rotation whilst also
// including control map rotation. avoids extra matrix op, and sin/cos calls.
id_cs_angle = vec2(
@@ -407,8 +367,7 @@ void accumulate_material(vec3 base_ddx, vec3 base_ddy, const mat3 TNB, const flo
float ao = length(nrm.xyz) * 2.0 - 1.0;
ao = mix(ao * ao * _texture_ao_strength_array[id] + 1.0 - _texture_ao_strength_array[id], 1.0, alb.a * alb.a);
nrm.xyz = normalize(nrm.xyz);
- nrm.xz = rotate_vec2(nrm.xz, id_cs_angle);
- nrm.xz = fma((nrm.xz * p_align), vec2(float(projected)), nrm.xz * vec2(float(!projected)));
+ nrm.xz = rotate_vec2(nrm.xz, id_cs_angle) * p_align;
//INSERT: DUAL_SCALING_MIX
float id_weight = exp2(sharpness * log2(weight + id_w + alb.a * clamp(world_normal, 0., 1.))) * weight;
@@ -420,6 +379,13 @@ void accumulate_material(vec3 base_ddx, vec3 base_ddy, const mat3 TNB, const flo
mat.total_weight += id_weight;
}
}
+
+float get_height(vec2 index_id, vec2 offset) {
+ float height = texelFetch(_height_maps, get_index_coord(index_id + offset), 0).r;
+//INSERT: FLAT_FRAGMENT
+ return height;
+}
+
)"
R"(
@@ -442,18 +408,18 @@ void fragment() {
);
ivec3 index[4];
- // control map lookups, used for some normal lookups as well
- index[0] = get_index_coord(index_id + offsets.xy, FRAGMENT_PASS);
- index[1] = get_index_coord(index_id + offsets.yy, FRAGMENT_PASS);
- index[2] = get_index_coord(index_id + offsets.yx, FRAGMENT_PASS);
- index[3] = get_index_coord(index_id + offsets.xx, FRAGMENT_PASS);
+ // control map lookups
+ index[0] = get_index_coord(index_id + offsets.xy);
+ index[1] = get_index_coord(index_id + offsets.yy);
+ index[2] = get_index_coord(index_id + offsets.yx);
+ index[3] = get_index_coord(index_id + offsets.xx);
vec3 base_ddx = dFdxCoarse(v_vertex);
vec3 base_ddy = dFdyCoarse(v_vertex);
// Calculate the effective mipmap for regionspace, and when less than 0,
// skip all extra lookups required for bilinear blend.
float region_mip = log2(max(length(base_ddx.xz), length(base_ddy.xz)) * _vertex_density);
- bool bilerp = region_mip < 0.0 && region_uv.z > -1.;
+ bool bilerp = region_mip < 4.0 && any(greaterThan(ivec4(index[0].z, index[1].z, index[2].z, index[3].z), ivec4(-1)));
// Terrain normals
vec3 index_normal[4];
@@ -462,12 +428,11 @@ void fragment() {
float u = 0.0;
float v = 0.0;
-//INSERT: FLAT_FRAGMENT
//INSERT: WORLD_NOISE_FRAGMENT
- // Re-use index[] for the first lookups, skipping some math. 3 lookups
- h[3] = texelFetch(_height_maps, index[3], 0).r; // 0 (0, 0)
- h[2] = texelFetch(_height_maps, index[2], 0).r; // 1 (1, 0)
- h[0] = texelFetch(_height_maps, index[0], 0).r; // 2 (0, 1)
+
+ h[3] = get_height(index_id, offsets.xx); // 0 (0, 0)
+ h[2] = get_height(index_id, offsets.yx); // 1 (1, 0)
+ h[0] = get_height(index_id, offsets.xy); // 2 (0, 1)
index_normal[3] = normalize(vec3(h[3] - h[2] + u, _vertex_spacing, h[3] - h[0] + v));
// Set flat world normal - overwritten if bilerp is true
@@ -504,11 +469,11 @@ void fragment() {
// 5 lookups
// Fetch the additional required height values for smooth normals
- h[1] = texelFetch(_height_maps, index[1], 0).r; // 3 (1, 1)
- float h_4 = texelFetch(_height_maps, get_index_coord(index_id + offsets.yz, FRAGMENT_PASS), 0).r; // 4 (1, 2)
- float h_5 = texelFetch(_height_maps, get_index_coord(index_id + offsets.zy, FRAGMENT_PASS), 0).r; // 5 (2, 1)
- float h_6 = texelFetch(_height_maps, get_index_coord(index_id + offsets.zx, FRAGMENT_PASS), 0).r; // 6 (2, 0)
- float h_7 = texelFetch(_height_maps, get_index_coord(index_id + offsets.xz, FRAGMENT_PASS), 0).r; // 7 (0, 2)
+ h[1] = get_height(index_id, offsets.yy); // 3 (1, 1)
+ float h_4 = get_height(index_id, offsets.yz); // 4 (1, 2)
+ float h_5 = get_height(index_id, offsets.zy); // 5 (2, 1)
+ float h_6 = get_height(index_id, offsets.zx); // 6 (2, 0)
+ float h_7 = get_height(index_id, offsets.xz); // 7 (0, 2)
// Calculate the normal for the remaining index ids.
index_normal[0] = normalize(vec3(h[0] - h[1] + u, _vertex_spacing, h[0] - h_7 + v));
@@ -571,7 +536,6 @@ void fragment() {
vec2(weights_id_0[2], weights_id_1[2]),
vec2(weights_id_0[3], weights_id_1[3]));
// interpolated weights
- #if CURRENT_RENDERER == RENDERER_FORWARD_PLUS
if (bilerp) {
t_weights = {vec2(0), vec2(0), vec2(0), vec2(0)};
weights_id_0 *= weights;
@@ -587,7 +551,6 @@ void fragment() {
t_weights[3] += fma(w_0, vec2(equal(texture_ids[3], id_0)), w_1 * vec2(equal(texture_ids[3], id_1)));
}
}
- #endif
// Struct to accumulate all texture data.
material mat = material(vec4(0.0), vec4(0.0), 0., 0., 0., 0.);
@@ -614,28 +577,17 @@ void fragment() {
mat.ao *= weight_inv;
mat.ao_affect *= weight_inv;
- // Macro variation. 2 lookups
- vec3 macrov = vec3(1.);
- if (macro_variation_enabled) {
- float noise1 = texture(noise_texture, rotate_vec2(fma(uv, vec2(noise1_scale * .1), noise1_offset) , vec2(cos(noise1_angle), sin(noise1_angle)))).r;
- float noise2 = texture(noise_texture, uv * noise2_scale * .1).r;
- macrov = mix(macro_variation1, vec3(1.), noise1);
- macrov *= mix(macro_variation2, vec3(1.), noise2);
- macrov = mix(vec3(1.0), macrov, clamp(w_normal.y + macro_variation_slope, 0., 1.));
- }
+ //INSERT: MACRO_VARIATION
// Wetness/roughness modifier, converting 0 - 1 range to -1 to 1 range, clamped to Godot roughness values
float roughness = clamp(fma(color_map.a - 0.5, 2.0, mat.normal_rough.a), 0., 1.);
// Apply PBR
- ALBEDO = mat.albedo_height.rgb * color_map.rgb * macrov;
- ROUGHNESS = roughness;
- SPECULAR = 1. - mat.normal_rough.a;
- // Repack final normal map value.
- NORMAL_MAP = fma(normalize(mat.normal_rough.xzy), vec3(0.5), vec3(0.5));
- NORMAL_MAP_DEPTH = mat.normal_map_depth;
- AO = clamp(mat.ao, 0., 1.);
- AO_LIGHT_AFFECT = mat.ao_affect;
+//INSERT: OUTPUT_ALBEDO
+//INSERT: OUTPUT_ALBEDO_GREY
+//INSERT: OUTPUT_ROUGHNESS
+//INSERT: OUTPUT_NORMAL_MAP
+//INSERT: OUTPUT_AMBIENT_OCCLUSION
}
diff --git a/src/shaders/overlays.glsl b/src/shaders/overlays.glsl
index c6928ccc2..587d300c0 100644
--- a/src/shaders/overlays.glsl
+++ b/src/shaders/overlays.glsl
@@ -4,31 +4,21 @@
// Variables should be prefaced with __ to avoid name conflicts.
R"(
-//INSERT: OVERLAY_REGION_GRID
- // Show region grid
- {
- vec3 __pixel_pos = (INV_VIEW_MATRIX * vec4(VERTEX,1.0)).xyz;
- vec3 __camera_pos = INV_VIEW_MATRIX[3].xyz;
- float __region_line = 1.0; // Region line thickness
- __region_line *= .1*sqrt(length(__camera_pos - __pixel_pos));
- if (mod(__pixel_pos.x * _vertex_density + __region_line*.5, _region_size) <= __region_line ||
- mod(__pixel_pos.z * _vertex_density + __region_line*.5, _region_size) <= __region_line ) {
- ALBEDO = vec3(1.);
- }
- }
-
//INSERT: OVERLAY_INSTANCER_GRID
- // Show region grid
+ // Show instancer grid
{
- vec3 __pixel_pos = (INV_VIEW_MATRIX * vec4(VERTEX,1.0)).xyz;
- vec3 __camera_pos = INV_VIEW_MATRIX[3].xyz;
- float __cell_line = 0.5; // Cell line thickness
- __cell_line *= .1*sqrt(length(__camera_pos - __pixel_pos));
- #define CELL_SIZE 32
- if (mod(__pixel_pos.x * _vertex_density + __cell_line*.5, CELL_SIZE) <= __cell_line ||
- mod(__pixel_pos.z * _vertex_density + __cell_line*.5, CELL_SIZE) <= __cell_line ) {
- ALBEDO = vec3(.033);
- }
+ vec3 __grid_color = vec3(.05);
+ float __line_thickness = 0.01 * sqrt(-VERTEX.z);
+ vec3 __pixel_pos = (INV_VIEW_MATRIX * vec4(VERTEX, 1.0)).xyz * _vertex_density;
+ vec2 __p = __pixel_pos.xz;
+ // Instancer Grid
+ #define CELL_SIZE 32.0
+ vec2 __g = abs(fract((__p + CELL_SIZE * 0.5) / CELL_SIZE) - 0.5) * CELL_SIZE;
+ float __grid_d = min(__g.x, __g.y);
+ float __grid_mask = 1.0 - smoothstep(__line_thickness - fwidth(__grid_d), __line_thickness + fwidth(__grid_d), __grid_d);
+ // Clip Grid outside regions
+ __grid_mask *= float(clamp(get_index_coord(__pixel_pos.xz - 0.5).z + 1, 0, 1));
+ ALBEDO = mix(ALBEDO, __grid_mask * __grid_color, __grid_mask);
}
//INSERT: OVERLAY_VERTEX_GRID
@@ -57,7 +47,7 @@ R"(
}
//INSERT: OVERLAY_CONTOURS_SETUP
-group_uniforms contour_lines;
+group_uniforms shader_uniforms.contour_lines;
uniform float contour_interval: hint_range(0.25, 100.0, 0.001) = 1.0;
uniform float contour_thickness : hint_range(0.0, 10.0, 0.001) = 1.0;
uniform vec4 contour_color : source_color = vec4(.85, .85, .19, 1.);
diff --git a/src/shaders/pbr_views.glsl b/src/shaders/pbr_views.glsl
index dddfc2acd..d6c0993f6 100644
--- a/src/shaders/pbr_views.glsl
+++ b/src/shaders/pbr_views.glsl
@@ -5,7 +5,7 @@
R"(
//INSERT: PBR_TEXTURE_ALBEDO
- // Show height textures
+ // Show albedo textures
{
ALBEDO = mat.albedo_height.rgb;
ROUGHNESS = 0.7;
@@ -54,4 +54,19 @@ R"(
AO = 1.0;
}
-)"
\ No newline at end of file
+//INSERT: OUTPUT_ALBEDO
+ ALBEDO = mat.albedo_height.rgb * color_map.rgb;
+//INSERT: OUTPUT_ALBEDO_GREY
+ ALBEDO = vec3(0.2);
+//INSERT: OUTPUT_ROUGHNESS
+ ROUGHNESS = roughness;
+ SPECULAR = 1. - mat.normal_rough.a;
+//INSERT: OUTPUT_NORMAL_MAP
+ // Repack final normal map value.
+ NORMAL_MAP = fma(normalize(mat.normal_rough.xzy), vec3(0.5), vec3(0.5));
+ NORMAL_MAP_DEPTH = mat.normal_map_depth;
+//INSERT: OUTPUT_AMBIENT_OCCLUSION
+ AO = clamp(mat.ao, 0., 1.);
+ AO_LIGHT_AFFECT = mat.ao_affect;
+
+)"
diff --git a/src/shaders/projection.glsl b/src/shaders/projection.glsl
new file mode 100644
index 000000000..916f2099f
--- /dev/null
+++ b/src/shaders/projection.glsl
@@ -0,0 +1,21 @@
+// Copyright © 2025 Cory Petkovsek, Roope Palmroos, and Contributors.
+
+R"(
+
+//INSERT: PROJECTION
+if (i_normal.y <= 0.7071067811865475) { // sqrt(0.5)
+ // Projected normal map alignment matrix
+ p_align = mat2(vec2(i_normal.z, -i_normal.x), vec2(i_normal.x, i_normal.z));
+ // Fast 45 degree snapping https://iquilezles.org/articles/noatan/
+ vec2 xz = round(normalize(-i_normal.xz) * 1.3065629648763765); // sqrt(1.0 + sqrt(0.5))
+ xz *= abs(xz.x) + abs(xz.y) > 1.5 ? 0.7071067811865475 : 1.0; // sqrt(0.5)
+ xz = vec2(-xz.y, xz.x);
+ i_pos = floor(vec2(dot(i_pos, xz), -h));
+ i_uv = vec2(dot(i_vertex.xz, xz), -i_vertex.y);
+ #ifndef IS_DISPLACEMENT_BUFFER
+ i_dd.xy = vec2(dot(base_ddx.xz, xz), -base_ddx.y);
+ i_dd.zw = vec2(dot(base_ddy.xz, xz), -base_ddy.y);
+ #endif
+ }
+
+)"
diff --git a/src/shaders/samplers.glsl b/src/shaders/samplers.glsl
index fc08053e9..2d583586f 100644
--- a/src/shaders/samplers.glsl
+++ b/src/shaders/samplers.glsl
@@ -4,20 +4,10 @@ R"(
//INSERT: TEXTURE_SAMPLERS_LINEAR
#define FILTER_LINEAR
-uniform highp sampler2DArray _color_maps : source_color, filter_linear_mipmap_anisotropic, repeat_disable;
-uniform highp sampler2DArray _texture_array_albedo : source_color, filter_linear_mipmap_anisotropic, repeat_enable;
-uniform highp sampler2DArray _texture_array_normal : hint_normal, filter_linear_mipmap_anisotropic, repeat_enable;
+#define FILTER_METHOD filter_linear_mipmap_anisotropic
//INSERT: TEXTURE_SAMPLERS_NEAREST
#define FILTER_NEAREST
-uniform highp sampler2DArray _color_maps : source_color, filter_nearest_mipmap_anisotropic, repeat_disable;
-uniform highp sampler2DArray _texture_array_albedo : source_color, filter_nearest_mipmap_anisotropic, repeat_enable;
-uniform highp sampler2DArray _texture_array_normal : hint_normal, filter_nearest_mipmap_anisotropic, repeat_enable;
-
-//INSERT: NOISE_SAMPLER_LINEAR
-uniform highp sampler2D noise_texture : source_color, filter_linear_mipmap_anisotropic, repeat_enable;
-
-//INSERT: NOISE_SAMPLER_NEAREST
-uniform highp sampler2D noise_texture : source_color, filter_nearest_mipmap_anisotropic, repeat_enable;
+#define FILTER_METHOD filter_nearest_mipmap_anisotropic
)"
\ No newline at end of file
diff --git a/src/terrain_3d.cpp b/src/terrain_3d.cpp
index edcb9cb67..babb88c8d 100644
--- a/src/terrain_3d.cpp
+++ b/src/terrain_3d.cpp
@@ -66,9 +66,9 @@ void Terrain3D::_initialize() {
_data->connect("region_map_changed", callable_mp(_collision, &Terrain3DCollision::build));
}
// Any map was regenerated or regions changed, update material uniforms without rebuilding shaders
- if (!_data->is_connected("maps_changed", callable_mp(_material.ptr(), &Terrain3DMaterial::update).bind(false))) {
+ if (!_data->is_connected("maps_changed", callable_mp(_material.ptr(), &Terrain3DMaterial::update).bind(Terrain3DMaterial::REGION_ARRAYS))) {
LOG(DEBUG, "Connecting _data::maps_changed signal to _material->_update()");
- _data->connect("maps_changed", callable_mp(_material.ptr(), &Terrain3DMaterial::update).bind(false));
+ _data->connect("maps_changed", callable_mp(_material.ptr(), &Terrain3DMaterial::update).bind(Terrain3DMaterial::REGION_ARRAYS));
}
// Height map was regenerated, update aabbs
if (!_data->is_connected("height_maps_changed", callable_mp(this, &Terrain3D::_update_mesher_aabbs))) {
@@ -76,9 +76,9 @@ void Terrain3D::_initialize() {
_data->connect("height_maps_changed", callable_mp(this, &Terrain3D::_update_mesher_aabbs));
}
// Texture assets changed, update material uniforms without rebuilding shaders
- if (!_assets->is_connected("textures_changed", callable_mp(_material.ptr(), &Terrain3DMaterial::update).bind(false))) {
+ if (!_assets->is_connected("textures_changed", callable_mp(_material.ptr(), &Terrain3DMaterial::update).bind(Terrain3DMaterial::TEXTURE_ARRAYS))) {
LOG(DEBUG, "Connecting _assets.textures_changed to _material->update()");
- _assets->connect("textures_changed", callable_mp(_material.ptr(), &Terrain3DMaterial::update).bind(false));
+ _assets->connect("textures_changed", callable_mp(_material.ptr(), &Terrain3DMaterial::update).bind(Terrain3DMaterial::TEXTURE_ARRAYS));
}
// Initialize the system
if (!_initialized && _is_inside_world && is_inside_tree()) {
@@ -503,7 +503,7 @@ void Terrain3D::set_editor(Terrain3DEditor *p_editor) {
SET_IF_DIFF(_editor, p_editor);
LOG(INFO, "Setting Terrain3DEditor: ", _editor);
if (_material.is_valid()) {
- _material->update(true);
+ _material->update(Terrain3DMaterial::FULL_REBUILD);
}
}
@@ -694,7 +694,7 @@ void Terrain3D::set_tessellation_level(const int p_level) {
SET_IF_DIFF(_tessellation_level, CLAMP(p_level, 0, 6));
LOG(INFO, "Setting tessellation level: ", p_level);
if (_mesher && _material.is_valid()) {
- _material->update(true);
+ _material->update(Terrain3DMaterial::FULL_REBUILD);
_mesher->initialize(this);
_update_displacement_buffer();
}
@@ -1380,7 +1380,7 @@ void Terrain3D::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_roughmap"), "set_show_roughmap", "get_show_roughmap");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_displacement_buffer"), "set_show_displacement_buffer", "get_show_displacement_buffer");
- ADD_SUBGROUP("PBR", "show_");
+ ADD_SUBGROUP("PBR Maps", "show_");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_texture_albedo"), "set_show_texture_albedo", "get_show_texture_albedo");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_texture_height"), "set_show_texture_height", "get_show_texture_height");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_texture_normal"), "set_show_texture_normal", "get_show_texture_normal");
diff --git a/src/terrain_3d_assets.cpp b/src/terrain_3d_assets.cpp
index a8df14bb2..4cafb80dd 100644
--- a/src/terrain_3d_assets.cpp
+++ b/src/terrain_3d_assets.cpp
@@ -353,7 +353,6 @@ void Terrain3DAssets::_update_texture_settings() {
_texture_ao_light_affects.clear();
_texture_roughness_mods.clear();
_texture_uv_scales.clear();
- _texture_vertical_projections = 0u;
_texture_detiles.clear();
_texture_displacements.clear();
@@ -371,7 +370,6 @@ void Terrain3DAssets::_update_texture_settings() {
_texture_ao_light_affects.push_back(ta->get_ao_light_affect());
_texture_roughness_mods.push_back(ta->get_roughness());
_texture_uv_scales.push_back(ta->get_uv_scale());
- _texture_vertical_projections |= (ta->get_vertical_projection() ? (uint32_t(1u) << uint32_t(ta->get_id())) : uint32_t(0u));
_texture_detiles.push_back(Vector2(ta->get_detiling_rotation(), ta->get_detiling_shift()));
_texture_displacements.push_back(Vector2(ta->get_displacement_offset(), ta->get_displacement_scale()));
}
@@ -737,7 +735,6 @@ void Terrain3DAssets::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_texture_ao_light_affects"), &Terrain3DAssets::get_texture_ao_light_affects);
ClassDB::bind_method(D_METHOD("get_texture_roughness_mods"), &Terrain3DAssets::get_texture_roughness_mods);
ClassDB::bind_method(D_METHOD("get_texture_uv_scales"), &Terrain3DAssets::get_texture_uv_scales);
- ClassDB::bind_method(D_METHOD("get_texture_vertical_projections"), &Terrain3DAssets::get_texture_vertical_projections);
ClassDB::bind_method(D_METHOD("get_texture_detiles"), &Terrain3DAssets::get_texture_detiles);
ClassDB::bind_method(D_METHOD("get_texture_displacements"), &Terrain3DAssets::get_texture_displacements);
ClassDB::bind_method(D_METHOD("clear_textures", "update"), &Terrain3DAssets::clear_textures, DEFVAL(false));
diff --git a/src/terrain_3d_assets.h b/src/terrain_3d_assets.h
index 07b67f44c..72fe3128e 100644
--- a/src/terrain_3d_assets.h
+++ b/src/terrain_3d_assets.h
@@ -39,7 +39,6 @@ class Terrain3DAssets : public Resource {
PackedFloat32Array _texture_ao_light_affects;
PackedFloat32Array _texture_roughness_mods;
PackedFloat32Array _texture_uv_scales;
- uint32_t _texture_vertical_projections;
PackedVector2Array _texture_detiles;
PackedVector2Array _texture_displacements;
@@ -85,7 +84,6 @@ class Terrain3DAssets : public Resource {
PackedFloat32Array get_texture_ao_light_affects() const { return _texture_ao_light_affects; }
PackedFloat32Array get_texture_roughness_mods() const { return _texture_roughness_mods; }
PackedFloat32Array get_texture_uv_scales() const { return _texture_uv_scales; }
- uint32_t get_texture_vertical_projections() const { return _texture_vertical_projections; }
PackedVector2Array get_texture_detiles() const { return _texture_detiles; }
PackedVector2Array get_texture_displacements() const { return _texture_displacements; }
void clear_textures(const bool p_update = false);
diff --git a/src/terrain_3d_collision.cpp b/src/terrain_3d_collision.cpp
index 2d2bb2093..7a8eac77e 100644
--- a/src/terrain_3d_collision.cpp
+++ b/src/terrain_3d_collision.cpp
@@ -22,13 +22,55 @@
Dictionary Terrain3DCollision::_get_shape_data(const Vector2i &p_position, const int p_size) {
IS_DATA_INIT_MESG("Terrain not initialized", Dictionary());
const Terrain3DData *data = _terrain->get_data();
- int region_size = _terrain->get_region_size();
+
+ const Ref material = _terrain->get_material();
+ if (!material.is_valid()) {
+ return Dictionary();
+ }
+ const Terrain3DMaterial::WorldBackground bg_mode = material->get_world_background();
+ const bool is_bg_flat_or_noise = bg_mode == Terrain3DMaterial::WorldBackground::FLAT || bg_mode == Terrain3DMaterial::WorldBackground::NOISE;
+ const real_t ground_level = material->get("ground_level");
+ const real_t region_blend = material->get("region_blend");
+ const int region_map_size = Terrain3DData::REGION_MAP_SIZE;
+ const PackedInt32Array region_map = data->get_region_map();
+ const int region_size = _terrain->get_region_size();
+ const real_t region_texel_size = 1.f / real_t(region_size);
+
+ auto check_region = [&](const Vector2 &uv2) -> float {
+ Vector2i pos = Vector2i(Math::floor(uv2.x), Math::floor(uv2.y)) + Vector2i(region_map_size / 2, region_map_size / 2);
+ int layer_index = 0;
+ if ((uint32_t)(pos.x | pos.y) < (uint32_t)region_map_size) {
+ int v = region_map[pos.y * region_map_size + pos.x];
+ layer_index = Math::clamp(v - 1, -1, 0) + 1;
+ }
+ return real_t(layer_index);
+ };
+
+ auto get_region_blend = [&](Vector2 uv2) -> float {
+ // Floating point bias (must match shader)
+ uv2 -= Vector2(0.5011f, 0.5011f);
+
+ float a = check_region(uv2 + Vector2(0.0f, 1.0f));
+ float b = check_region(uv2 + Vector2(1.0f, 1.0f));
+ float c = check_region(uv2 + Vector2(1.0f, 0.0f));
+ float d = check_region(uv2 + Vector2(0.0f, 0.0f));
+
+ real_t blend_factor = 2.0f + 126.0f * (1.0f - region_blend);
+ Vector2 f = Vector2(uv2.x - Math::floor(uv2.x), uv2.y - Math::floor(uv2.y));
+ f.x = Math::clamp(f.x, 1e-8f, 1.f - 1e-8f);
+ f.y = Math::clamp(f.y, 1e-8f, 1.f - 1e-8f);
+ Vector2 w = Vector2(1.f / (1.f + Math::exp(blend_factor * Math::log((1.f - f.x) / f.x))),
+ 1.f / (1.f + Math::exp(blend_factor * Math::log((1.f - f.y) / f.y))));
+ float blend = Math::lerp(Math::lerp(d, c, w.x), Math::lerp(a, b, w.x), w.y);
+
+ return (1.f - blend) * 2.f;
+ };
int hshape_size = p_size + 1; // Calculate last vertex at end
PackedRealArray map_data = PackedRealArray();
map_data.resize(hshape_size * hshape_size);
real_t min_height = FLT_MAX;
- real_t max_height = FLT_MIN;
+ real_t max_height = -FLT_MAX;
Ref map, map_x, map_z, map_xz; // height maps
Ref cmap, cmap_x, cmap_z, cmap_xz; // control maps w/ holes
@@ -77,27 +119,19 @@ Dictionary Terrain3DCollision::_get_shape_data(const Vector2i &p_position, const
bool next_z = shape_region_loc.y > region_loc.y;
// Set heights on local map, or adjacent maps if on the last row/col
- real_t height = 0.f;
+ real_t height = NAN;
if (!next_x && !next_z && map.is_valid()) {
- height = is_hole(cmap->get_pixel(img_x, img_y).r) ? NAN : map->get_pixel(img_x, img_y).r;
- } else if (next_x && !next_z) {
- if (map_x.is_valid()) {
- height = is_hole(cmap_x->get_pixel(img_x, img_y).r) ? NAN : map_x->get_pixel(img_x, img_y).r;
- } else {
- height = is_hole(cmap->get_pixel(region_size - 1, img_y).r) ? NAN : map->get_pixel(region_size - 1, img_y).r;
- }
- } else if (!next_x && next_z) {
- if (map_z.is_valid()) {
- height = is_hole(cmap_z->get_pixel(img_x, img_y).r) ? NAN : map_z->get_pixel(img_x, img_y).r;
- } else {
- height = (is_hole(cmap->get_pixel(img_x, region_size - 1).r)) ? NAN : map->get_pixel(img_x, region_size - 1).r;
- }
- } else if (next_x && next_z) {
- if (map_xz.is_valid()) {
- height = is_hole(cmap_xz->get_pixel(img_x, img_y).r) ? NAN : map_xz->get_pixel(img_x, img_y).r;
- } else {
- height = (is_hole(cmap->get_pixel(region_size - 1, region_size - 1).r)) ? NAN : map->get_pixel(region_size - 1, region_size - 1).r;
- }
+ height = is_hole(cmap->get_pixel(img_x, img_y).r) ? NAN : map->get_pixel(img_x, img_y).r;
+ } else if (next_x && !next_z && map_x.is_valid()) {
+ height = is_hole(cmap_x->get_pixel(img_x, img_y).r) ? NAN : map_x->get_pixel(img_x, img_y).r;
+ } else if (!next_x && next_z && map_z.is_valid()) {
+ height = is_hole(cmap_z->get_pixel(img_x, img_y).r) ? NAN : map_z->get_pixel(img_x, img_y).r;
+ } else if (next_x && next_z && map_xz.is_valid()) {
+ height = is_hole(cmap_xz->get_pixel(img_x, img_y).r) ? NAN : map_xz->get_pixel(img_x, img_y).r;
+ }
+ if (!std::isnan(height) && is_bg_flat_or_noise) {
+ Vector2 uv2 = Vector2(shape_pos) * region_texel_size;
+ height = Math::lerp(height, ground_level, smoothstep(0.f, 1.f, get_region_blend(uv2)));
}
map_data[index] = height;
if (!std::isnan(height)) {
diff --git a/src/terrain_3d_data.cpp b/src/terrain_3d_data.cpp
index fff7bc80b..770e7dc31 100644
--- a/src/terrain_3d_data.cpp
+++ b/src/terrain_3d_data.cpp
@@ -753,7 +753,7 @@ Vector3 Terrain3DData::get_texture_id(const Vector3 &p_global_position) const {
// If material available, autoshader enabled, and pixel set to auto
if (_terrain) {
Ref t_material = _terrain->get_material();
- bool auto_enabled = t_material->get_auto_shader();
+ bool auto_enabled = t_material->get_auto_shader_enabled();
bool control_auto = is_auto(src);
if (auto_enabled && control_auto) {
real_t auto_slope = real_t(t_material->get_shader_param("auto_slope"));
diff --git a/src/terrain_3d_editor.cpp b/src/terrain_3d_editor.cpp
index 4abf0e281..db9922bcf 100644
--- a/src/terrain_3d_editor.cpp
+++ b/src/terrain_3d_editor.cpp
@@ -898,8 +898,8 @@ void Terrain3DEditor::set_brush_data(const Dictionary &p_data) {
void Terrain3DEditor::set_tool(const Tool p_tool) {
Tool old_tool = _tool;
SET_IF_DIFF(_tool, CLAMP(p_tool, Tool(0), TOOL_MAX));
- if (_terrain && (_tool == Tool::NAVIGATION || old_tool == Tool::NAVIGATION)) {
- _terrain->get_material()->update(true);
+ if (_terrain && (_tool == Tool::NAVIGATION || old_tool == Tool::NAVIGATION || _tool == Tool::REGION || old_tool == Tool::REGION)) {
+ _terrain->get_material()->update(Terrain3DMaterial::FULL_REBUILD);
}
}
diff --git a/src/terrain_3d_instancer.cpp b/src/terrain_3d_instancer.cpp
index bad01e769..ddfe8cd2a 100644
--- a/src/terrain_3d_instancer.cpp
+++ b/src/terrain_3d_instancer.cpp
@@ -519,7 +519,7 @@ Array Terrain3DInstancer::_get_usable_height(const Vector3 &p_global_position, c
real_t height = data->get_height(p_global_position);
Dictionary raycast_result;
bool raycast_hit = false;
- real_t raycast_height = FLT_MIN;
+ real_t raycast_height = -FLT_MAX;
Vector3 raycast_normal = V3_UP;
// Raycast physics if using on_collision
if (p_on_collision) {
diff --git a/src/terrain_3d_material.cpp b/src/terrain_3d_material.cpp
index e16b0697e..335ba4065 100644
--- a/src/terrain_3d_material.cpp
+++ b/src/terrain_3d_material.cpp
@@ -39,6 +39,12 @@ void Terrain3DMaterial::_preload_shaders() {
#include "shaders/displacement.glsl"
, "displacement");
_parse_shader(
+#include "shaders/macro_variation.glsl"
+ , "macro_variation");
+ _parse_shader(
+#include "shaders/projection.glsl"
+ , "projection");
+ _parse_shader(
#include "shaders/debug_views.glsl"
, "debug_views");
_parse_shader(
@@ -133,7 +139,11 @@ String Terrain3DMaterial::_apply_inserts(const String &p_shader, const Array &p_
String Terrain3DMaterial::_generate_shader_code() const {
LOG(INFO, "Generating default shader code");
Array excludes;
- if (_world_background != FLAT) {
+ if (_world_background != NONE) {
+ excludes.push_back("NONE_FUNCTIONS");
+ excludes.push_back("NONE_CHECK");
+ }
+ if (_world_background == NONE) {
excludes.push_back("FLAT_UNIFORMS");
excludes.push_back("FLAT_FUNCTIONS");
excludes.push_back("FLAT_VERTEX");
@@ -147,28 +157,46 @@ String Terrain3DMaterial::_generate_shader_code() const {
}
if (_texture_filtering == LINEAR) {
excludes.push_back("TEXTURE_SAMPLERS_NEAREST");
- excludes.push_back("NOISE_SAMPLER_NEAREST");
} else {
excludes.push_back("TEXTURE_SAMPLERS_LINEAR");
- excludes.push_back("NOISE_SAMPLER_LINEAR");
}
- if (!_auto_shader) {
+ if (!_auto_shader_enabled) {
excludes.push_back("AUTO_SHADER_UNIFORMS");
excludes.push_back("AUTO_SHADER");
}
- if (!_dual_scaling) {
+ if (!_dual_scaling_enabled) {
excludes.push_back("DUAL_SCALING_UNIFORMS");
excludes.push_back("DUAL_SCALING");
excludes.push_back("DUAL_SCALING_CONDITION_0");
excludes.push_back("DUAL_SCALING_CONDITION_1");
excludes.push_back("DUAL_SCALING_MIX");
- excludes.push_back("TRI_SCALING");
+ }
+ if (!_macro_variation_enabled) {
+ excludes.push_back("MACRO_VARIATION_UNIFORMS");
+ excludes.push_back("MACRO_VARIATION");
+ }
+ if (!_projection_enabled) {
+ excludes.push_back("PROJECTION");
}
if (_terrain->get_tessellation_level() == 0) {
excludes.push_back("DISPLACEMENT_UNIFORMS");
excludes.push_back("DISPLACEMENT_FUNCTIONS");
excludes.push_back("DISPLACEMENT_VERTEX");
}
+ if (!_output_albedo_enabled) {
+ excludes.push_back("OUTPUT_ALBEDO");
+ } else {
+ excludes.push_back("OUTPUT_ALBEDO_GREY");
+ }
+ if (!_output_roughness_enabled) {
+ excludes.push_back("OUTPUT_ROUGHNESS");
+ }
+ if (!_output_normal_map_enabled) {
+ excludes.push_back("OUTPUT_NORMAL_MAP");
+ }
+ if (!_output_ambient_occlusion_enabled) {
+ excludes.push_back("OUTPUT_AMBIENT_OCCLUSION");
+ }
String shader = _apply_inserts(_shader_code["main"], excludes);
return shader;
}
@@ -263,20 +291,30 @@ String Terrain3DMaterial::_strip_comments(const String &p_shader) const {
return vector_to_string(stripped);
}
-String Terrain3DMaterial::_generate_buffer_shader_code() {
+String Terrain3DMaterial::_generate_buffer_shader_code() const {
LOG(INFO, "Generating default displacement buffer shader code");
Array excludes;
+ if (_world_background != NONE) {
+ excludes.push_back("NONE_FUNCTIONS");
+ excludes.push_back("NONE_CHECK");
+ }
+ if (_world_background == NONE) {
+ excludes.push_back("FLAT_UNIFORMS");
+ excludes.push_back("FLAT_FUNCTIONS");
+ excludes.push_back("FLAT_FRAGMENT");
+ }
if (_texture_filtering == LINEAR) {
excludes.push_back("TEXTURE_SAMPLERS_NEAREST");
- excludes.push_back("NOISE_SAMPLER_NEAREST");
} else {
excludes.push_back("TEXTURE_SAMPLERS_LINEAR");
- excludes.push_back("NOISE_SAMPLER_LINEAR");
}
- if (!_auto_shader) {
+ if (!_auto_shader_enabled) {
excludes.push_back("AUTO_SHADER_UNIFORMS");
excludes.push_back("AUTO_SHADER");
}
+ if (!_projection_enabled) {
+ excludes.push_back("PROJECTION");
+ }
String shader = _apply_inserts(_shader_code["displacement_buffer"], excludes);
return shader;
}
@@ -412,9 +450,6 @@ String Terrain3DMaterial::_inject_editor_code(const String &p_shader) const {
if (_show_contours) {
insert_names.push_back("OVERLAY_CONTOURS_RENDER");
}
- if (_show_region_grid) {
- insert_names.push_back("OVERLAY_REGION_GRID");
- }
if (_show_instancer_grid) {
insert_names.push_back("OVERLAY_INSTANCER_GRID");
}
@@ -425,6 +460,9 @@ String Terrain3DMaterial::_inject_editor_code(const String &p_shader) const {
if (_show_navigation || (_terrain && _terrain->get_editor() && _terrain->get_editor()->get_tool() == Terrain3DEditor::NAVIGATION)) {
insert_names.push_back("EDITOR_NAVIGATION");
}
+ if (_show_region_grid || (_terrain && _terrain->get_editor() && _terrain->get_editor()->get_tool() == Terrain3DEditor::REGION)) {
+ insert_names.push_back("EDITOR_REGION_GRID");
+ }
if (_terrain && _terrain->get_editor()) {
insert_names.push_back("EDITOR_DECAL_RENDER");
}
@@ -552,7 +590,7 @@ void Terrain3DMaterial::_update_shader() {
notify_property_list_changed();
}
-void Terrain3DMaterial::_update_uniforms(const RID &p_material) {
+void Terrain3DMaterial::_update_uniforms(const RID &p_material, const uint32_t p_flags) {
IS_DATA_INIT(VOID);
LOG(EXTREME, "Updating uniforms in shader");
@@ -582,13 +620,14 @@ void Terrain3DMaterial::_update_uniforms(const RID &p_material) {
LOG(EXTREME, "Setting region size in material: ", region_size);
RS->material_set_param(p_material, "_region_size", region_size);
RS->material_set_param(p_material, "_region_texel_size", 1.0f / region_size);
-
- RS->material_set_param(p_material, "_height_maps", data->get_height_maps_rid());
- RS->material_set_param(p_material, "_control_maps", data->get_control_maps_rid());
- RS->material_set_param(p_material, "_color_maps", data->get_color_maps_rid());
- LOG(EXTREME, "Height map RID: ", data->get_height_maps_rid());
- LOG(EXTREME, "Control map RID: ", data->get_control_maps_rid());
- LOG(EXTREME, "Color map RID: ", data->get_color_maps_rid());
+ if (p_flags & REGION_ARRAYS) {
+ RS->material_set_param(p_material, "_height_maps", data->get_height_maps_rid());
+ RS->material_set_param(p_material, "_control_maps", data->get_control_maps_rid());
+ RS->material_set_param(p_material, "_color_maps", data->get_color_maps_rid());
+ LOG(EXTREME, "Height map RID: ", data->get_height_maps_rid());
+ LOG(EXTREME, "Control map RID: ", data->get_control_maps_rid());
+ LOG(EXTREME, "Color map RID: ", data->get_color_maps_rid());
+ }
real_t spacing = _terrain->get_vertex_spacing();
LOG(EXTREME, "Setting vertex spacing in material: ", spacing);
@@ -612,15 +651,16 @@ void Terrain3DMaterial::_update_uniforms(const RID &p_material) {
return;
}
- RS->material_set_param(p_material, "_texture_array_albedo", asset_list->get_albedo_array_rid());
- RS->material_set_param(p_material, "_texture_array_normal", asset_list->get_normal_array_rid());
+ if (p_flags & TEXTURE_ARRAYS) {
+ RS->material_set_param(p_material, "_texture_array_albedo", asset_list->get_albedo_array_rid());
+ RS->material_set_param(p_material, "_texture_array_normal", asset_list->get_normal_array_rid());
+ }
RS->material_set_param(p_material, "_texture_color_array", asset_list->get_texture_colors());
RS->material_set_param(p_material, "_texture_normal_depth_array", asset_list->get_texture_normal_depths());
RS->material_set_param(p_material, "_texture_ao_strength_array", asset_list->get_texture_ao_strengths());
RS->material_set_param(p_material, "_texture_ao_affect_array", asset_list->get_texture_ao_light_affects());
RS->material_set_param(p_material, "_texture_roughness_mod_array", asset_list->get_texture_roughness_mods());
RS->material_set_param(p_material, "_texture_uv_scale_array", asset_list->get_texture_uv_scales());
- RS->material_set_param(p_material, "_texture_vertical_projections", asset_list->get_texture_vertical_projections());
RS->material_set_param(p_material, "_texture_detile_array", asset_list->get_texture_detiles());
RS->material_set_param(p_material, "_texture_displacement_array", asset_list->get_texture_displacements());
@@ -665,7 +705,7 @@ void Terrain3DMaterial::initialize(Terrain3D *p_terrain) {
}
_shader.instantiate();
_buffer_shader.instantiate();
- update(true);
+ update(FULL_REBUILD);
}
void Terrain3DMaterial::uninitialize() {
@@ -691,14 +731,14 @@ void Terrain3DMaterial::destroy() {
}
}
-void Terrain3DMaterial::update(bool p_full) {
- if (p_full) {
+void Terrain3DMaterial::update(uint32_t p_flags) {
+ if (p_flags & FULL_REBUILD) {
_update_shader();
}
- _update_uniforms(_material);
+ _update_uniforms(_material, p_flags);
IS_INIT(VOID);
if (_terrain->get_tessellation_level() > 0) {
- _update_uniforms(_buffer_material);
+ _update_uniforms(_buffer_material, p_flags);
// Snap to update buffer
_terrain->snap();
}
@@ -731,18 +771,30 @@ void Terrain3DMaterial::set_texture_filtering(const TextureFiltering p_filtering
_update_shader();
}
-void Terrain3DMaterial::set_auto_shader(const bool p_enabled) {
- SET_IF_DIFF(_auto_shader, p_enabled);
+void Terrain3DMaterial::set_auto_shader_enabled(const bool p_enabled) {
+ SET_IF_DIFF(_auto_shader_enabled, p_enabled);
LOG(INFO, "Enable auto shader: ", p_enabled);
_update_shader();
}
-void Terrain3DMaterial::set_dual_scaling(const bool p_enabled) {
- SET_IF_DIFF(_dual_scaling, p_enabled);
+void Terrain3DMaterial::set_dual_scaling_enabled(const bool p_enabled) {
+ SET_IF_DIFF(_dual_scaling_enabled, p_enabled);
LOG(INFO, "Enable dual scaling: ", p_enabled);
_update_shader();
}
+void Terrain3DMaterial::set_macro_variation_enabled(const bool p_enabled) {
+ SET_IF_DIFF(_macro_variation_enabled, p_enabled);
+ LOG(INFO, "Enable macro variation: ", p_enabled);
+ _update_shader();
+}
+
+void Terrain3DMaterial::set_projection_enabled(const bool p_enabled) {
+ SET_IF_DIFF(_projection_enabled, p_enabled);
+ LOG(INFO, "Enable projection: ", p_enabled);
+ _update_shader();
+}
+
void Terrain3DMaterial::set_shader_override_enabled(const bool p_enabled) {
SET_IF_DIFF(_shader_override_enabled, p_enabled);
LOG(INFO, "Enable shader override: ", p_enabled);
@@ -787,6 +839,30 @@ Variant Terrain3DMaterial::get_shader_param(const StringName &p_name) const {
return value;
}
+void Terrain3DMaterial::set_output_albedo_enabled(const bool p_enabled) {
+ SET_IF_DIFF(_output_albedo_enabled, p_enabled);
+ LOG(INFO, "Enable PBR output albedo: ", p_enabled);
+ _update_shader();
+}
+
+void Terrain3DMaterial::set_output_roughness_enabled(const bool p_enabled) {
+ SET_IF_DIFF(_output_roughness_enabled, p_enabled);
+ LOG(INFO, "Enable PBR output roughness: ", p_enabled);
+ _update_shader();
+}
+
+void Terrain3DMaterial::set_output_normal_map_enabled(const bool p_enabled) {
+ SET_IF_DIFF(_output_normal_map_enabled, p_enabled);
+ LOG(INFO, "Enable PBR output normal map: ", p_enabled);
+ _update_shader();
+}
+
+void Terrain3DMaterial::set_output_ambient_occlusion_enabled(const bool p_enabled) {
+ SET_IF_DIFF(_output_ambient_occlusion_enabled, p_enabled);
+ LOG(INFO, "Enable PBR output ambient occlusion: ", p_enabled);
+ _update_shader();
+}
+
void Terrain3DMaterial::set_show_region_grid(const bool p_enabled) {
SET_IF_DIFF(_show_region_grid, p_enabled);
LOG(INFO, "Enable show_region_grid: ", p_enabled);
@@ -1006,7 +1082,7 @@ void Terrain3DMaterial::_get_property_list(List *p_list) const {
_active_params.clear();
Dictionary grouped_params;
- StringName current_group = StringName("General");
+ StringName current_group = StringName("shader_uniforms.general");
grouped_params[current_group] = Array();
for (int i = 0; i < param_list.size(); i++) {
Dictionary dict = param_list[i];
@@ -1014,7 +1090,7 @@ void Terrain3DMaterial::_get_property_list(List *p_list) const {
// An empty name indicates a group being closed, reset to the "general" group.
if (name.is_empty() && i < buffer_param) {
- current_group = StringName("General");
+ current_group = StringName("shader_uniforms.general");
}
// Filter out private uniforms that start with _ and nulls
@@ -1025,7 +1101,7 @@ void Terrain3DMaterial::_get_property_list(List *p_list) const {
dict["name"] = split_name[MAX(split_name.size() - 1, 0)].capitalize();
// Ensure sub groups are batched with their parent group
current_group = split_name[0].capitalize();
- dict["usage"] = (name.contains("::") ? PROPERTY_USAGE_SUBGROUP : PROPERTY_USAGE_GROUP) | PROPERTY_USAGE_EDITOR;
+ dict["usage"] = name.contains("::") ? PROPERTY_USAGE_SUBGROUP : PROPERTY_USAGE_GROUP;
} else {
// Filter out duplicate non-groups entries from displacement buffer shader
if (_active_params.has(name)) {
@@ -1152,6 +1228,11 @@ void Terrain3DMaterial::_bind_methods() {
BIND_ENUM_CONSTANT(NOISE);
BIND_ENUM_CONSTANT(LINEAR);
BIND_ENUM_CONSTANT(NEAREST);
+ BIND_ENUM_CONSTANT(UNIFORMS_ONLY);
+ BIND_ENUM_CONSTANT(TEXTURE_ARRAYS);
+ BIND_ENUM_CONSTANT(REGION_ARRAYS);
+ BIND_ENUM_CONSTANT(UPDATE_ARRAYS);
+ BIND_ENUM_CONSTANT(FULL_REBUILD);
// Private
ClassDB::bind_method(D_METHOD("_set_shader_parameters", "dict"), &Terrain3DMaterial::_set_shader_parameters);
@@ -1159,7 +1240,7 @@ void Terrain3DMaterial::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "_shader_parameters", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE), "_set_shader_parameters", "_get_shader_parameters");
// Public
- ClassDB::bind_method(D_METHOD("update", "full"), &Terrain3DMaterial::update, DEFVAL(false));
+ ClassDB::bind_method(D_METHOD("update", "flags"), &Terrain3DMaterial::update, DEFVAL(Terrain3DMaterial::UNIFORMS_ONLY));
ClassDB::bind_method(D_METHOD("get_material_rid"), &Terrain3DMaterial::get_material_rid);
ClassDB::bind_method(D_METHOD("get_shader_rid"), &Terrain3DMaterial::get_shader_rid);
ClassDB::bind_method(D_METHOD("get_buffer_material_rid"), &Terrain3DMaterial::get_buffer_material_rid);
@@ -1169,10 +1250,14 @@ void Terrain3DMaterial::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_world_background"), &Terrain3DMaterial::get_world_background);
ClassDB::bind_method(D_METHOD("set_texture_filtering", "filtering"), &Terrain3DMaterial::set_texture_filtering);
ClassDB::bind_method(D_METHOD("get_texture_filtering"), &Terrain3DMaterial::get_texture_filtering);
- ClassDB::bind_method(D_METHOD("set_auto_shader", "enabled"), &Terrain3DMaterial::set_auto_shader);
- ClassDB::bind_method(D_METHOD("get_auto_shader"), &Terrain3DMaterial::get_auto_shader);
- ClassDB::bind_method(D_METHOD("set_dual_scaling", "enabled"), &Terrain3DMaterial::set_dual_scaling);
- ClassDB::bind_method(D_METHOD("get_dual_scaling"), &Terrain3DMaterial::get_dual_scaling);
+ ClassDB::bind_method(D_METHOD("set_auto_shader_enabled", "enabled"), &Terrain3DMaterial::set_auto_shader_enabled);
+ ClassDB::bind_method(D_METHOD("get_auto_shader_enabled"), &Terrain3DMaterial::get_auto_shader_enabled);
+ ClassDB::bind_method(D_METHOD("set_dual_scaling_enabled", "enabled"), &Terrain3DMaterial::set_dual_scaling_enabled);
+ ClassDB::bind_method(D_METHOD("get_dual_scaling_enabled"), &Terrain3DMaterial::get_dual_scaling_enabled);
+ ClassDB::bind_method(D_METHOD("set_macro_variation_enabled", "enabled"), &Terrain3DMaterial::set_macro_variation_enabled);
+ ClassDB::bind_method(D_METHOD("get_macro_variation_enabled"), &Terrain3DMaterial::get_macro_variation_enabled);
+ ClassDB::bind_method(D_METHOD("set_projection_enabled", "enabled"), &Terrain3DMaterial::set_projection_enabled);
+ ClassDB::bind_method(D_METHOD("get_projection_enabled"), &Terrain3DMaterial::get_projection_enabled);
ClassDB::bind_method(D_METHOD("set_shader_override_enabled", "enabled"), &Terrain3DMaterial::set_shader_override_enabled);
ClassDB::bind_method(D_METHOD("is_shader_override_enabled"), &Terrain3DMaterial::is_shader_override_enabled);
@@ -1191,6 +1276,16 @@ void Terrain3DMaterial::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_shader_param", "name", "value"), &Terrain3DMaterial::set_shader_param);
ClassDB::bind_method(D_METHOD("get_shader_param", "name"), &Terrain3DMaterial::get_shader_param);
+ // PBR output
+ ClassDB::bind_method(D_METHOD("set_output_albedo_enabled", "enabled"), &Terrain3DMaterial::set_output_albedo_enabled);
+ ClassDB::bind_method(D_METHOD("get_output_albedo_enabled"), &Terrain3DMaterial::get_output_albedo_enabled);
+ ClassDB::bind_method(D_METHOD("set_output_roughness_enabled", "enabled"), &Terrain3DMaterial::set_output_roughness_enabled);
+ ClassDB::bind_method(D_METHOD("get_output_roughness_enabled"), &Terrain3DMaterial::get_output_roughness_enabled);
+ ClassDB::bind_method(D_METHOD("set_output_normal_map_enabled", "enabled"), &Terrain3DMaterial::set_output_normal_map_enabled);
+ ClassDB::bind_method(D_METHOD("get_output_normal_map_enabled"), &Terrain3DMaterial::get_output_normal_map_enabled);
+ ClassDB::bind_method(D_METHOD("set_output_ambient_occlusion_enabled", "enabled"), &Terrain3DMaterial::set_output_ambient_occlusion_enabled);
+ ClassDB::bind_method(D_METHOD("get_output_ambient_occlusion_enabled"), &Terrain3DMaterial::get_output_ambient_occlusion_enabled);
+
// Overlays
ClassDB::bind_method(D_METHOD("set_show_region_grid", "enabled"), &Terrain3DMaterial::set_show_region_grid);
ClassDB::bind_method(D_METHOD("get_show_region_grid"), &Terrain3DMaterial::get_show_region_grid);
@@ -1246,8 +1341,18 @@ void Terrain3DMaterial::_bind_methods() {
// These must be different from the names of uniform groups
ADD_PROPERTY(PropertyInfo(Variant::INT, "world_background", PROPERTY_HINT_ENUM, "None,Flat,Noise"), "set_world_background", "get_world_background");
ADD_PROPERTY(PropertyInfo(Variant::INT, "texture_filtering", PROPERTY_HINT_ENUM, "Linear,Nearest"), "set_texture_filtering", "get_texture_filtering");
- ADD_PROPERTY(PropertyInfo(Variant::BOOL, "auto_shader_enabled"), "set_auto_shader", "get_auto_shader");
- ADD_PROPERTY(PropertyInfo(Variant::BOOL, "dual_scaling_enabled"), "set_dual_scaling", "get_dual_scaling");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "auto_shader_enabled"), "set_auto_shader_enabled", "get_auto_shader_enabled");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "dual_scaling_enabled"), "set_dual_scaling_enabled", "get_dual_scaling_enabled");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "macro_variation_enabled"), "set_macro_variation_enabled", "get_macro_variation_enabled");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "projection_enabled"), "set_projection_enabled", "get_projection_enabled");
+
+ ADD_GROUP("PBR Output", "output_");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "output_albedo"), "set_output_albedo_enabled", "get_output_albedo_enabled");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "output_roughness"), "set_output_roughness_enabled", "get_output_roughness_enabled");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "output_normal_map"), "set_output_normal_map_enabled", "get_output_normal_map_enabled");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "output_ambient_occlusion"), "set_output_ambient_occlusion_enabled", "get_output_ambient_occlusion_enabled");
+
+ ADD_GROUP("Custom Shader", "");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "shader_override_enabled"), "set_shader_override_enabled", "is_shader_override_enabled");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "shader_override", PROPERTY_HINT_RESOURCE_TYPE, "Shader"), "set_shader_override", "get_shader_override");
diff --git a/src/terrain_3d_material.h b/src/terrain_3d_material.h
index 3ea4d824b..5a5f3fd21 100644
--- a/src/terrain_3d_material.h
+++ b/src/terrain_3d_material.h
@@ -26,6 +26,14 @@ class Terrain3DMaterial : public Resource {
NEAREST,
};
+ enum UpdateFlags {
+ UNIFORMS_ONLY = 0,
+ TEXTURE_ARRAYS = 1 << 0,
+ REGION_ARRAYS = 1 << 1,
+ UPDATE_ARRAYS = TEXTURE_ARRAYS | REGION_ARRAYS,
+ FULL_REBUILD = (1 << 2) | UPDATE_ARRAYS,
+ };
+
private:
Terrain3D *_terrain = nullptr;
@@ -47,8 +55,16 @@ class Terrain3DMaterial : public Resource {
// Material Features
WorldBackground _world_background = FLAT;
TextureFiltering _texture_filtering = LINEAR;
- bool _dual_scaling = false;
- bool _auto_shader = false;
+ bool _dual_scaling_enabled = false;
+ bool _auto_shader_enabled = false;
+ bool _macro_variation_enabled = false;
+ bool _projection_enabled = false;
+
+ // PBR Outputs
+ bool _output_albedo_enabled = true;
+ bool _output_roughness_enabled = true;
+ bool _output_normal_map_enabled = true;
+ bool _output_ambient_occlusion_enabled = true;
// Overlays
bool _show_region_grid = false;
@@ -84,11 +100,11 @@ class Terrain3DMaterial : public Resource {
void _parse_shader(const String &p_shader, const String &p_name);
String _apply_inserts(const String &p_shader, const Array &p_excludes = Array()) const;
String _generate_shader_code() const;
- String _generate_buffer_shader_code();
+ String _generate_buffer_shader_code() const;
String _strip_comments(const String &p_shader) const;
String _inject_editor_code(const String &p_shader) const;
void _update_shader();
- void _update_uniforms(const RID &p_material);
+ void _update_uniforms(const RID &p_material, const uint32_t p_update = UNIFORMS_ONLY);
void _set_shader_parameters(const Dictionary &p_dict);
Dictionary _get_shader_parameters() const { return _shader_params; }
@@ -100,7 +116,7 @@ class Terrain3DMaterial : public Resource {
void uninitialize();
void destroy();
- void update(bool p_full = false);
+ void update(const uint32_t p_flags = UNIFORMS_ONLY);
RID get_material_rid() const { return _material; }
RID get_shader_rid() const { return _shader.is_valid() ? _shader->get_rid() : RID(); }
@@ -116,10 +132,14 @@ class Terrain3DMaterial : public Resource {
WorldBackground get_world_background() const { return _world_background; }
void set_texture_filtering(const TextureFiltering p_filtering);
TextureFiltering get_texture_filtering() const { return _texture_filtering; }
- void set_auto_shader(const bool p_enabled);
- bool get_auto_shader() const { return _auto_shader; }
- void set_dual_scaling(const bool p_enabled);
- bool get_dual_scaling() const { return _dual_scaling; }
+ void set_auto_shader_enabled(const bool p_enabled);
+ bool get_auto_shader_enabled() const { return _auto_shader_enabled; }
+ void set_dual_scaling_enabled(const bool p_enabled);
+ bool get_dual_scaling_enabled() const { return _dual_scaling_enabled; }
+ void set_macro_variation_enabled(const bool p_enabled);
+ bool get_macro_variation_enabled() const { return _macro_variation_enabled; }
+ void set_projection_enabled(const bool p_enabled);
+ bool get_projection_enabled() const { return _projection_enabled; }
void set_shader_override_enabled(const bool p_enabled);
bool is_shader_override_enabled() const { return _shader_override_enabled; }
@@ -134,6 +154,19 @@ class Terrain3DMaterial : public Resource {
void set_shader_param(const StringName &p_name, const Variant &p_value);
Variant get_shader_param(const StringName &p_name) const;
+ // PBR outputs
+ void set_output_albedo_enabled(const bool p_enabled);
+ bool get_output_albedo_enabled() const { return _output_albedo_enabled; }
+
+ void set_output_roughness_enabled(const bool p_enabled);
+ bool get_output_roughness_enabled() const { return _output_roughness_enabled; }
+
+ void set_output_normal_map_enabled(const bool p_enabled);
+ bool get_output_normal_map_enabled() const { return _output_normal_map_enabled; }
+
+ void set_output_ambient_occlusion_enabled(const bool p_enabled);
+ bool get_output_ambient_occlusion_enabled() const { return _output_ambient_occlusion_enabled; }
+
// Overlays
void set_show_region_grid(const bool p_enabled);
bool get_show_region_grid() const { return _show_region_grid; }
@@ -198,5 +231,6 @@ class Terrain3DMaterial : public Resource {
VARIANT_ENUM_CAST(Terrain3DMaterial::WorldBackground);
VARIANT_ENUM_CAST(Terrain3DMaterial::TextureFiltering);
+VARIANT_ENUM_CAST(Terrain3DMaterial::UpdateFlags);
#endif // TERRAIN3D_MATERIAL_CLASS_H
\ No newline at end of file
diff --git a/src/terrain_3d_texture_asset.cpp b/src/terrain_3d_texture_asset.cpp
index 6044cc9eb..39b170c7a 100644
--- a/src/terrain_3d_texture_asset.cpp
+++ b/src/terrain_3d_texture_asset.cpp
@@ -55,7 +55,6 @@ void Terrain3DTextureAsset::clear() {
_displacement_scale = 0.f;
_displacement_offset = 0.f;
_uv_scale = 0.1f;
- _vertical_projection = false;
_detiling_rotation = 0.0f;
_detiling_shift = 0.0f;
}
@@ -200,13 +199,6 @@ void Terrain3DTextureAsset::set_uv_scale(const real_t p_scale) {
emit_signal("setting_changed");
}
-void Terrain3DTextureAsset::set_vertical_projection(const bool p_projection) {
- SET_IF_DIFF(_vertical_projection, p_projection);
- LOG(INFO, "Setting uv projection: ", _vertical_projection);
- LOG(DEBUG, "Emitting setting_changed");
- emit_signal("setting_changed");
-}
-
void Terrain3DTextureAsset::set_detiling_rotation(const real_t p_detiling_rotation) {
SET_IF_DIFF(_detiling_rotation, CLAMP(p_detiling_rotation, 0.0f, 1.0f));
LOG(INFO, "Setting detiling_rotation: ", _detiling_rotation);
@@ -259,8 +251,6 @@ void Terrain3DTextureAsset::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_displacement_offset"), &Terrain3DTextureAsset::get_displacement_offset);
ClassDB::bind_method(D_METHOD("set_uv_scale", "scale"), &Terrain3DTextureAsset::set_uv_scale);
ClassDB::bind_method(D_METHOD("get_uv_scale"), &Terrain3DTextureAsset::get_uv_scale);
- ClassDB::bind_method(D_METHOD("set_vertical_projection", "projection"), &Terrain3DTextureAsset::set_vertical_projection);
- ClassDB::bind_method(D_METHOD("get_vertical_projection"), &Terrain3DTextureAsset::get_vertical_projection);
ClassDB::bind_method(D_METHOD("set_detiling_rotation", "detiling_rotation"), &Terrain3DTextureAsset::set_detiling_rotation);
ClassDB::bind_method(D_METHOD("get_detiling_rotation"), &Terrain3DTextureAsset::get_detiling_rotation);
ClassDB::bind_method(D_METHOD("set_detiling_shift", "detiling_shift"), &Terrain3DTextureAsset::set_detiling_shift);
@@ -278,8 +268,7 @@ void Terrain3DTextureAsset::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "displacement_scale", PROPERTY_HINT_RANGE, "0.0, 2.0"), "set_displacement_scale", "get_displacement_scale");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "displacement_offset", PROPERTY_HINT_RANGE, "-1.0, 1.0"), "set_displacement_offset", "get_displacement_offset");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "uv_scale", PROPERTY_HINT_RANGE, "0.001, 2.0, or_greater"), "set_uv_scale", "get_uv_scale");
- ADD_GROUP("Projection", "");
- ADD_PROPERTY(PropertyInfo(Variant::BOOL, "vertical_projection", PROPERTY_HINT_NONE), "set_vertical_projection", "get_vertical_projection");
+ ADD_GROUP("Detiling", "");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "detiling_rotation", PROPERTY_HINT_RANGE, "0.0, 1.0"), "set_detiling_rotation", "get_detiling_rotation");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "detiling_shift", PROPERTY_HINT_RANGE, "0.0, 1.0"), "set_detiling_shift", "get_detiling_shift");
}
diff --git a/src/terrain_3d_texture_asset.h b/src/terrain_3d_texture_asset.h
index 3db29724d..90b3265e6 100644
--- a/src/terrain_3d_texture_asset.h
+++ b/src/terrain_3d_texture_asset.h
@@ -24,7 +24,6 @@ class Terrain3DTextureAsset : public Terrain3DAssetResource {
real_t _displacement_scale = 0.0f;
real_t _displacement_offset = 0.0f;
real_t _uv_scale = 0.1f;
- bool _vertical_projection = false;
real_t _detiling_rotation = 0.0f;
real_t _detiling_shift = 0.0f;
@@ -81,9 +80,6 @@ class Terrain3DTextureAsset : public Terrain3DAssetResource {
void set_uv_scale(const real_t p_scale);
real_t get_uv_scale() const { return _uv_scale; }
- void set_vertical_projection(const bool p_projection);
- bool get_vertical_projection() const { return _vertical_projection; }
-
void set_detiling_rotation(const real_t p_detiling_rotation);
real_t get_detiling_rotation() const { return _detiling_rotation; }
diff --git a/src/terrain_3d_util.cpp b/src/terrain_3d_util.cpp
index 1345b25e9..6c4a429ec 100644
--- a/src/terrain_3d_util.cpp
+++ b/src/terrain_3d_util.cpp
@@ -161,7 +161,7 @@ Vector2 Terrain3DUtil::get_min_max(const Ref &p_image) {
return V2(INFINITY);
}
- Vector2 min_max = Vector2(FLT_MAX, FLT_MIN);
+ Vector2 min_max = Vector2(FLT_MAX, -FLT_MAX);
for (int y = 0; y < p_image->get_height(); y++) {
for (int x = 0; x < p_image->get_width(); x++) {
diff --git a/src/terrain_3d_util.h b/src/terrain_3d_util.h
index dd470e9b2..448635bab 100644
--- a/src/terrain_3d_util.h
+++ b/src/terrain_3d_util.h
@@ -198,6 +198,27 @@ inline Rect2 aabb2rect(const AABB &p_aabb) {
return rect;
}
+// Functions to match GLSL when needing to duplicate shader code CPU side.
+inline real_t smoothstep(const real_t p_low, const real_t p_high, const real_t p_value) {
+ real_t t = CLAMP((p_value - p_low) / (p_high - p_low), 0.f, 1.f);
+ return t * t * (3.f - 2.f * t);
+}
+
+inline Vector2 smoothstep(const real_t p_low, const real_t p_high, const Vector2 &p_value) {
+ Vector2 t = (p_value - Vector2(p_low, p_low)) / (p_high - p_low);
+ t.x = CLAMP(t.x, 0.f, 1.f);
+ t.y = CLAMP(t.y, 0.f, 1.f);
+ return t * t * (Vector2(3.f, 3.f) - 2.f * t);
+}
+
+inline Vector3 smoothstep(const real_t p_low, const real_t p_high, const Vector3 &p_value) {
+ Vector3 t = (p_value - Vector3(p_low, p_low, p_low)) / (p_high - p_low);
+ t.x = CLAMP(t.x, 0.f, 1.f);
+ t.y = CLAMP(t.y, 0.f, 1.f);
+ t.z = CLAMP(t.z, 0.f, 1.f);
+ return t * t * (Vector3(3.f, 3.f, 3.f) - 2.f * t);
+}
+
///////////////////////////
// Controlmap Handling
///////////////////////////