【问题标题】:Pyglet Image RenderingPyglet 图像渲染
【发布时间】:2016-04-23 03:30:46
【问题描述】:

我正在为我的第一个深度 Pyglet 项目开发一种 2D Minecraft 克隆,但我遇到了一个问题。每当我在屏幕上有相当数量的块时,帧速率就会急剧下降。

这是我的渲染方法: 我使用字典,键是元组(表示块的坐标),项目是纹理。

我遍历整个字典并渲染每个块:

for key in self.blocks:
    self.blocks[key].blit(key[0] * 40 + sx,key[1] * 40+ sy)

附: sx 和 sy 是屏幕滚动的坐标偏移量

我想知道是否有一种方法可以更有效地渲染每个块。

【问题讨论】:

  • 你应该通过使用items(或python2中的iteritems)迭代你的dict来提高性能。您的self.blocks 也可能不会更好地实现为列表吗?鉴于您以这种线性方式访问它,字典可能没有这样的优势。您将花费大量时间计算哈希。
  • @PaulRooney 他/她的问题是,以任何方式使用循环都会成为瓶颈。 Batched rendering 基本上是目前唯一的出路。那并为事件触发和时钟速率制作您的自定义main 类。我在下面发布了一个小示例,希望对您有所帮助。

标签: python graphics rendering pyglet


【解决方案1】:

我将尽我所能解释为什么以及如何优化你的代码,而不知道你的代码是什么样的。

我会假设你有一些类似的东西:

self.blocks['monster001'] = pyglet.image.load('./roar.png')

如果你想加载一个你不想做太多的静态图像,这一切都很好。但是,您正在制作一款游戏,并且您将使用大量的精灵和对象,而不仅仅是一个简单的图像文件。

现在这是共享对象、批处理和精灵派上用场的地方。 首先,将您的图像输入到 sprite 中,这是一个好的开始。

sprite = pyglet.sprite.Sprite(pyglet.image.load('./roar.png'))
sprite.draw() # This is instead of blit. Position is done via sprite.x = ...

现在,由于多种原因,draw 比 .blit() 快得多,但我们现在将跳过原因,只坚持极速升级

再说一次,这只是朝着成功的帧速率迈出的一小步(除了硬件有限..duh)。

无论如何,回到 pew pew 升级代码。
现在您还想将精灵添加到批处理中,这样您就可以同时渲染 LOT 的东西(阅读:批处理),而不是手动将东西推送到显卡。 显卡的灵魂目的是为了能够在一次疯狂的快速运行中处理千兆位的计算吞吐量,而不是处理多个小型 I/O

为此,您需要创建一个批处理容器。并为其添加“层”。
其实很简单,你只需要:

main_batch = pyglet.graphics.Batch()
background = pyglet.graphics.OrderedGroup(0)
# stuff_above_background = pyglet.graphics.OrderedGroup(1)
# ...

我们暂时使用批处理,您可能不需要更多用于此学习目的。
好的,所以你拿到了你的批次,现在怎么办?好吧,现在我们尽最大努力从你的显卡中扼杀这个活生生的地狱,看看我们是否能在压力下把它扣住(在这个过程中没有任何显卡受到伤害,请不要扼杀东西..)

还有一件事,还记得关于共享对象的说明吗?好吧,我们将在这里创建一个共享图像对象,然后将其推送到 sprite 中,而不是每次……一次……加载一个新图像。monster_image 我们会调用它。

monster_image = pyglet.image.load('./roar.png')
for i in range(100): # We'll create 100 test monsters
    self.blocks['monster'+str(i)] = pyglet.sprite.Sprite(imgage=monster_image, x=0, y=0, batch=main_batch, group=background)

现在您已创建 100 怪物并将其添加到批次 main_batch 到子组 background 中。很简单。

这就是关键,我们现在可以调用main_batch.draw(),而不是调用self.blocks[key].blit().draw(),它会将所有怪物发射到显卡上并产生奇迹。

好的,现在您已经优化了代码的速度,但从长远来看,如果您正在制作游戏,这对您没有帮助。或者在这种情况下,您的游戏的图形引擎。你想做的就是进入大联盟并使用。如果您在完成之前可能会惊讶于您的代码将看起来多么棒,那么您可能会感到惊讶。

好的,首先,您想为屏幕上的对象创建一个基类,让我们调用baseSprite
现在有一些你需要使用 Pyglet 解决的问题和问题,例如,当继承 Sprite 对象时尝试设置 image 会在使用这些东西时导致各种不确定的故障和错误,所以我们将设置 @987654336 @ 直接,这基本上是一样的,但我们挂钩到 pyglet 库变量而不是;D pew pew hehe.

