我在WPF中遇到过像素着色器的奇怪行为。
这个问题100%可重复,所以我写了一个小的演示程序。您可以下载源代码here。
所有邪恶的根源是名为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依赖项属性。
所以这个框架元素是像素着色器效果的目标。为简单起见,我使用了here的灰度着色器效果。所以我将GrayscaleEffect应用于MyFrameworkElement。你可以看到结果,看起来不错。
直到我大幅增加EndX属性。
小线模糊,大线很好!
但如果我删除灰度效果,所有线条都会看起来应该如此。
有人可以解释这种模糊的原因是什么? 或者甚至更好,我该如何解决这个问题?
答案 0 :(得分:1)
请注意,图像在垂直方向上通常是模糊的,但在水平方向上是锯齿状的。
由于着色器应用于光栅图像而非矢量,因此将线条栅格化为纹理。硬件通常支持高达8198 * 8192的纹理。 在我的情况下,正如你所说的那样,“模糊”出现在滑块值16384.因此,我的virtualBox虚拟图形卡最多支持16384 * 16384。 您的限制可能不同。 所以只要保持这个值低于那个值。
但是,WPF光栅化整个图像很奇怪,因为只有一小部分可见。 所以还有另一个可能的原因,就是在着色器本身,但它被编译成二进制,所以我无法检查它。
更新: 就我而言,它看起来像这样:
看起来它是垂直过滤但不是水平过滤。
答案 1 :(得分:1)
使用自定义像素着色器,它必须创建一个中间位图,然后由像素着色器对该纹理进行采样。
您正在创建一个大型渲染,因此您在渲染路径中遇到了一些限制。
快速解决方法是剪辑您想要呈现的内容,如下所示:
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将其用于过滤的默认过滤器更改为较低质量的过滤器......双线性,而不是幻想......这可能会影响您获得的质量。
http://10rem.net/blog/2010/05/16/more-on-image-resizing-in-net-4-vs-net-35sp1-bilinear-vs-fant
UPDATE2:
这种证实了我上面的想法。
如果您使用Windows性能工具包/套件(Windows SDK的一部分),那么您可以在增加滑块值时看到橙色图形中的视频内存被吞噬,因为正在创建更大的中间位图纹理。它一直在增加,直到达到极限,然后它变得平坦......那就是当像素化变得明显时。
UPDATE3:
如果您将渲染模式设置为“软件渲染器”(第0层),那么您可以看到它如何处理渲染如此大的视觉效果 - 工件开始出现在不同的点....可能是因为纹理大小限制比您的GPU更大/不同。但是工件仍然出现,因为它在内部使用了双线性滤波器。
尝试使用RenderOptions.SetBitmapScalingMode将过滤器升级为Fant似乎不会以任何方式改变渲染质量(我猜是因为它在通过自定义像素着色器路径时不受尊重)。
将其放入Application_Startup以查看软件渲染器结果:
RenderOptions.ProcessRenderMode = RenderMode.SoftwareOnly;
答案 2 :(得分:1)
好的,我有这个! 我用你的灰度效果反编译了库,并反编译了WCF PresentationCore库,以检查为什么 BlurEffect 在相同的情况下完美。 我发现 BlurEffect 实现了 GrayscaleEffect 中不存在的抽象方法 Effect.GetRenderBounds 。我还注意到 GrayscaleEffect 是针对PresentationCore v 3.0.0构建的,其中Effect没有 GetRenderBound 。
因此,这是WPF的第3版和第4版之间的不兼容性。 有三种方法可以解决它:
我尝试了第二种方式,问题就消失了。
答案 3 :(得分:0)
旧问题,但对于在应用Custom ShaderEffect后出现图像模糊问题的人可能会有用。
提到的OP问题也可能与渲染内容的规模相关, 将WPderhadersLibrary中的ShaderEffects应用于正常窗口中的视频,文本和任何其他内容后,我遇到了类似的模糊问题。
我注意到该图像向下移动一点点,导致“像素分裂”,因此我为选择的ShaderEffect创建了两个新属性:XOffset和YOffset,并将它们应用于HLSL(参见下面的代码),然后绑定XAML中的滑块:
float2 newPos;
newPos.x = uv.x + offsetX;
newPos.y = uv.y + offsetY;
然后我尝试了一些任意的偏移,并能够重新对齐图片。仍然存在一些最小的模糊(或细节上的损失),但结果明显更好。
目前这个解决方案存在问题,我不知道如何根据分辨率或窗口大小预测偏移量。