XNA的SpriteBatch如何工作?


38

更准确地说,如果我需要在另一个API(例如OpenGL)中从头开始重新创建此功能,它将需要做什么?

我确实对某些步骤有一个大致的了解,例如它如何准备正交投影矩阵并为每个绘图调用创建一个四边形。

但是,我对批处理过程本身不太熟悉。所有四边形是否存储在同一顶点缓冲区中?是否需要索引缓冲区?如何处理不同的纹理?

如果可以的话,至少在使用默认的Deferred模式时,如果您能指导我完成从SpriteBatch.Begin()到SpriteBatch.End()的整个过程,将不胜感激。


看看它是如何在MonoGame通过OpenGL实现的: github.com/mono/MonoGame/blob/master/MonoGame.Framework/...
书斋

您是否尝试过在Microsoft.Xna.Graphics上使用DotPeek或Reflector(尽管我不建议做任何非法的:)?
Den的

感谢您的链接。这个想法确实使我心烦意乱(我什至在手头就有dotPeek),但我认为向以前可能已经做过并且能通过记忆了解程序的人寻求一般描述可能是一个更好的主意。这样,答案将保留在此处并可供其他人使用。在没有人提供这种描述的情况下,我将自己进行分析并提出自己的问题的答案,但现在我还要再等一会儿。
David Gouveia

是的,这就是我发表评论的原因。我认为Andrew Russell可能是回答这个问题的好人。他在这个社区中很活跃,并且正在开发ExMon,它可以替代MonoGame:andrewrussell.net/exen
Den

是的,这通常是Andrew Russel(或XNA论坛的Shawn Hargreaves)能够提供很多洞察力的问题。
David Gouveia

Answers:


42

对于我正在使用的跨平台引擎,我已经在延迟模式下复制了SpriteBatch的行为,因此,这是到目前为止我进行反向工程的步骤:

  1. SpriteBatch构造:创建一个DynamicIndexBufferDynamicVertexBuffer和的阵列VertexPositionColorTexture固定大小的(在这种情况下,最大批量大小- 2048为精灵和8192为顶点)。

    • 索引缓冲区充满了将要绘制的四边形的顶点索引(0-1-2、0-2-3、4-5-6、4-6-7等)。
    • 内部SpriteInfo结构数组也被创建。这将存储在批处理时使用的时间精灵设置。
  2. SpriteBatch.Begin:在内部存储指定的BlendState,等值,SamplerState并检查是否已两次调用它,而SpriteBatch.End中间没有插入。

  3. SpriteBatch.Draw:获取所有Sprite信息(纹理,位置,颜色)并将其复制到SpriteInfo。如果达到最大批处理大小,则会绘制整个批处理以腾出空间容纳新的精灵。

    • SpriteBatch.DrawStringDraw考虑到字距调整和间距,仅对字符串的每个字符都发出一个。
  4. SpriteBatch.End:执行以下操作:

    • 设置中指定的渲染状态Begin
    • 创建正交投影矩阵。
    • 应用SpriteBatch着色器。
    • DynamicVertexBuffer和绑定DynamicIndexBuffer
    • 执行以下批处理操作:

      startingOffset = 0;
      currentTexture, oldTexture = null;
      
      // Iterate through all sprites
      foreach SpriteInfo in SpriteBuffer
      {
          // Store sprite index and texture
          spriteIndex = SpriteBuffer.IndexOf(SpriteInfo);
          currentTexture = SpriteInfo.Texture;
      
          // Issue draw call if batch count > 0 and there is a texture change
          if (currentTexture != oldTexture)
          {
              if (spriteIndex > startingOffset)
              {
                  RenderBatch(currentTexture, SpriteBuffer, startingOffset,
                              spriteIndex - startingOffset);
              }
              startingOffset = spriteIndex;
              oldTexture = currentTexture;
          }
      }
      
      // Draw remaining batch and clear the sprite data
      RenderBatch(currentTexture, SpriteBuffer, startingOffset,
                  SpriteBuffer.Count - startingOffset);
      SpriteBuffer.Clear();
  5. SpriteBatch.RenderBatch:SpriteInfo对批处理中的每个执行以下操作:

    • 获取子画面的位置,并根据原点和大小计算四个顶点的最终位置。应用现有的旋转。
    • 计算UV坐标并对其应用指定的坐标SpriteEffects
    • 复制精灵颜色。
    • 然后将这些值存储在VertexPositionColorTexture先前创建的元素数组中。计算SetData完所有子画面后,在上调用并发出DynamicVertexBuffer一个DrawIndexedPrimitives调用。
    • 顶点着色器仅执行变形操作,而像素着色器将着色应用于从纹理获取的颜色。

这正是我所需要的,非常感谢!我花了一点时间吸收所有这些,但是我想我终于得到了所有细节。引起我注意的一个问题是,即使在延迟模式下,如果我能够以某种方式对我的SpriteBatch.Draw调用进行Texture排序(即如果我不在乎这些调用的顺序),它也会引起我的注意。减少RenderBatch操作的数量,并可能加快渲染速度。
David Gouveia

顺便说一下,我似乎在文档中找不到-在缓冲区变满之前可以进行Draw调用的限制是什么?调用DrawText是否会用字符串中的全部字符填充缓冲区?
David Gouveia

令人印象深刻!您的引擎与ExEn和MonoGame相比如何?它还需要MonoTouch和MonoAndroid吗?谢谢。

@DavidGouveia:最大批处理大小为2048个sprites-编辑了答案并为方便起见添加了它。是的,按纹理排序并让z缓冲区负责子画面排序会使用最少的绘制调用。如果需要,请尝试实现SpriteSortMode.Texture以所需顺序绘制,然后SpriteBatch进行排序。
r2d2rigo 2011年

1
@Den:ExEn和MonoGame是较低级别的API,它们允许XNA代码在非Windows平台上运行。我正在研究的项目是一个纯粹的用C#编写的基于组件的引擎,但是我们需要一个非常薄的层来提供对系统API的访问(这就是我正在研究的地方)。SpriteBatch为了方便起见,我们只是选择重新实现。是的,由于全部为C#,因此需要MonoTouch / MonoDroid!
r2d2rigo 2011年

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.