快速WPF粒子背景

时间:2009-04-13 02:34:12

标签: c# wpf algorithm

我正在构建一个WPF应用程序,我希望它的背景可以随机填充粒子

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

我发现了a really good example我想要的内容,但遗憾的是它是在Flash中并且它不是免费的......

我试图实现它,但我无法设法实现顺利 ......

所以我想知道你是否有人可以帮助我改进它,以便让它使用更少的CPU和更多的GPU,所以它更平滑,即使有更多的粒子和全屏模式。

代码“Particle.cs” :定义具有所有属性的粒子的类

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” :窗口的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;
    }
}

非常感谢!

9 个答案:

答案 0 :(得分:4)

我认为问题不在于表现。该应用程序无法接近我的CPU,但帧速率仍然不会显得平滑。

我会看两件事。您如何计算您的职位更新,以及您多久发起一次这样的活动。

timer.Interval = TimeSpan.FromMilliseconds(10);

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

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

单凭这一点可能无法解决顺畅问题。您也不应该假设事件之间的时间是固定的:

double elapsed = 0.1;

当您每隔.01秒触发计时器进行更新时,这并不意味着它实际上是在一致的时间内完成的。垃圾收集,操作系统调度,任何可能影响它实际花费的时间的东西。测量自上次更新实际完成以来经过的时间,并根据该数字进行计算。

祝你好运!

答案 1 :(得分:2)

Erich Mirabal&gt;&gt;我确实尝试过HLSL,学习一些新东西很有趣但是因为我是一个全新手,我没有设法做一个盒子/高斯模糊......

无论如何,我找到了一种根本不使用CPU的方法。

我没有移动Ellipse,而是移动他们的Image。

我使用 RenderTargetBitmap PngBitmapEncoder 类生成内存中的PNG,并移动这些已模糊且透明的图像!

非常感谢大家回答!

答案 2 :(得分:1)

如果我是你,我会考虑使用WPF的内置动画系统,而不是像你一样使用回调手动更新位置。例如,可能值得查看Point3DAnimation命名空间中的System.Windows.Media.Animation类等。另外,它看起来不像使用3D点实际上是在为你买任何东西(据我所知,你在实际渲染省略号时忽略了Z值),所以你可能想要改为简单地使用PointŠ

答案 3 :(得分:1)

感谢大家回答我。

我已经考虑了你的每个答案:

  • Lucas Aardvark&gt;&gt;我已经做到了,它增加了应用程序的速度,并减少了CPU /内存。
  • Rob Walker&gt;&gt;我按照链接进行了操作,但是当我看到时,我停了下来:“ 模拟器非常占用CPU ,原因是零优化和大量数组副本
  • kvb&gt;&gt;我尝试过使用Animations,但它更复杂,并没有改善应用程序的 smoothness ...也许我做错了!我也删除了Point3D的使用,因为实际上没有必要使用它们
  • Jogn Noonan&gt;&gt;真的指示答案,但我不确定这会有所帮助。如果我测量2次更新之间的时间,那么它越长,该比率就越大。所以粒子会像“传送”一样,对吗?

我已更新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)
}

Window1.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,你会发现它仍然不是顺利

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

有什么想法吗?

答案 4 :(得分:1)

MSDN WPF有一个很棒的粒子效果演示。另外,O'Reilly的书“学习XNA”介绍了如何使用XNA使用粒子效果。

答案 5 :(得分:1)

我通过删除ellipse.Effect行解决了您的问题,而是将以下内容添加到Window1.xaml

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

当然,它们具有相同的外观,每个都有自己的模糊半径。

答案 6 :(得分:0)

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

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

答案 7 :(得分:0)

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

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

答案 8 :(得分:0)

您是否考虑过使用HLSL在ShaderEffect上进行渲染? 你可以写一个PixelShader。以下是其中一个announcements的其他样本,它也有一些不错的链接。它在渲染中肯定是平滑的。