- c - 在位数组中找到第一个零
- linux - Unix 显示有关匹配两种模式之一的文件的信息
- 正则表达式替换多个文件
- linux - 隐藏来自 xtrace 的命令
下面是一个最小的、完整的和可验证的例子,基于我在 WPF 模型渲染中遇到的问题,这里我们只是在任意 2D 平面上渲染随机分布的“粒子”,其中每个粒子的颜色对应于它的顺序产卵。
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
此类提供了一个方便的对象,用于管理 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 模型,提供粒子的渲染属性(即 Material 、颜色以及渲染目标/视觉)。
// 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();
}
}
MainWindow.xaml
(粗略的)XAML 代码只是为了完整性,以防有人想要验证此示例。
<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
- 我试图通过使用标志 来缓解由此产生的任何性能问题>is_done
在 Component
类中,当粒子属性(positions
、textures
和 triangles
) 是第一次计算的。然后,或者我认为,在每次为组件调用 Component::Update()
时,将使用这些集合的先前计算值。
但是,这在这里不起作用,因为如上所述将 is_done
设置为 true 只会导致不呈现任何内容。如果我注释掉 is_done = true;
那么所有内容都会被渲染但是速度非常慢 - 很可能是由于大量元素被添加到 positions
等集合每个 Component
(如调试器诊断所示,内存使用量激增)。
问题
Why do I have to keep adding previously calculated elements to these collections for rendering to occur?
In other words, why does it not just take the already calculated
Positions
,TextureCoordinates
andTriangleIndices
from eachComponent
and use these when rendering?
最佳答案
看来这里可能有几个问题。
我发现的第一个是您调用comp_mgr.Update()
每次您添加一个粒子。反过来,这会在每个 粒子上调用Update()
。所有这些都会导致 O(n^2) 操作,这意味着对于 200 个粒子(您的最小值),您将运行组件更新逻辑 40,000 次。这绝对是导致它变慢的原因。
为了消除这种情况,我将 comp_mgr.Update()
调用移出了 while 循环。但是后来我没有得到任何分数,就像你取消注释 is_done = true;
行一样。
有趣的是,当我第二次调用 comp_mgr.Update()
时,我得到了一个点。通过连续的通话,我每次通话都得到额外的分数。这意味着,即使使用较慢的代码,您仍然只能在 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(); });
}
复制 Update()
调用 n 次导致 n-1 点被渲染。
关于c# - 为什么需要将元素不断添加到已计算渲染发生的 GeometryModel3D 属性集合中?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37952301/
我是一名优秀的程序员,十分优秀!