class baseSprite(pyglet.sprite.Sprite):
    def __init__(self, texture, x, y, batch, subgroup):
        self.texture = texture

        super(baseSprite, self).__init__(self.texture, batch=batch, group=subgroup)
        self.x = x
        self.y = y

    def move(self, x, y):
        """ This function is just to show you
            how you could improve your base class
            even further """
        self.x += x
        self.y += y

    def _draw(self):
        """
        Normally we call _draw() instead of .draw() on sprites
        because _draw() will contains so much more than simply
        drawing the object, it might check for interactions or
        update inline data (and most likely positioning objects).
        """
        self.draw()

现在这是你的基地,你现在可以通过以下方式创建怪物:

main_batch = pyglet.graphics.Batch()
background = pyglet.graphics.OrderedGroup(0)
monster_image = pyglet.image.load('./roar.png')
self.blocks['monster001'] = baseSprite(monster_image, 10, 50, main_batch, background)
self.blocks['monster002'] = baseSprite(monster_image, 70, 20, main_batch, background)

...
main_batch.draw()

如何,您可能使用默认的@on_window_draw() 示例,每个人 都在使用,这很好,但我发现它缓慢、丑陋并且从长远来看不实用。你想做面向对象的编程.. 对吗?
这就是它的名称,我称之为您喜欢整天观看的可读代码。简称RCTYLTWADL。

为此,我们需要创建一个模仿 Pyglet 行为的class,并按顺序调用它的后续函数并轮询事件处理程序,否则 sh** 会卡住,相信我.. 完成了几次,瓶颈很容易创建。
但是我的错误已经够多了,这里有一个基本的 main 类,您可以使用它使用基于轮询的事件处理,从而限制您的编程的刷新率,而不是 Pyglet 中的内置行为。

class main(pyglet.window.Window):
    def __init__ (self):
        super(main, self).__init__(800, 800, fullscreen = False)
        self.x, self.y = 0, 0
        self.sprites = {}
        self.batches = {}
        self.subgroups = {}

        self.alive = 1

    def on_draw(self):
        self.render()

    def on_close(self):
        self.alive = 0

    def render(self):
        self.clear()

        for batch_name, batch in self.batches.items():
            batch.draw()

        for sprite_name, sprite in self.sprites.items():
            sprite._draw()

        self.flip() # This updates the screen, very much important.

    def run(self):
        while self.alive == 1:
            self.render()

            # -----------> This is key <----------
            # This is what replaces pyglet.app.run()
            # but is required for the GUI to not freeze.
            # Basically it flushes the event pool that otherwise
            # fill up and block the buffers and hangs stuff.
            event = self.dispatch_events()

x = main()
x.run()

现在这又只是一个基本的main 类,除了渲染黑色背景以及放入self.spritesself.batches 中的任何内容之外什么都不做。

请注意!我们在 sprite 上调用 ._draw() 是因为我们之前创建了自己的 sprite 类?是的,这是一个很棒的精灵基础类,您可以在 draw() 对每个单独的精灵完成之前挂钩自己的东西。

任何人,这一切都归结为几件事。

  1. 制作游戏时使用精灵,您的生活会更轻松
  2. 使用批处理,您的 GPU 会爱上您,刷新率会惊人
  3. 使用类和东西,你的眼睛和代码魔力最终会爱上你。

下面是一个完整的示例,将所有部分拼凑在一起:

import pyglet
from pyglet.gl import *

glEnable(GL_BLEND)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
glEnable(GL_LINE_SMOOTH)
glHint(GL_LINE_SMOOTH_HINT, GL_DONT_CARE)

pyglet.clock.set_fps_limit(60)

class baseSprite(pyglet.sprite.Sprite):
    def __init__(self, texture, x, y, batch, subgroup):
        self.texture = texture

        super(baseSprite, self).__init__(self.texture, batch=batch, group=subgroup)
        self.x = x
        self.y = y

    def move(self, x, y):
        """ This function is just to show you
            how you could improve your base class
            even further """
        self.x += x
        self.y += y

    def _draw(self):
        """
        Normally we call _draw() instead of .draw() on sprites
        because _draw() will contains so much more than simply
        drawing the object, it might check for interactions or
        update inline data (and most likely positioning objects).
        """
        self.draw()

