1
\$\begingroup\$

I'm a total beginner with Directx/3D programming. I need help with implementing hardware instancing on Directx 11. I'm trying to render multiple cubes on the screen, to create some sort of Minecraft-esque voxel engine. The problem is, I don't know where to start to achieve this. This is how my "render frame" function looks:

void RenderFrame(void)
{
    D3DXMATRIX matView, matProjection;
    D3DXMATRIX matFinal;

    // create a view matrix
    D3DXMatrixLookAtLH(&matView,
    &D3DXVECTOR3(0.0f, 9.0f, 24.0f),   // the camera position
    &D3DXVECTOR3(0.0f, 0.0f, 0.0f),    // the look-at position
    &D3DXVECTOR3(0.0f, 1.0f, 0.0f));   // the up direction

    // create a projection matrix
    D3DXMatrixPerspectiveFovLH(&matProjection,
    (FLOAT)D3DXToRadian(45),                    // field of view
    (FLOAT)SCREEN_WIDTH / (FLOAT)SCREEN_HEIGHT, // aspect ratio
    1.0f,                                       // near view-plane
    100.0f);                                    // far view-plane

    // create the final transform
    matFinal = matView * matProjection;

    devcon->ClearRenderTargetView(backbuffer, D3DXCOLOR(0.0f, 0.2f, 0.4f, 1.0f));

    devcon->ClearDepthStencilView(zbuffer, D3D11_CLEAR_DEPTH, 1.0f, 0);

    UINT stride = sizeof(VERTEX);
    UINT offset = 0;
    devcon->IASetVertexBuffers(0, 1, &pVBuffer, &stride, &offset);
    devcon->IASetIndexBuffer(pIBuffer, DXGI_FORMAT_R32_UINT, 0);
    devcon->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

    devcon->UpdateSubresource(pCBuffer, 0, 0, &matFinal, 0, 0);
    devcon->DrawIndexed(24, 0, 0);

    swapchain->Present(0, 0);
}

Notice that there's a single vertex buffer containing the verteces of a cube, and index buffer containing its indeces. I want to render many (5000+) cubes on the screen at once on a single draw call, without performance issues, so I know instancing is the way to go, but I don't know how to implement it in my code. What changes do I need to do to my code in order to display multiple instances of the cube?

Thanks in advance!

\$\endgroup\$
  • \$\begingroup\$ Instancing will not be enough to get good performance from a large voxel world. Instead, you should be looking into combining cubes into chunk meshes, with invisible faces removed, then rendering these chunks. You can also apply culling on the chunks to further reduce unnecessary drawing. \$\endgroup\$ – DMGregory Apr 20 '19 at 10:26
  • 1
    \$\begingroup\$ You can look at the old Instancing sample from the legacy DirectX SDK on GitHub, but keep in mind the sample itself uses a lot of old-school stuff. See also this blog post and the DirectX Tool Kit. \$\endgroup\$ – Chuck Walbourn Sep 30 '19 at 5:02
  • \$\begingroup\$ What chuck posted is what I have myself implemented. Pay attention to the code defining the buffer in Instancing.cpp as this basically implements 2 vertex buffers. 1 for the geometry and the other for the instance data (matrices in this case). Adding the 2nd buffer is like adding another for loop around the draw call (but alot more efficient). The example file uses fx files, and they are deprecated and not available in latest hlsl compiler, but the code is still correct and you can move it into your hlsl files (rather than an fx setup). \$\endgroup\$ – ErnieDingo Sep 30 '19 at 21:49
0
\$\begingroup\$

Instancing is basically a technology that allows you to draw many instances using only GPU. You basically tell the GPU "draw this many instances of this buffer" and it does this by adding an additional shader input value called Instance ID (it's just an instance index). You do this by adding an additional element to your input layout:

static const D3D12_INPUT_ELEMENT_DESC s_inputElementDesc[] =
{
        { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0,  D3D11_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
        { "NORMAL",   0, DXGI_FORMAT_R32G32B32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
        { "SV_InstanceID",   0, DXGI_FORMAT_R32_UINT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
};

Note the SV_ (system value) prefix before the semantic name. It basically means that this value can be added to the input layout of any shader except PS but only once in a draw call (I don't recommend adding it to anything else than a VS, instead just pass the value between pipeline stages).

After that modify the VS_INPUT structure in your shader:

struct VS_INPUT
{
    float4 Pos : POSITION;
    float3 Normal : NORMAL;
    uint Instance: SV_InstanceID;
};

Make sure to add your own logic that does something depending on the Instance ID or else all your instances will have the same vertices.

The last step is the draw call itself:

// The last argument is the vertex buffer increment; it is zero if you do all the processing on the GPU
pImmCtx->DrawIndexedInstanced(24, YOUR_INSTANCE_COUNT, 0, 0, 0);

Don't hesitate to ask questions in the comments if you don't fully understand my explanation.

\$\endgroup\$
  • \$\begingroup\$ Your instancing example only discusses adding a instanceid system variable. Instancing requires a 2nd vertex buffer attached bound to the draw context which contains unique data such as say World translation matrices. You then update your signature with the definition of the 2nd buffer, defining also in your HLSL code that it will receive instancing data also. Your example is a single buffer version where you may use a constant buffer and the instance id to look up with in that. This is less efficient. \$\endgroup\$ – ErnieDingo Oct 30 '19 at 3:43

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.