Refresh generated terrain assets and projection
This commit is contained in:
parent
3608be41ab
commit
05838406d0
53 changed files with 319 additions and 51 deletions
|
|
@ -481,6 +481,9 @@ func _validate_generated_region(
|
|||
assert(river_body_count == river_placement_count)
|
||||
|
||||
_validate_authored_chunk_surfaces(region, generator)
|
||||
_validate_projected_terrain_materials(
|
||||
generator.get_generated_chunks_root()
|
||||
)
|
||||
|
||||
var decorations := region.get_node("Decorations") as Node3D
|
||||
var anchors := region.get_node(
|
||||
|
|
@ -1147,21 +1150,28 @@ func _validate_tree_gatherable_anchors(
|
|||
anchors: GatherableAnchorSet3D,
|
||||
) -> void:
|
||||
var eligible_props: Dictionary[StringName, Node3D] = {}
|
||||
var expected_anchor_count := 0
|
||||
for child: Node in decorations.get_children():
|
||||
var prop := child as Node3D
|
||||
if prop == null:
|
||||
continue
|
||||
var prop_id := StringName(prop.get_meta(&"terrain_prop_id", &""))
|
||||
var definition := region.get_prop_catalog().definition_for_id(prop_id)
|
||||
if definition != null and definition.has_gatherable_surface():
|
||||
var socket_count := _count_authored_beetle_sockets(prop)
|
||||
if (
|
||||
definition != null
|
||||
and definition.has_gatherable_surface()
|
||||
and socket_count > 0
|
||||
):
|
||||
eligible_props[prop.name] = prop
|
||||
expected_anchor_count += socket_count
|
||||
var positions := anchors.get_spawn_positions()
|
||||
assert(positions.size() == eligible_props.size())
|
||||
assert(positions.size() == expected_anchor_count)
|
||||
assert(positions.size() >= 12)
|
||||
for child: Node in anchors.get_children():
|
||||
var anchor := child as Marker3D
|
||||
assert(anchor != null)
|
||||
assert(bool(anchor.get_meta(&"mesh_surface_sampled", false)))
|
||||
assert(bool(anchor.get_meta(&"authored_beetle_socket", false)))
|
||||
var prop_name := StringName(anchor.get_meta(&"terrain_prop_name", &""))
|
||||
assert(eligible_props.has(prop_name))
|
||||
var prop: Node3D = eligible_props[prop_name]
|
||||
|
|
@ -1169,14 +1179,17 @@ func _validate_tree_gatherable_anchors(
|
|||
var definition := region.get_prop_catalog().definition_for_id(prop_id)
|
||||
assert(definition != null and definition.has_gatherable_surface())
|
||||
var local_anchor := prop.to_local(anchor.global_position)
|
||||
assert(
|
||||
local_anchor.y
|
||||
>= definition.gatherable_surface_minimum_height - 0.001
|
||||
)
|
||||
assert(
|
||||
local_anchor.y
|
||||
<= definition.gatherable_surface_maximum_height + 0.001
|
||||
)
|
||||
assert(prop_id != &"prop_palm")
|
||||
assert(local_anchor.y >= 0.25 and local_anchor.y <= 2.0)
|
||||
|
||||
|
||||
func _count_authored_beetle_sockets(root_node: Node) -> int:
|
||||
var result := 0
|
||||
if String(root_node.name).to_lower().contains("beetle_socket"):
|
||||
result += 1
|
||||
for child: Node in root_node.get_children():
|
||||
result += _count_authored_beetle_sockets(child)
|
||||
return result
|
||||
|
||||
|
||||
func _find_mesh_instance(root_node: Node) -> MeshInstance3D:
|
||||
|
|
@ -1357,6 +1370,56 @@ func _material_names(mesh_instance: MeshInstance3D) -> PackedStringArray:
|
|||
return result
|
||||
|
||||
|
||||
func _validate_projected_terrain_materials(root: Node) -> void:
|
||||
var expected_sizes := {
|
||||
"grass_lite": 1.75,
|
||||
"sand": 2.6,
|
||||
"dirt": 2.5,
|
||||
}
|
||||
var surface_counts := {
|
||||
"grass_lite": 0,
|
||||
"sand": 0,
|
||||
"dirt": 0,
|
||||
}
|
||||
_validate_projected_material_node(root, expected_sizes, surface_counts)
|
||||
for material_name: String in expected_sizes:
|
||||
assert(surface_counts[material_name] > 0)
|
||||
|
||||
|
||||
func _validate_projected_material_node(
|
||||
root: Node,
|
||||
expected_sizes: Dictionary,
|
||||
surface_counts: Dictionary,
|
||||
) -> void:
|
||||
var mesh_instance := root as MeshInstance3D
|
||||
if mesh_instance != null and mesh_instance.mesh != null:
|
||||
for surface_index: int in mesh_instance.mesh.get_surface_count():
|
||||
var material := mesh_instance.get_active_material(surface_index)
|
||||
if material == null or not expected_sizes.has(material.resource_name):
|
||||
continue
|
||||
var shader_material := material as ShaderMaterial
|
||||
assert(shader_material != null and shader_material.shader != null)
|
||||
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"
|
||||
)),
|
||||
float(expected_sizes[material.resource_name]),
|
||||
)
|
||||
)
|
||||
surface_counts[material.resource_name] += 1
|
||||
for child: Node in root.get_children():
|
||||
_validate_projected_material_node(
|
||||
child,
|
||||
expected_sizes,
|
||||
surface_counts,
|
||||
)
|
||||
|
||||
|
||||
func _validate_pond_collision(
|
||||
region: GeneratedWorldRegion,
|
||||
pond: MeshInstance3D,
|
||||
|
|
|
|||
|
|
@ -10,10 +10,11 @@ Run through Blender rather than a standalone Python interpreter:
|
|||
Collections use ``chunk_####_description`` and contain one primary terrain
|
||||
mesh named ``chunk_####``. Additional production objects may live in the same
|
||||
collection. Reusable procedural props use individual ``prop_description``
|
||||
mesh objects. They may be arranged anywhere in the source file because each
|
||||
object's authored origin becomes the exported runtime anchor. A same-named
|
||||
``prop_description`` collection remains supported for multi-object props.
|
||||
Unrelated objects and collections are ignored.
|
||||
mesh objects. Their child empties are exported with them so authored sockets
|
||||
remain attached to the prop. Props may be arranged anywhere in the source file
|
||||
because each root object's authored origin becomes the exported runtime anchor.
|
||||
A same-named ``prop_description`` collection remains supported for multi-object
|
||||
props. Unrelated objects and collections are ignored.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
|
@ -43,6 +44,12 @@ PROP_COLLECTION_PATTERN = re.compile(
|
|||
r"^prop_(?P<label>[a-z0-9]+(?:_[a-z0-9]+)*)$"
|
||||
)
|
||||
ALLOWED_OBJECT_TYPES = {"EMPTY", "MESH"}
|
||||
UPWARD_SURFACE_MATERIALS = {
|
||||
"dirt",
|
||||
"grass_lite",
|
||||
"sand",
|
||||
"water_reference",
|
||||
}
|
||||
|
||||
|
||||
class ExportValidationError(RuntimeError):
|
||||
|
|
@ -167,6 +174,33 @@ def _validate_transform(
|
|||
return problems
|
||||
|
||||
|
||||
def _validate_upward_surface_normals(
|
||||
mesh_object: bpy.types.Object,
|
||||
) -> list[str]:
|
||||
"""Reject exposed terrain materials whose faces point into the ground."""
|
||||
downward_counts: dict[str, int] = {}
|
||||
materials = mesh_object.data.materials
|
||||
for polygon in mesh_object.data.polygons:
|
||||
if polygon.normal.z >= -0.25 or polygon.material_index >= len(materials):
|
||||
continue
|
||||
material = materials[polygon.material_index]
|
||||
if material is None:
|
||||
continue
|
||||
material_name = re.sub(r"\.\d{3}$", "", material.name)
|
||||
if material_name not in UPWARD_SURFACE_MATERIALS:
|
||||
continue
|
||||
downward_counts[material_name] = (
|
||||
downward_counts.get(material_name, 0) + 1
|
||||
)
|
||||
return [
|
||||
(
|
||||
f"{count} {material_name} face(s) point downward; "
|
||||
"terrain surface normals must face up"
|
||||
)
|
||||
for material_name, count in sorted(downward_counts.items())
|
||||
]
|
||||
|
||||
|
||||
def _validate_bounds(
|
||||
bounds: Bounds,
|
||||
tolerance: float,
|
||||
|
|
@ -318,6 +352,10 @@ def _discover_chunks(tolerance: float) -> list[ChunkSource]:
|
|||
errors.append(
|
||||
f"{collection.name}/{primary_mesh.name}: mesh has no material"
|
||||
)
|
||||
errors.extend(
|
||||
f"{collection.name}/{primary_mesh.name}: {problem}"
|
||||
for problem in _validate_upward_surface_normals(primary_mesh)
|
||||
)
|
||||
|
||||
chunks.append(
|
||||
ChunkSource(
|
||||
|
|
@ -522,20 +560,38 @@ def _discover_props(tolerance: float) -> list[PropSource]:
|
|||
if not collections:
|
||||
errors.append(f"{item.name}: prop object belongs to no collection")
|
||||
continue
|
||||
objects = tuple(
|
||||
sorted(
|
||||
(item, *item.children_recursive),
|
||||
key=lambda value: value.name,
|
||||
)
|
||||
)
|
||||
overlapping_sources = {
|
||||
claimed_objects[descendant.as_pointer()]
|
||||
for descendant in objects
|
||||
if descendant.as_pointer() in claimed_objects
|
||||
}
|
||||
if overlapping_sources:
|
||||
errors.append(
|
||||
f"{item.name}: child object is already exported by "
|
||||
f"{', '.join(sorted(overlapping_sources))}"
|
||||
)
|
||||
continue
|
||||
prop, source_errors = _make_prop_source(
|
||||
stable_id,
|
||||
match.group("label"),
|
||||
"object",
|
||||
item.name,
|
||||
collections[0],
|
||||
(item,),
|
||||
objects,
|
||||
tolerance,
|
||||
)
|
||||
errors.extend(source_errors)
|
||||
if prop is not None:
|
||||
props.append(prop)
|
||||
claimed_ids[stable_id] = item.name
|
||||
claimed_objects[item.as_pointer()] = item.name
|
||||
for descendant in objects:
|
||||
claimed_objects[descendant.as_pointer()] = item.name
|
||||
|
||||
if errors:
|
||||
raise ExportValidationError("\n".join(errors))
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -9,9 +9,6 @@ const FishingShopInteractionType = preload(
|
|||
const PlayerStorageInteractionType = preload(
|
||||
"res://world/player_storage_interaction.gd"
|
||||
)
|
||||
const MeshSurfaceAnchorSamplerType = preload(
|
||||
"res://world/generation/mesh_surface_anchor_sampler.gd"
|
||||
)
|
||||
const PrecipitationOcclusionType = preload(
|
||||
"res://world/environment/precipitation_occlusion.gd"
|
||||
)
|
||||
|
|
@ -699,38 +696,41 @@ func _instantiate_prop(
|
|||
)
|
||||
_placed_prop_groups.append(definition.procedural_group)
|
||||
if definition.has_gatherable_surface():
|
||||
var surface_sample: Dictionary = (
|
||||
MeshSurfaceAnchorSamplerType.sample_vertical_surface(
|
||||
visual_root,
|
||||
var authored_sockets := _authored_beetle_sockets(visual_root)
|
||||
for socket: Node3D in authored_sockets:
|
||||
_add_tree_gatherable_anchor(
|
||||
prop,
|
||||
definition.gatherable_surface_material_names,
|
||||
definition.gatherable_surface_minimum_height,
|
||||
definition.gatherable_surface_maximum_height,
|
||||
definition.gatherable_surface_maximum_up_dot,
|
||||
definition.gatherable_surface_clearance,
|
||||
random,
|
||||
)
|
||||
)
|
||||
if surface_sample.is_empty():
|
||||
push_warning(
|
||||
"No reachable gatherable mesh surface found on %s."
|
||||
% definition.stable_id
|
||||
socket.global_position,
|
||||
definition,
|
||||
)
|
||||
return true
|
||||
|
||||
|
||||
func _authored_beetle_sockets(root: Node) -> Array[Node3D]:
|
||||
var sockets: Array[Node3D] = []
|
||||
var node_3d := root as Node3D
|
||||
if (
|
||||
node_3d != null
|
||||
and String(node_3d.name).to_lower().contains("beetle_socket")
|
||||
):
|
||||
sockets.append(node_3d)
|
||||
for child: Node in root.get_children():
|
||||
sockets.append_array(_authored_beetle_sockets(child))
|
||||
return sockets
|
||||
|
||||
|
||||
func _add_tree_gatherable_anchor(
|
||||
prop: Node3D,
|
||||
global_anchor_position: Vector3,
|
||||
definition: TerrainPropDefinition,
|
||||
) -> void:
|
||||
var anchor := Marker3D.new()
|
||||
anchor.name = "TreeAnchor_%d" % _tree_anchors.get_child_count()
|
||||
var local_anchor_position: Vector3 = surface_sample["position"]
|
||||
anchor.position = _tree_anchors.to_local(
|
||||
prop.to_global(local_anchor_position)
|
||||
)
|
||||
anchor.set_meta(
|
||||
&"terrain_prop_id",
|
||||
definition.stable_id,
|
||||
)
|
||||
anchor.position = _tree_anchors.to_local(global_anchor_position)
|
||||
anchor.set_meta(&"terrain_prop_id", definition.stable_id)
|
||||
anchor.set_meta(&"terrain_prop_name", prop.name)
|
||||
anchor.set_meta(&"mesh_surface_sampled", true)
|
||||
anchor.set_meta(&"authored_beetle_socket", true)
|
||||
_tree_anchors.add_child(anchor)
|
||||
return true
|
||||
|
||||
|
||||
func _prop_yaw(
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -45,6 +45,32 @@ func matches_above_height(
|
|||
return _surfaces_match(first_surface, second_surface, tolerance)
|
||||
|
||||
|
||||
## Compares the visible terrain seam at and above a shared waterline while
|
||||
## deliberately ignoring authored underwater wall depth. Terrain chunks may
|
||||
## extend different distances below the water without creating a visible gap,
|
||||
## but their banks still need to meet exactly at the surface.
|
||||
func matches_at_or_above_height(
|
||||
other: TerrainChunkEdgeProfile,
|
||||
minimum_height: float,
|
||||
tolerance: float,
|
||||
) -> bool:
|
||||
if other == null:
|
||||
return false
|
||||
var first_surface := _points_at_or_above_height(
|
||||
_upper_surface_points(),
|
||||
minimum_height,
|
||||
tolerance,
|
||||
)
|
||||
var second_surface := _points_at_or_above_height(
|
||||
other._upper_surface_points(),
|
||||
minimum_height,
|
||||
tolerance,
|
||||
)
|
||||
if first_surface.is_empty() or second_surface.is_empty():
|
||||
return first_surface.is_empty() and second_surface.is_empty()
|
||||
return _surfaces_match(first_surface, second_surface, tolerance)
|
||||
|
||||
|
||||
func _surfaces_match(
|
||||
first_surface: PackedVector2Array,
|
||||
second_surface: PackedVector2Array,
|
||||
|
|
@ -103,6 +129,18 @@ func _points_above_height(
|
|||
return result
|
||||
|
||||
|
||||
func _points_at_or_above_height(
|
||||
surface: PackedVector2Array,
|
||||
minimum_height: float,
|
||||
tolerance: float,
|
||||
) -> PackedVector2Array:
|
||||
var result := PackedVector2Array()
|
||||
for point: Vector2 in surface:
|
||||
if point.y >= minimum_height - tolerance:
|
||||
result.append(point)
|
||||
return result
|
||||
|
||||
|
||||
func _upper_surface_points() -> PackedVector2Array:
|
||||
var result := PackedVector2Array()
|
||||
for point: Vector2 in points:
|
||||
|
|
|
|||
|
|
@ -19,6 +19,15 @@ const RIVER_FEATURE_CLEARANCE := 1
|
|||
# Keep packed domain bits below the signed 64-bit sign bit. Catalogs larger
|
||||
# than this remain correct through the unpacked solver path.
|
||||
const MAX_PACKED_SOLVER_VARIANTS := 62
|
||||
const PROJECTED_GRASS_MATERIAL: Material = preload(
|
||||
"res://world/materials/generated_terrain_grass.tres"
|
||||
)
|
||||
const PROJECTED_SAND_MATERIAL: Material = preload(
|
||||
"res://world/materials/generated_terrain_sand.tres"
|
||||
)
|
||||
const PROJECTED_DIRT_MATERIAL: Material = preload(
|
||||
"res://world/materials/generated_terrain_dirt.tres"
|
||||
)
|
||||
|
||||
@export var catalog: TerrainChunkCatalog
|
||||
@export var grid_size := Vector2i(7, 7)
|
||||
|
|
@ -292,9 +301,14 @@ func _resolved_layout_validation_error(
|
|||
east,
|
||||
TerrainChunkTopology.Edge.WEST,
|
||||
):
|
||||
return "cells %d and %d have incompatible east/west edges." % [
|
||||
return (
|
||||
"cells %d (%s) and %d (%s) have incompatible "
|
||||
+ "east/west edges."
|
||||
) % [
|
||||
index,
|
||||
current.stable_key(),
|
||||
index + 1,
|
||||
east.stable_key(),
|
||||
]
|
||||
if coordinate.y + 1 < grid_size.y:
|
||||
var south := layout[index + grid_size.x]
|
||||
|
|
@ -304,9 +318,14 @@ func _resolved_layout_validation_error(
|
|||
south,
|
||||
TerrainChunkTopology.Edge.NORTH,
|
||||
):
|
||||
return "cells %d and %d have incompatible south/north edges." % [
|
||||
return (
|
||||
"cells %d (%s) and %d (%s) have incompatible "
|
||||
+ "south/north edges."
|
||||
) % [
|
||||
index,
|
||||
current.stable_key(),
|
||||
index + grid_size.x,
|
||||
south.stable_key(),
|
||||
]
|
||||
var neighbor_error := _neighbor_requirement_validation_error(layout)
|
||||
if not neighbor_error.is_empty():
|
||||
|
|
@ -3427,6 +3446,12 @@ func _calculate_edge_compatibility(
|
|||
0.0,
|
||||
profile_tolerance,
|
||||
)
|
||||
if not profiles_match and first_has_water and second_has_water:
|
||||
profiles_match = first.profile(first_edge).matches_at_or_above_height(
|
||||
second.profile(second_edge),
|
||||
0.0,
|
||||
profile_tolerance,
|
||||
)
|
||||
if (
|
||||
not profiles_match
|
||||
and first_has_water
|
||||
|
|
@ -4072,6 +4097,7 @@ func _add_stacked_elevated_chunks(solution_root: Node3D) -> bool:
|
|||
&"terrain_chunk_coordinate",
|
||||
support_coordinate,
|
||||
)
|
||||
_apply_projected_surface_materials(stacked_root)
|
||||
support_root.add_child(stacked_root)
|
||||
if build_collision:
|
||||
_add_collision(stacked_root, variant.definition)
|
||||
|
|
@ -4098,12 +4124,14 @@ func _instantiate_chunk_root(
|
|||
var terrain_visual := definition.packed_scene.instantiate() as Node3D
|
||||
if terrain_visual == null:
|
||||
return null
|
||||
_apply_projected_surface_materials(terrain_visual)
|
||||
if definition.base_layer_scene == null:
|
||||
return terrain_visual
|
||||
var base_layer_visual := definition.base_layer_scene.instantiate() as Node3D
|
||||
if base_layer_visual == null:
|
||||
terrain_visual.free()
|
||||
return null
|
||||
_apply_projected_surface_materials(base_layer_visual)
|
||||
var layered_root := Node3D.new()
|
||||
layered_root.name = "LayeredTerrainChunk"
|
||||
base_layer_visual.name = "TerrainBaseLayer"
|
||||
|
|
@ -4116,6 +4144,41 @@ func _instantiate_chunk_root(
|
|||
return layered_root
|
||||
|
||||
|
||||
func _apply_projected_surface_materials(root: Node) -> void:
|
||||
var mesh_instance := root as MeshInstance3D
|
||||
if mesh_instance != null and mesh_instance.mesh != null:
|
||||
for surface_index: int in mesh_instance.mesh.get_surface_count():
|
||||
var source_material := mesh_instance.get_active_material(surface_index)
|
||||
var projected_material := _projected_material_for(source_material)
|
||||
if projected_material != null:
|
||||
mesh_instance.set_surface_override_material(
|
||||
surface_index,
|
||||
projected_material,
|
||||
)
|
||||
for child: Node in root.get_children():
|
||||
_apply_projected_surface_materials(child)
|
||||
|
||||
|
||||
static func _projected_material_for(source_material: Material) -> Material:
|
||||
if source_material == null:
|
||||
return null
|
||||
var material_name := source_material.resource_name
|
||||
if material_name == "grass_lite" or material_name.begins_with(
|
||||
"grass_lite."
|
||||
):
|
||||
return PROJECTED_GRASS_MATERIAL
|
||||
if material_name == "sand" or material_name.begins_with("sand."):
|
||||
return PROJECTED_SAND_MATERIAL
|
||||
if (
|
||||
material_name == "dirt"
|
||||
or material_name.begins_with("dirt.")
|
||||
or material_name == "water_reference"
|
||||
or material_name.begins_with("water_reference.")
|
||||
):
|
||||
return PROJECTED_DIRT_MATERIAL
|
||||
return null
|
||||
|
||||
|
||||
func _replace_generated_chunks(solution_root: Node3D) -> void:
|
||||
_clear_generated_chunks()
|
||||
_generated_chunks = solution_root
|
||||
|
|
|
|||
16
world/materials/generated_terrain_dirt.tres
Normal file
16
world/materials/generated_terrain_dirt.tres
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
[gd_resource type="ShaderMaterial" load_steps=3 format=3]
|
||||
|
||||
[ext_resource type="Shader" uid="uid://d2ju2vpykktuo" path="res://world/materials/terrain_surface_projection.gdshader" id="1_shader"]
|
||||
[ext_resource type="Texture2D" uid="uid://rmlno7rb2e8p" path="res://world/generation/chunks/assets/chunk_0031_dirt.png" id="2_texture"]
|
||||
|
||||
[resource]
|
||||
resource_name = "dirt"
|
||||
render_priority = 0
|
||||
shader = ExtResource("1_shader")
|
||||
shader_parameter/albedo_texture = ExtResource("2_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
|
||||
16
world/materials/generated_terrain_grass.tres
Normal file
16
world/materials/generated_terrain_grass.tres
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
[gd_resource type="ShaderMaterial" load_steps=3 format=3]
|
||||
|
||||
[ext_resource type="Shader" uid="uid://d2ju2vpykktuo" path="res://world/materials/terrain_surface_projection.gdshader" id="1_shader"]
|
||||
[ext_resource type="Texture2D" uid="uid://doju2lbj2ghwa" path="res://world/generation/chunks/assets/chunk_0000_grass_lite.png" id="2_texture"]
|
||||
|
||||
[resource]
|
||||
resource_name = "grass_lite"
|
||||
render_priority = 0
|
||||
shader = ExtResource("1_shader")
|
||||
shader_parameter/albedo_texture = ExtResource("2_texture")
|
||||
shader_parameter/albedo_tint = Color(1, 1, 1, 1)
|
||||
shader_parameter/tile_world_size = 1.75
|
||||
shader_parameter/blend_sharpness = 6.0
|
||||
shader_parameter/top_projection_bias = 2.0
|
||||
shader_parameter/roughness = 1.0
|
||||
shader_parameter/specular = 0.5
|
||||
16
world/materials/generated_terrain_sand.tres
Normal file
16
world/materials/generated_terrain_sand.tres
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
[gd_resource type="ShaderMaterial" load_steps=3 format=3]
|
||||
|
||||
[ext_resource type="Shader" uid="uid://d2ju2vpykktuo" path="res://world/materials/terrain_surface_projection.gdshader" id="1_shader"]
|
||||
[ext_resource type="Texture2D" uid="uid://c3s1ebrqrsylj" path="res://world/generation/chunks/assets/chunk_0001_sand.png" id="2_texture"]
|
||||
|
||||
[resource]
|
||||
resource_name = "sand"
|
||||
render_priority = 0
|
||||
shader = ExtResource("1_shader")
|
||||
shader_parameter/albedo_texture = ExtResource("2_texture")
|
||||
shader_parameter/albedo_tint = Color(1, 1, 1, 1)
|
||||
shader_parameter/tile_world_size = 2.6
|
||||
shader_parameter/blend_sharpness = 6.0
|
||||
shader_parameter/top_projection_bias = 2.0
|
||||
shader_parameter/roughness = 1.0
|
||||
shader_parameter/specular = 0.5
|
||||
Loading…
Add table
Add a link
Reference in a new issue