基本上,您有一些模型(在您的情况下为三角形)。
模型是顶点数组:
struct Vertex
{
float3 position;
float3 normal;
float2 texcoord;
float4 color;
// other vertex attributes goes here
};
您在初始化时创建一次顶点(和索引)缓冲区。
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 上执行此操作,因为它非常慢。Vertex buffer保持不变(当然你可以改变它,达到变形、tessellation等效果,但我们的情况不是这样)。
因此,您在顶点(或几何)着色器中进行变换。而且您必须以某种方式将当前对象的变换信息(和其他实例属性)发送到顶点着色器。
一种方法是常量缓冲区。
假设您在顶点着色器中有 cbuffer:
cbuffer Instance
{
matrix worldMatrix;
// other instance parameters goes here
};
你必须用数据填充它。
在绘制每个对象之前,使用当前实例数据更新缓冲区(每个对象一次(每帧多次)):
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
缓冲区更新。
另一种方法是实例缓冲区。
您再创建一个顶点缓冲区,它保存的不是顶点数据,而是实例数据,准备供着色器使用。
您计算实例数据并创建实例缓冲区一次:
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
循环,只有一个(!)绘制调用。
renderer->SetVertexBuffers(2, vb, instanceBuffer); // Set needed model data and instances data
renderer->DrawInstanced(); // or DrawIndexedInstanced();
仅当对象更改其参数时才更新实例缓冲区:位置、颜色等。
绘制复杂场景时,大多数情况下同时使用:常量缓冲区(用于所有或多个对象共享的属性:视图矩阵、投影矩阵等)和实例化(如果您有具有相同模型几何形状但属性不同的对象),发挥他们的优势。