From cce10db0cd2528cc4363060fae7aff59e9d93ea9 Mon Sep 17 00:00:00 2001 From: lfxu Date: Tue, 9 Jan 2024 20:46:16 -0800 Subject: [PATCH] Implements vertex spacing (mesh density) --- project/addons/terrain_3d/editor/editor.gd | 5 ++- src/shaders/editor_functions.glsl | 2 +- src/shaders/main.glsl | 44 ++++++++++++------- src/terrain_3d.cpp | 50 +++++++++++++++------- src/terrain_3d.h | 3 ++ src/terrain_3d_editor.cpp | 1 - src/terrain_3d_material.cpp | 6 +++ src/terrain_3d_material.h | 3 ++ 8 files changed, 80 insertions(+), 34 deletions(-) diff --git a/project/addons/terrain_3d/editor/editor.gd b/project/addons/terrain_3d/editor/editor.gd index a79c729d4..6735df364 100644 --- a/project/addons/terrain_3d/editor/editor.gd +++ b/project/addons/terrain_3d/editor/editor.gd @@ -133,6 +133,9 @@ func _forward_3d_gui_input(p_viewport_camera: Camera3D, p_event: InputEvent) -> ui.decal.albedo_mix = 1.0 ui.decal_timer.start() + # Incooporate vertex spacing + mouse_global_position /= terrain.get_mesh_vertex_spacing() + ## Update region highlight var region_size = terrain.get_storage().get_region_size() var region_position: Vector2 = (Vector2(mouse_global_position.x, mouse_global_position.z) / region_size).floor() @@ -216,7 +219,7 @@ func update_region_grid() -> void: 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_storage().get_region_size() + region_gizmo.region_size = terrain.get_storage().get_region_size() * terrain.get_mesh_vertex_spacing() region_gizmo.grid = terrain.get_storage().get_region_offsets() terrain.update_gizmos() diff --git a/src/shaders/editor_functions.glsl b/src/shaders/editor_functions.glsl index a4ccb3b94..012b4036e 100644 --- a/src/shaders/editor_functions.glsl +++ b/src/shaders/editor_functions.glsl @@ -6,7 +6,7 @@ R"( //INSERT: EDITOR_NAVIGATION // Show navigation - if(bool(texelFetch(_control_maps, get_region_uv(floor(UV)), 0).r >>1u & 0x1u)) { + if(bool(texelFetch(_control_maps, get_region_uv(floor(uv)), 0).r >>1u & 0x1u)) { ALBEDO *= vec3(.5, .0, .85); } diff --git a/src/shaders/main.glsl b/src/shaders/main.glsl index 1abf44995..d2c561c67 100644 --- a/src/shaders/main.glsl +++ b/src/shaders/main.glsl @@ -23,6 +23,7 @@ render_mode blend_mix,depth_draw_opaque,cull_back,diffuse_burley,specular_schlic // Private uniforms uniform float _region_size = 1024.0; uniform float _region_texel_size = 0.0009765625; // = 1/1024 +uniform float _mesh_vertex_density = 1.0; uniform int _region_map_size = 16; uniform int _region_map[256]; uniform vec2 _region_offsets[256]; @@ -61,9 +62,11 @@ struct Material { }; varying vec3 v_vertex; // World coordinate vertex location -varying vec3 v_camera_pos; +varying flat vec3 v_camera_pos; varying float v_vertex_dist; varying flat ivec3 v_region; +varying flat vec2 v_uv_offset; +varying flat vec2 v_uv2_offset; //////////////////////// // Vertex @@ -110,7 +113,7 @@ void vertex() { v_vertex = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz; // UV coordinates in world space. Values are 0 to _region_size within regions - UV = v_vertex.xz; + UV = v_vertex.xz * _mesh_vertex_density; // Discard vertices if designated as a hole or background disabled. 1 lookup. v_region = get_region_uv(UV); @@ -120,14 +123,19 @@ void vertex() { VERTEX.x = 0./0.; } else { // UV coordinates in region space + texel offset. Values are 0 to 1 within regions - UV2 = (UV + vec2(0.5)) * _region_texel_size; + UV2 = (round(v_vertex.xz * _mesh_vertex_density) + vec2(0.5)) * _region_texel_size; // Get final vertex location and save it VERTEX.y = get_height(UV2); - v_vertex = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz; v_vertex_dist = length(v_vertex - v_camera_pos); //INSERT: DUAL_SCALING_VERTEX } + + // Transform UVs to local to avoid poor precision during varying interpolation. + v_uv_offset = MODEL_MATRIX[3].xz * _mesh_vertex_density; + UV -= v_uv_offset; + v_uv2_offset = v_uv_offset * _region_texel_size; + UV2 -= v_uv_offset; } //////////////////////// @@ -248,16 +256,20 @@ float blend_weights(float weight, float detail) { } void fragment() { + // Recover UVs + vec2 uv = UV + v_uv_offset; + vec2 uv2 = UV2 + v_uv_offset; + // Calculate Terrain Normals. 4 lookups vec3 w_tangent, w_binormal; - vec3 w_normal = get_normal(UV2, w_tangent, w_binormal); + vec3 w_normal = get_normal(uv2, w_tangent, w_binormal); NORMAL = mat3(VIEW_MATRIX) * w_normal; TANGENT = mat3(VIEW_MATRIX) * w_tangent; BINORMAL = mat3(VIEW_MATRIX) * w_binormal; // Idenfity 4 vertices surrounding this pixel - vec2 texel_pos = UV; - highp vec2 texel_pos_floor = floor(UV); + vec2 texel_pos = uv; + highp vec2 texel_pos_floor = floor(uv); // Create a cross hatch grid of alternating 0/1 horizontal and vertical stripes 1 unit wide in XY vec4 mirror = vec4(fract(texel_pos_floor * 0.5) * 2.0, 1.0, 1.0); @@ -278,18 +290,18 @@ void fragment() { // Get the textures for each vertex. 8-16 lookups (2-4 ea) Material mat[4]; - get_material(UV, control00, index00UV, w_normal, mat[0]); - get_material(UV, control01, index01UV, w_normal, mat[1]); - get_material(UV, control10, index10UV, w_normal, mat[2]); - get_material(UV, control11, index11UV, w_normal, mat[3]); + get_material(uv, control00, index00UV, w_normal, mat[0]); + get_material(uv, control01, index01UV, w_normal, mat[1]); + get_material(uv, control10, index10UV, w_normal, mat[2]); + get_material(uv, control11, index11UV, w_normal, mat[3]); // Calculate weight for the pixel position between the vertices - // Bilinear interpolation of difference of UV and floor(UV) + // Bilinear interpolation of difference of uv and floor(uv) vec2 weights1 = clamp(texel_pos - texel_pos_floor, 0, 1); weights1 = mix(weights1, vec2(1.0) - weights1, mirror.xy); vec2 weights0 = vec2(1.0) - weights1; // Adjust final weights by noise. 1 lookup - float noise3 = texture(noise_texture, UV*noise3_scale).r; + float noise3 = texture(noise_texture, uv*noise3_scale).r; vec4 weights; weights.x = blend_weights(weights0.x * weights0.y, noise3); weights.y = blend_weights(weights0.x * weights1.y, noise3); @@ -313,7 +325,7 @@ void fragment() { mat[3].nrm_rg * weights.w ); // Determine if we're in a region or not (region_uv.z>0) - vec3 region_uv = get_region_uv2(UV2); + vec3 region_uv = get_region_uv2(uv2); // Colormap. 1 lookup vec4 color_map = vec4(1., 1., 1., .5); @@ -324,8 +336,8 @@ void fragment() { } // Macro variation. 2 Lookups - float noise1 = texture(noise_texture, rotate(UV*noise1_scale*.1, cos(noise1_angle), sin(noise1_angle)) + noise1_offset).r; - float noise2 = texture(noise_texture, UV*noise2_scale*.1).r; + float noise1 = texture(noise_texture, rotate(uv*noise1_scale*.1, cos(noise1_angle), sin(noise1_angle)) + noise1_offset).r; + float noise2 = texture(noise_texture, uv*noise2_scale*.1).r; vec3 macrov = mix(macro_variation1, vec3(1.), clamp(noise1 + v_vertex_dist*.0002, 0., 1.)); macrov *= mix(macro_variation2, vec3(1.), clamp(noise2 + v_vertex_dist*.0002, 0., 1.)); diff --git a/src/terrain_3d.cpp b/src/terrain_3d.cpp index 93df9dcfb..cd3a5e746 100644 --- a/src/terrain_3d.cpp +++ b/src/terrain_3d.cpp @@ -66,6 +66,7 @@ void Terrain3D::_initialize() { // Initialize the system if (!_initialized && _is_inside_world && is_inside_tree()) { _material->initialize(_storage->get_region_size()); + _material->set_mesh_vertex_spacing(_mesh_vertex_spacing); _storage->update_regions(true); // generate map arrays _texture_list->update_list(); // generate texture arrays _build(_mesh_lods, _mesh_size); @@ -363,6 +364,7 @@ void Terrain3D::_update_collision() { // Rotated shape Y=90 for -90 rotated array index Transform3D xform = Transform3D(Basis(Vector3(0, 1.0, 0), Math_PI * .5), global_pos + Vector3(region_size, 0, region_size) * .5); + xform.scale(Vector3(_mesh_vertex_spacing, 1, _mesh_vertex_spacing)); if (!_show_debug_collision) { RID shape = PhysicsServer3D::get_singleton()->heightmap_shape_create(); @@ -494,10 +496,10 @@ void Terrain3D::_generate_triangles(PackedVector3Array &p_vertices, PackedVector } } } else { - int32_t z_start = (int32_t)Math::ceil(p_global_aabb.position.z); - int32_t z_end = (int32_t)Math::floor(p_global_aabb.get_end().z) + 1; - int32_t x_start = (int32_t)Math::ceil(p_global_aabb.position.x); - int32_t x_end = (int32_t)Math::floor(p_global_aabb.get_end().x) + 1; + int32_t z_start = (int32_t)Math::ceil(p_global_aabb.position.z / _mesh_vertex_spacing); + int32_t z_end = (int32_t)Math::floor(p_global_aabb.get_end().z / _mesh_vertex_spacing) + 1; + int32_t x_start = (int32_t)Math::ceil(p_global_aabb.position.x / _mesh_vertex_spacing); + int32_t x_end = (int32_t)Math::floor(p_global_aabb.get_end().x / _mesh_vertex_spacing) + 1; for (int32_t z = z_start; z < z_end; ++z) { for (int32_t x = x_start; x < x_end; ++x) { @@ -516,11 +518,12 @@ void Terrain3D::_generate_triangle_pair(PackedVector3Array &p_vertices, PackedVe uint32_t control1 = _storage->get_control(Vector3(x, 0.0, z)); uint32_t control2 = _storage->get_control(Vector3(x + step, 0.0, z + step)); uint32_t control3 = _storage->get_control(Vector3(x, 0.0, z + step)); + Vector3 vertex_scaler = Vector3(_mesh_vertex_spacing, 1.0, _mesh_vertex_spacing); if (!Util::is_hole(control1) && !Util::is_hole(control2) && !Util::is_hole(control3)) { if (!p_require_nav || (Util::is_nav(control1) && Util::is_nav(control2) && Util::is_nav(control3))) { - Vector3 v1 = _storage->get_mesh_vertex(p_lod, p_filter, Vector3(x, 0.0, z)); - Vector3 v2 = _storage->get_mesh_vertex(p_lod, p_filter, Vector3(x + step, 0.0, z + step)); - Vector3 v3 = _storage->get_mesh_vertex(p_lod, p_filter, Vector3(x, 0.0, z + step)); + Vector3 v1 = _storage->get_mesh_vertex(p_lod, p_filter, Vector3(x, 0.0, z)) * vertex_scaler; + Vector3 v2 = _storage->get_mesh_vertex(p_lod, p_filter, Vector3(x + step, 0.0, z + step)) * vertex_scaler; + Vector3 v3 = _storage->get_mesh_vertex(p_lod, p_filter, Vector3(x, 0.0, z + step)) * vertex_scaler; p_vertices.push_back(v1); p_vertices.push_back(v2); p_vertices.push_back(v3); @@ -537,9 +540,9 @@ void Terrain3D::_generate_triangle_pair(PackedVector3Array &p_vertices, PackedVe control3 = _storage->get_control(Vector3(x + step, 0.0, z + step)); if (!Util::is_hole(control1) && !Util::is_hole(control2) && !Util::is_hole(control3)) { if (!p_require_nav || (Util::is_nav(control1) && Util::is_nav(control2) && Util::is_nav(control3))) { - Vector3 v1 = _storage->get_mesh_vertex(p_lod, p_filter, Vector3(x, 0.0, z)); - Vector3 v2 = _storage->get_mesh_vertex(p_lod, p_filter, Vector3(x + step, 0.0, z)); - Vector3 v3 = _storage->get_mesh_vertex(p_lod, p_filter, Vector3(x + step, 0.0, z + step)); + Vector3 v1 = _storage->get_mesh_vertex(p_lod, p_filter, Vector3(x, 0.0, z)) * vertex_scaler; + Vector3 v2 = _storage->get_mesh_vertex(p_lod, p_filter, Vector3(x + step, 0.0, z)) * vertex_scaler; + Vector3 v3 = _storage->get_mesh_vertex(p_lod, p_filter, Vector3(x + step, 0.0, z + step)) * vertex_scaler; p_vertices.push_back(v1); p_vertices.push_back(v2); p_vertices.push_back(v3); @@ -604,6 +607,18 @@ void Terrain3D::set_mesh_size(int p_size) { } } +void Terrain3D::set_mesh_vertex_spacing(float p_spacing) { + if (_mesh_vertex_spacing != p_spacing) { + LOG(INFO, "Setting mesh vertex spacing: ", p_spacing); + _mesh_vertex_spacing = p_spacing; + _clear(); + _initialize(); + } + if (Engine::get_singleton()->is_editor_hint() && _plugin != nullptr) { + _plugin->call("update_region_grid"); + } +} + void Terrain3D::set_material(const Ref &p_material) { if (_storage != p_material) { LOG(INFO, "Setting material"); @@ -695,17 +710,19 @@ void Terrain3D::snap(Vector3 p_cam_pos) { p_cam_pos.y = 0; LOG(DEBUG_CONT, "Snapping terrain to: ", String(p_cam_pos)); - Transform3D t = Transform3D(Basis(), p_cam_pos.floor()); + Vector3 snapped_pos = (p_cam_pos / _mesh_vertex_spacing).floor() * _mesh_vertex_spacing; + Transform3D t = Transform3D().scaled(Vector3(_mesh_vertex_spacing, 1, _mesh_vertex_spacing)); + t.origin = snapped_pos; RS->instance_set_transform(_data.cross, t); int edge = 0; int tile = 0; for (int l = 0; l < _mesh_lods; l++) { - real_t scale = real_t(1 << l); + real_t scale = real_t(1 << l) * _mesh_vertex_spacing; Vector3 snapped_pos = (p_cam_pos / scale).floor() * scale; - Vector3 tile_size = Vector3(real_t(_mesh_size << l), 0, real_t(_mesh_size << l)); - Vector3 base = snapped_pos - Vector3(real_t(_mesh_size << (l + 1)), 0, real_t(_mesh_size << (l + 1))); + Vector3 tile_size = Vector3(real_t(_mesh_size << l), 0, real_t(_mesh_size << l)) * _mesh_vertex_spacing; + Vector3 base = snapped_pos - Vector3(real_t(_mesh_size << (l + 1)), 0, real_t(_mesh_size << (l + 1))) * _mesh_vertex_spacing; // Position tiles for (int x = 0; x < 4; x++) { @@ -756,7 +773,7 @@ void Terrain3D::snap(Vector3 p_cam_pos) { // Position seams { - Vector3 next_base = next_snapped_pos - Vector3(real_t(_mesh_size << (l + 1)), 0, real_t(_mesh_size << (l + 1))); + Vector3 next_base = next_snapped_pos - Vector3(real_t(_mesh_size << (l + 1)), 0, real_t(_mesh_size << (l + 1))) * _mesh_vertex_spacing; Transform3D t = Transform3D().scaled(Vector3(scale, 1, scale)); t.origin = next_base; RS->instance_set_transform(_data.seams[edge], t); @@ -1016,6 +1033,8 @@ void Terrain3D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_mesh_lods"), &Terrain3D::get_mesh_lods); ClassDB::bind_method(D_METHOD("set_mesh_size", "size"), &Terrain3D::set_mesh_size); ClassDB::bind_method(D_METHOD("get_mesh_size"), &Terrain3D::get_mesh_size); + ClassDB::bind_method(D_METHOD("set_mesh_vertex_spacing", "scale"), &Terrain3D::set_mesh_vertex_spacing); + ClassDB::bind_method(D_METHOD("get_mesh_vertex_spacing"), &Terrain3D::get_mesh_vertex_spacing); ClassDB::bind_method(D_METHOD("set_material", "material"), &Terrain3D::set_material); ClassDB::bind_method(D_METHOD("get_material"), &Terrain3D::get_material); @@ -1077,6 +1096,7 @@ void Terrain3D::_bind_methods() { ADD_GROUP("Mesh", "mesh_"); ADD_PROPERTY(PropertyInfo(Variant::INT, "mesh_lods", PROPERTY_HINT_RANGE, "1,10,1"), "set_mesh_lods", "get_mesh_lods"); ADD_PROPERTY(PropertyInfo(Variant::INT, "mesh_size", PROPERTY_HINT_RANGE, "8,64,1"), "set_mesh_size", "get_mesh_size"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "mesh_vertex_spacing", PROPERTY_HINT_RANGE, "0.5,50,0.5"), "set_mesh_vertex_spacing", "get_mesh_vertex_spacing"); ADD_GROUP("Debug", "debug_"); ADD_PROPERTY(PropertyInfo(Variant::INT, "debug_level", PROPERTY_HINT_ENUM, "Errors,Info,Debug,Debug Continuous"), "set_debug_level", "get_debug_level"); diff --git a/src/terrain_3d.h b/src/terrain_3d.h index 93134b8ab..c723b442e 100644 --- a/src/terrain_3d.h +++ b/src/terrain_3d.h @@ -35,6 +35,7 @@ class Terrain3D : public Node3D { // Terrain settings int _mesh_size = 48; int _mesh_lods = 7; + float _mesh_vertex_spacing = 1; Ref _storage; Ref _material; @@ -104,6 +105,8 @@ class Terrain3D : public Node3D { int get_mesh_lods() const { return _mesh_lods; } void set_mesh_size(int p_size); int get_mesh_size() const { return _mesh_size; } + void set_mesh_vertex_spacing(float p_spacing); + float get_mesh_vertex_spacing() const { return _mesh_vertex_spacing; } void set_storage(const Ref &p_storage); Ref get_storage() const { return _storage; } diff --git a/src/terrain_3d_editor.cpp b/src/terrain_3d_editor.cpp index 9c36268a6..410d96db1 100644 --- a/src/terrain_3d_editor.cpp +++ b/src/terrain_3d_editor.cpp @@ -71,7 +71,6 @@ void Terrain3DEditor::_operate_region(Vector3 p_global_position) { if (!has_region) { _terrain->get_storage()->add_region(p_global_position); modified = true; - } } if (_operation == SUBTRACT) { diff --git a/src/terrain_3d_material.cpp b/src/terrain_3d_material.cpp index 704081f94..1a82f2c3c 100644 --- a/src/terrain_3d_material.cpp +++ b/src/terrain_3d_material.cpp @@ -508,6 +508,12 @@ Variant Terrain3DMaterial::get_shader_param(const StringName &p_name) const { return value; } +void Terrain3DMaterial::set_mesh_vertex_spacing(float p_spacing) { + LOG(INFO, "Setting mesh vertex spacing in material: ", p_spacing); + _mesh_vertex_spacing = p_spacing; + RS->material_set_param(_material, "_mesh_vertex_density", 1.0f / p_spacing); +} + void Terrain3DMaterial::set_show_checkered(bool p_enabled) { LOG(INFO, "Enable set_show_checkered: ", p_enabled); _debug_view_checkered = p_enabled; diff --git a/src/terrain_3d_material.h b/src/terrain_3d_material.h index 8bc98fdef..c86a7e21e 100644 --- a/src/terrain_3d_material.h +++ b/src/terrain_3d_material.h @@ -63,6 +63,7 @@ class Terrain3DMaterial : public Resource { // Cached data from Storage int _texture_count = 0; int _region_size = 1024; + float _mesh_vertex_spacing = 1; Vector2i _region_sizev = Vector2i(_region_size, _region_size); PackedInt32Array _region_map; GeneratedTex _generated_region_blend_map; // 512x512 blurred image of region_map @@ -108,6 +109,8 @@ 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; + void set_mesh_vertex_spacing(float p_spacing); + // Editor functions / Debug views void set_show_checkered(bool p_enabled); bool get_show_checkered() const { return _debug_view_checkered; }