基本的に、いくつかのモデル(あなたの場合は三角形)があります。
モデルは頂点の配列です。
struct Vertex
{
float3 position;
float3 normal;
float2 texcoord;
float4 color;
// other vertex attributes goes here
};
初期化時に、頂点 (およびインデックス) バッファーを 1 回作成します。
std::vector<Vertex> vertices = { /*vertex data goes here*/ };
VertexBuffer vb = renderer->CreateVertexBuffer(&vertices[0], vertices.size());
3D ワールドは、モデルのインスタンスであるオブジェクトの配列です。
struct MyObject
{
float3 position;
float3 rotation;
float3 scale;
// other instance attributes goes here (it can be whatever you want)
};
std::vector<MyObject> objects = { /*objects data goes here*/ };
基本的に、オブジェクトのアトリビュートは頂点アトリビュートの修飾子であるため、すべてのオブジェクトは同じモデルを持ちますが、世界では異なって見えます (この例では、位置が異なります)。
通常、モデル空間で定義される各頂点の位置 (および法線、接線、複接線)。position
それをワールド空間に移動 (変換) するには、各頂点に現在のオブジェクトの行列を掛けます。非常に遅いため、CPU で実行したくありません。頂点バッファーは変更されません(もちろん、変形、テッセレーションなどの効果を得るために変更できますが、これは私たちの場合ではありません)。
したがって、頂点(またはジオメトリ)シェーダーで変換を行います。そして、どういうわけか、現在のオブジェクトの変換 (およびその他のインスタンス属性) の情報を頂点シェーダーに送信する必要があります。
1 つの方法は定数 buffer(s)です。
頂点シェーダーに cbuffer があるとします。
cbuffer Instance
{
matrix worldMatrix;
// other instance parameters goes here
};
そして、データを入力する必要があります。
各オブジェクトを描画する前に、現在のインスタンス データでバッファを更新します (オブジェクトごとに 1 回 (フレームごとに複数回))。
renderer->SetVertexBuffer(vb); // Set needed geometry
for(int i = 0; i < objects.size(); ++i) // for each object
{
matrix worldMatrix = CalculateWorldMatrix(objects[i]); // prepare data of current object
renderer->UpdateConstantBuffer(&worldMatrix); // Tell shaders about current object's attributes (world matrix in our case)
renderer->Draw(); // or DrawIndexed();
}
n
オブジェクトの場合、描画n
呼び出しとn
バッファ更新があります。
もう 1 つの方法は、インスタンス バッファーです。
頂点データではなくインスタンス データを保持するもう 1 つの頂点バッファーを作成し、シェーダーによって消費されるように準備します。
インスタンス データを計算し、インスタンス バッファを 1 回作成します。
for(int i = 0; i < objects.size(); ++i) // for each object
{
std::vector<Instance> instances;
instances[i].worldMatrix = CalculateWorldMatrix(objects[i]);
// other instance parameters goes here
}
VertexBuffer instanceBuffer = renderer->CreateVertexBuffer(&instances[0], instances.size());
また、入力レイアウトも変更する必要があるため、シェーダーは頂点データに加えてインスタンス データを期待します。
描画するときは、頂点とインスタンス バッファーの両方をバインドするだけです。バッファの更新は必要ありません (三角形が移動されていない場合)。そして行列計算はもう必要ありません。したがって、for
ループはなく、描画呼び出しは 1 つ (!) だけです。
renderer->SetVertexBuffers(2, vb, instanceBuffer); // Set needed model data and instances data
renderer->DrawInstanced(); // or DrawIndexedInstanced();
オブジェクトのパラメータ (位置、色など) を変更する場合にのみ、インスタンス バッファを更新します。
複雑なシーンを描画するときは、ほとんどの場合、定数バッファー (ビュー マトリックス、射影マトリックスなどのすべてまたは多くのオブジェクトで共有される属性用) とインスタンス化 (モデル ジオメトリが同じで属性が異なるオブジェクトがある場合) の両方を使用して、彼らの利点を取ります。