2016-06-21 11 views
6

以下は、私がWPFモデルレンダリングで遭遇している問題に基づいて、最小限で完全で検証可能な例です。ここでは、ランダムに分布した「パーティクル」を任意の2D平面上にレンダリングしています。 。レンダリングのために既に計算されているGeometryModel3Dプロパティコレクションに、要素を継続的に追加する必要があるのはなぜですか?


MainWindow.cs

public partial class MainWindow : Window { 
    // prng for position generation 
    private static Random rng = new Random(); 
    private readonly ComponentManager comp_manager; 
    private List<Color> color_list; 
    // counter for particle no. 
    private int current_particles; 

    public MainWindow() { 
     InitializeComponent(); 
     comp_manager = new ComponentManager(); 
     current_particles = 0; 
     color_list = new List<Color>(); 
    } 
    // computes the colours corresponding to each particle in 
    // order based on a rough temperature-gradient 
    private void ComputeColorList(int total_particles) { 
     for (int i = 0; i < total_particles; ++i) { 
      Color color = new Color(); 
      color.ScA = 1; 
      color.ScR = (float)i/total_particles; 
      color.ScB = 1 - (float)i/total_particles; 
      color.ScG = (i < total_particles/2) ? (float)i/total_particles : (1 - (float)i/total_particles); 
      // populate color_list 
      color_list.Add(color); 
     } 
    } 
    // clear the simulation view and all Children of WorldModels 
    private void Clear() { 
     comp_manager.Clear(); 
     color_list.Clear(); 
     current_particles = 0; 
     // clear Model3DCollection and re-add the ambient light 
     // NOTE: WorldModels is a Model3DGroup declared in MainWindow.xaml 
     WorldModels.Children.Clear(); 
     WorldModels.Children.Add(new AmbientLight(Colors.White)); 
    } 
    private void Generate(int total) { 
     const int min = -75; 
     const int max = 75; 
     // generate particles 
     while (current_particles < total) { 
      int rand_x = rng.Next(min, max); 
      int rand_y = rng.Next(min, max); 
      comp_manager.AddParticleToComponent(new Point3D(rand_x, rand_y, .0), 1.0); 
      Dispatcher.Invoke(() => { comp_manager.Update(); }); 
      ++current_particles; 
     } 
    } 
    // generate_button click handler 
    private void OnGenerateClick(object sender, RoutedEventArgs e) { 
     if (current_particles > 0) Clear(); 
     int n_particles = (int)particles_slider.Value; 
     // pre-compute colours of each particle 
     ComputeColorList(n_particles); 
     // add GeometryModel3D instances for each particle component to WorldModels (defined in the XAML code below) 
     for (int i = 0; i < n_particles; ++i) { 
      WorldModels.Children.Add(comp_manager.CreateComponent(color_list[i])); 
     } 
     // generate particles in separate thread purely to maintain 
     // similarities between this minimal example and the actual code 
     Task.Factory.StartNew(() => Generate(n_particles)); 
    } 
} 

ComponentManager.cs

このクラスはListComponentの粒子を添加することができるような事例と更新アプリを管理するための便利なオブジェクトを提供しますListの各Componentに送信されます。

public class ComponentManager { 
    // also tried using an ObservableCollection<Component> but no difference 
    private readonly List<Component> comp_list; 
    private int id_counter = 0; 
    private int current_counter = -1; 

    public ComponentManager() { 
     comp_list = new List<Component>(); 
    } 
    public Model3D CreateComponent(Color color) { 
     comp_list.Add(new Component(color, ++id_counter)); 
     // get the Model3D of most-recently-added Component and return it 
     return comp_list[comp_list.Count - 1].ComponentModel; 
    } 
    public void AddParticleToComponent(Point3D pos, double size) { 
     comp_list[++current_counter].SpawnParticle(pos, size); 
    } 
    public void Update() { 
     // VERY SLOW, NEED WAY TO CACHE ALREADY RENDERED COMPONENTS 
     foreach (var p in comp_list) { p.Update(); } 
    } 
    public void Clear() { 
     id_counter = 0; 
     current_counter = -1; 
     foreach(var p in comp_list) { p.Clear(); } 
     comp_list.Clear(); 
    } 
} 

Component.cs

このクラスは、粒子の演色性を与える関連GeometryModel3Dと単一粒子のインスタンスのGUIのモデルを表す(すなわち、材料ひいては着色ならびにレンダリングターゲット/ビジュアル)。


MainWindow.xaml

誰もこの例を確認したい場合にだけ完全性(粗)XAMLコード

// single particle of systems 
public class Particle { 
    public Point3D position; 
    public double size; 
} 
public class Component { 
    private GeometryModel3D component_model; 
    private Point3DCollection positions; // model Positions collection 
    private Int32Collection triangles; // model TriangleIndices collection 
    private PointCollection textures; // model TextureCoordinates collection 
    private Particle p; 
    private int id; 
    // flag determining if this component has been rendered 
    private bool is_done = false; 

