以下は、私が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
このクラスはList
Component
の粒子を添加することができるような事例と更新アプリを管理するための便利なオブジェクトを提供します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>
問題
あなたがコードから推測かもしれませんが、問題はComponentManager
とComponent
のUpdate
方法です。レンダリングが成功するためには、パーティクルがパーティクルシステムに追加されるたびに、毎回Component
を更新する必要があります。Component
のフラグis_done
を使用してパフォーマンスの問題を緩和しようとしました。粒子特性(positions
,textures
およびtriangles
)が最初に計算されたときに真となる。それで、私は、後でコンポーネントのためにComponent::Update()
を呼び出すたびに、これらのコレクションの以前に計算された値が使用されると考えました。
ただし、上記の説明どおりにis_done
をtrueに設定すると、何も表示されないため、ここでは機能しません。私がis_done = true;
をコメントアウトするとすべてがレンダリングされますが、それは非常に遅いです - おそらく、各Component
のpositions
などのコレクションに追加される膨大な数の要素が原因です(デバッガの診断で示されるようにメモリ使用量が爆発します)。
質問
なぜ発生するレンダリングのためにこれらのコレクションに以前に算出した要素を追加し続けるために必要があるのですか?言い換えれば
、なぜそれだけで、各
Component
から既に計算Positions
、TextureCoordinates
とTriangleIndices
を取ると、レンダリング時にこれらを使用していませんか?
XAMLにクローズウィンドウタグがありません。 WorldModelsはどこで宣言されていますか? –
あなたのコレクションを再作成するたびに現在Clear()が呼び出されるので、クリックが生成されるたびに、追加のパーティクルがコレクションに追加されることを意図していますか? –
@DamianGreen私は、 'WorldModels'がXAMLコードで' Model3DGroup'として宣言されていることを修正します。また、生成ボタンをクリックするたびに、単一の粒子システムを作成することはありません。後続のクリックで前のものを消去し、新しいものを作成する必要があります。これは、この問題が発生した実際のコードがどのように機能するかと同様です。 – ArchbishopOfBanterbury