【问题标题】:XNA/Monogame, Fastest way to draw multiple sheared/skewed spritesXNA/Monogame,绘制多个剪切/倾斜精灵的最快方法
【发布时间】:2013-01-25 16:46:21
【问题描述】:

我通常在 XNA/Monogame 中使用 SpriteBatch 进行 2d 游戏,并且最近刚刚深入研究了 DrawUserIndexedPrimatives 等 3D 绘图方法。我正在做一个项目,我们的动画师希望能够剪切精灵和纹理。

使用SpriteBatch,您可以在SpriteBatch 上传入一个矩阵,开始剪切一个对象。比如:

//translate object to origin
Matrix translate1 = Matrix.CreateTranslation(-rectangle.X, -rectangle.Y, 0);

//skew the sprite 33 degrees on the X and Y axis
Matrix skew = Matrix.Identity;
skew.M12 = (float)Math.Tan(33 * 0.0174532925f);
skew.M21 = (float)Math.Tan(33 * 0.0174532925f);

//translate object back
Matrix translate2 = Matrix.CreateTranslation(rectangle.X, rectangle.Y, 0);
Matrix transform = translate1 * skew * translate2;

_spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied,
                    SamplerState.PointWrap, DepthStencilState.Default,
                    RasterizerState.CullCounterClockwise, null, transform);
_spriteBatch.Draw(_texture, rectangle, Color.White);
_spriteBatch.End();

这个明显的缺点是它需要你为每个剪切的精灵创建一个新的SpriteBatch 开始和结束调用。我们目前只需要 2 次调用 SpriteBatch 在我们的游戏中开始。一个用于 UI,一个用于 World 的东西。我们的美工想要使用剪切来处理摇摆的树木或为生物的腿和四肢设置动画,所以如果我们给他们选择的话,我可以看到这个数字会跃升至 10 多个单独的批次。

平均水平有大约 250 个元素,每个元素包含 10-20 个精灵。

我为 Android 编写了一个测试,该测试调用了 1000 个精灵的绘制。在没有任何倾斜的情况下,它可以在大约 11 秒或大约 53fps 内绘制全部 1000、600 次。但如果我每十个精灵倾斜一次(添加 100 个新的SpriteBatch 调用),则需要 47 秒,或大约 12fps。

这真的很糟糕。即使只有 200 个精灵(每十分之一倾斜),测试也会下降到 28fps。

所以我还使用用DrawUserIndexedPrimitives 绘制的四边形创建了相同的测试。每个 Quad 使用在 Game 类中创建并通过 Sprite 类构造函数传入的共享 BasicEffect。我在每个pass.Apply() 之前设置了世界矩阵和纹理,如下所示:

if (_basicEffect != null)
{
     foreach (EffectPass pass in _basicEffect.CurrentTechnique.Passes)
     {
        _basicEffect.World = Transform;
        _basicEffect.Texture = _texture;
        pass.Apply();

        GraphicsDevice.DrawUserIndexedPrimitives
            <VertexPositionNormalTexture>(
            PrimitiveType.TriangleList,
            _quad.Vertices, 0, 4,
            _quad.Indices, 0, 2);
}

对于 1000 个精灵,没有倾斜,这给了我 12fps(我想这就像拨打 1000 次 spriteBatch 电话)。这真的很糟糕。但是对于每 10 个精灵倾斜的只有 200 个精灵,我得到 46fps,这明显优于 SpriteBatch(即使我调用了 200 次 DrawUserIndexedPrimitives)。

---我的问题---

我怎样才能将我的调用批处理到DrawUserIndexedPrimitives(或类似的东西),同时让我的精灵每个都包含在它们自己的继承DrawableGameComponent的类中?由于我们游戏引擎的性质以及它处理动画和碰撞等的方式,最后一部分非常重要。

我已经阅读了有关 Vertex Buffers 和 DrawIndexedPrimitives 的所有内容,但我的头并没有完全理解它,也不知道如何为在此绘制的精灵分配新的纹理和世界变换方式。

如果我批处理这些调用,我是否应该期待与 SpriteBatch 相似/更好的性能?

【问题讨论】:

    标签: 3d xna drawing monogame spritebatch


    【解决方案1】:

    在我看来你有几个选择,在这里。请注意,我主要熟悉 PC 上的 XNA 4.0,因此在您的情况下,并非所有这些都是可能的/高性能的。

    简单的黑客方式

    您在绘制精灵时似乎没有使用颜色通道;此技术假定您的示例代表您的真实代码。

    如果您不需要精灵颜色来为精灵着色,您可以劫持它作为将每个精灵数据传递到自定义顶点/像素着色器的一种方式。例如,您可以这样做:

    var shearX = MathHelper.ToRadians(33) / MathHelper.TwoPi;
    var shearY = MathHelper.ToRadians(33) / MathHelper.TwoPi;
    var color = new Color(shearX, shearY, 0f, 0f);
    _spriteBatch.Draw(_texture, rectangle, color);
    

    这将 x 和 y 切变值表示为分别存储在红色和绿色通道中的 2 * pi 的因子。

    然后您可以创建一个自定义顶点着色器来检索这些值并即时执行剪切计算。请参阅 Shawn Hargreaves 的文章 here,了解如何执行此操作。

    混合方法

    另一个相对简单的可能性是将传统的精灵批处理与您的DrawUserIndexedPrimitives 代码结合起来。

    良好性能的关键是尽量减少状态变化,因此仔细排列您的精灵可以大有帮助。组织你的精灵,这样你就可以使用SpriteBatch 一次绘制所有非倾斜的精灵,然后只使用较慢的DrawUserIndexedPrimitives 技术来绘制真正需要它的精灵。假设给定帧中的大多数精灵没有倾斜,这应该会显着减少发送到 GPU 的批次数量。

    批处理 + 自定义顶点格式

    这可能是最好的技术,但它也涉及编写最多的代码。并不是说其中任何一个都特别复杂。

    SpriteBatch 在内部工作的方式是它维护一个动态顶点缓冲区,该缓冲区填充在 CPU 上,然后在一次调用中全部绘制。肖恩·哈格里夫斯 (Shawn Hargreaves) 提供了有关如何完成此类事情的高级概述 here

    扩展DrawUserIndexedPrimitives 以使用此技术的问题是讨厌的世界矩阵;着色器并没有一个很好的方法将特定的世界矩阵附加到特定的精灵(除非您使用的是硬件实例化,我认为您的平台不支持)。那你能做什么呢?

    如果您创建自定义顶点格式,您可以将剪切值附加到每个顶点,并使用这些值在顶点着色器中执行剪切,就像第一种技术一样。这将允许您在一次调用中绘制所有游戏的精灵,这应该非常快。

    您可以找到有关自定义顶点声明here 的信息。

    【讨论】:

    • 感谢您的回复。第一种技术对我们不起作用,因为我们需要那个颜色通道,但它非常简洁。第二种技术还不错,但我仍然需要多次指定和结束 SpriteBatch,因为我们正在为 3/4 rpg 类型的视图在 Y 轴上对内容进行排序。第三个选项听起来像我们想要的,但我还没有真正包裹我的头顶点缓冲区等。如果我有 100 个具有不同纹理的不同精灵,或者作为精灵表有 100 个不同的纹理。您如何指定哪些纹理对应于哪些顶点?你能指点我一个好的教程吗?
    • 您将希望在 spritesheet 上使用单个纹理。交换纹理需要更改状态,这可以防止绘制调用被批处理。顶点可以被分配纹理坐标,它决定了纹理的哪一部分被绘制在它们上面。 MSDN 有一个关于绘制纹理四边形的基本教程;它使用一种内置的顶点格式,但它应该可以理解:msdn.microsoft.com/en-us/library/…
    • 好的,有道理。我认为错误地理解了纹理坐标的工作原理,这让我感到困惑。谢谢。
    猜你喜欢
    • 2013-12-07
    • 2019-12-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-06-21
    • 1970-01-01
    相关资源
    最近更新 更多