    public Component(Color _color, int _id) { 
     p = null; 
     id = _id; 
     component_model = new GeometryModel3D { Geometry = new MeshGeometry3D() }; 
     Ellipse e = new Ellipse { 
      Width = 32.0, 
      Height = 32.0 
     }; 
     RadialGradientBrush rb = new RadialGradientBrush(); 
     // set colours of the brush such that each particle has own colour 
     rb.GradientStops.Add(new GradientStop(_color, 0.0)); 
     // fade boundary of particle 
     rb.GradientStops.Add(new GradientStop(Colors.Black, 1.0)); 
     rb.Freeze(); 
     e.Fill = rb; 
     e.Measure(new Size(32.0, 32.0)); 
     e.Arrange(new Rect(0.0, 0.0, 32.0, 32.0)); 
     // cached for increased performance 
     e.CacheMode = new BitmapCache(); 
     BitmapCacheBrush bcb = new BitmapCacheBrush(e); 
     DiffuseMaterial dm = new DiffuseMaterial(bcb); 
     component_model.Material = dm; 
     positions = new Point3DCollection(); 
     triangles = new Int32Collection(); 
     textures = new PointCollection(); 
     ((MeshGeometry3D)component_model.Geometry).Positions = positions; 
     ((MeshGeometry3D)component_model.Geometry).TextureCoordinates = textures; 
     ((MeshGeometry3D)component_model.Geometry).TriangleIndices = triangles; 
    } 
    public Model3D ComponentModel => component_model; 
    public void Update() { 
     if (p == null) return; 
     if (!is_done) { 
      int pos_index = id * 4; 
      // compute positions 
      positions.Add(new Point3D(p.position.X, p.position.Y, p.position.Z)); 
      positions.Add(new Point3D(p.position.X, p.position.Y + p.size, p.position.Z)); 
      positions.Add(new Point3D(p.position.X + p.size, p.position.Y + p.size, p.position.Z)); 
      positions.Add(new Point3D(p.position.X + p.size, p.position.Y, p.position.Z)); 
      // compute texture co-ordinates 
      textures.Add(new Point(0.0, 0.0)); 
      textures.Add(new Point(0.0, 1.0)); 
      textures.Add(new Point(1.0, 1.0)); 
      textures.Add(new Point(1.0, 0.0)); 
      // compute triangle indices 
      triangles.Add(pos_index); 
      triangles.Add(pos_index+2); 
      triangles.Add(pos_index+1); 
      triangles.Add(pos_index); 
      triangles.Add(pos_index+3); 
      triangles.Add(pos_index+2); 
      // commenting out line below enables rendering of components but v. slow 
      // due to continually filling up above collections 
      is_done = true; 
     } 
    } 
    public void SpawnParticle(Point3D _pos, double _size) { 
     p = new Particle { 
      position = _pos, 
      size = _size 
     }; 
    } 
    public void Clear() { 
     ((MeshGeometry3D)component_model.Geometry).Positions.Clear(); 
     ((MeshGeometry3D)component_model.Geometry).TextureCoordinates.Clear(); 
     ((MeshGeometry3D)component_model.Geometry).TriangleIndices.Clear(); 
    } 
} 

<Window x:Class="GraphicsTestingWPF.MainWindow" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
    xmlns:local="clr-namespace:GraphicsTestingWPF" 
    mc:Ignorable="d" 
    Title="MainWindow" Height="768" Width="1366"> 
<Grid> 
    <Grid Background="Black" Visibility="Visible" Width ="Auto" Height="Auto" Margin="5,3,623,10" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"> 
     <Viewport3D Name="World" Focusable="True"> 
      <Viewport3D.Camera> 
       <OrthographicCamera x:Name="orthograghic_camera" Position="0,0,32" LookDirection="0,0,-32" UpDirection="0,1,0" Width="256"/> 
      </Viewport3D.Camera> 

      <Viewport3D.Children> 
       <ModelVisual3D> 
        <ModelVisual3D.Content> 
         <Model3DGroup x:Name="WorldModels"> 
          <AmbientLight Color="#FFFFFFFF" /> 
         </Model3DGroup> 
        </ModelVisual3D.Content> 
       </ModelVisual3D> 
      </Viewport3D.Children> 
     </Viewport3D> 
    </Grid> 
    <Slider Maximum="1000" TickPlacement="BottomRight" TickFrequency="50" IsSnapToTickEnabled="True" x:Name="particles_slider" Margin="0,33,130,0" VerticalAlignment="Top" Height="25" HorizontalAlignment="Right" Width="337"/> 
    <Label x:Name="NParticles_Label" Content="Number of Particles" Margin="0,29,472,0" VerticalAlignment="Top" RenderTransformOrigin="1.019,-0.647" HorizontalAlignment="Right" Width="123"/> 
    <TextBox Text="{Binding ElementName=particles_slider, Path=Value, UpdateSourceTrigger=PropertyChanged}" x:Name="particle_val" Height="23" Margin="0,32,85,0" TextWrapping="Wrap" VerticalAlignment="Top" TextAlignment="Right" HorizontalAlignment="Right" Width="40"/> 
    <Button x:Name="generate_button" Content="Generate" Margin="0,86,520,0" VerticalAlignment="Top" Click="OnGenerateClick" HorizontalAlignment="Right" Width="75"/> 