class main(pyglet.window.Window):
    def __init__ (self):
        super(main, self).__init__(800, 800, fullscreen = False)
        self.x, self.y = 0, 0
        self.sprites = {}
        self.batches = {}
        self.subgroups = {}

        self._handles = {}

        self.batches['main'] = pyglet.graphics.Batch()
        self.subgroups['base'] = pyglet.graphics.OrderedGroup(0)

        monster_image = pyglet.image.load('./roar.png')
        for i in range(100):
            self._handles['monster'+str(i)] = baseSprite(monster_image, randint(0, 50), randint(0, 50), self.batches['main'], self.subgroups['base'])

        # Note: We put the sprites in `_handles` because they will be rendered via
        # the `self.batches['main']` batch, and placing them in `self.sprites` will render
        # them twice. But we need to keep the handle so we can use `.move` and stuff
        # on the items later on in the game making process ;)

        self.alive = 1

    def on_draw(self):
        self.render()

    def on_close(self):
        self.alive = 0

    def render(self):
        self.clear()

        for batch_name, batch in self.batches.items():
            batch.draw()

        for sprite_name, sprite in self.sprites.items():
            sprite._draw()

        self.flip() # This updates the screen, very much important.

    def run(self):
        while self.alive == 1:
            self.render()

            # -----------> This is key <----------
            # This is what replaces pyglet.app.run()
            # but is required for the GUI to not freeze.
            # Basically it flushes the event pool that otherwise
            # fill up and block the buffers and hangs stuff.
            event = self.dispatch_events()

            # Fun fact:
            #   If you want to limit your FPS, this is where you do it
            #   For a good example check out this SO link:
            #   http://stackoverflow.com/questions/16548833/pyglet-not-running-properly-on-amd-hd4250/16548990#16548990

x = main()
x.run()

一些额外的东西,我添加了通常为你做一些有益的东西的 GL 选项。 我还添加了一个 FPS 限制器,您可以修改和使用它。

编辑:

批量更新

由于 sprite 对象可以通过将其全部发送到显卡来一次性完成大量渲染,因此您同样希望进行批量更新。 例如,如果您想更新每个对象的位置、颜色或任何可能的内容。

这就是聪明的编程而不是漂亮的小工具发挥作用的地方。
看,我与编程相关的一切......如果你想要的话。

假设您(在代码的顶部)有一个名为:

global_settings = {'player position' : (50, 50)}
# The player is at X cord 50 and Y cord 50.

在您的基础精灵中,您可以简单地执行以下操作:

class baseSprite(pyglet.sprite.Sprite):
    def __init__(self, texture, x, y, batch, subgroup):
        self.texture = texture

        super(baseSprite, self).__init__(self.texture, batch=batch, group=subgroup)
        self.x = x + global_settings['player position'][0]#X
        self.y = y + global_settings['player position'][1]#Y

请注意,您必须稍微调整draw()(注意,不是_draw(),因为批处理渲染将调用draw 而不是_draw)函数,以支持和更新每个渲染序列的位置更新.或者您可以创建一个继承 baseSprite 的新类,并且只更新这些类型的精灵:

class monster(baseSprite):
    def __init__(self, monster_image, main_batch, background):
        super(monster, self).__init__(imgage=monster_image, x=0, y=0, batch=main_batch, group=background)
    def update(self):
        self.x = x + global_settings['player position'][0]#X
        self.y = y + global_settings['player position'][1]#Y

因此只能在 monster 类型类/精灵上调用 .update()
让它达到最佳状态有点棘手,有办法解决它并仍然使用批处理渲染,但沿着这些思路可能是一个好的开始。



重要提示 我只是从头顶写了很多(不是我第一次在 Pyglet 中编写 GUI 类),无论出于何种原因,我的这个 *Nix 实例都找不到我的 X-server..所以无法测试代码。

我会在下班后的一个小时内对其进行测试,但这会让您大致了解在 Pyglet 中制作游戏时应该做什么以及应该考虑什么。请记住,玩得开心,否则您甚至在开始之前就退出了,因为制作游戏需要时间 ^^

Pew pew lazors 之类的,祝你好运!

【讨论】:

  • 老兄,你是一本活的百科全书!你说的一切都完全有道理。非常感谢!我将执行你所说的一切。另外,感谢您解释显卡的工作原理,我对如何在 3D 游戏上获得 60 fps 而在简单的 2D 渲染器上获得 15 fps 感到困惑。
  • 还有一个问题,“event = self.dispatch_events()”是做什么的?在你的主要课程中?
  • @Areeb 它清空了 Pyglet 的缓冲区。如果你不清空缓冲区,它们就会变满,一旦有东西试图将更多的东西输入到缓冲区中,缓冲区就会说“dude wait a second”,任何代码试图输入都会变成“hmm ok sure I'我会等待”,并经常挂起所有东西。对于subprocess.Popen stdout 管道等文件缓冲区也是如此。所以是的,它会清空缓冲区,这样你的程序就不会挂起。
  • @Areeb NP,祝您游戏制作愉快!最后别忘了享受你的游戏:)
  • 有没有办法一次移动所有的精灵?无需使用 for 循环并更改每个精灵的坐标?
猜你喜欢
  • 1970-01-01
  • 2022-12-21
  • 2017-09-17
  • 2011-12-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-07-06
相关资源
最近更新 更多