【问题标题】:Rendering Multiple objects in one scene DirectX C#在一个场景中渲染多个对象 DirectX C#
【发布时间】:2013-01-07 17:31:21
【问题描述】:

我对 directX 并不是真的不了解,但我会尽力解释。 首先,我有多个模型对象(带有顶点数据的自定义对象)。目前,我的代码(见下文)可以使用该模型的 vertexBuffer 和 indexBuffer 在场景中渲染单个模型。我想做的是给定一系列模型。将它们全部渲染在一个场景中。这是我当前的代码:

    private void Render()
    {
        device.Clear(ClearFlags.Target | ClearFlags.ZBuffer, Color.White, 1.0f, 0);
        device.BeginScene();

        float x = (float)Math.Cos(0);
        float z = (float)Math.Sin(0);
        device.Transform.Projection = Matrix.PerspectiveFovLH((float)Math.PI / 4, this.Width / this.Height, 1f, 50f);
        device.Transform.View = Matrix.LookAtLH(new Vector3(x, 6, z), new Vector3(0, 0, 0), new Vector3(0, 1, 0));
        device.RenderState.Lighting = true;
        device.Lights[0].Type = LightType.Directional;
        device.Lights[0].Diffuse = Color.White;
        device.Lights[0].Direction = new Vector3(-x, -6, -z);
        device.Lights[0].Position = new Vector3(x, 6, z);
        device.Lights[0].Enabled = true;

        device.Transform.World = model.transform;

        device.VertexFormat = CustomVertex.PositionNormalColored.Format;
        device.SetStreamSource(0, vertexBuffer, 0);
        device.Indices = indexBuffer;

        device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, model.getVertices().Length, 0, model.getIndices().Length / 3);

        device.EndScene();
        device.Present();
    }

    public void RenderModel(Model model)
    {
        this.model = model;
        vertexBuffer = new VertexBuffer(typeof(CustomVertex.PositionNormalColored), model.getVertices().Length,
                                    device, Usage.Dynamic | Usage.WriteOnly, CustomVertex.PositionNormalColored.Format, Pool.Default);
        vertexBuffer.SetData(model.getVertices(), 0, LockFlags.None);

        indexBuffer = new IndexBuffer(typeof(ushort), model.getIndices().Length, device, Usage.WriteOnly, Pool.Default);
        indexBuffer.SetData(model.getIndices(), 0, LockFlags.None);

        Render();
    }

