OpenGL Instanced Rendering with Level of Detail
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&lt;matrix&gt; lod0;
vector&lt;matrix&gt; lod1;
vector&lt;matrix&gt; 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 &lt; bushes.size(); ++i) {
    float distance = distance(bushes[i].pos, camera.origin);
    if (distance &lt; 50.0f) {
        lod0.push_back(bushes[i].matrix);
    } else if (distance &lt; 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-&gt;Map(instanceBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &amp;mappedResource);
memcpy(mappedResource.pData, &amp;lod0[0], sizeof(matrix) * lod0.size());
context-&gt;Unmap(instanceBuffer, 0);

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


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

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


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

// Draw lod2
vertexBuffers[0] = lod2VertexBuffer;
context-&gt;IASetVertexBuffers(0, 2, vertexBuffers, nullptr, nullptr);
context-&gt;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.