【问题标题】:Combining multiple pixel shaders efficiently有效组合多个像素着色器
【发布时间】:2012-11-29 03:11:49
【问题描述】:

所以我正在使用 XNA 3.1 制作一个东西,并且我有很多通过像素着色器应用的单独效果。这些来自各种来源,例如特殊攻击,环境等。我遇到的问题是我注意到帧速率显着降低。

目前,我正在将整个场景绘制到 RenderTarget2D,然后将所有效果应用到该 RenderTarget2D。我存储了一个包含效果及其 ID 的 SortedDictionary(ID 用于在运行时更改参数),并且我正在对其进行迭代并一个接一个地应用每个效果:

foreach(KeyValuePair<Ref<int>,Effect> p in renderEffects)
{
    Effect r = p.Value;
    g.SetRenderTarget(0, MainGame.MainRenderTarget);
    //Change RenderTarget to allow code to grab existing texture in the same draw area.
    levelDraw = MainGame.LevelRenderTarget.GetTexture();
    //Change back to draw back to this texture, allowing render effects to be layered.
    g.SetRenderTarget(0, MainGame.LevelRenderTarget);

    MainGame.StartDraw(MainGame.GameBatch);
    //Starts the sprite batch and sets some parameters
    r.Begin();
    r.CurrentTechnique.Passes[0].Begin();
    MainGame.GameBatch.Draw(levelDraw, new Rectangle(0, 0, levelDraw.Width, levelDraw.Height), Color.White);
    r.CurrentTechnique.Passes[0].End();
    r.End();
    MainGame.GameBatch.End();
}

现在,当仅分层 3 个效果时,这会产生明显的帧丢失,而当应用 10 个效果时,它会从 60FPS 下降到 16FPS,这当然是不可接受的。我想知道是否有更有效的方法来做到这一点。考虑到我只有一个纹理,我认为我可以将效果组合到一个文件中并执行多个通道,而无需重新抓取纹理。不过,我不确定这是否可能。

我不太确定最好的方法是什么,但我想肯定有比我现在的方法更好的方法。

【问题讨论】:

  • 我已经编辑了你的标题。请参阅“Should questions include “tags” in their titles?”,其中的共识是“不,他们不应该”。
  • 谢谢。我倾向于将它们包括在内,以便人们一眼就知道他们是否可以提供帮助,而不必查看标签,但我可以看到在那里不使用它们的逻辑。
  • 标签包含一种比看乱码标题更好的机制。您可以设置“最喜欢的标签”,这将使带有这些标签的消息突出显示;和“忽略标签”,这将导致带有这些标签的消息被隐藏。
  • 如果你能找出哪些阶段最慢,那就太好了——应用每个效果,切换效果,或者介于这些动作之间的东西。或者可能只是您使用的是全高清分辨率,所以它自然很慢。
  • 我做了一些测试。应用着色器本身会将速率从 60 降低到大约 50 或 40。交换 RenderTarget,从中获取纹理,然后将其交换回来完成剩下的工作。

标签: c# xna effect pixel-shader xna-3.0


【解决方案1】:

您是否在绘制时对所有内容进行了完全着色?如果你的着色器计算量很大,你应该先做一个“深度通道”,只测试/写入 Z 缓冲区(颜色缓冲区写入关闭)。此外,使用非常简单的着色器来“深度填充”屏幕。

换句话说,渲染所有不透明对象仅在第一遍时更新深度缓冲区。

在第二次通过时,您打开着色器(并关闭深度写入,无需使用带宽来写回已经存在的相同值)。这将掩盖所有过度绘制像素的所有不必要工作,因为它们将立即通过深度测试。

编辑:现在我意识到 OP 正在做全屏效果,而不是场景渲染。

【讨论】:

  • 我只是将着色器应用到单个纹理,所以深度缓冲区应该没有问题,因为每个像素只为每个通道绘制一次,并且不会透支任何像素在深度缓冲区中。
  • 我明白你的意思,你只是在一个巨大的四边形“广告牌”上应用一系列全屏效果,这就是性能瓶颈?
  • 是否可以对效果循环进行排序以最小化状态变化?通常,在降低成本方面:着色器程序、纹理绑定、缓冲区绑定等。也许您可以通过重新排序传递以最小化状态更改来避免状态更改。顶点缓冲区和纹理可以平铺到更大的纹理/更大的顶点缓冲区上,以消除一些不断变化的状态。
  • 我的效果都是像素着色器,它们必须以特定的顺序应用才能达到我需要的效果(例如,在波纹效果之前应用的发光效果会产生与在发光之前应用波纹)。
  • 我会使用它。使用 Stopwatch 对象来测量每条开始/绘制/结束线的纳秒,并注销以调试时间以查看哪个效果和/或操作是您的实际瓶颈。
【解决方案2】:

sn-p 中的方法可能会非常慢,因为您正在为每个效果进行纹理抓取和全屏绘制,这会在任何情况下强调 CPU 和 GPU 之间的内存带宽在着色器内部。正如您在帖子中建议的那样,您可能需要创建一组着色器,每个着色器都包含多个操作,而不是一遍又一遍地运行读写循环:一个昂贵的着色器通常仍然比许多读写重复更快简单的着色器。

您可能想查看Shawn Hargreaves article on shader fragments in HLSL 和Tim Jones's code for doing this in XNA

【讨论】:

  • Tim Jones 的代码对我来说毫无用处,就像在 XNA 4 中一样,而我使用的是 3.1。不过,感谢您链接这些文章。
  • 对于您的问题大小,您可能最好还是手动进行组合操作——您可能会发现堆叠效果会使事情更难视觉破译,这意味着复杂的多效果版本可以在不影响外观的情况下进行优化以使它们更便宜。
  • 这让我开始思考,我正在考虑编写一个解析器和编译器来获取一个 HLSL 着色器列表,每个都有特定的参数,并将它们全部推送到一个文件中。它将代码作为字符串读取,然后将代码从字符串形式转换为字节数组,可以将其写入新的效果文件。我不确定这是否可行,或者是否有任何好处,但它可能值得一试。我只是不确定如何从文件中获取原始代码作为字符串...
猜你喜欢
  • 2012-12-14
  • 2011-02-26
  • 2010-12-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多