【问题讨论】:

    标签: c# directx direct3d


    【解决方案1】:

    tl;博士;

    您可以简单地循环调用RenderModel。不过,为了获得更好的性能,您可能希望按更新频率对操作进行排序始终处理不需要的资源并且不要保留相同数据的多个副本 em>。


    好吧,使用您的设置渲染多个对象的简单方法如下所示:

    foreach (var model in models)
    {
        RenderModel(model);
    }
    

    我说天真,因为有很多事情我会先解决。

    提高性能

    实时 3D 应用程序都与良好的性能有关。为了实现它,您必须非常小心执行昂贵任务的频率。例如,当您正在创建一个新的顶点和索引缓冲区时,每次都会调用RenderModel 方法。 (我想这会发生在每一帧?)此外,效果状态(如视图/投影矩阵和照明)将为每个模型单独设置,即使它们可能在整个帧中保持相同。

    按更新频率区分效果状态。

    确定昂贵的操作(设置/创建着色器、常量、缓冲区;大致上任何使用 GraphicsDevice 的操作)并考虑它们需要多久更改一次。为了获得最佳性能,请不要更频繁地执行此操作。例子:

    • 网格的顶点缓冲区:如果网格不是动态的并且顶点在一段时间内不会改变(或根本不改变),那么顶点缓冲区将始终包含相同的数据。当您加载网格然后一遍又一遍地重用它时,创建它就足够了。 (也适用于索引缓冲区。)(更新频率:每级一次)

    • 设置光照、视图和投影矩阵:光照通常在一帧中保持不变。相机矩阵也是如此。在帧的开头设置一次,你就完成了。 (更新频率:每帧一次)

    • 绑定纹理:通常场景中有不同的材质,与各个模型相关联。大多数情况下,材质属于多个对象(因为您将它们的纹理组合在一个大纹理贴图中)。因此,为所有使用它的网格绑定一次这些纹理和常量将节省最多的 GPU 容量。 (更新频率:每种材料一次)

    • 绑定顶点和索引缓冲区:顶点和索引数据对于网格来说通常是唯一的。因此,如果您的场景中有多个网格,则在完成一个网格后立即在设备上切换缓冲区是不可避免的。 (更新频率:每个网格一次)

    • 设置世界矩阵:您的效果状态已设置,并且网格数据已绑定到设备。你终于可以画出你的对象了。所以你设置世界矩阵并调用设备的绘制方法。但是等等,你想在场景的其他地方再复制一份网格吗?现在是最好的时机。只需绑定另一个世界矩阵并再次绘制。您刚刚在设备上绘制了两个状态变化最小的对象。完美!1(更新频率:每个网格实例一次)

    1如果您真的需要在场景中大量复制相同网格的副本,我建议您查看Hardware instancing,从Direc3D 9.

    处理不再需要的资源。

    通常垃圾收集器会处理您的旧对象,并且它在这方面真的做得很好。但在这里,您使用的是 unmanged 资源。它们被称为非托管的,因为它们不受 CLR 管理,因此不受垃圾回收的影响。

    为什么要打扰你?

    保留这些对象可能会导致内存泄漏。您的内存中会充斥着不需要的资源,从而导致性能下降。2

    .NET 为使用非托管资源的类提供了一个接口,称为IDisposable 接口。它公开了一个Disposable 方法,应该从中释放非托管资源。您只需在使用完对象后立即调用该方法。

    if (myTexture != null)
        myTexture.Dispose();
    

    例如,在您的情况下,您每帧创建一个新的顶点和索引缓冲区。在用新缓冲区覆盖它们并且旧缓冲区消失之前,您绝对应该处置它们。 (更不用说,您不应该如此频繁地创建它们。)只要看看一些 Direct3D 类,您就会发现它们中的大多数都实现了 IDisposable。

    2据我了解,这实际上并没有那么糟糕。正确实现 IDisposable 通常意味着类有一个析构函数,只要对象被垃圾回收,它就会调用Dispose。不过,由于您无法知道何时进行垃圾回收,因此一般建议还是手动处置 Disposable 对象。

    不要保留相同数据的多个副本。

    目前您可能正在将顶点和索引存储在数组中。创建缓冲区时,基本上是在创建数据的副本。正如我之前提到的:顶点和索引缓冲区可以为每个网格创建一次。所以当你这样做时,将顶点和索引写入缓冲区,然后只保留它们。您的模型类当然会略有变化:

    public class Model
    {
        public VertexBuffer Vertices { get; private set; }
        public IndexBuffer Indices { get; private set; }
    }
    

    如果您不需要直接访问顶点和索引,这将是一个更好的解决方案。

    我知道,这是一个小步骤,但我认为意识到这一点是件好事。

    伪代码渲染方法

    public void RenderFrame(Model[] models)
    {
        // Per frame
        Bind(View);
        Bind(Projection);
        BindLighting();
    
        // Per effect
        BindEffect();
    
        foreach (var material in GetMaterials(models))
        {
            // Per material
            Bind(material.Color);
            Bind(material.DiffuseMap);
    
            foreach (var model in GetModelsByMaterial(material, models))
            {
                // Per mesh
                Bind(model.VertexBuffer);
                Bind(model.IndexBuffer);
    
                foreach (var instance in model.Instances)
                {
                    // Per instance
                    Bind(instance.World);
    
                    // Draw the instance
                    Draw();
                }
            }
        }
    }
    

    免责声明:我使用 Direct3D 9 (XNA) 仅几个月。我写的大部分内容都应该适用于 D3D9 和 Direct3D 10/11,这是我迄今为止更熟悉的。

    【讨论】:

    • 感谢您非常详细的帖子,之后我会研究性能。但是,您发布的代码不起作用,因为每次调用 RenderModel() 都会呈现一个新场景,因此数组的最后一个模型就是显示的模型。我想同时渲染数组中的模型,这样所有模型都不仅仅是我要解决的问题。
    • 在完成所有模型的渲染后立即致电device.Present()
    • 不行,请再看一遍我的代码,确保你理解它是如何工作的。
    • 好吧,在这种情况下,问题是每次调用Render 时都会清除渲染目标。将调用移动到您的渲染方法之外的device.Clear。你的问题很笼统,我的回答也是。我没有意识到,您只是在跟踪一个小错误。
    • 谢谢你的工作。我并没有试图找到错误。我只是不确定如何在一个场景中实现和渲染多个模型。没想到这么容易。再次感谢
    猜你喜欢
    • 2015-07-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-03-25
    • 2010-09-27
    • 1970-01-01
    • 2015-09-27
    相关资源
    最近更新 更多