我需要在WPF中绘制大量的2D元素,例如线条和多边形。他们的立场也需要不断更新。
我在这里看了很多答案,主要建议使用DrawingVisual或覆盖OnRender功能。为了测试这些方法,我实现了一个渲染10000个椭圆的简单粒子系统,我发现使用这两种方法绘制性能仍然非常糟糕。在我的电脑上,我每秒钟不能超过5-10帧。如果您认为我可以使用其他技术轻松地平滑绘制50万个粒子,这是完全不可接受的。
所以我的问题是,我是否违反了WPF的技术限制,或者我错过了什么?还有其他我可以使用的东西吗?欢迎任何建议。
这里是我尝试过的代码
MainWindow.xaml的内容:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="500" Width="500" Loaded="Window_Loaded">
<Grid Name="xamlGrid">
</Grid>
</Window>
MainWindow.xaml.cs的内容:
using System.Windows.Threading;
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
EllipseBounce[] _particles;
DispatcherTimer _timer = new DispatcherTimer();
private void Window_Loaded(object sender, RoutedEventArgs e)
{
//particles with Ellipse Geometry
_particles = new EllipseBounce[10000];
//define area particles can bounce around in
Rect stage = new Rect(0, 0, 500, 500);
//seed particles with random velocity and position
Random rand = new Random();
//populate
for (int i = 0; i < _particles.Length; i++)
{
Point pos = new Point((float)(rand.NextDouble() * stage.Width + stage.X), (float)(rand.NextDouble() * stage.Height + stage.Y));
Point vel = new Point((float)(rand.NextDouble() * 5 - 2.5), (float)(rand.NextDouble() * 5 - 2.5));
_particles[i] = new EllipseBounce(stage, pos, vel, 2);
}
//add to particle system - this will draw particles via onrender method
ParticleSystem ps = new ParticleSystem(_particles);
//at this element to the grid (assumes we have a Grid in xaml named 'xmalGrid'
xamlGrid.Children.Add(ps);
//set up and update function for the particle position
_timer.Tick += _timer_Tick;
_timer.Interval = new TimeSpan(0, 0, 0, 0, 1000 / 60); //update at 60 fps
_timer.Start();
}
void _timer_Tick(object sender, EventArgs e)
{
for (int i = 0; i < _particles.Length; i++)
{
_particles[i].Update();
}
}
}
/// <summary>
/// Framework elements that draws particles
/// </summary>
public class ParticleSystem : FrameworkElement
{
private DrawingGroup _drawingGroup;
public ParticleSystem(EllipseBounce[] particles)
{
_drawingGroup = new DrawingGroup();
for (int i = 0; i < particles.Length; i++)
{
EllipseGeometry eg = particles[i].EllipseGeometry;
Brush col = Brushes.Black;
col.Freeze();
GeometryDrawing gd = new GeometryDrawing(col, null, eg);
_drawingGroup.Children.Add(gd);
}
}
protected override void OnRender(DrawingContext drawingContext)
{
base.OnRender(drawingContext);
drawingContext.DrawDrawing(_drawingGroup);
}
}
/// <summary>
/// simple class that implements 2d particle movements that bounce from walls
/// </summary>
public class SimpleBounce2D
{
protected Point _position;
protected Point _velocity;
protected Rect _stage;
public SimpleBounce2D(Rect stage, Point pos,Point vel)
{
_stage = stage;
_position = pos;
_velocity = vel;
}
public double X
{
get
{
return _position.X;
}
}
public double Y
{
get
{
return _position.Y;
}
}
public virtual void Update()
{
UpdatePosition();
BoundaryCheck();
}
private void UpdatePosition()
{
_position.X += _velocity.X;
_position.Y += _velocity.Y;
}
private void BoundaryCheck()
{
if (_position.X > _stage.Width + _stage.X)
{
_velocity.X = -_velocity.X;
_position.X = _stage.Width + _stage.X;
}
if (_position.X < _stage.X)
{
_velocity.X = -_velocity.X;
_position.X = _stage.X;
}
if (_position.Y > _stage.Height + _stage.Y)
{
_velocity.Y = -_velocity.Y;
_position.Y = _stage.Height + _stage.Y;
}
if (_position.Y < _stage.Y)
{
_velocity.Y = -_velocity.Y;
_position.Y = _stage.Y;
}
}
}
/// <summary>
/// extend simplebounce2d to add ellipse geometry and update position in the WPF construct
/// </summary>
public class EllipseBounce : SimpleBounce2D
{
protected EllipseGeometry _ellipse;
public EllipseBounce(Rect stage,Point pos, Point vel, float radius)
: base(stage, pos, vel)
{
_ellipse = new EllipseGeometry(pos, radius, radius);
}
public EllipseGeometry EllipseGeometry
{
get
{
return _ellipse;
}
}
public override void Update()
{
base.Update();
_ellipse.Center = _position;
}
}
}
答案 0 :(得分:10)
我相信提供的示例代码非常好,并且展示了框架的限制。在我的测量中,我描述了15-25ms的平均成本归因于渲染开销。本质上,我们在这里只谈到中心(依赖)属性的修改,这是非常昂贵的。我认为它很昂贵,因为它直接将变化传播到mil-core。
一个重要的注意事项是,间接成本与在模拟中位置发生变化的对象数量成正比。当大多数对象是时间相干的,即不改变位置时,在其自身上渲染大量对象不是问题。
针对这种情况的最佳替代方法是使用D3DImage,这是Windows Presentation Foundation的一个元素,用于呈现使用DirectX呈现的信息。一般来说,这种方法应该是有效的,表现明智的。
答案 1 :(得分:3)
您可以尝试使用WriteableBitmap,并在后台线程上使用更快的代码生成图像。但是,你唯一可以做的就是复制位图数据,所以你要么必须编写你自己的原始绘图例程,或者(甚至可能在你的情况下工作)创建一个“图章”图像,你可以复制到你的粒子的任何地方去...
答案 2 :(得分:0)
在Windows窗体中,这些东西让我回归;
不确定WPF是否支持此功能。
答案 3 :(得分:0)
我找到的最快的WPF绘图方法是:
对于我来说,来自Windows.Forms的令人惊讶的事情是,我可以在之后更新我的DrawingGroup 我在OnRender()期间将它添加到DrawingContext。这是更新WPF绘图树中现有的保留绘图命令并触发有效的重绘。
在我用Windows.Forms和WPF(SoundLevelMonitor)编写的简单应用程序中,这种方法在性能上与直接的OnPaint()GDI绘图非常相似。
我认为WPF通过调用OnRender()方法来解决这个问题,它可能更好地称为AccumulateDrawingObjects()
这基本上看起来像:
DrawingGroup backingStore = new DrawingGroup();
protected override void OnRender(DrawingContext drawingContext) {
base.OnRender(drawingContext);
Render(); // put content into our backingStore
drawingContext.DrawDrawing(backingStore);
}
// I can call this anytime, and it'll update my visual drawing
// without ever triggering layout or OnRender()
private void Render() {
var drawingContext = backingStore.Open();
Render(drawingContext);
drawingContext.Close();
}
我也尝试将RenderTargetBitmap和WriteableBitmap用于Image.Source,并直接写入DrawingContext。上面的方法更快。
答案 4 :(得分:-4)
以下是您可能会尝试的一些事项:(我尝试使用您的示例,看起来看起来更快(至少在我的系统上)。
使用Canvas而不是Grid(除非您有其他原因)。播放BitmapScalingMode和CachingHint:
<Canvas Name="xamlGrid" RenderOptions.BitmapScalingMode="LowQuality" RenderOptions.CachingHint="Cache" IsHitTestVisible = "False">
</Canvas>
为GeometryDrawing中使用的Brush添加一个StaticResource:
<SolidColorBrush x:Key="MyBrush" Color="DarkBlue"/>
用作:
GeometryDrawing gd = new GeometryDrawing((SolidColorBrush)this.FindResource("MyBrush"), null, eg);
我希望这会有所帮助。