C# XNA 2D拖尾效果优化

3

目前在我的游戏中,每隔5帧就会添加一个半透明的纹理复制品到拖尾的List<>中,作为拖尾效果。

这些拖尾的alpha值每帧都会减少,绘制函数会遍历列表并绘制每个纹理。一旦它们的alpha值达到0,它们就会从List<>中移除。

结果是在移动实体后面产生了一个不错的拖尾效果。问题是对于100多个实体,帧率开始急剧下降。

所有的拖尾纹理都来自同一张精灵表,所以我认为这不是批处理问题。我对代码进行了分析,并且发现在FPS下降峰值时,CPU强度比正常FPS时低,因此我认为这是GPU限制的问题?

有没有更有效地实现这个效果的方法?

以下是我使用的通用代码:

// fade alpha
m_alpha -= (int)(gameTime.ElapsedGameTime.TotalMilliseconds / 10.0f);

// draw
if (m_alpha > 0) {
    // p is used to alter RGB of the trails color (m_tint) depending on alpha value
    float p = (float)m_alpha/255.0f;
    Color blend = new Color((int)(m_tint.R*p), (int)(m_tint.G*p), (int)(m_tint.B*p), m_alpha);               
    // draw texture to sprite batch
    Globals.spriteBatch.Draw(m_texture, getOrigin(), m_rectangle, blend, getAngle(), new Vector2(m_rectangle.Width/2, m_rectangle.Height/2), m_scale, SpriteEffects.None, 0.0f);                
} else {
        // flag to remove from List<>
        m_isDone = true;               
}

我想我应该指出,给Trail类的m_texture是对所有Trail共享的全局纹理的引用。我不会为每个Trail创建一份硬拷贝。
编辑:如果我仅仅注释掉SpriteBatch.Draw调用,即使我为数百个对象每一帧都分配一个新的trail,在帧率上也没有下降……一定有更好的方法来做这件事。

你尝试过禁用/删除轨迹效果来确保它是原因吗? - House
2
你应该发布一些代码。很明显这是可能的,粒子系统可以轻松实现超过100个逐渐消失的粒子。 - House
粒子的源矩形有多大? - ClassicThunder
4
你是不是每5帧就复制一次纹理?如果是,那就别这么做了,只需在不同的透明度下绘制相同的纹理即可。 - user155407
1
根据Scott W所说的,不要从列表中删除它们,只需更改alpha值,然后根据需要将它们设置回默认值即可。你当前所做的是缓慢的,因为你不停地进行释放和重新分配操作。GPU可以处理更多的轨迹,如果它不能处理那么多就会感到惊讶... - Aleks
显示剩余2条评论
1个回答

3
通常情况下,对于轨迹而言,不需要在每一帧清除屏幕,而是在绘制当前帧之前绘制一个透明的屏幕大小矩形。这样,先前的帧被“变暗”或“颜色模糊”,而新帧则完全“清晰”和“明亮”。随着重复进行,从所有以前的帧生成了一个轨迹,它们永远不会被清除,而是被“变暗”。
这种技术非常高效,它被用于著名的Flurry屏幕保护程序(www.youtube.com/watch?v=pKPEivA8x4g)。
为了使轨迹更长,您只需增加用于清除屏幕的矩形的透明度。否则,您可以使其更不透明以使轨迹更短。但是请注意,如果通过使矩形太透明来使轨迹过长,则由于alpha混合,可能会留下一些光痕迹,即使很长时间后也可能无法完全擦除。 Flurry屏幕保护程序遭受了这种伪影问题,但有方法可以补偿。
根据您的情况,您可能需要调整技术。例如,您可能希望具有多个绘图层,允许某些对象留下轨迹,而其他对象则不生成轨迹。
相比于您当前的方法尝试重绘数千次精灵,这种技术对于长轨迹更加高效。
另一方面,我认为您代码中的瓶颈在于以下行:
Globals.spriteBatch.Draw(m_texture, getOrigin(), m_rectangle, blend, getAngle(), new Vector2(m_rectangle.Width/2, m_rectangle.Height/2), m_scale, SpriteEffects.None, 0.0f);   

如果像Draw()这样的GPU调用数量达到数千次,效率会很低下。相比之下,如果你有一个缓冲区列表中包含多边形,每个多边形都位于正确的位置并且具有透明度信息存储,那么只需一次Draw()调用,就可以使用正确的纹理和透明度渲染所有多边形。很抱歉我无法为您提供此代码,但如果您想继续使用您的方法,这可能是您正在追求的方向。简而言之,您的GPU当然可以一次性绘制数百万个多边形,但不能调用那么多次Draw()。


谢谢,谢谢,谢谢!这正是我所期望的答案! - kbirk
那些由于 alpha 混合留下的痕迹,有什么方法可以防止或避免它们出现? - kbirk
@Pondwater:我知道两种解决方案:(1)找出那些痕迹的最终色调,并使您的原始图层具有该颜色。例如,如果这些痕迹的颜色为(5,5,5),则不要从黑色屏幕(0,0,0)开始,而是从(5,5,5)开始。虽然不完美,但它有效且易于操作。(2)在渲染过程中,您应该能够选择使用的alpha混合类型。默认值为“乘法”。应该有一种是“加法”或“减法”。您只需偶尔添加(-1,-1,-1)到整个屏幕上,以确保颜色最终变为(0,0,0)。 - Eugenio De Hoyos
@Pondwater: 希望"减法"混合不会导致颜色值为负数。我想XNA会将小于0的数字截断为0。这些伪像的原因是"乘法"混合。当你将两个加权整数相乘,并四舍五入结果时,可能永远无法收敛到预期的值。如果伪像的色调与预期的最终值相差1或2个值,并且最终的色调是灰度值,我建议选择选项1。在背景为(0,0,0)、(1,1,1)甚至(10,10,10)之间没有太大区别... - Eugenio De Hoyos

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接