Godot Version
4.6
Question
Here is a fast and furious Grid Terrain that I made based loosely on Henry’s work
instead of generating terrain I load up a 4k terrain texture and use that to displace the chunks …
unfortunately I messed up the setup and the chunks no longer load in place at different resolution … not sure why they just appear smaller.
Anyway I implemented an Occlusion system, so you need to check occlusion culling in the renderer settings and then the script should do the work …
Anyway if anyone can tell me how to get this modified script working as chunk LOD again that would be great, then your a better coder than I am after a days work. The FPS is comparable to other terrain addons… with Terrain3D I only get about 20 FPS with Sky3D, Terrabrush broke, the chunk LOD implementations, hterrain, SimpleTerrain, both ran really slow … so I suppose I am also sharing this … Occlusion …I get the min_height of a chunk and build a box that extends down to zero at that point …
As you can see from the screen shots the FPS is OK (like 50-77) when stood at the side of the hills … usually 40 ish, and low when viewing the entire thing.
script:
@tool
class_name STRIP
extends MeshInstance3D
#@export_tool_button("Rebuild mesh","Callable") var _rebuild_mesh_action = run_it
var hmap:Image
var albedo_tex:Texture2D
var splat2:Texture2D
func load_image():
hmap = LoadLargeHeightMap("res://assets/Heightmaps/ISLAND_4K/export.exr")
func LoadLargeHeightMap(filename:String)->Image:
var height:Texture2D = load(filename)
albedo_tex = load("res://assets/Heightmaps/ISLAND_4K/export_preview.png")
splat2 = load("res://assets/Heightmaps/ISLAND_4K/color_grad.png")
return height.get_image()
func get_altitude(pos:Vector3)->float:
if hmap:
if pos.x < 4096 and pos.z < 4096:
return hmap.get_pixel(pos.x,pos.z).r* 500.0
return hmap.get_pixel(0,0).r
else:
print("error no heightmap")
return 0.0
@export var _noise: FastNoiseLite
var resolution:int = 128
var occluders = []
var min_h = 100000
func generate_mesh_data(rect: Rect2) -> Array:
var grid_size = resolution + 1
var step_size = rect.size.x / resolution
# Step 1: Generate Base Height Data
var height_data = PackedFloat32Array()
height_data.resize(grid_size * grid_size)
var rect2 = rect
rect2.size.x +=1
rect2.size.y +=1
#var h_region = hmap.get_region(rect2)
#var texture:Texture2D = ImageTexture.create_from_image(h_region)
min_h = 100000
#texture.
for z in grid_size:
for x in grid_size:
var pos_x = rect.position.x + (x * step_size)
var pos_z = rect.position.y + (z * step_size)
var h:float = get_altitude(Vector3(pos_x, 0, pos_z))
if h < min_h:
min_h = h
height_data[x + z * grid_size] = h
# Step 3: Build the Mesh Arrays
var vertices = PackedVector3Array()
var uvs = PackedVector2Array()
var indices = PackedInt32Array()
vertices.resize(grid_size * grid_size)
uvs.resize(grid_size * grid_size)
for z in grid_size:
for x in grid_size:
var i = x + z * grid_size
var pos_x = (x * step_size)
var pos_z = (z * step_size)
vertices[i] = Vector3(pos_x, height_data[i], pos_z)
uvs[i] = Vector2( (rect.position.x+float(x)) / (4096.0), (rect.position.y+float(z)) / (4096.0))
# Indices logic
for z in resolution:
for x in resolution:
var i = x + z * grid_size
indices.append(i)
indices.append(i + 1)
indices.append(i + grid_size)
indices.append(i + 1)
indices.append(i + grid_size + 1)
indices.append(i + grid_size)
var arrays = []
arrays.resize(Mesh.ARRAY_MAX)
arrays[Mesh.ARRAY_VERTEX] = vertices
arrays[Mesh.ARRAY_TEX_UV] = uvs
arrays[Mesh.ARRAY_INDEX] = indices
return arrays
func thread_generate_mesh(node: Rect2):
# 1. Generate the raw data
var mesh_data = generate_mesh_data(node)
# 2. DO THE HEAVY LIFTING HERE (Off the main thread)
var array_mesh = ArrayMesh.new()
array_mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, mesh_data)
var st = SurfaceTool.new()
st.create_from(array_mesh, 0)
st.generate_normals()
var final_mesh = st.commit() # This is now ready for the GPU
# Pass the completed mesh to the main thread
#call_deferred("finalize_node", node, final_mesh, min_h)
finalize_node(node, final_mesh, min_h)
func finalize_node(node: Rect2, final_mesh: Mesh,h:float):
#pending_nodes = max(0, pending_nodes - 1)
# Create the instance - this is now very light
var mesh_instance = MeshInstance3D.new()
mesh_instance.mesh = final_mesh
mesh_instance.material_override = load("uid://dlywemjv2a6fg")
(mesh_instance.material_override as ShaderMaterial).set_shader_parameter("splat",albedo_tex)
(mesh_instance.material_override as ShaderMaterial).set_shader_parameter("splat2",splat2)
mesh_instance.set_meta("node", node)
var occ = OccluderInstance3D.new()
var box_occ = BoxOccluder3D.new()
box_occ.size = Vector3(resolution, h, resolution)
occ.occluder = box_occ
#var box = MeshInstance3D.new()
#box.mesh = BoxMesh.new()
#(box.mesh as BoxMesh).size = Vector3(resolution, h, resolution)
#box.global_position = Vector3(node.position.x+resolution/2.0, h/2.0, node.position.y+resolution/2.0)
#
add_child(mesh_instance)
add_child(occ)
occ.global_position = Vector3(node.position.x+resolution/2.0, h/2.0, node.position.y+resolution/2.0)
#add_child(box)
mesh_instance.global_position = Vector3(node.position.x,0.0,node.position.y)
var first_time:bool = true
#func _process(delta:float) -> void:
func _ready() -> void:
if not Engine.is_editor_hint() and first_time:
first_time = false
load_image()
for i in range(0,4096/resolution):
for j in range(4096/resolution):
var rect:Rect2
rect.position = Vector2(float(i)*resolution,float(j)*resolution)
rect.size = Vector2(resolution, resolution)
thread_generate_mesh(rect)




