2009-04-13 94 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模糊半徑。