5

我在 WPF 中遇到了像素着色器的奇怪行为。

这个问题是 100% 可重现的,所以我写了一个小演示程序。你可以在这里下载源代码。

万恶之源是一个名为 MyFrameworkElement 的小类:

internal sealed class MyFrameworkElement : FrameworkElement
{
    public double EndX
    {
        get
        {
            return (double)this.GetValue(MyFrameworkElement.EndXProperty);
        }
        set
        {
            this.SetValue(MyFrameworkElement.EndXProperty, value);
        }
    }

    public static readonly DependencyProperty EndXProperty =
        DependencyProperty.Register("EndX",
            typeof(double),
            typeof(MyFrameworkElement),
            new FrameworkPropertyMetadata(0d, FrameworkPropertyMetadataOptions.AffectsRender));

    protected override void OnRender(DrawingContext dc)
    {
        dc.DrawLine(new Pen(Brushes.Red, 2), new Point(0, 0), new Point(this.EndX, 100));
        dc.DrawLine(new Pen(Brushes.Green, 3), new Point(10, 300), new Point(200, 10));
    }
}

如您所见,此框架元素呈现 2 行:下一行具有永久坐标,但上一行取决于 EndX 依赖属性。

所以这个框架元素是像素着色器效果的目标。为简单起见,我使用在这里找到的灰度着色器效果。所以我将 GrayscaleEffect 应用于 MyFrameworkElement。你可以看到结果,它看起来不错。

直到我大幅增加 EndX 属性。

小线模糊,大线没问题!

但是如果我删除灰度效果,所有的线条都会看起来像它们应该的那样。

任何人都可以解释这种模糊的原因是什么?或者更好的是如何解决这个问题?

4

4 回答 4

2

请注意,图像通常在垂直方向上是模糊的,但在水平方向上是锯齿状的。

由于着色器应用于光栅图像,而不是矢量,因此线条被光栅化为纹理。硬件通常支持最高 8198*8192 的纹理。在我的例子中,你所说的“模糊”出现在滑块值 16384 处。所以,我的 virtualBox 虚拟显卡最多支持 16384*16384。您的限制可能会有所不同。所以只要保持这个值低于那个值。

但奇怪的是,WPF 光栅化了整个图像,因为只有一小部分可见。所以还有另一个可能的原因,那就是着色器本身,但它被编译成二进制,所以我无法检查它。

更新:就我而言,它看起来是这样的: 应用了着色器

看起来它是垂直过滤的,但不是水平过滤的。

于 2012-07-25T14:17:38.310 回答
2

使用自定义像素着色器,它必须创建一个中间位图,然后该纹理由像素着色器采样。

您正在创建大量渲染,因此您在渲染路径中遇到了一些限制。

一个快速的解决方法是剪辑您想要渲染的内容,如下所示:

Geometry clip = new RectangleGeometry(new Rect(0,0,this.ActualWidth, this.ActualHeight));

dc.PushClip(clip);

dc.DrawLine(new Pen(Brushes.Red, 2), new Point(0, 0), new Point(this.EndX, 100));
dc.DrawLine(new Pen(Brushes.Green, 3), new Point(200, 10), new Point(10, 300));

dc.Pop();

更新:

一种理论是,当位图超过最大纹理大小(可能会因您的显卡架构而异)时,它使用过滤器来缩放位图......所以它以不同大小通过像素着色器......然后它缩小到原始大小。

因此,缩放过滤器会根据您的位图的内容造成伪影(即水平线和垂直线比对角线更好地按比例缩小和放大)。

.NET 4 将其用于过滤的默认过滤器更改为较低质量的过滤器...双线性,而不是 Fant...也许这也会影响您获得的质量。

http://10rem.net/blog/2010/05/16/more-on-image-resizing-in-net-4-vs-net-35sp1-bilinear-vs-fant

更新2:

这证实了我上面的想法。

如果您使用 Windows Performance Toolkit/Suite(Windows SDK 的一部分),那么您可以在增加滑块值的同时在橙色图中看到视频内存被吞噬,因为正在创建更大的中间位图纹理。它不断增加,直到达到极限,然后变得平坦……这就是像素化变得明显的时候。

在此处输入图像描述

更新3:

如果您将渲染模式设置为“软件渲染器”(第 0 层),那么您可以看到它如何处理如此大的视觉效果 - 伪影开始出现在不同的点......大概是因为纹理大小限制更大/与您的 GPU 不同。但是工件仍然出现,因为它在内部使用了双线性过滤器。

尝试使用 RenderOptions.SetBitmapScalingMode 将过滤器提升到 Fant 似乎不会以任何方式改变渲染质量(我猜是因为它在通过自定义像素着色器路径时不受尊重)。

将其放入 Application_Startup 以查看软件渲染器结果:

RenderOptions.ProcessRenderMode = RenderMode.SoftwareOnly;
于 2012-07-25T14:25:29.523 回答
1

好的,我有这个!我用你的灰度效果反编译了库,还反编译了 WCF PresentationCore 库,以检查为什么BlurEffect在相同情况下可以完美运行。而且我发现BlurEffect实现了GrayscaleEffect中不存在的抽象方法Effect.GetRenderBounds。我还注意到GrayscaleEffect是针对 PresentationCore v 3.0.0 构建的,其中 Effect 没有GetRenderBound

所以这是 WPF 第 3 版和第 4 版之间的不兼容。有三种方法可以修复它:

  1. 如果您有GrayscaleEffect的源代码- 添加所需的方法并针对 4.0.0 版本的运行时进行编译。
  2. 您可以将应用程序使用的运行时切换到版本 3.*。
  3. 如果您没有GrayscaleEffect的来源但不能使用第三版运行时,请编写继承Effect (v4) 并实现缺失方法的GrayscaleEffect包装器。

我尝试了第二种方法,问题消失了。

于 2012-07-26T07:07:20.850 回答
1

老问题,但对于在应用自定义 ShaderEffect 后出现图像模糊问题的人可能有用。

还提到的问题 OP 可能与渲染内容的比例有关,在将 WPFShadersLibrary 中的 ShaderEffects 应用于普通窗口中的视频、文本和任何其他内容后,我遇到了类似的模糊问题。

我注意到图像向下移动了一点点,导致“像素分裂”,所以我为选定的 ShaderEffect 创建了两个新属性:XOffset 和 YOffset,并将它们应用到 HLSL(见下面的代码),然后绑定到 Sliders XAML:

float2 newPos;
newPos.x = uv.x + offsetX;
newPos.y = uv.y + offsetY;

然后我尝试了一些任意偏移并能够重新对齐图片。仍然有一些最小的模糊(或细节损失),但结果明显更好。

目前这个解决方案存在问题,我不知道如何根据分辨率或窗口大小来预测偏移量。

于 2015-01-27T14:36:46.037 回答