Python实现Diamond-Square算法生成均匀噪声而非分形地形问题
问题
我尝试用Python结合NumPy和Matplotlib实现Diamond–Square算法生成分形地形高度图,但输出结果不是预期的山丘山谷,而是均匀的“斑点状”噪声。
已尝试操作
- 用随机值初始化N×N网格的四个角落
- 交替执行方形步骤与菱形步骤,每次迭代将随机偏移的
scale值减半 - 确保
half_step = step // 2,最终返回地形数据 - 调用
plt.imshow(terrain, cmap="terrain")进行可视化
尽管做了这些操作,结果仍呈现为纯随机噪声,而非平滑的分形地形。
代码
import matplotlib.pyplot as plt import numpy as np import random def diamond_square(size, scale): terrain = np.zeros((size, size)) # 初始化四个角落的随机值 terrain[0, 0] = random.uniform(-scale, scale) terrain[0, size -1] = random.uniform(-scale, scale) terrain[size -1, 0] = random.uniform(-scale, scale) terrain[size -1, size -1] = random.uniform(-scale, scale) step = size-1 while step > 1: half_step = step//2 # 遍历网格 for x in range(0, size-1, step): for y in range(0, size -1, step): square(terrain, half_step, x,y, scale) for x in range(0, size-1, step): for y in range(0, size -1, step): diamond(x,y, size, scale, terrain, half_step) step = half_step # 减半随机值范围 scale = scale/2 return terrain def diamond(x,y, size, scale, terrain, half_step): if x ==0: # 取东、北、南邻居的平均值 avg = ( terrain[x + half_step, y] + terrain[x, y - half_step] + terrain[x, y + half_step] ) / 3.0 terrain[x, y] = avg + random.uniform(-scale, scale) elif y == 0: avg = ( terrain[x - half_step, y] + terrain[x + half_step, y] + terrain[x, y + half_step] ) / 3.0 terrain[x, y] = avg + random.uniform(-scale, scale) elif x == size -1: # x为网格最大索引时 avg = ( terrain[x - half_step, y] + terrain[x, y - half_step] + terrain[x, y + half_step] ) / 3.0 terrain[x, y] = avg + random.uniform(-scale, scale) elif y == size -1: # y为网格最大索引时 avg = ( terrain[x - half_step, y] + terrain[x + half_step, y] + terrain[x, y - half_step] ) / 3.0 terrain[x, y] = avg + random.uniform(-scale, scale) else: # 取四个方向邻居的平均值 avg = ( terrain[x - half_step, y] + terrain[x + half_step, y] + terrain[x, y - half_step] + terrain[x, y + half_step] ) / 4.0 # 赋予带随机扰动的新值 terrain[x, y] = avg + random.uniform(-scale, scale) # x,y为正方形的起始角落 def square(terrain, half_step,x,y, scale): terrain[x + half_step//2, y + half_step//2 ] =( terrain[x,y] + terrain[x + half_step, y ] +terrain[x + half_step, y + half_step] +terrain[x + half_step, y])/4.0 + random.uniform(-scale, scale) def main(): size = 129 # 网格尺寸 scale = 100 # 随机值范围 terrain = diamond_square(size,scale) plt.figure(figsize=(6, 6)) plt.imshow(terrain, cmap="terrain") plt.colorbar(label="Height") plt.title("Diamond–Square Terrain") plt.tight_layout() plt.show() if __name__ == "__main__": main()
预期效果
生成经典分形地形:粗尺度上有大型山丘与山谷,后续细分逐步添加更精细的褶皱细节。即初始可见平滑的块状地形,再填充小凸起,而非纯随机噪声纹理。
问题分析与修正
你的代码存在三个核心错误,导致生成纯噪声而非分形地形:
1. Square步骤的坐标与平均值计算错误
原square函数中,正方形中心坐标错误使用half_step//2,正确的中心应该是x + half_step和y + half_step(因为step = 2*half_step);同时计算平均值时重复累加了同一个点,漏掉了正方形的右上角点,导致平均值完全偏离。
修正后的square函数:
def square(terrain, half_step, x, y, scale): center_x = x + half_step center_y = y + half_step # 取正方形四个角的平均值 avg = ( terrain[x, y] + terrain[x + 2*half_step, y] + terrain[x, y + 2*half_step] + terrain[x + 2*half_step, y + 2*half_step] ) / 4.0 terrain[center_x, center_y] = avg + random.uniform(-scale, scale)
2. Diamond步骤的遍历逻辑错误
原代码中菱形步骤的循环仅遍历正方形的角落点,没有覆盖需要计算的正方形边中点。正确的菱形步骤需要分两次遍历:一次处理水平边的中点,一次处理垂直边的中点。
修正后的diamond_square函数中菱形步骤的循环:
# 菱形步骤:先处理水平边中点 for x in range(0, size, step): for y in range(half_step, size - half_step, step): diamond(terrain, x, y, half_step, scale, size) # 再处理垂直边中点 for x in range(half_step, size - half_step, step): for y in range(0, size, step): diamond(terrain, x, y, half_step, scale, size)
3. Diamond函数的越界处理与目标点错误
原diamond函数接收的参数是角落点而非需要计算的菱形点,且越界判断会导致负索引错误。修正后的函数直接接收目标点坐标,动态判断有效邻居并计算平均值。
修正后的diamond函数:
def diamond(terrain, x, y, half_step, scale, size): total = 0.0 count = 0 # 上邻居 if y - half_step >= 0: total += terrain[x, y - half_step] count += 1 # 下邻居 if y + half_step < size: total += terrain[x, y + half_step] count += 1 # 左邻居 if x - half_step >= 0: total += terrain[x - half_step, y] count += 1 # 右邻居 if x + half_step < size: total += terrain[x + half_step, y] count += 1 avg = total / count terrain[x, y] = avg + random.uniform(-scale, scale)
完整修正代码
import matplotlib.pyplot as plt import numpy as np import random def diamond_square(size, scale): terrain = np.zeros((size, size)) # 初始化四个角落 terrain[0, 0] = random.uniform(-scale, scale) terrain[0, size - 1] = random.uniform(-scale, scale) terrain[size - 1, 0] = random.uniform(-scale, scale) terrain[size - 1, size - 1] = random.uniform(-scale, scale) step = size - 1 while step > 1: half_step = step // 2 # 方形步骤:处理所有正方形的中心 for x in range(0, size - 1, step): for y in range(0, size - 1, step): square(terrain, half_step, x, y, scale) # 菱形步骤:先处理水平边中点 for x in range(0, size, step): for y in range(half_step, size - half_step, step): diamond(terrain, x, y, half_step, scale, size) # 再处理垂直边中点 for x in range(half_step, size - half_step, step): for y in range(0, size, step): diamond(terrain, x, y, half_step, scale, size) step = half_step scale /= 2 return terrain def square(terrain, half_step, x, y, scale): center_x = x + half_step center_y = y + half_step # 取正方形四个角的平均值 avg = ( terrain[x, y] + terrain[x + 2*half_step, y] + terrain[x, y + 2*half_step] + terrain[x + 2*half_step, y + 2*half_step] ) / 4.0 terrain[center_x, center_y] = avg + random.uniform(-scale, scale) def diamond(terrain, x, y, half_step, scale, size): total = 0.0 count = 0 # 上邻居 if y - half_step >= 0: total += terrain[x, y - half_step] count += 1 # 下邻居 if y + half_step < size: total += terrain[x, y + half_step] count += 1 # 左邻居 if x - half_step >= 0: total += terrain[x - half_step, y] count += 1 # 右邻居 if x + half_step < size: total += terrain[x + half_step, y] count += 1 avg = total / count terrain[x, y] = avg + random.uniform(-scale, scale) def main(): size = 129 # 必须为2^n +1格式,比如129=2^7+1 scale = 100 terrain = diamond_square(size, scale) plt.figure(figsize=(6, 6)) plt.imshow(terrain, cmap="terrain") plt.colorbar(label="Height") plt.title("Diamond–Square Terrain") plt.tight_layout() plt.show() if __name__ == "__main__": main()
额外说明
- 网格尺寸
size必须是2^n +1的形式,这是Diamond-Square算法的硬性要求,否则无法完成所有细分步骤。 - 每次迭代将
scale减半,确保大尺度地形特征占主导,小尺度细节作为补充,才能生成符合预期的分形地形。
内容的提问来源于stack exchange,提问作者Programmer




