You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

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_stepy + 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

火山引擎 最新活动