XNA/Monogame,绘制多个剪切/倾斜精灵的最快方法

Mat*_*ttB 7 3d xna drawing monogame spritebatch

我通常SpriteBatch在XNA/Monogame中使用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();
Run Code Online (Sandbox Code Playgroud)

明显的缺点是它需要你SpriteBatch为每个剪切的精灵进行一个新的开始和结束调用.我们目前只需要2个电话即可SpriteBatch开始游戏.一个用于UI,一个用于世界的东西.我们的艺术家想要使用剪切来做摇晃的树木或动物的腿和四肢的动物,所以如果我们给他们选择,我可以看到这个数字跳到10多个不同的批次.

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

我已经为Android编写了一个测试,调用1000个精灵.没有任何偏斜,它可以在大约11秒或大约53fps中绘制所有1000,600次.但是,如果我倾斜每十个精灵(增加100个新的SpriteBatch调用),它需要47秒,或大约12fps.

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

所以我也使用绘制的四边形创建了相同的测试DrawUserIndexedPrimitives.每个Quad使用BasicEffect在Game类中创建的共享,并通过Sprite类构造函数传递.我在每个之前设置了World Matrix和Texture,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);
}
Run Code Online (Sandbox Code Playgroud)

对于1000个精灵,没有歪斜,这给了我12fps(我想它就像打1000个spriteBatch电话).那真的很糟糕.但是对于每10个精灵倾斜的只有200个精灵,我得到46fps,这明显好于SpriteBatch(即使我打电话DrawUserIndexedPrimitives200次).

- -我的问题 - -

我怎样才能将我的调用批处理DrawUserIndexedPrimitives(或类似的东西)同时保持我的精灵每个都包含在他们继承的类中DrawableGameComponent?最后一部分非常重要,仅仅是因为我们的游戏引擎的性质以及它处理动画和碰撞的方式.

我已经阅读了关于Vertex Buffers的内容DrawIndexedPrimitives,但是我的脑袋并没有完全缠绕它,也不知道我是如何为这样绘制的精灵分配新的纹理和世界变换.

SpriteBatch批量调用这些电话相比,我是否应该期待相似/更好的性能?

Col*_*ell 2

在我看来,你在这里有几个选择。请注意,我主要熟悉 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);
Run Code Online (Sandbox Code Playgroud)

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

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

混合方法

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

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

批处理+自定义顶点格式

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

其内部工作方式SpriteBatch是维护一个动态顶点缓冲区,该缓冲区填充在 CPU 上,然后在一次调用中全部绘制。Shawn Hargreaves在这里对此类事情的完成方式进行了高度概述。

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

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

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