Created
July 11, 2024 21:36
-
-
Save Sorok-Dva/b04497fa132da99da6f3f1a668c2c38b to your computer and use it in GitHub Desktop.
Python script for Blender for terrain generation (v1)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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