Terreno por ruído

Um mapa de altura virando uma ArrayMesh com SurfaceTool, colorido por altura através de cores de vértice.

O editor acima está rodando este projeto. Mude uma linha e ele recarrega.

Classes do Godot usadas

SurfaceToolArrayMeshMeshInstance3D

O código

scripts/main.gd
extends Node

const GRID: int = 96
const SIZE: float = 34.0
const AMPLITUDE: float = 7.0
const SEED: int = 20260814

const BANDS: Array = [
	{limit = 0.12, color = '#2a3a63'},
	{limit = 0.20, color = '#f3d48d'},
	{limit = 0.45, color = '#8eef97'},
	{limit = 0.66, color = '#4c9c60'},
	{limit = 0.86, color = '#2a2f3d'},
	{limit = 2.00, color = '#e6ecff'},
]

var camera: Camera3D
var elapsed: float = 0.0


func _ready() -> void:
	_add_environment()
	_add_light()

	var terrain: MeshInstance3D = MeshInstance3D.new()
	terrain.mesh = _build_terrain()
	terrain.material_override = _make_material()
	add_child(terrain)

	camera = Camera3D.new()
	add_child(camera)
	camera.current = true

	print('the heightmap becomes an ArrayMesh through SurfaceTool, change SEED for another terrain')


func _process(delta: float) -> void:
	elapsed += delta

	var angle: float = elapsed * 0.15
	camera.position = Vector3(sin(angle) * 27.0, 13.0, cos(angle) * 27.0)
	camera.look_at(Vector3(0, 2.5, 0), Vector3.UP)


func _build_terrain() -> ArrayMesh:
	var noise: FastNoiseLite = FastNoiseLite.new()
	noise.seed = SEED
	noise.noise_type = FastNoiseLite.TYPE_SIMPLEX
	noise.fractal_octaves = 5
	noise.frequency = 0.9 / GRID

	var heights: PackedFloat32Array = PackedFloat32Array()
	var lowest: float = 1.0
	var highest: float = 0.0

	for z in GRID + 1:
		for x in GRID + 1:
			var height: float = pow(noise.get_noise_2d(x, z) * 0.5 + 0.5, 1.6)
			lowest = minf(lowest, height)
			highest = maxf(highest, height)
			heights.append(height)

	for i in heights.size():
		heights[i] = inverse_lerp(lowest, highest, heights[i])

	var tool: SurfaceTool = SurfaceTool.new()
	tool.begin(Mesh.PRIMITIVE_TRIANGLES)

	for z in GRID:
		for x in GRID:
			var a: Vector3 = _vertex(heights, x, z)
			var b: Vector3 = _vertex(heights, x + 1, z)
			var c: Vector3 = _vertex(heights, x + 1, z + 1)
			var d: Vector3 = _vertex(heights, x, z + 1)

			for corner in [a, b, c, a, c, d]:
				tool.set_color(_band_color(corner.y / AMPLITUDE))
				tool.add_vertex(corner)

	tool.generate_normals()

	return tool.commit()


func _vertex(heights: PackedFloat32Array, x: int, z: int) -> Vector3:
	var step: float = SIZE / GRID

	return Vector3(x * step - SIZE * 0.5, heights[z * (GRID + 1) + x] * AMPLITUDE, z * step - SIZE * 0.5)


func _band_color(height: float) -> Color:
	for band in BANDS:
		if height < band.limit:
			return Color(band.color)

	return Color(BANDS[BANDS.size() - 1].color)


func _make_material() -> StandardMaterial3D:
	var material: StandardMaterial3D = StandardMaterial3D.new()
	material.vertex_color_use_as_albedo = true
	material.roughness = 0.95

	return material


func _add_environment() -> void:
	var sky_material: ProceduralSkyMaterial = ProceduralSkyMaterial.new()
	sky_material.sky_top_color = Color(0.04, 0.05, 0.1)
	sky_material.sky_horizon_color = Color(0.12, 0.14, 0.22)
	sky_material.ground_bottom_color = Color(0.02, 0.02, 0.04)
	sky_material.ground_horizon_color = Color(0.09, 0.1, 0.15)

	var sky: Sky = Sky.new()
	sky.sky_material = sky_material

	var env: Environment = Environment.new()
	env.background_mode = Environment.BG_SKY
	env.sky = sky
	env.ambient_light_source = Environment.AMBIENT_SOURCE_SKY
	env.ambient_light_energy = 0.8

	var world: WorldEnvironment = WorldEnvironment.new()
	world.environment = env
	add_child(world)


func _add_light() -> void:
	var light: DirectionalLight3D = DirectionalLight3D.new()
	light.rotation_degrees = Vector3(-52, -40, 0)
	light.light_energy = 1.2
	add_child(light)

Mais exemplos de Procedural