Draw instances with dynamic count


#1

Hi All,

is it possible to dynamically change the number of instances being drawn?

for example, in the InstancedTeapot sample, if I add this line in the keyUp fn:

void InstancedTeapotsApp::keyUp(cinder::app::KeyEvent event){
    
    
    if( event.getChar() == 'i'  ){
        NUM_INSTANCES_Y += 1;
    }else if( event.getChar() == 'u' ){
        NUM_INSTANCES_Y -= 1;
    }
    
    mInstanceDataVbo.reset();
    
    std::vector<vec3> positions;
    for( size_t potX = 0; potX < NUM_INSTANCES_X; ++potX ) {
        for( size_t potY = 0; potY < NUM_INSTANCES_Y; ++potY ) {
            float instanceX = potX / (float)NUM_INSTANCES_X - 0.5f;
            float instanceY = potY / (float)NUM_INSTANCES_Y - 0.5f;
            positions.push_back( vec3( instanceX * vec3( DRAW_SCALE, 0, 0 ) + instanceY * vec3( 0, 0, DRAW_SCALE ) ) );
        }
    }
    
    // create the VBO which will contain per-instance (rather than per-vertex) data
    mInstanceDataVbo = gl::Vbo::create( GL_ARRAY_BUFFER, positions.size() * sizeof(vec3), positions.data(), GL_DYNAMIC_DRAW );
    console() << NUM_INSTANCES_X * NUM_INSTANCES_Y << endl;
}

The program does not crash, but the teapots stop moving… Recreating the actual teapot VBO every time the count changes works:

geom::BufferLayout instanceDataLayout;
instanceDataLayout.append( geom::Attrib::CUSTOM_0, 3, 0, 0, 1 /* per instance */ );

gl::VboMeshRef mesh = gl::VboMesh::create( geom::Teapot().subdivisions( 4 ) );
mesh->appendVbo( instanceDataLayout, mInstanceDataVbo );

// and finally, build our batch, mapping our CUSTOM_0 attribute to the "vInstancePosition" GLSL vertex attribute
mBatch = gl::Batch::create( mesh, mGlsl, { { geom::Attrib::CUSTOM_0, "vInstancePosition" } } );

but it feels kinda cumbersome. Is there a better way to do this?


#2

Hi,

yes, this is possible, but I would not recommend the approach you took. It’s better to create a buffer large enough to draw the maximum number of instances, then use this buffer over and over again:

static const int kMaxCount = 4096;
mInstanceDataVbo = gl::Vbo::create( GL_ARRAY_BUFFER, kMaxCount * sizeof(vec3), nullptr, GL_DYNAMIC_DRAW );

Every update, fill it with the data of the current number of instances, no more and no less.

const float scale = 10.0f;
const unsigned count = Rand::randUint( kMaxCount );

auto ptr = (vec3*) mInstanceDataVbo->mapReplace();
for( unsigned i=0; i < count; ++i ) {
  ptr->x = ( i % 64 ) * scale; // This is...
  ptr->y = ( i / 64 ) * scale; // ...just a...
  ptr->z = 0.0f;               // ...stupid example.
  ptr++;
}
mInstanceDataVbo->unmap(); // <-- Important!

Then in your draw() method, use the following call:

if( count > 0 )
  mBatch->drawInstanced( glm::min( count, kMaxCount ) );

-Paul


#3

Ah!
I see, thanks paul!


#4

By the way: the reason your teapots stopped moving, is that by creating a new Vbo for your instances, you no longer use the Vbo attached to the VboMesh in use by the Batch (whoa, that’s a lot of info in one sentence).

One cool thing you can do with a dynamic instance count, is performing frustum culling and then sorting the instances by visibility. Then count and upload the visible instances to the buffer and draw them.


#5

yeah, I naively thought that because they were shared_ptr’s the underling data would also be swapped…

nice, thanks for the example!

out of curiosity, I haven’t had time to test this out.
But I’m guessing that if the GL_DEPTH_TEST is disabled, the instance shapes will be drawn according to their order in the buffer right? What would be your approach to draw a bunch of instance shapes a couple of gl::Batch’s in between in a 2D environment?


#6

The instances are always drawn in the order in which they appear in the buffer. If depth testing is enabled, the depth test happens per fragment (at the end of the fragment shader). If the fragment’s depth is greater than the value stored in the depth buffer, the fragment is discarded.

It does not really matter if you’re drawing in 2D or 3D, for OpenGL this is just a matter of different projections and whether or not depth testing is enabled. But if you want to draw something with depth testing disabled, make sure to draw the furthest instance first (a.k.a. painter’s algorithm).