Add proc terrain toy
This commit is contained in:
7
proc-terrain/default_noise.tres
Normal file
7
proc-terrain/default_noise.tres
Normal file
@@ -0,0 +1,7 @@
|
||||
[gd_resource type="FastNoiseLite" format=3 uid="uid://clpxlmv87brc7"]
|
||||
|
||||
[resource]
|
||||
noise_type = 4
|
||||
seed = 42
|
||||
frequency = 0.008
|
||||
fractal_octaves = 4
|
||||
14
proc-terrain/default_terrain_material.tres
Normal file
14
proc-terrain/default_terrain_material.tres
Normal file
@@ -0,0 +1,14 @@
|
||||
[gd_resource type="ShaderMaterial" format=3 uid="uid://4a05tpi1tu14"]
|
||||
|
||||
[ext_resource type="Shader" uid="uid://d4b4lfd2r51iu" path="res://proc-terrain/triplanar_slope.gdshader" id="1_shader"]
|
||||
[ext_resource type="Texture2D" uid="uid://5pfh43bjvef4" path="res://proc-terrain/rock.png" id="2_50hjp"]
|
||||
[ext_resource type="Texture2D" uid="uid://rc8iqoij2c5w" path="res://proc-terrain/grass.png" id="3_8s6t3"]
|
||||
|
||||
[resource]
|
||||
render_priority = 0
|
||||
shader = ExtResource("1_shader")
|
||||
shader_parameter/texture_top = ExtResource("3_8s6t3")
|
||||
shader_parameter/texture_side = ExtResource("2_50hjp")
|
||||
shader_parameter/blend_sharpness = 5.0
|
||||
shader_parameter/slope_threshold = 0.7
|
||||
shader_parameter/uv_scale = Vector3(0.1, 0.1, 0.1)
|
||||
BIN
proc-terrain/grass.png
Normal file
BIN
proc-terrain/grass.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.6 MiB |
41
proc-terrain/grass.png.import
Normal file
41
proc-terrain/grass.png.import
Normal file
@@ -0,0 +1,41 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://rc8iqoij2c5w"
|
||||
path.s3tc="res://.godot/imported/grass.png-288495857c635d40287c0bebe39e1299.s3tc.ctex"
|
||||
metadata={
|
||||
"imported_formats": ["s3tc_bptc"],
|
||||
"vram_texture": true
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://proc-terrain/grass.png"
|
||||
dest_files=["res://.godot/imported/grass.png-288495857c635d40287c0bebe39e1299.s3tc.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=2
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=true
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=0
|
||||
BIN
proc-terrain/rock.png
Normal file
BIN
proc-terrain/rock.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 MiB |
41
proc-terrain/rock.png.import
Normal file
41
proc-terrain/rock.png.import
Normal file
@@ -0,0 +1,41 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://5pfh43bjvef4"
|
||||
path.s3tc="res://.godot/imported/rock.png-342cc50a309b9d723500126a447b3982.s3tc.ctex"
|
||||
metadata={
|
||||
"imported_formats": ["s3tc_bptc"],
|
||||
"vram_texture": true
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://proc-terrain/rock.png"
|
||||
dest_files=["res://.godot/imported/rock.png-342cc50a309b9d723500126a447b3982.s3tc.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=2
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=true
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=0
|
||||
204
proc-terrain/terrain_generator.gd
Normal file
204
proc-terrain/terrain_generator.gd
Normal file
@@ -0,0 +1,204 @@
|
||||
@tool
|
||||
class_name TerrainGenerator
|
||||
extends MeshInstance3D
|
||||
|
||||
@export var noise_setting: FastNoiseLite:
|
||||
set(value):
|
||||
if noise_setting and noise_setting.changed.is_connected(_on_settings_changed):
|
||||
noise_setting.changed.disconnect(_on_settings_changed)
|
||||
noise_setting = value
|
||||
if noise_setting:
|
||||
noise_setting.changed.connect(_on_settings_changed)
|
||||
_request_update()
|
||||
|
||||
@export var material_setting: ShaderMaterial:
|
||||
set(value):
|
||||
material_setting = value
|
||||
if mesh:
|
||||
mesh.surface_set_material(0, material_setting)
|
||||
|
||||
@export_group("Terrain Size")
|
||||
@export var terrain_size: Vector2 = Vector2(200, 200):
|
||||
set(value):
|
||||
terrain_size = value
|
||||
_request_update()
|
||||
|
||||
@export_range(2, 512) var subdivision: int = 128:
|
||||
set(value):
|
||||
subdivision = value
|
||||
_request_update()
|
||||
|
||||
@export var max_height: float = 50.0:
|
||||
set(value):
|
||||
max_height = value
|
||||
_request_update()
|
||||
|
||||
@export_group("Island Mask")
|
||||
@export var island_mask: bool = true:
|
||||
set(value):
|
||||
island_mask = value
|
||||
_request_update()
|
||||
|
||||
@export_range(0.0, 1.0) var mask_falloff_start: float = 0.5:
|
||||
set(value):
|
||||
mask_falloff_start = value
|
||||
_request_update()
|
||||
|
||||
@export_range(0.1, 2.0) var mask_power: float = 1.5:
|
||||
set(value):
|
||||
mask_power = value
|
||||
_request_update()
|
||||
|
||||
@export_group("Actions")
|
||||
@export var regenerate: bool = false:
|
||||
set(_value):
|
||||
generate_terrain()
|
||||
|
||||
@export var bake_collision: bool = false:
|
||||
set(_value):
|
||||
_bake_collision()
|
||||
|
||||
var _update_queued: bool = false
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
if noise_setting:
|
||||
if not noise_setting.changed.is_connected(_on_settings_changed):
|
||||
noise_setting.changed.connect(_on_settings_changed)
|
||||
generate_terrain()
|
||||
|
||||
|
||||
func _on_settings_changed() -> void:
|
||||
_request_update()
|
||||
|
||||
|
||||
func _request_update() -> void:
|
||||
if _update_queued:
|
||||
return
|
||||
_update_queued = true
|
||||
call_deferred("_do_update")
|
||||
|
||||
|
||||
func _do_update() -> void:
|
||||
_update_queued = false
|
||||
generate_terrain()
|
||||
|
||||
|
||||
func generate_terrain() -> void:
|
||||
if not noise_setting:
|
||||
return
|
||||
|
||||
var st := SurfaceTool.new()
|
||||
st.begin(Mesh.PRIMITIVE_TRIANGLES)
|
||||
|
||||
var step_x: float = terrain_size.x / float(subdivision - 1)
|
||||
var step_z: float = terrain_size.y / float(subdivision - 1)
|
||||
var half_x: float = terrain_size.x / 2.0
|
||||
var half_z: float = terrain_size.y / 2.0
|
||||
|
||||
var heights: Array[float] = []
|
||||
heights.resize(subdivision * subdivision)
|
||||
|
||||
for z in range(subdivision):
|
||||
for x in range(subdivision):
|
||||
var world_x: float = x * step_x - half_x
|
||||
var world_z: float = z * step_z - half_z
|
||||
|
||||
var height: float = noise_setting.get_noise_2d(world_x, world_z) * max_height
|
||||
|
||||
if island_mask:
|
||||
height *= _calculate_island_mask(world_x, world_z, half_x, half_z)
|
||||
|
||||
heights[z * subdivision + x] = height
|
||||
|
||||
for z in range(subdivision - 1):
|
||||
for x in range(subdivision - 1):
|
||||
var idx00: int = z * subdivision + x
|
||||
var idx10: int = z * subdivision + (x + 1)
|
||||
var idx01: int = (z + 1) * subdivision + x
|
||||
var idx11: int = (z + 1) * subdivision + (x + 1)
|
||||
|
||||
var x0: float = x * step_x - half_x
|
||||
var x1: float = (x + 1) * step_x - half_x
|
||||
var z0: float = z * step_z - half_z
|
||||
var z1: float = (z + 1) * step_z - half_z
|
||||
|
||||
var v00 := Vector3(x0, heights[idx00], z0)
|
||||
var v10 := Vector3(x1, heights[idx10], z0)
|
||||
var v01 := Vector3(x0, heights[idx01], z1)
|
||||
var v11 := Vector3(x1, heights[idx11], z1)
|
||||
|
||||
# Triangle 1: v00, v01, v10
|
||||
var normal1: Vector3 = (v01 - v00).cross(v10 - v00).normalized()
|
||||
st.set_normal(normal1)
|
||||
st.set_uv(Vector2(x0 / terrain_size.x + 0.5, z0 / terrain_size.y + 0.5))
|
||||
st.add_vertex(v00)
|
||||
st.set_uv(Vector2(x0 / terrain_size.x + 0.5, z1 / terrain_size.y + 0.5))
|
||||
st.add_vertex(v01)
|
||||
st.set_uv(Vector2(x1 / terrain_size.x + 0.5, z0 / terrain_size.y + 0.5))
|
||||
st.add_vertex(v10)
|
||||
|
||||
# Triangle 2: v10, v01, v11
|
||||
var normal2: Vector3 = (v01 - v10).cross(v11 - v10).normalized()
|
||||
st.set_normal(normal2)
|
||||
st.set_uv(Vector2(x1 / terrain_size.x + 0.5, z0 / terrain_size.y + 0.5))
|
||||
st.add_vertex(v10)
|
||||
st.set_uv(Vector2(x0 / terrain_size.x + 0.5, z1 / terrain_size.y + 0.5))
|
||||
st.add_vertex(v01)
|
||||
st.set_uv(Vector2(x1 / terrain_size.x + 0.5, z1 / terrain_size.y + 0.5))
|
||||
st.add_vertex(v11)
|
||||
|
||||
mesh = st.commit()
|
||||
|
||||
if material_setting:
|
||||
mesh.surface_set_material(0, material_setting)
|
||||
|
||||
|
||||
func _calculate_island_mask(x: float, z: float, half_x: float, half_z: float) -> float:
|
||||
var nx: float = x / half_x
|
||||
var nz: float = z / half_z
|
||||
var dist: float = sqrt(nx * nx + nz * nz)
|
||||
|
||||
if dist < mask_falloff_start:
|
||||
return 1.0
|
||||
elif dist > 1.0:
|
||||
return 0.0
|
||||
else:
|
||||
var t: float = (dist - mask_falloff_start) / (1.0 - mask_falloff_start)
|
||||
return pow(1.0 - t, mask_power)
|
||||
|
||||
|
||||
func _bake_collision() -> void:
|
||||
if not mesh:
|
||||
push_warning("TerrainGenerator: No mesh to bake collision from")
|
||||
return
|
||||
|
||||
var static_body: StaticBody3D
|
||||
var collision_shape: CollisionShape3D
|
||||
|
||||
for child in get_children():
|
||||
if child is StaticBody3D:
|
||||
static_body = child
|
||||
break
|
||||
|
||||
if not static_body:
|
||||
static_body = StaticBody3D.new()
|
||||
static_body.name = "TerrainCollision"
|
||||
add_child(static_body)
|
||||
if Engine.is_editor_hint():
|
||||
static_body.owner = get_tree().edited_scene_root
|
||||
|
||||
for child in static_body.get_children():
|
||||
if child is CollisionShape3D:
|
||||
collision_shape = child
|
||||
break
|
||||
|
||||
if not collision_shape:
|
||||
collision_shape = CollisionShape3D.new()
|
||||
collision_shape.name = "CollisionShape"
|
||||
static_body.add_child(collision_shape)
|
||||
if Engine.is_editor_hint():
|
||||
collision_shape.owner = get_tree().edited_scene_root
|
||||
|
||||
collision_shape.shape = mesh.create_trimesh_shape()
|
||||
print("TerrainGenerator: Collision baked successfully")
|
||||
1
proc-terrain/terrain_generator.gd.uid
Normal file
1
proc-terrain/terrain_generator.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://brr6het2uwnp5
|
||||
39
proc-terrain/terrain_toy.tscn
Normal file
39
proc-terrain/terrain_toy.tscn
Normal file
File diff suppressed because one or more lines are too long
48
proc-terrain/triplanar_slope.gdshader
Normal file
48
proc-terrain/triplanar_slope.gdshader
Normal file
@@ -0,0 +1,48 @@
|
||||
shader_type spatial;
|
||||
render_mode cull_disabled;
|
||||
|
||||
group_uniforms textures;
|
||||
uniform sampler2D texture_top : source_color, filter_linear_mipmap, repeat_enable;
|
||||
uniform sampler2D texture_side : source_color, filter_linear_mipmap, repeat_enable;
|
||||
|
||||
group_uniforms blending;
|
||||
uniform float blend_sharpness : hint_range(0.1, 20.0) = 5.0;
|
||||
uniform float slope_threshold : hint_range(0.0, 1.0) = 0.7;
|
||||
|
||||
group_uniforms uv_scaling;
|
||||
uniform vec3 uv_scale = vec3(0.1, 0.1, 0.1);
|
||||
|
||||
varying vec3 world_pos;
|
||||
varying vec3 world_normal;
|
||||
|
||||
void vertex() {
|
||||
world_pos = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz;
|
||||
world_normal = normalize((MODEL_MATRIX * vec4(NORMAL, 0.0)).xyz);
|
||||
}
|
||||
|
||||
void fragment() {
|
||||
vec3 abs_normal = abs(world_normal);
|
||||
|
||||
vec2 uv_xz = world_pos.xz * uv_scale.x;
|
||||
vec2 uv_xy = world_pos.xy * uv_scale.y;
|
||||
vec2 uv_zy = world_pos.zy * uv_scale.z;
|
||||
|
||||
vec3 weights = pow(abs_normal, vec3(blend_sharpness));
|
||||
weights /= (weights.x + weights.y + weights.z);
|
||||
|
||||
vec4 tex_xz = texture(texture_top, uv_xz);
|
||||
vec4 tex_xy = texture(texture_side, uv_xy);
|
||||
vec4 tex_zy = texture(texture_side, uv_zy);
|
||||
|
||||
vec4 triplanar_color = tex_xz * weights.y + tex_xy * weights.z + tex_zy * weights.x;
|
||||
|
||||
float up_factor = dot(world_normal, vec3(0.0, 1.0, 0.0));
|
||||
float slope_blend = smoothstep(slope_threshold - 0.1, slope_threshold + 0.1, up_factor);
|
||||
|
||||
vec4 top_color = texture(texture_top, uv_xz);
|
||||
vec4 side_color = (tex_xy * weights.z + tex_zy * weights.x) / max(weights.z + weights.x, 0.001);
|
||||
|
||||
vec4 final_color = mix(side_color, top_color, slope_blend);
|
||||
|
||||
ALBEDO = final_color.rgb;
|
||||
}
|
||||
1
proc-terrain/triplanar_slope.gdshader.uid
Normal file
1
proc-terrain/triplanar_slope.gdshader.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://d4b4lfd2r51iu
|
||||
Reference in New Issue
Block a user