双线性插值 - DirectX vs. GDI+

7
我有一个C#应用程序,其中我编写了使用位图/纹理刷渲染的GDI+代码来呈现2D图像,可以应用各种图像处理功能。这段代码是模仿现有DX9代码的应用程序中的新路径,并且它们共享一个常见库来执行所有矢量和矩阵(例如ViewToWorld / WorldToView)操作。我的测试床由与新的GDI+代码输出进行比较的DX9输出图像组成。
在视口渲染到匹配位图尺寸的简单测试用例中(即没有缩放或平移),匹配像素完美(没有二进制差异)-但是一旦图像被放大(放大),我在5-10%的像素中得到非常小的差异。差异的大小为1(偶尔为2)/ 256。我怀疑这是由于插值差异造成的。 问题:对于DX9正交投影(和恒等世界空间),使用垂直和居中于纹理四边形的相机,是否合理地期望DirectX.Direct3D.TextureFilter.Linear在使用System.Drawing.Drawing2D.InterpolationMode.Bilinear设置时生成与GDI+ TextureBrush填充的矩形/多边形相同的输出?
对于这种情况(放大),DX9代码正在使用此代码(MinFilter,MipFilter设置类似):
Device.SetSamplerState(0,SamplerStageStates.MagFilter,(int)TextureFilter.Linear); 而GDI+路径正在使用: g.InterpolationMode = InterpolationMode.Bilinear; 我认为“双线性插值”是一个相当具体的滤波器定义,但是然后我注意到GDI+中还有另一个选项“HighQualityBilinear”(我已经尝试过了,没有区别-这是有道理的,考虑到“添加预缩小预处理”)。 跟进问题:是否合理期望在DirectX和GDI+之间匹配像素完美的输出(假设所有传递的外部坐标相等)?如果不是,为什么? 澄清:我使用的图像是不透明的灰度图(R = G = B,A = 1),使用Format32bppPArgb。

最后,我还可以使用其他的API(例如Direct2D、WPF、GDI等)- 这个问题通常适用于在这些API中比较“相当”的双线性插值输出图像的输出。谢谢!


我不明白。当你使用插值时,你怎么能期望像素完美的值呢?插值的目的是改变像素值。如果你的观点是DX和GDI+不以相同的方式进行插值:那是的,它们使用不同的代码。 - Hans Passant
代码不同当然会有所不同,但由于双线性插值的定义是一种简单的算法(http://en.wikipedia.org/wiki/Bilinear_interpolation),用于查找相邻像素的加权贡献,为什么它们会产生不同的结果呢?如果它们使用相同的公式,结果应该是等价的(至少在浮点舍入误差范围内)。我正在寻找关于DX9和GDI+之间公式差异的任何具体知识。 - holtavolt
因为这种代码在精确之前被优化为快速,所以如果您想找到具有特定知识的人,您必须致电微软支持。我只知道GDI+的插值有些嘈杂,利用人眼无法观察到小差异来提高速度。那真的很重要,但那是10年前的事了。 - Hans Passant
@holtavolt,没错。DX内部将所有东西都表示为浮点数。X8R8G8B8只是显示颜色深度。它支持不同颜色深度的显示器。它只是将一个颜色(带有浮点组件)转换为所需位数的整数颜色。我知道这一点是*确定的,因为我以前写过D3D程序。 - Stephen Chung
@holtavolt,迫不及待地等待你的结果……只是好奇!:-) 关于银行家舍入法的补充说明--它是取最近的偶数整数,也是默认的IEEE舍入方式。 - Stephen Chung
显示剩余12条评论
3个回答

6
DirectX主要在GPU上运行,DX9可能正在运行着着色器。GDI+使用完全不同的算法。我认为期望这两者产生完全匹配的像素输出是不合理的。
我期望DX9的质量比GDI+更好,后者是旧版GDI的一次改进,但并没有太大的提升。众所周知,GDI+在抗锯齿线和保持图像缩放质量方面存在问题(这似乎是您的问题)。为了获得类似于最新一代GPU纹理处理的质量,您需要转向WPF图形。这可以提供类似于DX的质量。
WPF也使用GPU(如果可用),并会退回到软件渲染(如果没有GPU),因此GPU和软件渲染之间的输出相当接近。
编辑:尽管这被选为答案,但它只是一个早期尝试来解释,并没有真正解决真正的原因。读者应参考问题和答案中列出的讨论。

