new terrain plugin
This commit is contained in:
42
addons/terrain_3d/extras/3rd_party/import_sgt.gd
vendored
Normal file
42
addons/terrain_3d/extras/3rd_party/import_sgt.gd
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
# Copyright © 2025 Cory Petkovsek, Roope Palmroos, and Contributors.
|
||||
# Import From SimpleGrassTextured
|
||||
#
|
||||
# This script demonstrates how to import transforms from SimpleGrassTextured. To use it:
|
||||
#
|
||||
# 1. Setup the mesh asset you wish to use in the asset dock.
|
||||
# 1. Select your Terrain3D node.
|
||||
# 1. In the inspector, click Script (very bottom) and Quick Load import_sgt.gd.
|
||||
# 1. At the very top, assign your SimpleGrassTextured node.
|
||||
# 1. Input the desired mesh asset ID.
|
||||
# 1. Click import. The output window and console will report when finished.
|
||||
# 1. Clear the script from your Terrain3D node, and save your scene.
|
||||
#
|
||||
# The instance transforms are now stored in your region files.
|
||||
#
|
||||
# Use clear_instances to erase all instances that match the assign_mesh_id.
|
||||
#
|
||||
# The add_transforms function (called by add_multimesh) applies the height_offset specified in the
|
||||
# Terrain3DMeshAsset.
|
||||
# Once the transforms are imported, you can reassign any mesh you like into this mesh slot.
|
||||
|
||||
@tool
|
||||
extends Terrain3D
|
||||
|
||||
@export var simple_grass_textured: MultiMeshInstance3D
|
||||
@export var assign_mesh_id: int
|
||||
@export var import: bool = false : set = import_sgt
|
||||
@export var clear_instances: bool = false : set = clear_multimeshes
|
||||
|
||||
|
||||
func clear_multimeshes(value: bool) -> void:
|
||||
get_instancer().clear_by_mesh(assign_mesh_id)
|
||||
|
||||
|
||||
func import_sgt(value: bool) -> void:
|
||||
var sgt_mm: MultiMesh = simple_grass_textured.multimesh
|
||||
var global_xform: Transform3D = simple_grass_textured.global_transform
|
||||
print("Starting to import %d instances from SimpleGrassTextured using mesh id %d" % [ sgt_mm.instance_count, assign_mesh_id])
|
||||
var time: int = Time.get_ticks_msec()
|
||||
get_instancer().add_multimesh(assign_mesh_id, sgt_mm, simple_grass_textured.global_transform)
|
||||
print("Import complete in %.2f seconds" % [ float(Time.get_ticks_msec() - time)/1000. ])
|
||||
|
||||
1
addons/terrain_3d/extras/3rd_party/import_sgt.gd.uid
vendored
Normal file
1
addons/terrain_3d/extras/3rd_party/import_sgt.gd.uid
vendored
Normal file
@@ -0,0 +1 @@
|
||||
uid://bllcuwetve45k
|
||||
137
addons/terrain_3d/extras/3rd_party/project_on_terrain3d.gd
vendored
Normal file
137
addons/terrain_3d/extras/3rd_party/project_on_terrain3d.gd
vendored
Normal file
@@ -0,0 +1,137 @@
|
||||
# Copyright © 2025 Cory Petkovsek, Roope Palmroos, and Contributors.
|
||||
# This script is an addon for HungryProton's Scatter https://github.com/HungryProton/scatter
|
||||
# It provides a `Project on Terrain3D` modifier, which allows Scatter
|
||||
# to detect the terrain height from Terrain3D without using collision.
|
||||
#
|
||||
# Copy this file into /addons/proton_scatter/src/modifiers
|
||||
# Then uncomment everything below (select, press CTRL+K)
|
||||
# In the editor, add this modifier to Scatter, then set your Terrain3D node
|
||||
|
||||
#@tool
|
||||
#extends "base_modifier.gd"
|
||||
#
|
||||
#
|
||||
#signal projection_completed
|
||||
#
|
||||
#
|
||||
#@export var terrain_node : NodePath
|
||||
#@export var align_with_collision_normal : bool = false
|
||||
#@export_range(0.0, 90.0, 0.1) var max_slope : float = 90.0
|
||||
#@export var enable_texture_filtering : bool = false
|
||||
#@export_range(0, 31) var target_texture_id : int = 0
|
||||
#@export var not_target_texture : bool = false
|
||||
#@export_range(0.0, 1.0, 0.01) var texture_threshold : float = 0.8
|
||||
#
|
||||
#var _terrain: Terrain3D
|
||||
#
|
||||
#
|
||||
#func _init() -> void:
|
||||
#display_name = "Project On Terrain3D"
|
||||
#category = "Edit"
|
||||
#can_restrict_height = false
|
||||
#global_reference_frame_available = true
|
||||
#local_reference_frame_available = true
|
||||
#individual_instances_reference_frame_available = true
|
||||
#use_global_space_by_default()
|
||||
#
|
||||
#documentation.add_paragraph(
|
||||
#"This is a modified version of `Project on Colliders` that queries Terrain3D
|
||||
#for heights without using collision. It constrains placement by slope or texture.
|
||||
#
|
||||
#This modifier must have terrain_node set to a Terrain3D node.")
|
||||
#
|
||||
#var p := documentation.add_parameter("Terrain Node")
|
||||
#p.set_type("NodePath")
|
||||
#p.set_description("Set your Terrain3D node.")
|
||||
#
|
||||
#p = documentation.add_parameter("Align with collision normal")
|
||||
#p.set_type("bool")
|
||||
#p.set_description(
|
||||
#"Rotate the transform to align it with the collision normal in case
|
||||
#the ray cast hit a collider.")
|
||||
#
|
||||
#p = documentation.add_parameter("Enable Texture Filtering")
|
||||
#p.set_type("bool")
|
||||
#p.set_description(
|
||||
#"If enabled, objects will only be placed based on the ground texture specified.")
|
||||
#
|
||||
#p = documentation.add_parameter("Target Texture ID")
|
||||
#p.set_type("int")
|
||||
#p.set_description(
|
||||
#"The ID of the texture to place objects on (0-31). Objects will only be placed on this texture.")
|
||||
#
|
||||
#p = documentation.add_parameter("Not Target Texture")
|
||||
#p.set_type("bool")
|
||||
#p.set_description(
|
||||
#"If true, objects will be placed on all textures EXCEPT the target texture.")
|
||||
#
|
||||
#p = documentation.add_parameter("Texture Threshold")
|
||||
#p.set_type("float")
|
||||
#p.set_description("The blend value required for placement on the texture.")
|
||||
#
|
||||
#
|
||||
#func _process_transforms(transforms, domain, _seed) -> void:
|
||||
#if transforms.is_empty():
|
||||
#return
|
||||
#
|
||||
#if terrain_node:
|
||||
#_terrain = domain.get_root().get_node_or_null(terrain_node)
|
||||
#
|
||||
#if not _terrain:
|
||||
#warning += """No Terrain3D node found"""
|
||||
#return
|
||||
#
|
||||
#if not _terrain.data:
|
||||
#warning += """Terrain3DData is not initialized"""
|
||||
#return
|
||||
#
|
||||
## Review transforms
|
||||
#var gt: Transform3D = domain.get_global_transform()
|
||||
#var gt_inverse := gt.affine_inverse()
|
||||
#var new_transforms_array: Array[Transform3D] = []
|
||||
#var remapped_max_slope: float = remap(max_slope, 0.0, 90.0, 0.0, 1.0)
|
||||
#for i in transforms.list.size():
|
||||
#var t: Transform3D = transforms.list[i]
|
||||
#
|
||||
#var location: Vector3 = (gt * t).origin
|
||||
#var height: float = _terrain.data.get_height(location)
|
||||
#if is_nan(height):
|
||||
#continue
|
||||
#
|
||||
#var normal: Vector3 = _terrain.data.get_normal(location)
|
||||
#if not abs(Vector3.UP.dot(normal)) >= (1.0 - remapped_max_slope):
|
||||
#continue
|
||||
#
|
||||
#if enable_texture_filtering:
|
||||
#var texture_info: Vector3 = _terrain.data.get_texture_id(location)
|
||||
#var base_id: int = int(texture_info.x)
|
||||
#var overlay_id: int = int(texture_info.y)
|
||||
#var blend_value: float = texture_info.z
|
||||
## Skip if overlay or blend != target texture, unless inverted
|
||||
#if ((overlay_id != target_texture_id or blend_value < texture_threshold) and \
|
||||
#(base_id != target_texture_id or blend_value >= texture_threshold)) != not_target_texture:
|
||||
#continue
|
||||
#
|
||||
#if align_with_collision_normal and not is_nan(normal.x):
|
||||
#t.basis.y = normal
|
||||
#t.basis.x = -t.basis.z.cross(normal)
|
||||
#t.basis = t.basis.orthonormalized()
|
||||
#
|
||||
#t.origin.y = height - gt.origin.y
|
||||
#new_transforms_array.push_back(t)
|
||||
#
|
||||
#transforms.list.clear()
|
||||
#transforms.list.append_array(new_transforms_array)
|
||||
#
|
||||
#if transforms.is_empty():
|
||||
#warning += """All transforms have been removed. Possible reasons include: \n"""
|
||||
#if enable_texture_filtering:
|
||||
#warning += """+ No matching texture found at any position.
|
||||
#+ Texture threshold may be too high.
|
||||
#"""
|
||||
#warning += """+ No collider is close enough to the shapes.
|
||||
#+ Ray length is too short.
|
||||
#+ Ray direction is incorrect.
|
||||
#+ Collision mask is not set properly.
|
||||
#+ Max slope is too low.
|
||||
#"""
|
||||
1
addons/terrain_3d/extras/3rd_party/project_on_terrain3d.gd.uid
vendored
Normal file
1
addons/terrain_3d/extras/3rd_party/project_on_terrain3d.gd.uid
vendored
Normal file
@@ -0,0 +1 @@
|
||||
uid://g3opjh3m3iww
|
||||
@@ -0,0 +1,25 @@
|
||||
[gd_scene load_steps=5 format=3 uid="uid://d3sr0a7dxfkr8"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://bp7r4ppgq1m0g" path="res://addons/terrain_3d/extras/particle_example/terrain_3D_particles.gd" id="1_gl3qg"]
|
||||
[ext_resource type="Material" uid="uid://el5y10hnh13g" path="res://addons/terrain_3d/extras/particle_example/process_material.tres" id="2_2gon1"]
|
||||
[ext_resource type="Material" uid="uid://ljo1wt61kbkq" path="res://addons/terrain_3d/extras/particle_example/grass_material.tres" id="3_qyjnw"]
|
||||
|
||||
[sub_resource type="RibbonTrailMesh" id="RibbonTrailMesh_fwrtk"]
|
||||
shape = 0
|
||||
section_length = 0.18
|
||||
section_segments = 1
|
||||
|
||||
[node name="Terrain3DParticles" type="Node3D"]
|
||||
script = ExtResource("1_gl3qg")
|
||||
instance_spacing = 0.25
|
||||
cell_width = 24.0
|
||||
grid_width = 5
|
||||
rows = 96
|
||||
amount = 9216
|
||||
process_material = ExtResource("2_2gon1")
|
||||
mesh = SubResource("RibbonTrailMesh_fwrtk")
|
||||
shadow_mode = 0
|
||||
mesh_material_override = ExtResource("3_qyjnw")
|
||||
min_draw_distance = 60.0
|
||||
particle_count = 230400
|
||||
metadata/_edit_lock_ = true
|
||||
69
addons/terrain_3d/extras/particle_example/grass.gdshader
Normal file
69
addons/terrain_3d/extras/particle_example/grass.gdshader
Normal file
@@ -0,0 +1,69 @@
|
||||
// Copyright © 2025 Cory Petkovsek, Roope Palmroos, and Contributors.
|
||||
|
||||
shader_type spatial;
|
||||
|
||||
render_mode skip_vertex_transform, cull_disabled, blend_mix;
|
||||
|
||||
uniform vec2 wind_direction = vec2(1.0, 1.0);
|
||||
|
||||
varying vec4 data;
|
||||
|
||||
// A lot of hard coded things here atm.
|
||||
void vertex() {
|
||||
// Wind effect from model data, in this case no vertex colors,
|
||||
// so just use vertex Y component, including mesh offset
|
||||
data[2] = (VERTEX.y + 0.55);
|
||||
data[2] *= data[2]; // make non-linear
|
||||
|
||||
// Ribbon used as a grass mesh.. so pinch the top.
|
||||
VERTEX.xz *= (1.0 - data[2]);
|
||||
|
||||
// Brighten tips
|
||||
COLOR = mix(COLOR, vec4(1.0), smoothstep(0.9, 1.0, data[2]));
|
||||
// Darken base, skip is scale is less than threshold, as this means "grow in" is occuring.
|
||||
COLOR *= INSTANCE_CUSTOM[3] < 0.35 ? 1. : mix(1.0, 0.75, smoothstep(0.35, 0.0, data[2]));
|
||||
// Save red/green shift for fragment
|
||||
data.rg = INSTANCE_CUSTOM.rg;
|
||||
|
||||
// World space vertex
|
||||
vec3 w_vertex = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz;
|
||||
// Get wind force and scale from process()
|
||||
float scale = pow(INSTANCE_CUSTOM[3] * INSTANCE_CUSTOM[3], 0.707);
|
||||
float force = INSTANCE_CUSTOM[2] * data[2] * scale;
|
||||
// Add some cheap jitter at high wind values
|
||||
force -= fract(force * 256.0) * force * 0.05;
|
||||
// Curve the result
|
||||
force = pow(force, 0.707);
|
||||
|
||||
// These 2 combined result in a decent bend without resorting to matrices or pivot data.
|
||||
// Lateral move and wobble
|
||||
float lateral_wobble = sin(TIME * 2.0 * (1.0 + data.r + data.g)) * 0.25 * (1.0 - INSTANCE_CUSTOM[2]);
|
||||
vec2 direction = normalize(wind_direction);
|
||||
w_vertex.xz -= (vec2(-direction.y, direction.x) * lateral_wobble + direction) * force;
|
||||
// Flatten
|
||||
w_vertex.y -= INSTANCE_CUSTOM[2] * force * data[2];
|
||||
|
||||
// Save final wind force value for fragment.
|
||||
data[3] = force;
|
||||
|
||||
VERTEX = (VIEW_MATRIX * vec4(w_vertex, 1.0)).xyz;
|
||||
NORMAL = MODELVIEW_NORMAL_MATRIX * NORMAL;
|
||||
BINORMAL = MODELVIEW_NORMAL_MATRIX * BINORMAL;
|
||||
TANGENT = MODELVIEW_NORMAL_MATRIX * TANGENT;
|
||||
}
|
||||
|
||||
void fragment() {
|
||||
// Hard coded color.
|
||||
ALBEDO = vec3(0.20, 0.22, 0.05) * (data[2] * 0.5 + 0.5);
|
||||
ALBEDO.rg *= (data.rg * 0.3 + 0.9);
|
||||
ALBEDO *= pow(COLOR.rgb, vec3(2.2));
|
||||
// Modify roughness / specular based on wind force for added detail
|
||||
float spec_rough = clamp(max(data[2], data[3]), 0., 1.);
|
||||
ROUGHNESS = 1. - spec_rough;
|
||||
SPECULAR = clamp(spec_rough * 0.25, 0., .15);
|
||||
|
||||
BACKLIGHT = vec3(0.33);
|
||||
#if CURRENT_RENDERER == RENDERER_COMPATIBILITY
|
||||
ALBEDO = pow(ALBEDO, vec3(0.4));
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uid://dq3lfyp3u5oxt
|
||||
@@ -0,0 +1,8 @@
|
||||
[gd_resource type="ShaderMaterial" load_steps=2 format=3 uid="uid://ljo1wt61kbkq"]
|
||||
|
||||
[ext_resource type="Shader" uid="uid://dq3lfyp3u5oxt" path="res://addons/terrain_3d/extras/particle_example/grass.gdshader" id="1_nkru0"]
|
||||
|
||||
[resource]
|
||||
render_priority = 0
|
||||
shader = ExtResource("1_nkru0")
|
||||
shader_parameter/wind_direction = Vector2(1, 1)
|
||||
275
addons/terrain_3d/extras/particle_example/particles.gdshader
Normal file
275
addons/terrain_3d/extras/particle_example/particles.gdshader
Normal file
@@ -0,0 +1,275 @@
|
||||
// Copyright © 2025 Cory Petkovsek, Roope Palmroos, and Contributors.
|
||||
|
||||
// This is an example particle shader designed to procedurally place
|
||||
// particles like grass, small rocks, and other ground effects, on the terrain
|
||||
// surface by reading the Terrain3D data maps. It works in tandem with the
|
||||
// provided GDScript.
|
||||
|
||||
shader_type particles;
|
||||
render_mode disable_velocity, disable_force;
|
||||
|
||||
group_uniforms options;
|
||||
uniform sampler2D main_noise;
|
||||
uniform float main_noise_scale = 0.01;
|
||||
uniform vec3 position_offset = vec3(0.);
|
||||
uniform bool align_to_normal = true;
|
||||
uniform float normal_strength : hint_range(0.01, 1.0, 0.01) = 0.3;
|
||||
uniform bool random_rotation = true;
|
||||
uniform float random_spacing : hint_range(0.0, 1.0, 0.01) = 0.5;
|
||||
uniform vec3 min_scale = vec3(1.0);
|
||||
uniform vec3 max_scale = vec3(1.0);
|
||||
|
||||
group_uniforms wind;
|
||||
uniform float noise_scale = 0.0041;
|
||||
uniform float wind_speed = 0.025;
|
||||
uniform float wind_strength : hint_range(0.0, 1.0, 0.01) = 1.0;
|
||||
uniform float wind_dithering = 4.0;
|
||||
uniform vec2 wind_direction = vec2(1.0,1.0);
|
||||
|
||||
group_uniforms shapeing;
|
||||
uniform float clod_scale_boost = 3.0;
|
||||
uniform float clod_min_threshold : hint_range(0.0, 1.0, 0.001) = 0.2;
|
||||
uniform float clod_max_threshold : hint_range(0.0, 1.0, 0.001) = 0.5;
|
||||
uniform float patch_min_threshold : hint_range(0.0, 1.0, 0.001) = 0.025;
|
||||
uniform float patch_max_threshold : hint_range(0.0, 1.0, 0.001) = 0.2;
|
||||
|
||||
group_uniforms filtering;
|
||||
uniform float condition_dither_range : hint_range(0.0, 1.0, 0.01) = 0.15;
|
||||
uniform float surface_slope_min : hint_range(0.0, 1.0, 0.01) = 0.87;
|
||||
uniform float distance_fade_ammount : hint_range(0.0, 1.0, 0.01) = 0.5;
|
||||
|
||||
group_uniforms private;
|
||||
uniform float max_dist = 1.;
|
||||
uniform vec3 camera_position = vec3(0.);
|
||||
uniform uint instance_rows = 1;
|
||||
uniform float instance_spacing = 0.5;
|
||||
uniform uint _background_mode = 0u;
|
||||
uniform float _vertex_spacing = 1.0;
|
||||
uniform float _vertex_density = 1.0; // = 1/_vertex_spacing
|
||||
uniform float _region_size = 1024.0;
|
||||
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];
|
||||
uniform highp sampler2DArray _height_maps : repeat_disable;
|
||||
uniform highp sampler2DArray _control_maps : repeat_disable;
|
||||
uniform highp sampler2DArray _color_maps : repeat_disable;
|
||||
|
||||
// Defined Constants
|
||||
#define SKIP_PASS 0
|
||||
#define VERTEX_PASS 1
|
||||
#define FRAGMENT_PASS 2
|
||||
|
||||
// Takes in world space XZ (UV) coordinates & search depth (only applicable for background mode none)
|
||||
// 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) {
|
||||
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 < 0; 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);
|
||||
}
|
||||
|
||||
#if CURRENT_RENDERER == RENDERER_COMPATIBILITY
|
||||
#define fma(a, b, c) ((a) * (b) + (c))
|
||||
#endif
|
||||
float random(vec2 v) {
|
||||
return fract(1e4 * sin(fma(17.0, v.x, v.y * 0.1)) * (0.1 + abs(sin(fma(v.y, 13.0, v.x)))));
|
||||
}
|
||||
|
||||
mat3 rotation_matrix(vec3 axis, float angle) {
|
||||
float c = cos(angle);
|
||||
float s = sin(angle);
|
||||
float t = 1.0 - c;
|
||||
vec3 n = normalize(axis);
|
||||
float x = n.x;
|
||||
float y = n.y;
|
||||
float z = n.z;
|
||||
|
||||
return mat3(
|
||||
vec3(t * x * x + c, t * x * y - z * s, t * x * z + y * s),
|
||||
vec3(t * x * y + z * s, t * y * y + c, t * y * z - x * s),
|
||||
vec3(t * x * z - y * s, t * y * z + x * s, t * z * z + c));
|
||||
}
|
||||
|
||||
mat3 align_to_vector(vec3 normal) {
|
||||
vec3 up = vec3(0.0, 1.0, 0.0);
|
||||
if (abs(dot(normal, up)) > 0.9999) { // Avoid singularity
|
||||
up = vec3(1.0, 0.0, 0.0);
|
||||
}
|
||||
vec3 tangent = normalize(cross(up, normal));
|
||||
vec3 bitangent = normalize(cross(tangent, normal));
|
||||
return mat3(tangent, normal, bitangent);
|
||||
}
|
||||
|
||||
void start() {
|
||||
// Create centered a grid
|
||||
vec3 pos = vec3(float(INDEX % instance_rows), 0.0, float(INDEX / instance_rows)) - float(instance_rows >> 1u);
|
||||
|
||||
// Apply spcaing
|
||||
pos *= instance_spacing;
|
||||
// Move the grid to the emitter, snapping is handled CPU side
|
||||
pos.xz += EMISSION_TRANSFORM[3].xz;
|
||||
|
||||
// Create random values per-instance, incorporating the seed, mask bits to avoid NAN/INF
|
||||
float seed = fract(uintBitsToFloat(RANDOM_SEED & 0x7EFFFFFFu));
|
||||
vec3 r = fract(vec3(random(pos.xz), random(pos.xz + vec2(0.5)), random(pos.xz - vec2(0.5))) + seed);
|
||||
// Randomize instance spacing
|
||||
pos.x += ((r.x * 2.0) - 1.0) * random_spacing * instance_spacing;
|
||||
pos.z += ((r.z * 2.0) - 1.0) * random_spacing * instance_spacing;
|
||||
|
||||
// Lookup offsets, ID and blend weight
|
||||
const vec3 offsets = vec3(0, 1, 2);
|
||||
vec2 index_id = floor(pos.xz * _vertex_density);
|
||||
vec2 weight = fract(pos.xz * _vertex_density);
|
||||
vec2 invert = 1.0 - weight;
|
||||
vec4 weights = vec4(
|
||||
invert.x * weight.y, // 0
|
||||
weight.x * weight.y, // 1
|
||||
weight.x * invert.y, // 2
|
||||
invert.x * invert.y // 3
|
||||
);
|
||||
|
||||
ivec3 index[4];
|
||||
// Map lookups
|
||||
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);
|
||||
|
||||
highp float h[8];
|
||||
h[0] = texelFetch(_height_maps, index[0], 0).r; // 0 (0,1)
|
||||
h[1] = texelFetch(_height_maps, index[1], 0).r; // 1 (1,1)
|
||||
h[2] = texelFetch(_height_maps, index[2], 0).r; // 2 (1,0)
|
||||
h[3] = texelFetch(_height_maps, index[3], 0).r; // 3 (0,0)
|
||||
h[4] = texelFetch(_height_maps, get_index_coord(index_id + offsets.yz, VERTEX_PASS), 0).r; // 4 (1,2)
|
||||
h[5] = texelFetch(_height_maps, get_index_coord(index_id + offsets.zy, VERTEX_PASS), 0).r; // 5 (2,1)
|
||||
h[6] = texelFetch(_height_maps, get_index_coord(index_id + offsets.zx, VERTEX_PASS), 0).r; // 6 (2,0)
|
||||
h[7] = texelFetch(_height_maps, get_index_coord(index_id + offsets.xz, VERTEX_PASS), 0).r; // 7 (0,2)
|
||||
vec3 index_normal[4];
|
||||
index_normal[0] = vec3(h[0] - h[1], _vertex_spacing, h[0] - h[7]);
|
||||
index_normal[1] = vec3(h[1] - h[5], _vertex_spacing, h[1] - h[4]);
|
||||
index_normal[2] = vec3(h[2] - h[6], _vertex_spacing, h[2] - h[1]);
|
||||
index_normal[3] = vec3(h[3] - h[2], _vertex_spacing, h[3] - h[0]);
|
||||
vec3 w_normal = normalize(
|
||||
index_normal[0] * weights[0] +
|
||||
index_normal[1] * weights[1] +
|
||||
index_normal[2] * weights[2] +
|
||||
index_normal[3] * weights[3]);
|
||||
|
||||
// Set the height according to the heightmap data
|
||||
pos.y =
|
||||
h[0] * weights[0] +
|
||||
h[1] * weights[1] +
|
||||
h[2] * weights[2] +
|
||||
h[3] * weights[3] ;
|
||||
|
||||
// Offset, Rotation, Alignment.
|
||||
TRANSFORM = mat4(1.0);
|
||||
vec3 orientation = vec3(0., 1., 0.);
|
||||
|
||||
vec2 uv = (pos.xz) * main_noise_scale;
|
||||
float noise = textureLod(main_noise, uv, 0.0).r;
|
||||
float clods = smoothstep(clod_min_threshold, clod_max_threshold, noise) * clod_scale_boost;
|
||||
float patch = smoothstep(patch_min_threshold, patch_max_threshold, noise);
|
||||
float width_modifier = 1.0 + 3.0 * smoothstep(0., max_dist, length(camera_position - pos));
|
||||
|
||||
// Calculate scale
|
||||
vec3 scale = vec3(
|
||||
mix(min_scale.x, max_scale.x, r.x) * width_modifier,
|
||||
mix(min_scale.y, max_scale.y, r.y) + clods,
|
||||
mix(min_scale.z, max_scale.z, r.z) * width_modifier) * patch;
|
||||
|
||||
// Apply scale to offset
|
||||
vec3 offset = position_offset * scale;
|
||||
|
||||
// Apply normal orientation
|
||||
if (align_to_normal) {
|
||||
orientation = mix(orientation, w_normal, normal_strength);
|
||||
mat3 alignment = align_to_vector(orientation);
|
||||
offset = alignment * offset;
|
||||
TRANSFORM = mat4(alignment);
|
||||
}
|
||||
|
||||
// Apply rotation around orientation
|
||||
if (random_rotation) {
|
||||
mat3 rotation = rotation_matrix(orientation, r.x * TAU);
|
||||
TRANSFORM = mat4(rotation) * TRANSFORM;
|
||||
}
|
||||
|
||||
// Filtering - Causes some particles to be rendered as degenerate triangles
|
||||
// via 0./0. - Particles filtered this way are still processed by the GPU.
|
||||
// For compatibility it seems we must shift as well.
|
||||
// Surface slope filtering
|
||||
if (surface_slope_min > w_normal.y + (r.y - 0.5) * condition_dither_range) {
|
||||
pos.y = 0. / 0.;
|
||||
pos.xz = vec2(100000.0);
|
||||
}
|
||||
|
||||
// Read color map
|
||||
highp vec4 c[4];
|
||||
#define COLOR_MAP vec4(1., 1., 1., 0.5)
|
||||
c[0] = index[0].z >= 0 ? texelFetch(_color_maps, index[0], 0) : COLOR_MAP; // 0 (0,1)
|
||||
c[1] = index[1].z >= 0 ? texelFetch(_color_maps, index[1], 0) : COLOR_MAP; // 1 (1,1)
|
||||
c[2] = index[2].z >= 0 ? texelFetch(_color_maps, index[2], 0) : COLOR_MAP; // 2 (1,0)
|
||||
c[3] = index[3].z >= 0 ? texelFetch(_color_maps, index[3], 0) : COLOR_MAP; // 3 (0,0)
|
||||
vec4 color_map =
|
||||
c[0] * weights[0] +
|
||||
c[1] * weights[1] +
|
||||
c[2] * weights[2] +
|
||||
c[3] * weights[3] ;
|
||||
|
||||
COLOR = color_map;
|
||||
|
||||
// Read control map
|
||||
uint control = floatBitsToUint(texelFetch(_control_maps, index[3], 0).r);
|
||||
bool auto = bool(control & 0x1u);
|
||||
int base = int(control >>27u & 0x1Fu);
|
||||
int over = int(control >> 22u & 0x1Fu);
|
||||
float blend = float(control >> 14u & 0xFFu) * 0.003921568627450; // 1. / 255.
|
||||
|
||||
// Hardcoded example, hand painted texture id 0 is filtered out.
|
||||
if (!auto && ((base == 0 && blend < 0.7) || (over == 0 && blend >= 0.3))) {
|
||||
pos.y = 0. / 0.;
|
||||
pos.xz = vec2(100000.0);
|
||||
}
|
||||
|
||||
if (length(camera_position - pos) > max_dist) {
|
||||
pos.y = 0. / 0.;
|
||||
pos.xz = vec2(100000.0);
|
||||
} else {
|
||||
float fade_factor = 1.0 - smoothstep(max_dist * distance_fade_ammount, max_dist + 0.0001, length(camera_position - pos)) + 0.001;
|
||||
scale.y *= fade_factor;
|
||||
offset *= fade_factor;
|
||||
}
|
||||
|
||||
// Apply scale
|
||||
TRANSFORM[0] *= scale.x;
|
||||
TRANSFORM[1] *= scale.y;
|
||||
TRANSFORM[2] *= scale.z;
|
||||
|
||||
// Apply the position
|
||||
TRANSFORM[3].xyz = pos.xyz + offset;
|
||||
|
||||
// Save Fixed 2 Random values for Reg/Green color randomness
|
||||
CUSTOM.rg = r.rg;
|
||||
// Save Y component scale pre-rotation
|
||||
CUSTOM[3] = scale.y;
|
||||
}
|
||||
|
||||
void process() {
|
||||
// Extract world space UV from Transform Matrix
|
||||
vec2 uv = (TRANSFORM[3].xz + CUSTOM.rg * wind_dithering) * noise_scale;
|
||||
// Scaled wind noise, updated per instance, at process FPS. Passed to Vertex()
|
||||
CUSTOM[2] = textureLod(main_noise, uv + TIME * wind_speed * normalize(wind_direction), 0.0).r * wind_strength;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uid://dce675i014xcn
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,237 @@
|
||||
# Copyright © 2025 Cory Petkovsek, Roope Palmroos, and Contributors.
|
||||
#
|
||||
# This is an example of using a particle shader with Terrain3D.
|
||||
# To use it, add `Terrain3DParticles.tscn` to your scene and assign the terrain.
|
||||
# Then customize the settings, materials and shader to extend it and make it your own.
|
||||
|
||||
@tool
|
||||
extends Node3D
|
||||
|
||||
|
||||
#region settings
|
||||
## Auto set if attached as a child of a Terrain3D node
|
||||
@export var terrain: Terrain3D:
|
||||
set(value):
|
||||
terrain = value
|
||||
_create_grid()
|
||||
|
||||
|
||||
## Distance between instances
|
||||
@export_range(0.125, 2.0, 0.015625) var instance_spacing: float = 0.5:
|
||||
set(value):
|
||||
instance_spacing = clamp(round(value * 64.0) * 0.015625, 0.125, 2.0)
|
||||
rows = maxi(int(cell_width / instance_spacing), 1)
|
||||
amount = rows * rows
|
||||
_set_offsets()
|
||||
|
||||
|
||||
## Width of an individual cell of the grid
|
||||
@export_range(8.0, 256.0, 1.0) var cell_width: float = 32.0:
|
||||
set(value):
|
||||
cell_width = clamp(value, 8.0, 256.0)
|
||||
rows = maxi(int(cell_width / instance_spacing), 1)
|
||||
amount = rows * rows
|
||||
min_draw_distance = 1.0
|
||||
# Have to update aabb
|
||||
if terrain and terrain.data:
|
||||
var height_range: Vector2 = terrain.data.get_height_range()
|
||||
var height: float = height_range[0] - height_range[1]
|
||||
var aabb: AABB = AABB()
|
||||
aabb.size = Vector3(cell_width, height, cell_width)
|
||||
aabb.position = aabb.size * -0.5
|
||||
aabb.position.y = height_range[1]
|
||||
for p in particle_nodes:
|
||||
p.custom_aabb = aabb
|
||||
_set_offsets()
|
||||
|
||||
|
||||
## Grid width. Must be odd.
|
||||
## Higher values cull slightly better, draw further out.
|
||||
@export_range(1, 15, 2) var grid_width: int = 9:
|
||||
set(value):
|
||||
grid_width = value
|
||||
particle_count = 1
|
||||
min_draw_distance = 1.0
|
||||
_create_grid()
|
||||
|
||||
|
||||
@export_storage var rows: int = 1
|
||||
|
||||
@export_storage var amount: int = 1:
|
||||
set(value):
|
||||
amount = value
|
||||
particle_count = value
|
||||
last_pos = Vector3.ZERO
|
||||
for p in particle_nodes:
|
||||
p.amount = amount
|
||||
|
||||
|
||||
@export_range(1, 256, 1) var process_fixed_fps: int = 30:
|
||||
set(value):
|
||||
process_fixed_fps = maxi(value, 1)
|
||||
for p in particle_nodes:
|
||||
p.fixed_fps = process_fixed_fps
|
||||
p.preprocess = 1.0 / float(process_fixed_fps)
|
||||
|
||||
|
||||
## Access to process material parameters
|
||||
@export var process_material: ShaderMaterial
|
||||
|
||||
## The mesh that each particle will render
|
||||
@export var mesh: Mesh
|
||||
|
||||
@export var shadow_mode: GeometryInstance3D.ShadowCastingSetting = (
|
||||
GeometryInstance3D.ShadowCastingSetting.SHADOW_CASTING_SETTING_ON):
|
||||
set(value):
|
||||
shadow_mode = value
|
||||
for p in particle_nodes:
|
||||
p.cast_shadow = value
|
||||
|
||||
|
||||
## Override material for the particle mesh
|
||||
@export_custom(
|
||||
PROPERTY_HINT_RESOURCE_TYPE,
|
||||
"BaseMaterial3D,ShaderMaterial") var mesh_material_override: Material:
|
||||
set(value):
|
||||
mesh_material_override = value
|
||||
for p in particle_nodes:
|
||||
p.material_override = mesh_material_override
|
||||
|
||||
|
||||
@export_group("Info")
|
||||
## The minimum distance that particles will be drawn upto
|
||||
## If using fade out effects like pixel alpha this is the limit to use.
|
||||
@export var min_draw_distance: float = 1.0:
|
||||
set(value):
|
||||
min_draw_distance = float(cell_width * grid_width) * 0.5
|
||||
|
||||
|
||||
## Displays current total particle count based on Cell Width and Instance Spacing
|
||||
@export var particle_count: int = 1:
|
||||
set(value):
|
||||
particle_count = amount * grid_width * grid_width
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
var offsets: Array[Vector3]
|
||||
var last_pos: Vector3 = Vector3.ZERO
|
||||
var particle_nodes: Array[GPUParticles3D]
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
if not terrain:
|
||||
var parent: Node = get_parent()
|
||||
if parent is Terrain3D:
|
||||
terrain = parent
|
||||
_create_grid()
|
||||
|
||||
|
||||
func _notification(what: int) -> void:
|
||||
if what == NOTIFICATION_PREDELETE:
|
||||
_destroy_grid()
|
||||
|
||||
|
||||
func _physics_process(delta: float) -> void:
|
||||
if terrain:
|
||||
var camera: Camera3D = terrain.get_camera()
|
||||
if camera:
|
||||
if last_pos.distance_squared_to(camera.global_position) > 1.0:
|
||||
var pos: Vector3 = camera.global_position.snapped(Vector3.ONE)
|
||||
_position_grid(pos)
|
||||
RenderingServer.material_set_param(process_material.get_rid(), "camera_position", pos )
|
||||
last_pos = camera.global_position
|
||||
_update_process_parameters()
|
||||
else:
|
||||
set_physics_process(false)
|
||||
|
||||
|
||||
func _create_grid() -> void:
|
||||
_destroy_grid()
|
||||
if not terrain:
|
||||
return
|
||||
set_physics_process(true)
|
||||
_set_offsets()
|
||||
var hr: Vector2 = terrain.data.get_height_range()
|
||||
var height: float = hr.x - hr.y
|
||||
var aabb: AABB = AABB()
|
||||
aabb.size = Vector3(cell_width, height, cell_width)
|
||||
aabb.position = aabb.size * -0.5
|
||||
aabb.position.y = hr.y
|
||||
var half_grid: int = grid_width / 2
|
||||
# Iterating the array like this allows identifying grid position, in case setting
|
||||
# different mesh or materials is desired for LODs etc.
|
||||
for x in range(-half_grid, half_grid + 1):
|
||||
for z in range(-half_grid, half_grid + 1):
|
||||
#var ring: int = maxi(maxi(absi(x), absi(z)), 0)
|
||||
var particle_node = GPUParticles3D.new()
|
||||
particle_node.lifetime = 600.0
|
||||
particle_node.amount = amount
|
||||
particle_node.explosiveness = 1.0
|
||||
particle_node.amount_ratio = 1.0
|
||||
particle_node.process_material = process_material
|
||||
particle_node.draw_pass_1 = mesh
|
||||
particle_node.speed_scale = 1.0
|
||||
particle_node.custom_aabb = aabb
|
||||
particle_node.cast_shadow = shadow_mode
|
||||
particle_node.fixed_fps = process_fixed_fps
|
||||
# This prevent minor grid alignment errors when the camera is moving very fast
|
||||
particle_node.preprocess = 1.0 / float(process_fixed_fps)
|
||||
if mesh_material_override:
|
||||
particle_node.material_override = mesh_material_override
|
||||
particle_node.use_fixed_seed = true
|
||||
if (x > -half_grid and z > -half_grid): # Use the same seed across all nodes
|
||||
particle_node.seed = particle_nodes[0].seed
|
||||
self.add_child(particle_node)
|
||||
particle_node.emitting = true
|
||||
particle_nodes.push_back(particle_node)
|
||||
last_pos = Vector3.ZERO
|
||||
|
||||
|
||||
func _set_offsets() -> void:
|
||||
var half_grid: int = grid_width / 2
|
||||
offsets.clear()
|
||||
for x in range(-half_grid, half_grid + 1):
|
||||
for z in range(-half_grid, half_grid + 1):
|
||||
var offset := Vector3(
|
||||
float(x * rows) * instance_spacing,
|
||||
0.0,
|
||||
float(z * rows) * instance_spacing
|
||||
)
|
||||
offsets.append(offset)
|
||||
|
||||
|
||||
func _destroy_grid() -> void:
|
||||
for node: GPUParticles3D in particle_nodes:
|
||||
if is_instance_valid(node):
|
||||
node.queue_free()
|
||||
particle_nodes.clear()
|
||||
|
||||
|
||||
func _position_grid(pos: Vector3) -> void:
|
||||
for i in particle_nodes.size():
|
||||
var node: GPUParticles3D = particle_nodes[i]
|
||||
var snap = Vector3(pos.x, 0, pos.z).snapped(Vector3.ONE) + offsets[i]
|
||||
node.global_position = (snap / instance_spacing).round() * instance_spacing
|
||||
node.reset_physics_interpolation()
|
||||
node.restart(true) # keep the same seed.
|
||||
|
||||
|
||||
func _update_process_parameters() -> void:
|
||||
if process_material:
|
||||
var process_rid: RID = process_material.get_rid()
|
||||
if terrain and process_rid.is_valid():
|
||||
RenderingServer.material_set_param(process_rid, "_background_mode", terrain.material.world_background)
|
||||
RenderingServer.material_set_param(process_rid, "_vertex_spacing", terrain.vertex_spacing)
|
||||
RenderingServer.material_set_param(process_rid, "_vertex_density", 1.0 / terrain.vertex_spacing)
|
||||
RenderingServer.material_set_param(process_rid, "_region_size", terrain.region_size)
|
||||
RenderingServer.material_set_param(process_rid, "_region_texel_size", 1.0 / terrain.region_size)
|
||||
RenderingServer.material_set_param(process_rid, "_region_map_size", 32)
|
||||
RenderingServer.material_set_param(process_rid, "_region_map", terrain.data.get_region_map())
|
||||
RenderingServer.material_set_param(process_rid, "_region_locations", terrain.data.get_region_locations())
|
||||
RenderingServer.material_set_param(process_rid, "_height_maps", terrain.data.get_height_maps_rid())
|
||||
RenderingServer.material_set_param(process_rid, "_control_maps", terrain.data.get_control_maps_rid())
|
||||
RenderingServer.material_set_param(process_rid, "_color_maps", terrain.data.get_color_maps_rid())
|
||||
RenderingServer.material_set_param(process_rid, "instance_spacing", instance_spacing)
|
||||
RenderingServer.material_set_param(process_rid, "instance_rows", rows)
|
||||
RenderingServer.material_set_param(process_rid, "max_dist", min_draw_distance)
|
||||
@@ -0,0 +1 @@
|
||||
uid://bp7r4ppgq1m0g
|
||||
67
addons/terrain_3d/extras/shaders/hex_grid.gdshaderinc
Normal file
67
addons/terrain_3d/extras/shaders/hex_grid.gdshaderinc
Normal file
@@ -0,0 +1,67 @@
|
||||
// Copyright © 2025 Cory Petkovsek, Roope Palmroos, and Contributors.
|
||||
// This shader snippet draws a hex grid
|
||||
|
||||
// To use it, add this line to the top of your shader:
|
||||
// #include "res://addons/terrain_3d/extras/shaders/hex_grid.gdshaderinc"
|
||||
|
||||
// And this line at the bottom of your shader:
|
||||
// draw_hex_grid(uv2, _region_texel_size, w_normal, ALBEDO);
|
||||
|
||||
mat2 rotate2d(float _angle) {
|
||||
return mat2(vec2(cos(_angle),-sin(_angle)), vec2(sin(_angle), cos(_angle)));
|
||||
}
|
||||
|
||||
void draw_hex_grid(vec2 uv, float texel_size, vec3 normal, inout vec3 albedo) {
|
||||
float hex_size = 0.02;
|
||||
float line_thickness = 0.04;
|
||||
|
||||
vec2 guv = (uv - vec2(0.5 * texel_size)) / hex_size;
|
||||
|
||||
// Convert UV to axial hex coordinates
|
||||
float q = (sqrt(3.0) / 3.0 * guv.x - 1.0 / 3.0 * guv.y);
|
||||
float r = (2.0 / 3.0 * guv.y);
|
||||
|
||||
// Cube coordinates for the hex (q, r, -q-r)
|
||||
float x = q;
|
||||
float z = r;
|
||||
float y = -x - z;
|
||||
|
||||
// Round to the nearest hex center
|
||||
vec3 rounded = round(vec3(x, y, z));
|
||||
vec3 diff = abs(vec3(x, y, z) - rounded);
|
||||
|
||||
// Fix rounding errors
|
||||
if (diff.x > diff.y && diff.x > diff.z) {
|
||||
rounded.x = -rounded.y - rounded.z;
|
||||
} else if (diff.y > diff.z) {
|
||||
rounded.y = -rounded.x - rounded.z;
|
||||
} else {
|
||||
rounded.z = -rounded.x - rounded.y;
|
||||
}
|
||||
|
||||
// Find the hex center in UV space
|
||||
vec2 hex_center = vec2(
|
||||
sqrt(3.0) * rounded.x + sqrt(3.0) / 2.0 * rounded.z,
|
||||
3.0 / 2.0 * rounded.z
|
||||
);
|
||||
|
||||
// Relative position within the hex
|
||||
vec2 local_pos = guv - hex_center;
|
||||
vec2 lines_uv = local_pos;
|
||||
float line = 1.0;
|
||||
|
||||
for (int i = 0; i < 6; i++) {
|
||||
vec2 luv = lines_uv * rotate2d(radians(60.0 * float(i) + 30.0));
|
||||
float dist = abs(dot(luv + vec2(0.90), vec2(0.0, 1.0)));
|
||||
line = min(line, dist);
|
||||
}
|
||||
|
||||
// Filter lines by slope
|
||||
float slope = 4.; // Can also assign to (auto_slope * 4.) to match grass placement
|
||||
float slope_factor = clamp(dot(vec3(0., 1., 0.), slope * (normal - 1.) + 1.), 0., 1.);
|
||||
|
||||
// Draw hex grid
|
||||
albedo = mix(albedo, vec3(1.0), smoothstep(line_thickness + 0.02, line_thickness, line) * slope_factor);
|
||||
// Draw Hex center dot
|
||||
albedo = mix(albedo, vec3(0.0, 0.5, 0.5), smoothstep(0.11, 0.10, length(local_pos)) * slope_factor);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uid://mri8pfoj2mfk
|
||||
367
addons/terrain_3d/extras/shaders/lightweight.gdshader
Normal file
367
addons/terrain_3d/extras/shaders/lightweight.gdshader
Normal file
@@ -0,0 +1,367 @@
|
||||
shader_type spatial;
|
||||
render_mode blend_mix,depth_draw_opaque,cull_back,diffuse_burley,specular_schlick_ggx,skip_vertex_transform;
|
||||
|
||||
/* This is an example stripped down shader with maximum performance in mind.
|
||||
* Only Autoshader/Base/Over/Blend/Holes/Colormap are supported.
|
||||
* All terrain normal calculations take place in vetex().
|
||||
*
|
||||
* Control map indices are processed such that each ID only requires reading ONCE.
|
||||
* The following features: projection, detiling, and paintable rotation / scale
|
||||
* cannot work with this method, without the additional samples required for blending
|
||||
* between same ID textures with different values across indices.
|
||||
*/
|
||||
|
||||
// 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.
|
||||
|
||||
// Inline Functions
|
||||
#define DECODE_BLEND(control) float(control >> 14u & 0xFFu) * DIV_255
|
||||
#define DECODE_AUTO(control) bool(control & 0x1u)
|
||||
#define DECODE_BASE(control) int(control >> 27u & 0x1Fu)
|
||||
#define DECODE_OVER(control) int(control >> 22u & 0x1Fu)
|
||||
#define DECODE_HOLE(control) bool(control >>2u & 0x1u)
|
||||
|
||||
#if CURRENT_RENDERER == RENDERER_COMPATIBILITY
|
||||
#define fma(a, b, c) ((a) * (b) + (c))
|
||||
#define dFdxCoarse(a) dFdx(a)
|
||||
#define dFdyCoarse(a) dFdy(a)
|
||||
#endif
|
||||
|
||||
// Private uniforms
|
||||
uniform vec3 _camera_pos = vec3(0.f);
|
||||
uniform float _mesh_size = 48.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 _region_size = 1024.0;
|
||||
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];
|
||||
uniform float _texture_normal_depth_array[32];
|
||||
uniform float _texture_ao_strength_array[32];
|
||||
uniform float _texture_roughness_mod_array[32];
|
||||
uniform float _texture_uv_scale_array[32];
|
||||
uniform vec4 _texture_color_array[32];
|
||||
uniform highp sampler2DArray _height_maps : repeat_disable;
|
||||
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;
|
||||
|
||||
// Public uniforms
|
||||
uniform bool enable_texturing = true;
|
||||
uniform float blend_sharpness : hint_range(0, 1) = 0.5;
|
||||
uniform bool flat_terrain_normals = false;
|
||||
// Autoshader
|
||||
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;
|
||||
// Macro Variation
|
||||
uniform bool enable_macro_variation = 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;
|
||||
uniform highp sampler2D noise_texture : source_color, filter_linear_mipmap_anisotropic, 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
|
||||
|
||||
// Varyings & Types
|
||||
varying vec3 v_normal;
|
||||
varying vec3 v_vertex;
|
||||
varying mat3 TBN;
|
||||
|
||||
////////////////////////
|
||||
// Vertex
|
||||
////////////////////////
|
||||
|
||||
// Takes in world space XZ (UV) coordinates & search depth (only applicable for background mode none)
|
||||
// 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) {
|
||||
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);
|
||||
}
|
||||
|
||||
// Takes in descaled (world_space / region_size) world to region space XZ (UV2) coordinates, returns vec3 with:
|
||||
// XY: (0. to 1.) coordinates within a region
|
||||
// Z: layer index used for texturearrays, -1 if not in a region
|
||||
vec3 get_index_uv(const vec2 uv2) {
|
||||
ivec2 pos = ivec2(floor(uv2)) + (_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 vec3(uv2 - _region_locations[layer_index], float(layer_index));
|
||||
}
|
||||
|
||||
void vertex() {
|
||||
// Get vertex of flat plane in world coordinates and set world UV
|
||||
v_vertex = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz;
|
||||
|
||||
// Camera distance to vertex on flat plane
|
||||
float v_vertex_xz_dist = length(v_vertex.xz - _camera_pos.xz);
|
||||
|
||||
// Geomorph vertex, set end and start for linear height interpolate
|
||||
float scale = MODEL_MATRIX[0][0];
|
||||
float vertex_lerp = smoothstep(0.55, 0.95, (v_vertex_xz_dist / scale - _mesh_size - 4.0) / (_mesh_size - 2.0));
|
||||
vec2 v_fract = fract(VERTEX.xz * 0.5) * 2.0;
|
||||
// For LOD0 morph from a regular grid to an alternating grid to align with LOD1+
|
||||
vec2 shift = (scale < _vertex_spacing + 1e-6) ? // LOD0 or not
|
||||
// Shift from regular to symetric
|
||||
mix(v_fract, vec2(v_fract.x, -v_fract.y),
|
||||
round(fract(round(mod(v_vertex.z * _vertex_density, 4.0)) *
|
||||
round(mod(v_vertex.x * _vertex_density, 4.0)) * 0.25))
|
||||
) :
|
||||
// Symetric shift
|
||||
v_fract * round((fract(v_vertex.xz * 0.25 / scale) - 0.5) * 4.0);
|
||||
vec2 start_pos = v_vertex.xz * _vertex_density;
|
||||
vec2 end_pos = (v_vertex.xz - shift * scale) * _vertex_density;
|
||||
v_vertex.xz -= shift * scale * vertex_lerp;
|
||||
|
||||
// UV coordinates in world space. Values are 0 to _region_size within regions
|
||||
UV = v_vertex.xz * _vertex_density;
|
||||
|
||||
// UV coordinates in region space + texel offset. Values are 0 to 1 within regions
|
||||
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);
|
||||
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))) {
|
||||
v_vertex.x = 0. / 0.;
|
||||
} else {
|
||||
// Set final vertex height & calculate vertex normals. 3 lookups
|
||||
ivec3 uv_a = get_index_coord(start_pos, VERTEX_PASS);
|
||||
ivec3 uv_b = get_index_coord(end_pos, VERTEX_PASS);
|
||||
float h = mix(texelFetch(_height_maps, uv_a, 0).r,texelFetch(_height_maps, uv_b, 0).r,vertex_lerp);
|
||||
v_vertex.y = h;
|
||||
|
||||
// Vertex normals
|
||||
float u = mix(texelFetch(_height_maps, get_index_coord(start_pos + vec2(1,0), VERTEX_PASS), 0).r,
|
||||
texelFetch(_height_maps, get_index_coord(end_pos + vec2(1,0), VERTEX_PASS), 0).r, vertex_lerp);
|
||||
float v = mix(texelFetch(_height_maps, get_index_coord(start_pos + vec2(0,1), VERTEX_PASS), 0).r,
|
||||
texelFetch(_height_maps, get_index_coord(end_pos + vec2(0,1), VERTEX_PASS), 0).r, vertex_lerp);
|
||||
|
||||
v_normal = normalize(vec3(h - u, _vertex_spacing, h - v));
|
||||
}
|
||||
|
||||
// Convert model space to view space w/ skip_vertex_transform render mode
|
||||
VERTEX = (VIEW_MATRIX * vec4(v_vertex, 1.0)).xyz;
|
||||
|
||||
// Apply terrain normals
|
||||
vec3 w_tangent = normalize(cross(v_normal, vec3(0.0, 0.0, 1.0)));
|
||||
vec3 w_binormal = normalize(cross(v_normal, w_tangent));
|
||||
TBN = mat3(w_tangent, w_binormal, v_normal);
|
||||
|
||||
NORMAL = normalize((VIEW_MATRIX * vec4(v_normal, 0.0)).xyz);
|
||||
BINORMAL = normalize((VIEW_MATRIX * vec4(w_binormal, 0.0)).xyz);
|
||||
TANGENT = normalize((VIEW_MATRIX * vec4(w_tangent, 0.0)).xyz);
|
||||
}
|
||||
|
||||
////////////////////////
|
||||
// Fragment
|
||||
////////////////////////
|
||||
|
||||
mat2 rotate_plane(float angle) {
|
||||
float c = cos(angle), s = sin(angle);
|
||||
return mat2(vec2(c, s), vec2(-s, c));
|
||||
}
|
||||
|
||||
void fragment() {
|
||||
// Recover UVs
|
||||
vec2 uv = UV;
|
||||
vec2 uv2 = UV2;
|
||||
|
||||
// Lookup offsets, ID and blend weight
|
||||
vec3 region_uv = get_index_uv(uv2);
|
||||
const vec3 offsets = vec3(0, 1, 2);
|
||||
vec2 index_id = floor(uv);
|
||||
vec2 weight = fract(uv);
|
||||
vec2 invert = 1.0 - weight;
|
||||
vec4 weights = vec4(
|
||||
invert.x * weight.y, // 0
|
||||
weight.x * weight.y, // 1
|
||||
weight.x * invert.y, // 2
|
||||
invert.x * invert.y // 3
|
||||
);
|
||||
|
||||
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);
|
||||
|
||||
vec3 base_ddx = dFdxCoarse(v_vertex);
|
||||
vec3 base_ddy = dFdyCoarse(v_vertex);
|
||||
vec4 base_dd = vec4(base_ddx.xz, base_ddy.xz);
|
||||
// Calculate the effective mipmap for regionspace
|
||||
float region_mip = log2(max(length(base_ddx.xz), length(base_ddy.xz)) * _vertex_density);
|
||||
|
||||
// Color map
|
||||
vec4 color_map = region_uv.z > -1.0 ? textureLod(_color_maps, region_uv, region_mip) : COLOR_MAP;
|
||||
|
||||
if (flat_terrain_normals) {
|
||||
NORMAL = normalize(cross(dFdyCoarse(VERTEX),dFdxCoarse(VERTEX)));
|
||||
TANGENT = normalize(cross(NORMAL, VIEW_MATRIX[2].xyz));
|
||||
BINORMAL = normalize(cross(NORMAL, TANGENT));
|
||||
}
|
||||
|
||||
// defaults
|
||||
vec4 normal_rough = vec4(0., 1., 0., 0.7);
|
||||
vec4 albedo_height = vec4(1.);
|
||||
float normal_map_depth = 1.;
|
||||
float ao_strength = 0.;
|
||||
|
||||
if (enable_texturing) {
|
||||
// set to zero before accumulation
|
||||
albedo_height = vec4(0.);
|
||||
normal_rough = vec4(0.);
|
||||
normal_map_depth = 0.;
|
||||
ao_strength = 0.;
|
||||
float total_weight = 0.;
|
||||
float sharpness = fma(56., blend_sharpness, 8.);
|
||||
|
||||
// Get index control data
|
||||
// 1 - 4 lookups
|
||||
uvec4 control = floatBitsToUint(vec4(
|
||||
texelFetch(_control_maps, index[0], 0).r,
|
||||
texelFetch(_control_maps, index[1], 0).r,
|
||||
texelFetch(_control_maps, index[2], 0).r,
|
||||
texelFetch(_control_maps, index[3], 0).r));
|
||||
|
||||
{
|
||||
// Auto blend calculation
|
||||
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));
|
||||
uint u_auto =
|
||||
((uint(auto_base_texture) & 0x1Fu) << 27u) |
|
||||
((uint(auto_overlay_texture) & 0x1Fu) << 22u) |
|
||||
((uint(fma(auto_blend, 255.0 , 0.5)) & 0xFFu) << 14u);
|
||||
control = control * (1u - is_auto) + u_auto * is_auto;
|
||||
}
|
||||
|
||||
|
||||
// Texture weights
|
||||
// Vectorised Deocode of all texture IDs, then swizzle to per index mapping.
|
||||
ivec4 t_id[2] = {ivec4(control >> uvec4(27u) & uvec4(0x1Fu)),
|
||||
ivec4(control >> uvec4(22u) & uvec4(0x1Fu))};
|
||||
ivec2 texture_ids[4] = ivec2[4](
|
||||
ivec2(t_id[0].x, t_id[1].x),
|
||||
ivec2(t_id[0].y, t_id[1].y),
|
||||
ivec2(t_id[0].z, t_id[1].z),
|
||||
ivec2(t_id[0].w, t_id[1].w));
|
||||
|
||||
// interpolated weights.
|
||||
vec4 weights_id_1 = vec4(control >> uvec4(14u) & uvec4(0xFFu)) * DIV_255 * weights;
|
||||
vec4 weights_id_0 = weights - weights_id_1;
|
||||
vec2 t_weights[4] = {vec2(0), vec2(0), vec2(0), vec2(0)};
|
||||
for (int i = 0; i < 4; i++) {
|
||||
vec2 w_0 = vec2(weights_id_0[i]);
|
||||
vec2 w_1 = vec2(weights_id_1[i]);
|
||||
ivec2 id_0 = texture_ids[i].xx;
|
||||
ivec2 id_1 = texture_ids[i].yy;
|
||||
t_weights[0] += fma(w_0, vec2(equal(texture_ids[0], id_0)), w_1 * vec2(equal(texture_ids[0], id_1)));
|
||||
t_weights[1] += fma(w_0, vec2(equal(texture_ids[1], id_0)), w_1 * vec2(equal(texture_ids[1], id_1)));
|
||||
t_weights[2] += fma(w_0, vec2(equal(texture_ids[2], id_0)), w_1 * vec2(equal(texture_ids[2], id_1)));
|
||||
t_weights[3] += fma(w_0, vec2(equal(texture_ids[3], id_0)), w_1 * vec2(equal(texture_ids[3], id_1)));
|
||||
}
|
||||
|
||||
|
||||
// Process control data to determine each texture ID present, so that only
|
||||
// a single sample will be needed later, as all id are contiguous when features
|
||||
// like detiling, scale, rotation, and projection are not present.
|
||||
// 2 to 16 lookups
|
||||
uint id_read = 0u; // 1 bit per possible ID
|
||||
// world normal adjustment requires acess to previous id during next iteration
|
||||
vec4 nrm = vec4(0.0, 1.0, 0.0, 1.0);
|
||||
// adjust uv scale to account for vertex spacing
|
||||
uv *= _vertex_spacing;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
for (int t = 0; t < 2; t++) {
|
||||
int id = texture_ids[i][t];
|
||||
uint mask = 1u << uint(id);
|
||||
if ((id_read & mask) == 0u) {
|
||||
// Set this id bit
|
||||
id_read |= mask;
|
||||
float id_w = t_weights[i][t];
|
||||
float id_scale = _texture_uv_scale_array[id] * 0.5;
|
||||
vec2 id_uv = fma(uv, vec2(id_scale), vec2(0.5));
|
||||
vec4 i_dd = base_dd * id_scale;
|
||||
vec4 alb = textureGrad(_texture_array_albedo, vec3(id_uv, float(id)), i_dd.xy, i_dd.zw);
|
||||
float world_normal = clamp(fma(TBN[0], vec3(nrm.x), fma(TBN[1], vec3(nrm.z), v_normal * vec3(nrm.y))).y, 0., 1.);
|
||||
nrm = textureGrad(_texture_array_normal, vec3(id_uv, float(id)), i_dd.xy, i_dd.zw);
|
||||
alb.rgb *= _texture_color_array[id].rgb;
|
||||
nrm.a = clamp(nrm.a + _texture_roughness_mod_array[id], 0., 1.);
|
||||
// Unpack normal map for blending.
|
||||
nrm.xyz = fma(nrm.xzy, vec3(2.0), vec3(-1.0));
|
||||
// height weight modifier.
|
||||
float id_weight = exp2(sharpness * log2(id_w + alb.a * world_normal));
|
||||
albedo_height += alb * id_weight;
|
||||
normal_rough += nrm * id_weight;
|
||||
normal_map_depth += _texture_normal_depth_array[id] * id_weight;
|
||||
ao_strength += _texture_ao_strength_array[id] * id_weight;
|
||||
total_weight += id_weight;
|
||||
}
|
||||
}
|
||||
}
|
||||
// normalize accumulated values back to 0.0 - 1.0 range.
|
||||
float weight_inv = 1.0 / total_weight;
|
||||
albedo_height *= weight_inv;
|
||||
normal_rough *= weight_inv;
|
||||
normal_map_depth *= weight_inv;
|
||||
ao_strength *= weight_inv;
|
||||
}
|
||||
|
||||
// Macro variation. 2 lookups
|
||||
vec3 macrov = vec3(1.);
|
||||
if (enable_macro_variation) {
|
||||
float noise1 = texture(noise_texture, (uv * noise1_scale * .1 + noise1_offset) * rotate_plane(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(v_normal.y + macro_variation_slope, 0., 1.));
|
||||
}
|
||||
|
||||
// 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, normal_rough.a), 0., 1.);
|
||||
|
||||
// Apply PBR
|
||||
ALBEDO = albedo_height.rgb * color_map.rgb * macrov;
|
||||
ROUGHNESS = roughness;
|
||||
SPECULAR = 1. - normal_rough.a;
|
||||
// Repack final normal map value.
|
||||
NORMAL_MAP = fma(normalize(normal_rough.xzy), vec3(0.5), vec3(0.5));
|
||||
NORMAL_MAP_DEPTH = normal_map_depth;
|
||||
|
||||
// Higher and/or facing up, less occluded.
|
||||
float ao = (1. - (albedo_height.a * log(2.1 - ao_strength))) * (1. - normal_rough.y);
|
||||
AO = clamp(1. - ao * ao_strength, albedo_height.a, 1.0);
|
||||
AO_LIGHT_AFFECT = (1.0 - albedo_height.a) * clamp(normal_rough.y, 0., 1.);
|
||||
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uid://bbx2xhanpq5l3
|
||||
217
addons/terrain_3d/extras/shaders/minimum.gdshader
Normal file
217
addons/terrain_3d/extras/shaders/minimum.gdshader
Normal file
@@ -0,0 +1,217 @@
|
||||
// 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;
|
||||
|
||||
// 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)
|
||||
#define dFdyCoarse(a) dFdy(a)
|
||||
#endif
|
||||
|
||||
// Private uniforms
|
||||
// Commented uniforms aren't needed for this shader, but are available for your own needs.
|
||||
uniform vec3 _camera_pos = vec3(0.f);
|
||||
uniform float _mesh_size = 48.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 _region_size = 1024.0;
|
||||
uniform float _region_texel_size = 0.0009765625; // = 1/1024
|
||||
uniform int _region_map_size = 32;
|
||||
uniform int _region_map[1024];
|
||||
//uniform vec2 _region_locations[1024];
|
||||
//uniform float _texture_uv_scale_array[32];
|
||||
//uniform float _texture_detile_array[32];
|
||||
//uniform vec4 _texture_color_array[32];
|
||||
uniform highp sampler2DArray _height_maps : repeat_disable;
|
||||
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;
|
||||
|
||||
// Public Uniforms
|
||||
uniform bool flat_terrain_normals = false;
|
||||
|
||||
// Varyings & Types
|
||||
// Some are required for editor functions
|
||||
varying float v_vertex_xz_dist;
|
||||
varying vec3 v_vertex;
|
||||
|
||||
////////////////////////
|
||||
// Vertex
|
||||
////////////////////////
|
||||
|
||||
// Takes in world space XZ (UV) coordinates & search depth (only applicable for background mode none)
|
||||
// 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) {
|
||||
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);
|
||||
}
|
||||
|
||||
void vertex() {
|
||||
// Get vertex of flat plane in world coordinates and set world UV
|
||||
v_vertex = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz;
|
||||
|
||||
// Camera distance to vertex on flat plane
|
||||
v_vertex_xz_dist = length(v_vertex.xz - _camera_pos.xz);
|
||||
|
||||
// Geomorph vertex, set end and start for linear height interpolate
|
||||
float scale = MODEL_MATRIX[0][0];
|
||||
float vertex_lerp = smoothstep(0.55, 0.95, (v_vertex_xz_dist / scale - _mesh_size - 4.0) / (_mesh_size - 2.0));
|
||||
vec2 v_fract = fract(VERTEX.xz * 0.5) * 2.0;
|
||||
// For LOD0 morph from a regular grid to an alternating grid to align with LOD1+
|
||||
vec2 shift = (scale < _vertex_spacing + 1e-6) ? // LOD0 or not
|
||||
// Shift from regular to symetric
|
||||
mix(v_fract, vec2(v_fract.x, -v_fract.y),
|
||||
round(fract(round(mod(v_vertex.z * _vertex_density, 4.0)) *
|
||||
round(mod(v_vertex.x * _vertex_density, 4.0)) * 0.25))
|
||||
) :
|
||||
// Symetric shift
|
||||
v_fract * round((fract(v_vertex.xz * 0.25 / scale) - 0.5) * 4.0);
|
||||
vec2 start_pos = v_vertex.xz * _vertex_density;
|
||||
vec2 end_pos = (v_vertex.xz - shift * scale) * _vertex_density;
|
||||
v_vertex.xz -= shift * scale * vertex_lerp;
|
||||
|
||||
// UV coordinates in world space. Values are 0 to _region_size within regions
|
||||
UV = v_vertex.xz * _vertex_density;
|
||||
|
||||
// UV coordinates in region space + texel offset. Values are 0 to 1 within regions
|
||||
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);
|
||||
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))) {
|
||||
v_vertex.x = 0. / 0.;
|
||||
} else {
|
||||
// Interpolate Geomorph Start & End, set height. 2 Lookups.
|
||||
ivec3 uv_a = get_index_coord(start_pos, VERTEX_PASS);
|
||||
ivec3 uv_b = get_index_coord(end_pos, VERTEX_PASS);
|
||||
float h = mix(texelFetch(_height_maps, uv_a, 0).r, texelFetch(_height_maps, uv_b, 0).r, vertex_lerp);
|
||||
v_vertex.y = h;
|
||||
}
|
||||
|
||||
// Convert model space to view space w/ skip_vertex_transform render mode
|
||||
VERTEX = (VIEW_MATRIX * vec4(v_vertex, 1.0)).xyz;
|
||||
NORMAL = normalize((MODELVIEW_MATRIX * vec4(NORMAL, 0.0)).xyz);
|
||||
BINORMAL = normalize((MODELVIEW_MATRIX * vec4(BINORMAL, 0.0)).xyz);
|
||||
TANGENT = normalize((MODELVIEW_MATRIX * vec4(TANGENT, 0.0)).xyz);
|
||||
}
|
||||
|
||||
////////////////////////
|
||||
// Fragment
|
||||
////////////////////////
|
||||
|
||||
void fragment() {
|
||||
// Recover UVs
|
||||
vec2 uv = UV;
|
||||
//vec2 uv2 = UV2;
|
||||
|
||||
// Lookup offsets, ID and blend weight
|
||||
const vec3 offsets = vec3(0, 1, 2);
|
||||
vec2 index_id = floor(uv);
|
||||
vec2 weight = fract(uv);
|
||||
vec2 invert = 1.0 - weight;
|
||||
vec4 weights = vec4(
|
||||
invert.x * weight.y, // 0
|
||||
weight.x * weight.y, // 1
|
||||
weight.x * invert.y, // 2
|
||||
invert.x * invert.y // 3
|
||||
);
|
||||
|
||||
vec3 base_ddx = dFdxCoarse(v_vertex);
|
||||
vec3 base_ddy = dFdyCoarse(v_vertex);
|
||||
//vec4 base_derivatives = vec4(base_ddx.xz, base_ddy.xz);
|
||||
// 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;
|
||||
|
||||
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);
|
||||
|
||||
// Terrain normals
|
||||
vec3 index_normal[4];
|
||||
float h[4];
|
||||
// allows additional derivatives, eg world noise, brush previews etc
|
||||
float u = 0.0;
|
||||
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)
|
||||
index_normal[3] = normalize(vec3(h[3] - h[2] + u, _vertex_spacing, h[3] - h[0] + v));
|
||||
|
||||
// Set flat world normal - overriden if bilerp is true
|
||||
vec3 w_normal = index_normal[3];
|
||||
|
||||
// Branching smooth normals must be done seperatley for correct normals at all 4 index ids
|
||||
if (bilerp) {
|
||||
// 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)
|
||||
|
||||
// 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));
|
||||
index_normal[1] = normalize(vec3(h[1] - h_5 + u, _vertex_spacing, h[1] - h_4 + v));
|
||||
index_normal[2] = normalize(vec3(h[2] - h_6 + u, _vertex_spacing, h[2] - h[1] + v));
|
||||
|
||||
// Set interpolated world normal
|
||||
w_normal =
|
||||
index_normal[0] * weights[0] +
|
||||
index_normal[1] * weights[1] +
|
||||
index_normal[2] * weights[2] +
|
||||
index_normal[3] * weights[3] ;
|
||||
}
|
||||
|
||||
// Apply terrain normals
|
||||
vec3 w_tangent = normalize(cross(w_normal, vec3(0.0, 0.0, 1.0)));
|
||||
vec3 w_binormal = normalize(cross(w_normal, w_tangent));
|
||||
NORMAL = mat3(VIEW_MATRIX) * w_normal;
|
||||
TANGENT = mat3(VIEW_MATRIX) * w_tangent;
|
||||
BINORMAL = mat3(VIEW_MATRIX) * w_binormal;
|
||||
|
||||
if (flat_terrain_normals) {
|
||||
NORMAL = normalize(cross(dFdyCoarse(VERTEX),dFdxCoarse(VERTEX)));
|
||||
TANGENT = normalize(cross(NORMAL, VIEW_MATRIX[2].xyz));
|
||||
BINORMAL = normalize(cross(NORMAL, TANGENT));
|
||||
}
|
||||
|
||||
// Apply PBR
|
||||
ALBEDO = vec3(.2);
|
||||
ROUGHNESS = .7;
|
||||
}
|
||||
1
addons/terrain_3d/extras/shaders/minimum.gdshader.uid
Normal file
1
addons/terrain_3d/extras/shaders/minimum.gdshader.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://01qauauvd8aa
|
||||
Reference in New Issue
Block a user