6
$\begingroup$

Problem: I'm trying to draw a scene with a lot (about 10000) of similar objects (in my case bushes, but could be anything). For each bush I have three meshes with a different number of triangles and I want to use one of these meshes depending on the distance to the camera.

First approach: I can select the correct mesh on the CPU, update the model matrix after every bush and then draw the VBO with the correct mesh. The problem is that I need to update the model matrix 10000 times per frame ... you can guess what this means performance wise.

Second Approach: Again select the correct mesh on the CPU, but this time use instanced rendering to draw all bushes with lowest lod in one draw call, then the bushes with middle lod and then the bushes with high lod. I have stored the bush positions already on the GPU. But my problem here is that I still need to send an array as uniform variable which contains the information which mesh has to be drawn at which position. With this approach i run into the limit of uniform variables.

Question: What I would like to do is to calculate the distance from the bush to the camera on the GPU (no problem for example in the vertex shader). And then draw only the VBO with the correct level of detail depending on the outcome. Therefore my question is if there is any possibility to select which VBO to draw on the GPU (I don't see any possibility to do this, since in the Vertex and Fragment shader I am a already to far down the pipeline)? Or do you have another idea that I could try?

Best, Albert

$\endgroup$
  • $\begingroup$ If you set the w component in the vertex output to 0 the triangle it is part of will not get pushed down the rasterizer. Though that may not help if the bottle neck is vertex processing. $\endgroup$ – ratchet freak Jun 27 '16 at 15:20
  • $\begingroup$ Thanks for your answer. But if I understand you correctly I would need to render every position with every LOD and then check the distance to camera for every vertex and set w to 0 if it belongs to the wrong LOD for this position. If this is correct, I don't think that it will help because doing this test for all vertices of the highest LOD at every position leads to an insane number of tests. Even if I don't send most of this vertices to the rasterizer, I don't think that this will work, but if I don't find a better solution I will try it anyways and comment my result here. $\endgroup$ – AlbertM Jun 27 '16 at 15:32
  • $\begingroup$ Have you considered an imperfect solution spreading LOD update of the 10000 bushes across multiple cycles? I doubt your view changes alot anyways. And if it changes a huge amount, a little lag is usually not noticable. $\endgroup$ – Andreas Jun 30 '16 at 20:50
  • $\begingroup$ I have not tried it, since the performace that I gained by using instanced arrays (see my comment under the accepted answer) was enough in my case. But it is something that could be combined with the use of instanced arrays if even more performace would be needed. $\endgroup$ – AlbertM Jul 3 '16 at 17:09
2
$\begingroup$

IMO, a variation of your second approach is going to be the simplest, and could be quite fast.

Have you profiled anything yet? A CPU should chew through 10000 matrix multiplications extremely quickly. If you wanted to get even more performance, you could SIMD / thread the update. That said, the slowest part is probably going to be the bandwidth of sending 10000 matrices over the PCIE bus.

But, again, this is all supposition. Try it out, and profile.


I was thinking something like this:

(I apologize for the DX functions. I'm not super familiar with the corresponding OpenGL functions. That said, OpenGL should be quite similar)

// Variable definitions

vector<matrix> lod0;
vector<matrix> lod1;
vector<matrix> lod2;

ID3D11Buffer lod0VertexBuffer;
uint lod0VertexCount;

ID3D11Buffer lod1VertexBuffer;
uint lod1VertexCount;

ID3D11Buffer lod2VertexBuffer;
uint lod2VertexCount;

ID3D11Buffer instanceBuffer;

/// ..................


/// Per frame...
lod0.clear();
lod1.clear();
lod2.clear();

for (uint i = 0; i < bushes.size(); ++i) {
    float distance = distance(bushes[i].pos, camera.origin);
    if (distance < 50.0f) {
        lod0.push_back(bushes[i].matrix);
    } else if (distance < 100.0f) {
        lod1.push_back(bushes[i].matrix);
    } else {
        lod2.push_back(bushes[i].matrix);
    }
}

D3D11_MAPPED_SUBRESOURCE mappedResource;
ID3D11Buffer vertexBuffers[] = {
    lod0VertexBuffer,
    instanceBuffer
};

// Update lod0 matrices
context->Map(instanceBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);
memcpy(mappedResource.pData, &lod0[0], sizeof(matrix) * lod0.size());
context->Unmap(instanceBuffer, 0);

// Draw lod0
context->IASetVertexBuffers(0, 2, vertexBuffers, nullptr, nullptr);
context->DrawInstanced(lod0VertexCount, lod0.size(), 0, 0);


// Update lod1 matrices
context->Map(instanceBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);
memcpy(mappedResource.pData, &lod1[0], sizeof(matrix) * lod1.size());
context->Unmap(instanceBuffer, 0);

// Draw lod1
vertexBuffers[0] = lod1VertexBuffer;
context->IASetVertexBuffers(0, 2, vertexBuffers, nullptr, nullptr);
context->DrawInstanced(lod1VertexCount, lod1.size(), 0, 0);


// Update lod2 matrices
context->Map(instanceBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);
memcpy(mappedResource.pData, &lod2[0], sizeof(matrix) * lod1.size());
context->Unmap(instanceBuffer, 0);

// Draw lod2
vertexBuffers[0] = lod2VertexBuffer;
context->IASetVertexBuffers(0, 2, vertexBuffers, nullptr, nullptr);
context->DrawInstanced(lod1VertexCount, lod2.size(), 0, 0);

If this ends up being too slow, you could try to do this calculation in a compute shader, then draw the result using an indirect draw call. See AZDO.

$\endgroup$
  • $\begingroup$ Thanks for your answer and the extensive code sample. I'm not familiar with DX, but it pointed me in the right direction. $\endgroup$ – AlbertM Jun 28 '16 at 0:27
  • 1
    $\begingroup$ And your assumption that the pcie bus is the bottleneck seems to be indeed right (on my hardware I can now render about 300k bushes and I get the same framerate for the middle and the coarse LOD, therefore the number of vertices is not the limiting factor). For anyone who has the same problem: The use of instanced arrays did the trick for me (very nice explanation here: learnopengl.com/#!Advanced-OpenGL/Instancing). Therefore you don't run into the limit of uniforms, which was the problem of my 2nd approach. $\endgroup$ – AlbertM Jun 28 '16 at 0:33

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.