Procedural Terrain with Material

Godot Version

4.2

Question

Hi,
i’ll tried to make a Terrain generator from heightmap.
it creates several SurfaceTool and in the end commit these together to a ArrayMesh

The Geometry works fine, i think, but the when i assig these surfaces a material everythin looks so strange

(with DirectionalLight3D)

(without DirectionalLight3D)

Code for generation:

func generate_mesh() -> ArrayMesh:
	var terrain_mesh : ArrayMesh = ArrayMesh.new()
	var terrain_ground_surface : SurfaceTool = SurfaceTool.new()
	var terrain_mountain_surface : SurfaceTool = SurfaceTool.new()

	var terrain_vertices : PackedVector3Array = self.calculate_vertices()
	
	terrain_ground_surface.begin(Mesh.PRIMITIVE_TRIANGLES)
	terrain_mountain_surface.begin(Mesh.PRIMITIVE_TRIANGLES)
	
	for _index_y : int in terrain_height - 1:
		for _index_x : int in terrain_width - 1:
			var _index1 : int = _index_x + terrain_width * _index_y 
			var _index2 : int = _index_x + terrain_width * _index_y + 1
			var _index3 : int = _index_x + terrain_width * (_index_y + 1)
			var _index4 : int = _index_x + terrain_width * (_index_y + 1) + 1
			
			var vertex1 : Vector3 = terrain_vertices[_index1]
			var vertex2 : Vector3 = terrain_vertices[_index2]
			var vertex3 : Vector3 = terrain_vertices[_index3]
			var vertex4 : Vector3 = terrain_vertices[_index4]
			
			if vertex1.y <= (height_scale / 2):
				terrain_ground_surface.set_material(ground_material)
				terrain_ground_surface.add_vertex(vertex1)
				terrain_ground_surface.add_vertex(vertex2)
				terrain_ground_surface.add_vertex(vertex3)
			
			else: 
				terrain_mountain_surface.set_material(mountain_material)
				terrain_mountain_surface.add_vertex(vertex1)
				terrain_mountain_surface.add_vertex(vertex2)
				terrain_mountain_surface.add_vertex(vertex3)
			
			if vertex4.y <= (height_scale / 2):
				terrain_ground_surface.set_material(ground_material)
				terrain_ground_surface.add_vertex(vertex3)
				terrain_ground_surface.add_vertex(vertex2)
				terrain_ground_surface.add_vertex(vertex4)
			
			else:
				terrain_mountain_surface.set_material(mountain_material)
				terrain_mountain_surface.add_vertex(vertex3)
				terrain_mountain_surface.add_vertex(vertex2)
				terrain_mountain_surface.add_vertex(vertex4)
			
	
	terrain_mesh = terrain_ground_surface.commit(terrain_mesh)
	terrain_mesh = terrain_mountain_surface.commit(terrain_mesh)
	
	return terrain_mesh

my guess is, that materials got some settings i dont understand or that i’ll have to use shaders, which i dont understand too :confused:

ok, after many trial-error i found out, that my mesh is missing normals, so i updated my code, now the vertices for the triangle get sorted, the normals calculated and then added to the surfacetool :slight_smile:


func generate_mesh() -> ArrayMesh:
	var terrain_mesh : ArrayMesh = ArrayMesh.new()
	var terrain_seabed_surface : SurfaceTool = SurfaceTool.new()
	var terrain_ground_surface : SurfaceTool = SurfaceTool.new()
	var terrain_mountain_surface : SurfaceTool = SurfaceTool.new()
	var terrain_vertices : Array[PackedVector3Array] = self.calculate_vertices()
	
	terrain_seabed_surface.begin(Mesh.PRIMITIVE_TRIANGLES)
	terrain_ground_surface.begin(Mesh.PRIMITIVE_TRIANGLES)
	terrain_mountain_surface.begin(Mesh.PRIMITIVE_TRIANGLES)
	
	for _index : int in terrain_vertices.size():
		var triangle_vertices : PackedVector3Array = terrain_vertices[_index]
		var vertices_y_level : PackedFloat32Array = PackedFloat32Array([
			triangle_vertices[0].y,triangle_vertices[1].y,triangle_vertices[2].y])
		vertices_y_level.sort()
		var y_level_top : float = vertices_y_level[-1]
		var y_level_bottom : float = vertices_y_level[0]
		
		if y_level_top < sea_level: handle_surfacetool(
			terrain_seabed_surface,
			terrain_vertices[_index],
			seabed_material )
		
		elif y_level_top - y_level_bottom < mountain_gradient: handle_surfacetool(
			terrain_ground_surface,
			terrain_vertices[_index],
			ground_material )
		
		else: handle_surfacetool(
			terrain_mountain_surface,
			terrain_vertices[_index],
			mountain_material )
	
	terrain_mesh = terrain_seabed_surface.commit(terrain_mesh)
	terrain_mesh = terrain_ground_surface.commit(terrain_mesh)
	terrain_mesh = terrain_mountain_surface.commit(terrain_mesh)
	
	return terrain_mesh

func handle_surfacetool( _surface_tool : SurfaceTool, _vertices : PackedVector3Array, _material : Material ):
	var normal : Vector3 = self.get_normal(_vertices)
	_surface_tool.set_normal(normal)
	_surface_tool.set_material(_material)
	for _idx : int in 3: _surface_tool.add_vertex(_vertices[_idx])

func calculate_vertices() -> Array[PackedVector3Array]:
	var terrain_vertices : PackedVector3Array = []
	var triangle_vertices : Array[PackedVector3Array] = []
	
	for _y : int in terrain_height: for _x : int in terrain_width: 
		terrain_vertices.append(self.get_vertex(_x,_y))
	
	for _index_y : int in terrain_height - 1:
		for _index_x : int in terrain_width - 1:
			var _index1 : int = _index_x + terrain_width * _index_y 
			var _index2 : int = _index_x + terrain_width * _index_y + 1
			var _index3 : int = _index_x + terrain_width * (_index_y + 1)
			var _index4 : int = _index_x + terrain_width * (_index_y + 1) + 1
			
			var vertex1 : Vector3 = terrain_vertices[_index1]
			var vertex2 : Vector3 = terrain_vertices[_index2]
			var vertex3 : Vector3 = terrain_vertices[_index3]
			var vertex4 : Vector3 = terrain_vertices[_index4]
			
			triangle_vertices.append(PackedVector3Array([vertex1, vertex2, vertex3]))
			triangle_vertices.append(PackedVector3Array([vertex4, vertex3, vertex2]))
	
	return triangle_vertices

func get_vertex( _position_x : int, _position_z : int ) -> Vector3: 
	var heightmap_color : Color = heightmap_image.get_pixel(
		int(_position_x * heightmap_one_x),
		int(_position_z * heightmap_one_y) )
	var _position_y : float = heightmap_color.get_luminance() * max_height
	
	return Vector3(_position_x,_position_y,_position_z)

func get_normal( _triangle : PackedVector3Array ) -> Vector3: 
	var side_x : Vector3 = _triangle[1] - _triangle[0]
	var side_y : Vector3 = _triangle[2] - _triangle[0]
	var normal : Vector3 = side_x.cross(side_y)
	
	return normal

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.