WPF在Viewport3D中进行动画时出现闪烁问题

4
我正在编写一个WPF应用程序,遇到了一些奇怪的行为。当我在相机位置上运行动画(PerspectiveCamera.PositionProperty 上的 Point3DAnimation),我会在应用程序内部看到非常糟糕的闪烁伪影。3D 渲染对象似乎在某些帧中消失,并允许窗口的背景显示出来。
下面我编写了一个非常简单的示例应用程序来演示我的问题。要使用它,只需编译它并使用箭头向上和向下键进行缩放。该问题非常重复:每次尝试缩放时,对象都会在动画期间闪烁,然后在动画完成后再次变得“固定”。
我正在运行 Windows 7 32 位,并使用 NVIDIA GeForce 8600GT。以下是一些有趣的细节:
1)它似乎与硬件有关。我在 WPF 论坛上发布了一个帖子,其中一位用户回答说他一切正常。我请一些朋友尝试了一下,其中一位报告了与我经历的完全相同的闪烁,而另一位则说一切看起来都很好。
2)通过 NVIDIA 控制面板强制垂直同步并启用三重缓冲不会解决问题。
3)将所需的动画帧速率显着降低可以显著改善问题。在低帧速率(例如,5FPS)的情况下,闪烁会消失……但是动画看起来很可怕。我提供的示例应用程序仅显示映射到四边形上的单个图像,因此我认为这不应该是处理能力的问题!
4)问题似乎与多边形顶点超出可视窗口有关。如果我将程序中的closeDist值设置为4(这样,即使在“缩放”状态下,对象仍然完全适合窗口内),就不会有闪烁。但是,随着closeDist的增加,一旦顶点超出窗口,闪烁就会发生。随着closeDist的增加,闪烁似乎越来越严重。当 closeDist 的值为9.8(正好在相机的 NearPlaneDistance 将完全切断对象之前)时,闪烁最严重。
废话不多说,以下是示例代码!
MainWindow.xaml:
<Window x:Class="WPFFlickerTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        KeyDown="Window_KeyDown">
    <Grid>
        <Viewport3D Name="Viewport">
            <Viewport3D.Camera>
                <PerspectiveCamera LookDirection="0,0,1" FieldOfView="70" x:Name="viewportCam" />
            </Viewport3D.Camera>

            <ModelVisual3D>
                <ModelVisual3D.Content>
                    <AmbientLight />
                </ModelVisual3D.Content>
            </ModelVisual3D>
        </Viewport3D>
    </Grid>
</Window>

MainWindow.xaml.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Media.Media3D;
using System.Windows.Media.Animation;

namespace WPFFlickerTest
{
  public partial class MainWindow : Window
  {
    // time the camera animation takes to complete
    private const double animTime = 0.25;

    // path to an image to use (assuming it's 1920x1200 or 1.6 aspect ratio)
    private const string imagePath = "C:/Windows/Web/Wallpaper/Windows/img0.jpg";

    // far and close camera distances
    private const double closeDist = 8, farDist = 10;

    // chosen to align with aspect ratio of the image
    private const double halfW = 4.8, halfH = 3;

    public MainWindow()
    {
      InitializeComponent();

      Model3DGroup modelGroup = new Model3DGroup();

      // set up the mesh
      MeshGeometry3D mesh = new MeshGeometry3D();
      mesh.Positions.Add(new Point3D(-halfW, halfH, farDist));
      mesh.Positions.Add(new Point3D(halfW, halfH, farDist));
      mesh.Positions.Add(new Point3D(halfW, -halfH, farDist));
      mesh.Positions.Add(new Point3D(-halfW, -halfH, farDist));

      // set up triangle indices
      mesh.TriangleIndices = (Int32Collection)new Int32CollectionConverter().ConvertFromString(
        "0,1,2 2,3,0");

      // set up texture coords
      mesh.TextureCoordinates = (PointCollection)new PointCollectionConverter().ConvertFromString(
        "1,0 0,0 0,1 1,1");

      // set up the brush
      ImageBrush brush = new ImageBrush(new BitmapImage(new Uri(imagePath, UriKind.Relative)));

      // create a geometry model based on the mesh and give it a material based on an image
      GeometryModel3D geom = new GeometryModel3D(mesh, new DiffuseMaterial(brush));

      // add the object
      modelGroup.Children.Add(geom);

      // we should have filled in our objects now
      // so we'll just add them to the viewport
      ModelVisual3D modelVisual = new ModelVisual3D();
      modelVisual.Content = modelGroup;
      Viewport.Children.Add(modelVisual);
    }

    // react to keypresses
    private void Window_KeyDown(object sender, KeyEventArgs e)
    {
      switch (e.Key)
      {
        // move the camera to the centre
        case Key.Down: AnimateTo(new Point3D(0, 0, 0)); break;

        // move the camera to the currently targeted image
        case Key.Up: AnimateTo(new Point3D(0, 0, closeDist)); break;
      }
    }

    // animate to a given position
    void AnimateTo(Point3D position)
    {
      Point3DAnimation camPosAnim = new Point3DAnimation(position, TimeSpan.FromSeconds(animTime));
      viewportCam.BeginAnimation(PerspectiveCamera.PositionProperty, camPosAnim);
    }
  }
}

我刚试了一下,看起来没问题...所以你猜测它确实与硬件有关听起来是准确的。我在笔记本电脑上...没有什么特别的...运行.NET 3.5 SP1; 你更新了DirectX/Direct3D吗? - Aaron McIver
@Aaron 很好的建议。我尝试获取最新的DirectX运行时,结果被告知我已经拥有更新或等效版本。dxdiag显示我正在运行DirectX 11。我注意到另一个有趣的细节,我将在下面发布。 - aardvarkk
@Aaron 很有趣。我可能会尝试降级来看看是否可以解决这个问题。我刚刚添加了“有趣的细节#4”。似乎与多边形顶点超出窗口范围有关。也许为了让问题在您的机器上展示出来,可以尝试将closeDist值更改为9.8以增加缩放因子,看看是否会出现任何闪烁。感谢您的帮助! - aardvarkk
@aardvarkk 我在 closeDist = 8,farDist = 10 的情况下运行了它,并注意到动画中的转换,但我绝不认为它们是不好的;因此,我非常确定事物仍在很好地进行动画。 - Aaron McIver
@Aaron 看来你幸免于难了!在我的机器上,效果非常明显。感谢你的关注。我会看看是否还能找到更多有趣的细节... - aardvarkk
5个回答

2

这是一个老问题,但由于我曾经遇到过同样的问题并最终找到了解决方法,所以我认为在这里做个笔记可能会有价值:

正如Abram已经提到的,对我来说NearPlaneDistance是解决方案,但我根本没有将它设置为小值。实际上,在我绘制的模型中,一切都很大。1单位是1毫米,没有平面比彼此更近10个单位。随着我不断增加NearPlaneDistance的值,撕裂感越来越少,直到在25时一切都运行良好。

因此,如果其他人也遇到了这个问题,请尝试使用NearPlaneDistance进行实验。


感谢您的建议。我加载了一个非常大的地图,当相机远离时,闪烁渲染开始出现。通过相机移动时的 Y 位置,我为每个范围设置了不同的 NearPlaneDistance,得到了一个良好的结果,没有任何闪烁。 - Loghman

1

我在使用动态相机定位的PerspectiveCamera时遇到了类似的问题。当相机太靠近物体并且任何一个物体(即使是“背景”中的物体)的一部分被部分遮挡时,相机似乎会“困惑”是否显示该物体或其部分...

尝试将PerspectiveCamera的“NearPlaneDistance”设置为低但非零值,例如0.001。


谢谢您的建议!我相信我尝试将相机的NearPlaneDistance设置为非常小的值,但明天我会再次尝试以查看是否解决了问题。我们目前的解决方法是不要将对象“缩放”到足够远,以至于其任何顶点都不会超出窗口的边界。当顶点保持在窗口内时,动画不会闪烁。 - aardvarkk
刚刚用0.01的值再次尝试了一下,我可以确保所涉及的对象永远不会靠近这个值。闪烁问题没有解决。不过这是一个好主意! - aardvarkk
谢谢建议。对我来说,根据移动相机的Y位置,我将NearPlaneDistance设置为不同的值。 - Loghman

1

我知道这是一个比较老的问题,但我在工作中遇到了这个错误,在墙上撞了大约10个小时,最终找到了解决方案。希望这能帮助其他人-

您需要手动设置Viewport3d的高度/宽度。它不必是硬编码的(可以绑定它,硬编码它,将视口放置在网格中等)。从我的经验来看,视口的大小必须小于窗口大小。

这里的工作假设是WPF在决定视口中的图像是否在可视窗口内时遇到了一些麻烦。

无论如何,希望对你有所帮助


0

虽然没有找到实际的解决方案,但我打算将此标记为已解决。只要模型保持在屏幕区域内并且不超出范围,闪烁问题就不会发生。抱歉!


0
您的动画是由KeyDown事件触发的 - 如果用户长按键,您可能会用BeginAnimation调用来使应用程序过载。

好的建议,但我不认为那是问题所在。我尝试了两件事来测试这个问题(都没有解决问题):1)只在程序开始时执行一次动画,2)在keydown事件触发时添加控制台输出。它只显示一个输出,因此我认为如果我轻敲键盘,它只会被调用一次。还是谢谢你的帮助! - aardvarkk

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