2009-04-13 64 views
8

我建立一个WPF应用程序,我想它的背景,充满颗粒随机:快速WPF粒子背景

  • 不透明/ z顺序
  • 尺寸
  • 速度
  • 模糊”(模糊效果)
  • 方向(或路径)

我发现什么,我想它是a really good example,但不幸的是它在Flash和它不是免费的...

我试图实现它,但我不能设法得到它顺利 ...

所以我想知道如果任何你能帮助我提高它为了得到它使用较少的CPU和GPU多所以它是光滑的,即使有更多的颗粒,并在全屏幕模式。

代码 “Particle.cs”:窗口的一个组成XAML代码:定义与它的所有属性

public class Particle 
{ 
    public Point3D Position { get; set; } 
    public Point3D Velocity { get; set; } 
    public double Size { get; set; } 

    public Ellipse Ellipse { get; set; } 

    public BlurEffect Blur { get; set; } 
    public Brush Brush { get; set; } 
} 

XAML “Window1.xaml”一个粒子类径向背景和画布以承载粒子

<Window x:Class="Particles.Window1" 
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
Title="Window1" Height="600" Width="800" Loaded="Window_Loaded"> 
    <Grid> 
     <Grid.Background> 
      <RadialGradientBrush Center="0.54326,0.45465" RadiusX="0.602049" RadiusY="1.02049" GradientOrigin="0.4326,0.45465"> 
       <GradientStop Color="#57ffe6" Offset="0"/> 
       <GradientStop Color="#008ee7" Offset="0.718518495559692"/> 
       <GradientStop Color="#2c0072" Offset="1"/> 
      </RadialGradientBrush> 
     </Grid.Background> 
     <Canvas x:Name="ParticleHost" /> 
    </Grid> 
</Window> 

代码 “Window1.xaml.cs”:这里一切都发生

public partial class Window1 : Window 
{ 
    // ... some var/init code... 

    private void Window_Loaded(object sender, RoutedEventArgs e) 
    { 
     timer.Interval = TimeSpan.FromMilliseconds(10); 
     timer.Tick += new EventHandler(timer_Tick); 
     timer.Start(); 
    } 

    void timer_Tick(object sender, EventArgs e) 
    { 
     UpdateParticules(); 
    } 

    double elapsed = 0.1; 
    private void UpdateParticules() 
    { 
     // clear dead particles list 
     deadList.Clear(); 
     // update existing particles 
     foreach (Particle p in this.particles) 
     { 
      // kill a particle when its too high or on the sides 
      if (p.Position.Y < -p.Size || p.Position.X < -p.Size || p.Position.X > Width + p.Size) 
      { 
       deadList.Add(p); 
      } 
      else 
      { 
       // update position 
       p.Position.X += p.Velocity.X * elapsed; 
       p.Position.Y += p.Velocity.Y * elapsed; 
       p.Position.Z += p.Velocity.Z * elapsed; 
       TranslateTransform t = (p.Ellipse.RenderTransform as TranslateTransform); 
       t.X = p.Position.X; 
       t.Y = p.Position.Y; 

       // update brush/blur 
       p.Ellipse.Fill = p.Brush; 
       p.Ellipse.Effect = p.Blur; 
      } 
     } 

     // create new particles (up to 10 or 25) 
     for (int i = 0; i < 10 && this.particles.Count < 25; i++) 
     { 
      // attempt to recycle ellipses if they are in the deadlist 
      if (deadList.Count - 1 >= i) 
      { 
       SpawnParticle(deadList[i].Ellipse); 
       deadList[i].Ellipse = null; 
      } 
      else 
      { 
       SpawnParticle(null); 
      } 
     } 

     // Remove dead particles 
     foreach (Particle p in deadList) 
     { 
      if (p.Ellipse != null) ParticleHost.Children.Remove(p.Ellipse); 
      this.particles.Remove(p); 
     } 
    } 

