diff --git a/tests/terrain_projection_validation.gd b/tests/terrain_projection_validation.gd new file mode 100644 index 0000000..88a9999 --- /dev/null +++ b/tests/terrain_projection_validation.gd @@ -0,0 +1,60 @@ +extends SceneTree + +const REGION := preload("res://world/regions/starter_island_region.tscn") + + +func _initialize() -> void: + call_deferred("_run") + + +func _run() -> void: + var region := REGION.instantiate() + root.add_child(region) + await process_frame + var terrain := region.get_node( + "Terrain/Visual/starter_island" + ) as MeshInstance3D + assert(terrain != null) + assert(terrain.mesh.get_surface_count() == 5) + assert(terrain.mesh.surface_get_name(0) == "grass_lite") + assert(terrain.mesh.surface_get_name(1) == "dirt") + assert(terrain.mesh.surface_get_name(2) == "sand") + assert(terrain.mesh.surface_get_name(3) == "cliff_wall") + assert(terrain.mesh.surface_get_name(4) == "dirt_wall") + + _validate_projected_material(terrain.get_surface_override_material(0)) + _validate_projected_material(terrain.get_surface_override_material(2)) + for surface: int in [1, 3, 4]: + assert(terrain.get_surface_override_material(surface) == null) + assert(terrain.mesh.surface_get_material(surface) is StandardMaterial3D) + + print("Terrain projection validation: PASS") + region.free() + quit() + + +func _validate_projected_material(material: Material) -> void: + assert(material is ShaderMaterial) + var shader_material := material as ShaderMaterial + assert( + shader_material.shader.resource_path + == "res://world/materials/terrain_surface_projection.gdshader" + ) + assert( + is_equal_approx( + float(shader_material.get_shader_parameter("tile_world_size")), + 2.5 + ) + ) + assert( + is_equal_approx( + float(shader_material.get_shader_parameter("blend_sharpness")), + 6.0 + ) + ) + assert( + is_equal_approx( + float(shader_material.get_shader_parameter("top_projection_bias")), + 2.0 + ) + ) diff --git a/tests/terrain_projection_validation.gd.uid b/tests/terrain_projection_validation.gd.uid new file mode 100644 index 0000000..ad92fbb --- /dev/null +++ b/tests/terrain_projection_validation.gd.uid @@ -0,0 +1 @@ +uid://cto4rc6bn0n4o diff --git a/world/materials/starter_island_grass.tres b/world/materials/starter_island_grass.tres index f4ee32d..51ea444 100644 --- a/world/materials/starter_island_grass.tres +++ b/world/materials/starter_island_grass.tres @@ -1,10 +1,15 @@ -[gd_resource type="StandardMaterial3D" load_steps=2 format=3] +[gd_resource type="ShaderMaterial" load_steps=3 format=3] [ext_resource type="Texture2D" path="res://art/exported/environment/textures/grass_lite.png" id="1_texture"] +[ext_resource type="Shader" path="res://world/materials/terrain_surface_projection.gdshader" id="2_shader"] [resource] resource_name = "Starter Island Grass" -texture_filter = 4 -albedo_texture = ExtResource("1_texture") -roughness = 1.0 -uv1_scale = Vector3(21.406, 21.406, 21.406) +shader = ExtResource("2_shader") +shader_parameter/albedo_texture = ExtResource("1_texture") +shader_parameter/albedo_tint = Color(1, 1, 1, 1) +shader_parameter/tile_world_size = 2.5 +shader_parameter/blend_sharpness = 6.0 +shader_parameter/top_projection_bias = 2.0 +shader_parameter/roughness = 1.0 +shader_parameter/specular = 0.5 diff --git a/world/materials/starter_island_sand.tres b/world/materials/starter_island_sand.tres index 6c9be5a..b967425 100644 --- a/world/materials/starter_island_sand.tres +++ b/world/materials/starter_island_sand.tres @@ -1,10 +1,15 @@ -[gd_resource type="StandardMaterial3D" load_steps=2 format=3] +[gd_resource type="ShaderMaterial" load_steps=3 format=3] [ext_resource type="Texture2D" path="res://art/exported/environment/textures/sand.png" id="1_texture"] +[ext_resource type="Shader" path="res://world/materials/terrain_surface_projection.gdshader" id="2_shader"] [resource] resource_name = "Starter Island Sand" -texture_filter = 4 -albedo_texture = ExtResource("1_texture") -roughness = 1.0 -uv1_scale = Vector3(21.406, 21.406, 21.406) +shader = ExtResource("2_shader") +shader_parameter/albedo_texture = ExtResource("1_texture") +shader_parameter/albedo_tint = Color(1, 1, 1, 1) +shader_parameter/tile_world_size = 2.5 +shader_parameter/blend_sharpness = 6.0 +shader_parameter/top_projection_bias = 2.0 +shader_parameter/roughness = 1.0 +shader_parameter/specular = 0.5 diff --git a/world/materials/terrain_surface_projection.gdshader b/world/materials/terrain_surface_projection.gdshader new file mode 100644 index 0000000..4842e50 --- /dev/null +++ b/world/materials/terrain_surface_projection.gdshader @@ -0,0 +1,46 @@ +shader_type spatial; +render_mode cull_back; + +uniform sampler2D albedo_texture : source_color, repeat_enable, filter_nearest_mipmap_anisotropic; +uniform vec4 albedo_tint : source_color = vec4(1.0); +uniform float tile_world_size : hint_range(0.1, 20.0, 0.05) = 2.5; +uniform float blend_sharpness : hint_range(1.0, 16.0, 0.25) = 6.0; +uniform float top_projection_bias : hint_range(1.0, 8.0, 0.05) = 2.0; +uniform float roughness : hint_range(0.0, 1.0, 0.01) = 1.0; +uniform float specular : hint_range(0.0, 1.0, 0.01) = 0.5; + +varying vec3 world_position; +varying vec3 world_normal; + + +void vertex() { + world_position = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz; + world_normal = normalize(MODEL_NORMAL_MATRIX * NORMAL); +} + + +void fragment() { + vec3 normal = normalize(world_normal); + vec3 weights = pow(abs(normal), vec3(max(blend_sharpness, 1.0))); + float upward_amount = smoothstep(0.0, 0.8, normal.y); + weights.y *= mix(1.0, max(top_projection_bias, 1.0), upward_amount); + weights /= max(weights.x + weights.y + weights.z, 0.00001); + + float world_scale = 1.0 / max(tile_world_size, 0.001); + vec2 x_uv = world_position.zy * vec2(sign(normal.x), 1.0) * world_scale; + vec2 y_uv = world_position.xz * vec2(sign(normal.y), 1.0) * world_scale; + vec2 z_uv = world_position.xy * vec2(-sign(normal.z), 1.0) * world_scale; + vec4 x_sample = texture(albedo_texture, x_uv); + vec4 y_sample = texture(albedo_texture, y_uv); + vec4 z_sample = texture(albedo_texture, z_uv); + vec4 projected = ( + x_sample * weights.x + + y_sample * weights.y + + z_sample * weights.z + ); + + ALBEDO = projected.rgb * albedo_tint.rgb; + ROUGHNESS = roughness; + SPECULAR = specular; + METALLIC = 0.0; +} diff --git a/world/materials/terrain_surface_projection.gdshader.uid b/world/materials/terrain_surface_projection.gdshader.uid new file mode 100644 index 0000000..dd9fa70 --- /dev/null +++ b/world/materials/terrain_surface_projection.gdshader.uid @@ -0,0 +1 @@ +uid://d2ju2vpykktuo