Skip to content

Instantly share code, notes, and snippets.

@Sorok-Dva
Created July 11, 2024 21:36
Show Gist options
  • Save Sorok-Dva/b04497fa132da99da6f3f1a668c2c38b to your computer and use it in GitHub Desktop.
Save Sorok-Dva/b04497fa132da99da6f3f1a668c2c38b to your computer and use it in GitHub Desktop.
Python script for Blender for terrain generation (v1)
import bpy
import bmesh
import math
# Improved Perlin noise function
def fade(t):
return t * t * t * (t * (t * 6 - 15) + 10)
def lerp(t, a, b):
return a + t * (b - a)
def grad(hash, x, y):
h = hash & 7
u = x if h < 4 else y
v = y if h < 4 else x
return (u if (h & 1) == 0 else -u) + (v if (h & 2) == 0 else -v)
def perlin(x, y):
X = int(math.floor(x)) & 255
Y = int(math.floor(y)) & 255
x -= math.floor(x)
y -= math.floor(y)
u = fade(x)
v = fade(y)
p = [151,160,137,91,90,15,131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,
8,99,37,240,21,10,23,190, 6,148,247,120,234,75,0,26,197,62,94,252,219,203,
117,35,11,32,57,177,33,88,237,149,56,87,174,20,125,136,171,168, 68,175,74,
165,71,134,139,48,27,166,77,146,158,231,83,111,229,122,60,211,133,230,220,
105,92,41,55,46,245,40,244,102,143,54, 65,25,63,161,1,216,80,73,209,76,132,
187,208,89,18,169,200,196,135,130,116,188,159,86,164,100,109,198,173,186,
3,64,52,217,226,250,124,123,5,202,38,147,118,126,255,82,85,212,207,206,59,
227,47,16,58,17,182,189,28,42,223,183,170,213,119,248,152,2,44,154,163, 70,
221,153,101,155,167, 43,172,9,129,22,39,253,19,98,108,110,79,113,224,232,
178,185,112,104,218,246,97,228,251,34,242,193,238,210,144,12,191,179,162,
241,81,51,145,235,249,14,239,107,49,192,214,31,181,199,106,157,184, 84,
204,176,115,121,50,45,127, 4,150,254,138,236,205,93,222,114,67,29,24,72,
243,141,128,195,78,66,215,61,156,180]
p = p * 2
aa = p[p[X ]+Y ]
ab = p[p[X ]+Y + 1]
ba = p[p[X + 1]+Y ]
bb = p[p[X + 1]+Y + 1]
return lerp(v, lerp(u, grad(aa, x, y), grad(ba, x - 1, y)),
lerp(u, grad(ab, x, y - 1), grad(bb, x - 1, y - 1)))
# Clear existing mesh data
bpy.ops.object.select_all(action='SELECT')
bpy.ops.object.delete(use_global=False)
# Define the terrain size and features
terrain_size = 200
terrain_height = 30
noise_scale = 0.1
# Create a new terrain
bpy.ops.mesh.primitive_plane_add(size=200, enter_editmode=False, align='WORLD', location=(0, 0, 0))
terrain = bpy.context.object
terrain.name = "Terrain"
# Subdivide the terrain to add more vertices
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.subdivide(number_cuts=200)
bpy.ops.object.mode_set(mode='OBJECT')
# Access the bmesh for manipulation
bm = bmesh.new()
bm.from_mesh(terrain.data)
# Define a function to apply noise for terrain elevation
def apply_noise(bm, scale, height):
for vert in bm.verts:
x, y = vert.co.x * scale, vert.co.y * scale
noise_value = perlin(x, y)
vert.co.z += noise_value * height
apply_noise(bm, noise_scale, terrain_height)
# Apply a mask to create an island shape
def apply_island_mask(bm, radius):
for vert in bm.verts:
distance = math.sqrt(vert.co.x**2 + vert.co.y**2)
if distance > radius:
vert.co.z = -1.0
apply_island_mask(bm, terrain_size / 2.0)
# Write the bmesh back to the mesh
bm.to_mesh(terrain.data)
bm.free()
# Smooth the terrain
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.vertices_smooth(repeat=10)
bpy.ops.object.mode_set(mode='OBJECT')
# Ensure the terrain is selected and active
bpy.context.view_layer.objects.active = terrain
terrain.select_set(True)
# Create a material and set up basic colors for different biomes
material = bpy.data.materials.new(name="IslandMaterial")
material.use_nodes = True
terrain.data.materials.append(material)
nodes = material.node_tree.nodes
links = material.node_tree.links
# Create a noise texture for biome distribution
noise_tex = nodes.new('ShaderNodeTexNoise')
noise_tex.inputs['Scale'].default_value = 5.0
# Create a color ramp to define biome colors
color_ramp = nodes.new('ShaderNodeValToRGB')
color_ramp.color_ramp.elements[0].position = 0.4
color_ramp.color_ramp.elements[1].position = 0.7
color_ramp.color_ramp.elements[0].color = (0.2, 0.8, 0.2, 1) # Green for forests
color_ramp.color_ramp.elements[1].color = (0.9, 0.9, 0.9, 1) # White for mountains
# Connect noise texture to color ramp
links.new(noise_tex.outputs['Fac'], color_ramp.inputs['Fac'])
# Create a diffuse shader and connect the color ramp to it
diffuse_shader = nodes.new('ShaderNodeBsdfDiffuse')
links.new(color_ramp.outputs['Color'], diffuse_shader.inputs['Color'])
# Connect the diffuse shader to the material output
material_output = nodes.get('Material Output')
links.new(diffuse_shader.outputs['BSDF'], material_output.inputs['Surface'])
# Helper function to get the correct context for operations
def get_override_context():
window = bpy.context.window_manager.windows[0]
screen = window.screen
for area in screen.areas:
if area.type == 'VIEW_3D':
for region in area.regions:
if region.type == 'WINDOW':
return {
'window': window,
'screen': screen,
'area': area,
'region': region,
'scene': bpy.context.scene,
'active_object': terrain,
'selected_objects': [terrain],
'selected_editable_objects': [terrain],
}
# Get the correct context and export the terrain as a GLTF file
context = get_override_context()
output_path = 'D:/terrain_game_map.gltf' # Change this to your desired path
bpy.ops.export_scene.gltf(
context,
filepath=output_path,
export_format='GLTF_SEPARATE', # Ensure separate GLTF + BIN files
export_apply=True
)
print(f'Terrain model exported to {output_path}')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment