我有一个内置D3D的运行3D引擎(通过SlimDX)。为了避免中断渲染管道,我将许多具有相同材质的对象拼接到更大的网格中(以减少状态切换)。这样做效果很好,并且能够满足我的需求。
我遇到的问题是,在运行时,我需要更改那些较大批量网格的某些子集的材质属性。我正在使用属性缓冲区,它一直运行得相当好。我之前一直在使用有限数量的活动属性(每个网格约5个),但现在发现需要在材质上有更多的变化(不透明度/颜色混合的不同色调),因此最终可能有一百或更多组合。由于这些更改发生在运行时,我无法在渲染开始之前将它们捆绑在一起。当然,我可以重新构建网格,但我不愿意,因为它很慢并且切换回来,材料之间的第四个需要以交互速度完成。
所以我的问题是,最佳路线是什么?
我应该实现一个更强大的属性处理系统,该系统会根据需要动态屏蔽具有可用属性ID的面,然后在完成后重置它们吗?我听说属性缓冲区中的碎片会产生额外的性能损失,而且我也不确定随后的DrawSubset()调用的性能损失与之间的材料开关(即什么时候太多以及何时应该优化我的属性数组?)。有这方面经验的人吗?
我的另一个想法是使用参数化的像素着色器。我不需要任何奇特的效果,只需要最低限度(当前是内置的平面着色器,仅有颜色和某些对象的透明度),因此着色器模型1足以满足我的需求。这里的想法是使用一个通用着色器,而不是在调用之间切换材料只是改变一些着色器参数。但我不知道这是否比切换材料更快和/或可编程着色器比构建的更慢(给定相同的结果)。
我也很好奇切换网格或在一个大网格中绘制不同子集之间的性能差异(两种情况下给定相同数量的材料切换)。
据我所知,这些问题在GFX卡和它们各自的性能/年龄之间可能有所不同,但我只是在寻找关于如何集中精力的一般指导(即什么类型的状态切换/ CPU干扰,给出了最大的GPU击中率。内存也是一个问题,所以任何复制整个(或大部分)网格的实现对我来说都是不可能的。
我的重点是老款(5y)/功能不足/集成的GFX卡的性能,而不一定是顶级的游戏玩家卡或工作站卡(如Quadro)。根据着色器在特定电路板上的性能有多好,我想使用着色器可以决定解决方案。
非常感谢任何建议和反馈。
非常感谢提前!
答案 0 :(得分:3)
改变着色器参数也同样慢。理想情况下,您希望编写基于Shader 2的着色器,将大部分属性缓冲区上载到图形卡。然后,您有一个per-vertex属性字段,可以选择适当的属性缓冲区。
您的性能问题将来自您使用的绘制调用次数。您使用的绘制调用越多,性能就越高。着色器常量或纹理的任何更改都需要新的DIP调用。你想要做的是最小化着色器常数修改次数和DIP调用次数。
它成为一个相当复杂的过程思想。
例如,如果您正在处理其中包含64个骨骼的骨架模型,那么您有2个选项。 1为每个骨骼的网格数据设置世界矩阵并调用DIP。或者你可以一次加载尽可能多的骨骼矩阵,并使用顶点上的值来选择它将使用哪个骨骼并调用DIP一次。第二个会更快。你可能会发现你也可以很容易地通过这种方式做一些基于多骨的皮肤。
这适用于导致着色器不断变化的任何事情。请注意,即使您可以使用固定功能管道,大多数现代图形硬件(即自2002年发布的Radeon 9700以来的任何内容)都会将固定功能转换为基于着色器,因此同样的性能问题也适用。
基本上避免任何事情的事情都是导致你进行另一次DIP呼叫的事情。显然,避免为所有事情做这件事是不切实际的,而且某些变化成本更低。作为一个粗略的经验法则,按照费用的顺序要避免的事情如下:(因为我测试了这一点,所以你可能想要做一些关于这个问题的测试和替代阅读,请注意它已经有一段时间了)
1)更改着色器
2)改变纹理
3)更改着色器常数
4)更改顶点缓冲区
5)更改索引缓冲区
1是迄今为止最贵的。
与其他部分相比,4和5相当便宜,但不同的顶点格式可能会导致更大的问题,因为它可能会导致着色器发生变化。编辑:我不完全确定为什么改变常数会伤害很多。我原以为这样的改变会很好。也许在现代硬件上它不是一个问题。在一些早期硬件上,常量被编译到着色器中,因此更改常量会导致完整的着色器更改。
与任何你最好的尝试一样,看看会发生什么。
对所有调用进行排序的有趣解决方案可以是使用排序键,其中前8位为您提供着色器ID。纹理的下一个10位等等。然后,您可以执行常规数字排序,并且可以轻松地使用不同的排序顺序来查看提供最佳性能的内容:)
Edit2:值得注意的是,任何改变像素着色器状态的东西都比改变顶点缓冲区状态更昂贵,因为它在管道中更深。因此它需要更长的时间来泡沫......