我正在构建一个WPF应用程序,我希望它的背景可以随机填充粒子:
我发现了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;
}
}
非常感谢!
答案 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)
感谢大家回答我。
我已经考虑了你的每个答案:
我已更新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的其他样本,它也有一些不错的链接。它在渲染中肯定是平滑的。