2

私はdirectXに精通していませんが、説明するために最善を尽くします。まず、複数のモデルオブジェクト(頂点データを持つカスタムオブジェクト)があります。現時点では、私のコード(以下を参照)は、そのモデルのvertexBufferとindexBufferを使用して、シーン内の単一のモデルをレンダリングできます。私がやりたいのは、一連のモデルです。それらすべてを1つのシーンでレンダリングします。これが私の現在のコードです。

    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();
    }
4

1 に答える 1

9

tl;dr;

RenderModel単純にループで呼び出すことができます。ただし、パフォーマンスを向上させるには、操作を更新頻度で並べ替え常に不要なリソースを破棄し、同じデータの複数のコピーを保持しないようにします


さて、セットアップで複数のオブジェクトをレンダリングする単純なアプローチは次のようになります。

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

私がナイーブだと言うのは、私が最初に修正することがかなりたくさんあるからです。

パフォーマンスの向上

リアルタイム 3D アプリケーションは、優れたパフォーマンスがすべてです。それを達成するためには、コストのかかるタスクをどのくらいの頻度で行うかについて本当に注意する必要があります. たとえば、現時点では、メソッドが呼び出されるたびに新しい頂点とインデックス バッファーを作成しています。RenderModel(そして、これは各フレームで発生すると思いますか?) また、エフェクトの状態 (ビュー/プロジェクション マトリックスや照明など) は、フレーム全体で同じままである可​​能性がありますが、モデルごとに個別に設定されます。

更新の頻度によって効果状態を分けます。

コストのかかる操作 (シェーダー、定数、バッファーの設定/作成、GraphicsDevice を使用するほぼすべて) を特定し、それらを変更する必要がある頻度を検討します。最適なパフォーマンスを得るには、それ以上頻繁に実行しないでください。例:

  • メッシュの頂点バッファー:メッシュが動的ではなく、頂点がしばらくの間 (またはまったく変化しない) 場合、頂点バッファーには常に同じデータが含まれます。メッシュをロードして何度も再利用するときに作成するだけで十分です。(インデックス バッファにも適用されます。) (更新頻度: レベルごとに 1 回)

  • ライティング、ビュー、およびプロジェクション マトリックスの設定:ライティングは通常、1 つのフレームの間同じままです。カメラマトリックスも同様です。フレームの最初に一度設定すれば完了です。(更新頻度:1フレームに1回)

  • テクスチャのバインド:通常、シーンには個々のモデルに関連付けられたさまざまなマテリアルがあります。ほとんどの場合、マテリアルは複数のオブジェクトに属します (それらのテクスチャを 1 つの大きなテクスチャ マップに結合したため)。したがって、これらのテクスチャと定数を、それを使用するすべてのメッシュに対して 1 回バインドすると、GPU 容量を最大限に節約できます。(更新頻度:素材ごとに1回)

  • 頂点とインデックス バッファーのバインド:通常、頂点とインデックスのデータはメッシュに対して一意です。そのため、シーンに複数のメッシュがある場合、1 つのメッシュを使い終わったらすぐにデバイスのバッファーを切り替える必要があります。(更新頻度:1メッシュあたり1回)

  • ワールド マトリックスの設定:エフェクトの状態が設定され、メッシュ データがデバイスにバインドされます。ようやくオブジェクトを描画できます。そのため、ワールド マトリックスを設定し、デバイスの draw メソッドを呼び出します。しかし待ってください。シーン内の別の場所にメッシュの別のコピーが必要ですか? 今が最適な時期です。単純に別世界のマトリックスをバインドして、再度描画します。デバイスの状態が最小限に変化する 2 つのオブジェクトを描画しました。完全!1 (更新頻度: メッシュ インスタンスごとに 1 回)

1 シーン内に同じメッシュのコピーが本当にたくさん必要な場合は、Direc3D 9 以降で利用できるハードウェア インスタンス化を検討することをお勧めします。

不要になったリソースを廃棄します。

通常、ガベージ コレクターは古いオブジェクトを処理し、その点で非常に優れていますしかし、ここでは管理されていないリソースを使用しています。それらは CLR によって管理されず、ガベージ コレクションの影響を受けないため、アンマネージドと呼ばれます。

なぜそれを気にする必要があるのですか?

このようなオブジェクトを保持すると、メモリ リークが発生する可能性があります。メモリが不要なリソースでいっぱいになり、パフォーマンスが低下します。2

.NET は、 IDisposableインターフェイスと呼ばれる、アンマネージ リソースを使用するクラス用のインターフェイスを提供します。アンマネージ リソースを解放するDisposableメソッドを公開します。オブジェクトの使用が終了したらすぐにメソッドを呼び出すだけです。

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

たとえば、あなたの場合、フレームごとに新しい頂点とインデックス バッファを作成します。それらを新しいバッファで上書きし、古いバッファが涅槃に消える前に、必ずそれらを破棄する必要があります。いくつかの Direct3D クラスを見てみると、それらのほとんどが IDisposable を実装していることがわかります。

2私の理解では、実際にはそれほど悪くはありません。Dispose通常、IDisposable を正しく実装するということは、オブジェクトがガベージ コレクションされるとすぐに呼び出すデストラクタがクラスにあることを意味します。それでも、ガベージ コレクションがいつ発生するかを知ることはできないため、一般的には Disposable オブジェクトを手動で破棄することをお勧めします。

同じデータの複数のコピーを保持しないでください。

現時点では、おそらく頂点とインデックスを配列に格納しています。バッファを作成すると、基本的にデータのコピーが作成されます。前に述べたように、頂点とインデックス バッファーはメッシュごとに 1 回作成できます。そのため、頂点とインデックスをバッファーに書き込み、それらのみを保持します。もちろん、モデルクラスはわずかに変更されます。

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 の両方に当てはまるはずです。

于 2013-01-24T08:49:39.837 に答える