Pyglet中1D Perlin Noise生成行星外观异常及Batch转Sprite问题
解决Pyglet结合OpenSimplex生成行星的形状问题及Batch转Sprite方案
问题概述
尝试用Pyglet搭配1D OpenSimplex噪声生成圆形行星,但生成的对象呈现类似星星的不规则尖锐形状;同时需要将绘制用的Batch转换为Sprite,方便后续移动对象。初始实现时调整过和len(self.terrain)相关的数值,但没能让行星呈现圆形。
初始有问题的代码
import pyglet import math from opensimplex import OpenSimplex class Planet: def __init__(self, x, y, radius): self.x = x self.y = y self.radius = radius self.noise = OpenSimplex() self.terrain = [] self.batch = pyglet.graphics.Batch() # 生成地形点 for i in range(360): angle = i * math.pi / 180 noise_val = self.noise.noise1d(i * 0.01) # 噪声影响幅度过大,且未做平滑处理 r = self.radius + noise_val * 20 x = self.x + r * math.cos(angle) y = self.y + r * math.sin(angle) self.terrain.append((x, y)) # 绘制多边形 self.batch.add(len(self.terrain), pyglet.gl.GL_POLYGON, None, ('v2f', [coord for point in self.terrain for coord in point]), ('c3B', [200, 100, 50] * len(self.terrain))) window = pyglet.window.Window(800, 600) planet = Planet(400, 300, 100) @window.event def on_draw(): window.clear() planet.batch.draw() pyglet.app.run()
问题原因
- 噪声对半径的影响幅度过大,导致部分顶点过度偏离基础圆形轨道,形成尖锐凸起
- 多边形未完全闭合,首尾顶点连接时可能出现突兀的折线
- 单纯调整顶点数量(
len(self.terrain))无法解决噪声幅度和闭合问题
修复后的可运行代码
import pyglet import math from opensimplex import OpenSimplex from pyglet.image import ImageData class Planet: def __init__(self, x, y, radius, resolution=360): self.x = x self.y = y self.radius = radius self.resolution = resolution self.noise = OpenSimplex(seed=42) self.sprite = None # 生成平滑闭合的行星顶点 vertices = [] colors = [] for i in range(self.resolution + 1): # +1确保多边形首尾闭合 angle = i * 2 * math.pi / self.resolution # 控制噪声幅度,避免过度偏离基础半径 noise_factor = self.noise.noise1d(i * 0.02) * 15 adjusted_radius = self.radius + noise_factor # 计算顶点坐标 px = self.x + adjusted_radius * math.cos(angle) py = self.y + adjusted_radius * math.sin(angle) vertices.extend([px, py]) # 模拟行星表面颜色渐变 color = (150 + int(noise_factor * 3), 80 + int(noise_factor * 2), 30) colors.extend(color) # Batch转Sprite核心逻辑:通过帧缓冲区渲染到纹理 buffer_size = int(radius * 2 + 40) # 预留足够空间容纳行星及边缘噪声 framebuffer = pyglet.image.Framebuffer() texture = pyglet.image.Texture.create(buffer_size, buffer_size) framebuffer.attach_texture(texture) # 在帧缓冲区中绘制行星Batch内容 with framebuffer: pyglet.gl.glClearColor(0, 0, 0, 0) pyglet.gl.glClear(pyglet.gl.GL_COLOR_BUFFER_BIT) batch = pyglet.graphics.Batch() batch.add(len(vertices)//2, pyglet.gl.GL_POLYGON, None, ('v2f', vertices), ('c3B', colors * len(vertices)//2)) batch.draw() # 将纹理转为Sprite,并处理透明背景 self.sprite = pyglet.sprite.Sprite(texture, x=self.x - buffer_size//2, y=self.y - buffer_size//2) # 提取像素数据,透明化黑色背景 data = texture.get_image_data() pixels = data.get_data('RGBA', data.width * 4) new_pixels = bytearray(pixels) for i in range(0, len(new_pixels), 4): if new_pixels[i] == 0 and new_pixels[i+1] == 0 and new_pixels[i+2] == 0: new_pixels[i+3] = 0 self.sprite.image = ImageData(buffer_size, buffer_size, 'RGBA', bytes(new_pixels)) def move(self, dx, dy): # 通过Sprite属性轻松移动行星 self.x += dx self.y += dy self.sprite.x += dx self.sprite.y += dy window = pyglet.window.Window(800, 600) planet = Planet(400, 300, 100) @window.event def on_draw(): window.clear() planet.sprite.draw() @window.event def on_key_press(symbol, modifiers): # 方向键测试移动功能 if symbol == pyglet.window.key.LEFT: planet.move(-5, 0) elif symbol == pyglet.window.key.RIGHT: planet.move(5, 0) elif symbol == pyglet.window.key.UP: planet.move(0, 5) elif symbol == pyglet.window.key.DOWN: planet.move(0, -5) pyglet.app.run()
关键调整说明
- 噪声幅度控制:将噪声对半径的影响限制在
*15的范围内,既保留地形起伏,又不会过度偏离圆形 - 闭合多边形:生成
resolution +1个顶点,确保首尾顶点重合,避免出现缺口或尖锐折线 - Batch转Sprite方案:利用Pyglet的帧缓冲区将Batch内容渲染到纹理,再把纹理转为Sprite,同时处理透明背景,这样就可以通过修改Sprite的
x/y属性轻松实现对象移动
内容的提问来源于stack exchange,提问作者user432910
相关产品推荐
相关产品推荐

