Быстрый WPF Particle Background - PullRequest
       10

Быстрый WPF Particle Background

8 голосов
/ 13 апреля 2009

Я создаю приложение WPF и хочу, чтобы его фон был заполнен частицами со случайными значениями:

  • Непрозрачность / г-го порядка
  • размер
  • Скорость * * +1010
  • " Нечеткость " (эффект размытия)
  • Направления (или пути)

Я нашел действительно хороший пример того, что я хотел бы видеть, но, к сожалению, это во Flash и это не бесплатно ...

Я пытался реализовать его, но мне не удалось его получить smooth ...

Поэтому мне было интересно, может ли кто-нибудь из вас помочь мне улучшить его, чтобы заставить его использовать меньше ЦП и больше графического процессора, чтобы он был более плавным даже с большим количеством частиц и в полноэкранном режиме.

Код "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 ]

4 голосов
/ 14 апреля 2009

Не думаю, что проблема в производительности. Приложение почти не привязывается к моему ЦП, но частота кадров все еще не выглядит плавной.

Я бы посмотрел на две вещи. Как вы рассчитываете обновление вашей позиции и как часто вы запускаете событие для этого.

timer.Interval = TimeSpan.FromMilliseconds(10);

Это 100 кадров в секунду. Вместо этого выберите 30 кадров в секунду (каждое другое обновление на вашем мониторе) или 60 и т. Д. Вы должны пытаться синхронизировать обновления с монитором, как в видеоигре.

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

Одно это, вероятно, не решит гладкость. Вы также не должны предполагать, что время между событиями фиксировано:

double elapsed = 0.1;

Пока вы запускаете таймер для обновления каждые 0,01 секунды, это не значит, что он выполняется за одинаковое время. Сборка мусора, планирование ОС, все, что может повлиять на количество времени, которое на самом деле это занимает. Измерьте время, прошедшее с момента последнего обновления, и рассчитайте его на основе этого числа.

Удачи!

2 голосов
/ 15 апреля 2009

Эрих Мирабал >> Я попробовал HLSL, и было довольно интересно узнать что-то новое, но, поскольку я новичок, мне не удалось сделать размытие по Box / Gaussian ...

Во всяком случае, я нашел способ, который вообще не использует процессор.

Вместо того, чтобы двигать Эллипс, я перемещаю их Изображение.

Я генерирую PNG в памяти с RenderTargetBitmap и PngBitmapEncoder классов и перемещаю тезисы уже размыто-прозрачно Изображения!

Большое спасибо всем за ответы!

1 голос
/ 17 мая 2009

Я решил вашу проблему, удалив строку ellipse.Effect и вместо этого добавил следующее в Window1.xaml

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

Конечно, они не выглядят одинаково, каждый из которых имеет свой собственный радиус размытия.

1 голос
/ 19 апреля 2009

В MSDN WPF есть хорошая демонстрация Particle Effects. Также в книге O'Reilly Learning XNA рассказывается, как использовать эффекты частиц с помощью XNA.

1 голос
/ 14 апреля 2009

Спасибо всем, что ответили мне.

Я учел каждый из ваших ответов:

  • Лукас Аардварк >> Я сделал это, и это немного увеличило скорость приложения и использовало меньше CPU / Memory.
  • Роб Уокер >> Я перешел по ссылке, но остановился, увидев: " Эмулятор довольно сильно загружает процессор из-за нулевой оптимизации и большого количества копий массивов ».
  • kvb >> Я пытался использовать анимацию, но это было намного сложнее и не улучшало гладкость приложения ... Возможно Я сделал это неправильно! Я также исключил использование Point3D, так как фактически не было необходимости использовать их
  • Джогн Нунан >> Действительно инструктирую ответ, но я не уверен, что это поможет. Если я измерю время между двумя обновлениями, то чем дольше оно будет, тем больше будет соотношение. Так что частицы будут как "телепортироваться", верно?

Я обновил мой исходный код :

Класс Particle теперь имеет только следующие свойства:

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);
    }
}

Если вы попробуете эту новую версию , вы увидите, что она все еще не гладкая .

Но если вы прокомментируете линию, которая влияет на эффект размытия, вы увидите, что она очень плавная!

Есть мысли?

1 голос
/ 13 апреля 2009

На вашем месте я бы хотел использовать встроенную в WPF систему анимации, а не обновлять позиции вручную, используя обратный вызов, как вы это делаете. Например, возможно, стоит изучить класс Point3DAnimation в пространстве имен System.Windows.Media.Animation, среди прочего. С другой стороны, не похоже, что использование 3D-точек на самом деле вам что-то дает (насколько я могу судить, вы игнорируете значения Z при рендеринге эллипсов), поэтому вы можете захотеть перейти на простое использование Point S

0 голосов
/ 14 апреля 2009

Вы смотрели на выполнение ShaderEffect с использованием HLSL для рендеринга на GPU? Вы можете написать PixelShader . Вот некоторые другие примеры из одного анонса , и он также имеет несколько хороших ссылок. Это должно определенно быть гладким в рендеринге.

0 голосов
/ 13 апреля 2009

Не уверен, что это будет работать лучше, но кто-то собрал эмулятор Silverlight C64 , и метод, который они используют, заключается в том, чтобы в основном отображать фильм с пользовательским источником (вашим кодом), который обеспечивает кадры .

Преимущество заключается в том, что вы получаете обратные вызовы при отображении кадров, что позволяет адаптировать их к фактической скорости воспроизведения. Я не уверен, насколько хорошо это будет работать для более высоких разрешений, хотя пример C64 имеет только экран с низким разрешением для эмуляции.

0 голосов
/ 13 апреля 2009

Я читал чей-то блог, который пытался сделать то же самое, но я не могу найти его (я буду продолжать его искать). Способ, которым он мог ускорить его применение, был, повторно используя частицы. Каждый раз, когда вы создаете новую частицу, вы видите, как вы забираете память. Вы не можете позволить себе эту память, если у вас нет безумно хорошей системы, потому что .NET использует много памяти.

Решение: Повторно использовать частицы, как только частица больше не появляется на экране, либо освободить ее память (вероятно, не будет работать из-за ГХ), либо переместить эту частицу внизу и повторно использовать ее.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...