    private void SpawnParticle(Ellipse e) 
    { 
     // Randomization 
     double x = RandomWithVariance(Width/2, Width/2); 
     double y = Height; 
     double z = 10 * (random.NextDouble() * 100); 
     double speed = RandomWithVariance(20, 15); 
     double size = RandomWithVariance(75, 50); 

     Particle p = new Particle(); 
     p.Position = new Point3D(x, y, z); 
     p.Size = size; 

     // Blur 
     var blur = new BlurEffect(); 
     blur.RenderingBias = RenderingBias.Performance; 
     blur.Radius = RandomWithVariance(10, 15); 
     p.Blur = blur; 

     // Brush (for opacity) 
     var brush = (Brush)Brushes.White.Clone(); 
     brush.Opacity = RandomWithVariance(0.5, 0.5); 
     p.Brush = brush; 

     TranslateTransform t; 

     if (e != null) // re-use 
     { 
      e.Fill = null; 
      e.Width = e.Height = size; 
      p.Ellipse = e; 

      t = e.RenderTransform as TranslateTransform; 
     } 
     else 
     { 
      p.Ellipse = new Ellipse(); 
      p.Ellipse.Width = p.Ellipse.Height = size; 
      this.ParticleHost.Children.Add(p.Ellipse); 

      t = new TranslateTransform(); 
      p.Ellipse.RenderTransform = t; 
      p.Ellipse.RenderTransformOrigin = new Point(0.5, 0.5); 
     } 

     t.X = p.Position.X; 
     t.Y = p.Position.Y; 

     // Speed 
     double velocityMultiplier = (random.NextDouble() + 0.25) * speed; 
     double vX = (1.0 - (random.NextDouble() * 2.0)) * velocityMultiplier; 
     // Only going from the bottom of the screen to the top (for now) 
     double vY = -Math.Abs((1.0 - (random.NextDouble() * 2.0)) * velocityMultiplier); 

     p.Velocity = new Point3D(vX, vY, 0); 
     this.particles.Add(p); 
    } 


    private double RandomWithVariance(double midvalue, double variance) 
    { 
     double min = Math.Max(midvalue - (variance/2), 0); 
     double max = midvalue + (variance/2); 
     double value = min + ((max - min) * random.NextDouble()); 
     return value; 
    } 
} 

非常感谢!

回答

2

埃里希·米拉瓦尔>>我曾尝试HLSL,这是很有趣的学习新的东西,但我是一个总的新手,我没做箱/高斯模糊...

反正我发现一种根本不使用CPU的方式。

而不是移动椭圆,我移动他们的图像。

我生成RenderTargetBitmapPngBitmapEncoder类和移动论文已经模糊和透明图像在内存中的PNG!

非常感谢大家回答!

0

我正在读某人的博客,他试图做同样的事情,但我似乎无法找到它(我会继续寻找它)。他能够加速应用的方式是重复使用粒子。每当你创建一个新的粒子时,你就会看到你记忆中的东西。除非你有一个疯狂的好系统,否则你无法承受这种内存,因为.NET使用了大量的内存。

解决方案:重新使用粒子,一旦粒子不再在屏幕上释放它的内存(可能因为GC而不起作用)或将粒子重新定位在底部并重新使用它。

0

不知道这是否会更好,但有人已经放在一起Silverlight C64 emulator,他们使用的技术是基本上显示一个电影与提供帧的自定义源(您的代码)。

好处是您可以在显示帧时获得回调,因此可以适应实际的回放速率。我不确定这对于更高分辨率的效果有多好,但C64的例子只有一个低分辨率屏幕来模拟。

1

如果我是你,我会考虑使用WPF内置的动画系统,而不是像使用回调手动更新位置。例如,可能需要查看System.Windows.Media.Animation命名空间中的Point3DAnimation类以及其他类。在另一个笔记中,它看起来并不像使用3D点,实际上是在向您购买任何东西(据我所知,在实际渲染椭圆时您忽略了Z值),因此您可能需要更改为简单地使用Point s

4

我不认为问题是性能。该应用程序没有得到任何附近钉住我的CPU,但帧速率仍然不顺利。

我会看两件事。你如何计算你的位置更新,以及你发射事件的频率如何。

timer.Interval = TimeSpan.FromMilliseconds(10); 

这就是每秒100帧。改为选择30fps(在显示器上每刷新一次)或60等。您应该尝试与您的显示器同步进行更新,就像电子游戏一样。

timer.Interval = TimeSpan.FromMilliseconds(33.33); // 30 fps 

单靠这可能不会解决顺利。你也应该不认为事件之间的时间是固定的:

double elapsed = 0.1; 

当你射击的定时器做好每0.01秒该更新,这并不意味着它实际上得到的一致量的完成时间。垃圾收集,操作系统调度,无论可能会影响实际需要的时间。测量自上次更新实际完成以来的经过时间,并根据该数字进行计算。

祝你好运!

1

谢谢大家回答我。

