You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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()

问题原因

  1. 噪声对半径的影响幅度过大,导致部分顶点过度偏离基础圆形轨道,形成尖锐凸起
  2. 多边形未完全闭合,首尾顶点连接时可能出现突兀的折线
  3. 单纯调整顶点数量(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()

关键调整说明

  1. 噪声幅度控制:将噪声对半径的影响限制在*15的范围内,既保留地形起伏,又不会过度偏离圆形
  2. 闭合多边形:生成resolution +1个顶点,确保首尾顶点重合,避免出现缺口或尖锐折线
  3. Batch转Sprite方案:利用Pyglet的帧缓冲区将Batch内容渲染到纹理,再把纹理转为Sprite,同时处理透明背景,这样就可以通过修改Sprite的x/y属性轻松实现对象移动

内容的提问来源于stack exchange,提问作者user432910

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 16:20:26