同我之前回复 Hans 的一样,这里也适用。你能指出任何关于 GDI+ 图像缩放问题的具体问题吗?这可能会解释这个问题(例如,在双线性模式下存在文档缺陷吗?) - 我找不到任何信息。DX9 代码没有使用着色器,我很确定使用参考(软件)路径会看到相同的结果,但我会尝试一下。 - holtavolt
赏金仍然开放 - 有人知道为什么DX9生成不同的双线性插值结果而GDI+不是吗?提供源代码或权威参考资料即可获得赏金。 - holtavolt
@holtavolt,你尝试过在DX9中使用软件参考渲染器吗?我想如果你正在使用GPU硬件过滤,那可能取决于GPU硬件所做的优化——例如,在没有人注意到的地方提高性能。 - Stephen Chung
只是出于好奇,你能发布展示差异的图片吗? - Stephen Chung
DirectX,至少在9版本中,并没有使用着色器来处理很多内容。它确实在GPU上运行而不是CPU上,并且GPU可以以最优的方式处理其架构中的过滤。 - ssube
显示剩余3条评论

2
为什么你会假设他们使用相同的公式?
即使他们确实使用相同的公式,你认可实现不同,你也会期望输出结果相同吗?
最终,代码的设计是为了适应感知而不是数学精度。虽然如果你想要,可以通过CUDA获得这种精度。
与其惊讶于你得到不同的结果,我会非常惊讶如果你得到像素完美的匹配。
它们表示颜色的方式是不同的……我知道NVIDIA使用浮点数(也许是双精度)来表示颜色,而GDI使用整数。

http://en.wikipedia.org/wiki/GPGPU

在DX9着色器2.0中,颜色的实现方式从整型变为了24位和32位浮点数。如果比较ATI/AMD渲染和Nvidia渲染,可以清楚地看到颜色非常不同。我最初注意到这一点是在《地球quake 2》中...两张卡之间的差异令人震惊 - 当然,这归功于许多因素,其中最小的是它们的双线性插值实现。即使我对此有所错误,我通常认为期望两种不同的算法实现完全相同是例外的。它们都被优化以提高速度,因此优化的差异将直接转化为图像质量的差异,因为这种速度来自于裁剪角落/近似的不同方法。编辑:关于规范的制定是在我回答之后发生的。无论你如何指定,用于存储数据类型的数据类型都会不同。此外,浮点数的实现可能也不同。我可能错了,但我非常确定C#实现浮点数的方式与Nvidia使用的C编译器不同。(并且这假设GDI+不会将浮点数转换为等效的整数....)

+1 因为提出了浮点数/字节颜色表示问题。这一位的差异可能只是简单的四舍五入差异。 - Stephen Chung
@John - 颜色元素被指定为相同的格式(GDI+ = PixelFormat.Format32bppPArgb,DX9 = Format.X8R8G8B8) - holtavolt
@holtavolt,没错。@John Nicholas 是提出颜色表示问题的人——目前看来这似乎是最有可能的原因。我认为你仍然可以将赏金奖励改给他,我认为他值得。 - Stephen Chung
重新分配是不可能的。我已经开始了另一个赏金,一旦24小时的赏金分配计时器完成,我会把它给你,约翰。 - holtavolt
@ Stephen - 你的帮助也值得奖赏,所以请继续保持 - 谢谢! - holtavolt
显示剩余6条评论

0

舍入差异有两种可能性。第一种是显而易见的,当RGB值被计算为两侧值的分数时。第二种更微妙,当计算用于确定两点之间分数的比率时。

如果两种算法的实现不同,每个像素完全相同我会非常惊讶,因为有很多地方可能会出现+/-1的差异。除非获得两种方法的确切实现细节,否则无法比这更精确。


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