我已经考虑到每个答案:

  • Lucas Aardvark >>我做到了这一点,它增加了一点点应用程序的速度和使用更少的CPU /内存。
  • Rob Walker >>我跟着链接,但是当我看到我停了下来:“仿真器是非常CPU密集型由于阵副本零优化和大量的”。
  • kvb >>我试图使用动画,但它的方式更加复杂,并没有提高平滑的应用 ...也许我做错了!我也删除了Point3D的使用,因为实际上没有必要使用它们
  • Jogn Noonan >>真正指导答案,但我不确定这会有所帮助。如果我测量两次更新之间的时间,则时间越长,比率越大。所以粒子就像“传送”一样,对吧?

我已经更新my source code

粒子类现在只有具有以下属性:

public class Particle 
{ 
    public Point Velocity { get; set; } // Speed of move 

    public BlurEffect Blur { get; set; } // Blur effect 
    public Brush Brush { get; set; } // Brush (opacity) 
} 

窗口1。XAML没有改变,但我改变了他的身后代码:

public partial class Window1 : Window 
{ 
    DispatcherTimer timer = new DispatcherTimer(); 
    Random random = new Random(DateTime.Now.Millisecond); 

    // Some general values 
    double MaxSize = 150; 
    double NumberOfParticles = 25; 
    double VerticalVelocity = 0.4; 
    double HorizontalVelocity = -2.2; 

    private void Window_Loaded(object sender, RoutedEventArgs e) 
    { 
     for (int i = 0; i < NumberOfParticles; i++) 
     { 
      CreateParticle(); 
     } 

     timer.Interval = TimeSpan.FromMilliseconds(33.33); 
     timer.Tick += new EventHandler(timer_Tick); 
     timer.Start(); 
    } 

    void timer_Tick(object sender, EventArgs e) 
    { 
     // I control "particle" from their ellipse representation 
     foreach (Ellipse ellipse in ParticleHost.Children) 
     { 
      var p = ellipse.Tag as Particle; 
      var t = ellipse.RenderTransform as TranslateTransform; 

      // Update location 
      t.X += p.Velocity.X; 
      t.Y -= p.Velocity.Y; 

      // Check if the particle is too high 
      if (t.Y < -MaxSize) 
      { 
       t.Y = Height + MaxSize; 
      } 

      // Check if the particle has gone outside 
      if (t.X < -MaxSize || t.X > Width + MaxSize) 
      { 
       t.X = random.NextDouble() * Width; 
       t.Y = Height + MaxSize; 
      } 

      // Brush & Effect 
      ellipse.Fill = p.Brush; 
      // Comment this line to deactivate the Blur Effect 
      ellipse.Effect = p.Blur; 
     } 
    } 

    private void CreateParticle() 
    { 
     // Brush (White) 
     var brush = Brushes.White.Clone(); 
     // Opacity (0.2 <= 1) 
     brush.Opacity = 0.2 + random.NextDouble() * 0.8; 
     // Blur effect 
     var blur = new BlurEffect(); 
     blur.RenderingBias = RenderingBias.Performance; 
     // Radius (1 <= 40) 
     blur.Radius = 1 + random.NextDouble() * 39; 
     // Ellipse 
     var ellipse = new Ellipse(); 
     // Size (from 15% to 95% of MaxSize) 
     ellipse.Width = ellipse.Height = MaxSize * 0.15 + random.NextDouble() * MaxSize * 0.8; 
     // Starting location of the ellipse (anywhere in the screen) 
     ellipse.RenderTransform = new TranslateTransform(random.NextDouble() * Width, random.NextDouble() * Height); 
     ellipse.Tag = new Particle 
     { 
      Blur = blur, 
      Brush = brush, 
      Velocity = new Point 
      { 
       X = HorizontalVelocity + random.NextDouble() * 4, 
       Y = VerticalVelocity + random.NextDouble() * 2 
      } 
     }; 
     // Add the ellipse to the Canvas 
     ParticleHost.Children.Add(ellipse); 
    } 
} 

如果try this new version,你会发现它仍然不是顺利

但是,如果您评论影响模糊效果的线条,您会发现它会非常流畅!

有什么想法?

0

你看过使用HLSL做ShaderEffect在GPU上进行渲染吗? 你可以写一个PixelShader。这里有一些来自announcements之一的其他样本,它也有一些不错的链接。它在渲染中一定要平滑。

1

MSDN WPF有一个不错的粒子效果演示。另外,O'Reilly的书籍XNA学习如何使用XNA使用粒子效果。

1

我通过移除ellipse.Effect线解决您的问题,而是增加了以下到Window1.xaml

<Canvas x:Name="ParticleHost"> 
     <Canvas.Effect> 
      <BlurEffect /> 
     </Canvas.Effect> 
    </Canvas> 

就算它不具有相同的外观与他们各自有自己的thier模糊半径。