</Grid> 
</Window> 

問題

あなたがコードから推測かもしれませんが、問題はComponentManagerComponentUpdate方法です。レンダリングが成功するためには、パーティクルがパーティクルシステムに追加されるたびに、毎回Componentを更新する必要があります。Componentのフラグis_doneを使用してパフォーマンスの問題を緩和しようとしました。粒子特性(positions,texturesおよびtriangles)が最初に計算されたときに真となる。それで、私は、後でコンポーネントのためにComponent::Update()を呼び出すたびに、これらのコレクションの以前に計算された値が使用されると考えました。

ただし、上記の説明どおりにis_doneをtrueに設定すると、何も表示されないため、ここでは機能しません。私がis_done = true;をコメントアウトするとすべてがレンダリングされますが、それは非常に遅いです - おそらく、各Componentpositionsなどのコレクションに追加される膨大な数の要素が原因です(デバッガの診断で示されるようにメモリ使用量が爆発します)。


質問

なぜ発生するレンダリングのためにこれらのコレクションに以前に算出した要素を追加し続けるために必要があるのですか?言い換えれば

、なぜそれだけで、各Componentから既に計算PositionsTextureCoordinatesTriangleIndicesを取ると、レンダリング時にこれらを使用していませんか?

+0

XAMLにクローズウィンドウタグがありません。 WorldModelsはどこで宣言されていますか? –

+0

あなたのコレクションを再作成するたびに現在Clear()が呼び出されるので、クリックが生成されるたびに、追加のパーティクルがコレクションに追加されることを意図していますか? –

+0

@DamianGreen私は、 'WorldModels'がXAMLコードで' Model3DGroup'として宣言されていることを修正します。また、生成ボタンをクリックするたびに、単一の粒子システムを作成することはありません。後続のクリックで前のものを消去し、新しいものを作成する必要があります。これは、この問題が発生した実際のコードがどのように機能するかと同様です。 – ArchbishopOfBanterbury

答えて

4

ここにいくつかの問題があるようです。

私が最初に見つけたのは、にパーティクルを追加するたびにcomp_mgr.Update()と呼んでいたことでした。これは、順番にのUpdate()のパーティクルと呼びます。そのすべてがO(n^2)操作になります。つまり、200個のパーティクル(分)の場合、コンポーネント更新ロジックを40,000回実行しています。これは間違いなく、それが遅くなる原因です。

これを解消するために、私はcomp_mgr.Update()呼び出しをwhileループから外しました。しかし、あなたはis_done = true;行のコメントを外したときと同じように、私はポイントがありません。

興味深いことに、私がcomp_mgr.Update()に2番目の呼び出しを追加したとき、私は1つのポイントを得ました。連続して呼び出すと、それぞれの呼び出しで追加ポイントが得られます。これは、コードが遅い場合でも、200ポイント設定で199ポイントしか得られていないことを意味します。

どこかに深刻な問題があるようですが、見つけられません。私が更新すれば私は更新します。多分あなたや他の人が答えにつながるでしょう。今のところ

MainWindow.Generate()方法は次のようになります

private void Generate(int _total) 
{ 
    const int min = -75; 
    const int max = 75; 
    // generate particles 
    while (current_particles < _total) 
    { 
     int rand_x = rng.Next(min, max); 
     int rand_y = rng.Next(min, max); 
     comp_manager.AddParticleToComponent(new Point3D(rand_x, rand_y, .0), 1.0); 
     ++current_particles; 
    } 
    Dispatcher.Invoke(() => { comp_manager.Update(); }); 
} 

レンダリングさN -1点でUpdate()コールN回結果を複製する場合。

+0

私は、パフォーマンスの問題を説明するために何らかの方法があると思う。しかし、この問題が発生する実際のコードでは、whileループで実行されるコードはネイティブのC++コードであり、非常に計算集約的な別のスレッドで実行されるため、リアルタイムで実現しますシミュレーションループの実行が終了した後は、単にモデル/ GUIを '更新 'することはできません。 – ArchbishopOfBanterbury

+0

'comp_manager.Update()'呼び出しを動かすだけで、レンダリングされたパーティクルは表示されません。また、 'Component :: Render'の' is_done'行をコメントアウトしますか? – Zack

+0

しかし、whileループでまだパーティクルの更新機能を実行したくないということを明確にするには? @ggsdennisは良い点を作っています –

関連する問題