c# - 为什么需要将元素不断添加到已计算渲染发生的 GeometryModel3D 属性集合中?

标签 c# wpf caching rendering

下面是一个最小的、完整的和可验证的例子,基于我在 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>

问题

正如您从代码中推测的那样,问题在于 ComponentManagerComponentUpdate 方法。为了使渲染成功,每次将粒子添加到粒子系统时,我都必须更新每个 Component - 我试图通过使用标志 来缓解由此产生的任何性能问题>is_doneComponent 类中,当粒子属性(positionstexturestriangles ) 是第一次计算的。然后,或者我认为,在每次为组件调用 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 and TriangleIndices from each Component 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/

相关文章:

c# - Winforms应用程序如何回调上一个窗体?

c# - 如何创建c# exe?

c# - 如何修复这个设计糟糕的代码

java - 如何使用 Spring MVC 和 Spring Security 为资源处理程序启用 HTTP 缓存

session - 清除所有网站缓存?

c# - CefSharp - 捕获资源响应数据

c# - 为什么绑定(bind)到结构不起作用?

wpf - 使用服务定位器的 MVVM 模态对话框

c# - 在 WPF 工具箱中找不到我的 winforms 控件

java - 支持 ETags、If-Modified-Since 的 Android 图像加载器库