Animating vertex color in VBOMesh

Hi, there,

I’m having some trouble animating the vertex color when using a VBO mesh. In a nutshell, I’m animating a texture drawn onto a plane. I’m animating the model matrix to get the appropriate movement, and I’d like to animate the color of the vertices in conjunction with this. Using the VboMesh sample as a model, I’m setting up the VBO like this (and for the moment, the customShader is currently the stock shader created using ci::gl::ShaderDef().texture().color():

//	create VBO and batch
ci::geom::Plane plane = ci::geom::Plane().size( ci::vec2( 1 ) ).normal( ci::vec3( 0, 0, 1 ) );
std::vector<ci::gl::VboMesh::Layout> bufferLayout = {
	ci::gl::VboMesh::Layout().usage( GL_STATIC_DRAW ).attrib( ci::geom::Attrib::POSITION, 3 ),
	ci::gl::VboMesh::Layout().usage( GL_STATIC_DRAW ).attrib( ci::geom::Attrib::TEX_COORD_0, 2 ),
	ci::gl::VboMesh::Layout().usage( GL_DYNAMIC_DRAW ).attrib( ci::geom::Attrib::COLOR, 4 )
};
mPlaneVBOMesh = ci::gl::VboMesh::create( plane, bufferLayout );
mTiltedTextureVBOBatch = ci::gl::Batch::create( mPlaneVBOMesh, customShader );

I’m trying to change the color attributes in the update loop. To start, I’m just trying to change them based on time, to see if it’s working correctly, which it isn’t:

//
float testFloat = sin( ci::app::getElapsedSeconds() );
auto mappedColorAttrib = mPlaneVBOMesh->mapAttrib4f( ci::geom::Attrib::COLOR, true );

for( int i = 0; i < mPlaneVBOMesh->getNumVertices(); i++ ) {
	if( i == 0 ) {
		mappedColorAttrib->r = testFloat;
	}
	else if( i == 1 ) {
		mappedColorAttrib->g = testFloat;
	}
	else if( i == 2 ) {
		mappedColorAttrib->b = testFloat;
	}
	++mappedColorAttrib;
}

mappedColorAttrib.unmap();
mTiltedTextureVBOBatch->draw();

I’m getting really inconsistent results: sometimes when I build it, it won’t draw at all; other times the plane will show all white or a different color. This makes me think I’m setting up the attributes incorrectly when I’m creating the VboMesh.

I’ve tried changing the position attribute dimensions from 3 to 4, but that doesn’t solve the issue. The VboMesh sample uses 3 here, but the shader itself seems to expect a vec4.

Any advice on getting this working would be much appreciated. In addition, I’d also love to get a better way to update each vertex individually, rather than looping over them and testing which iteration I’m on. This feels very clunky, and I’m sure there’s a cleaner way.

Thanks for any help!

Best,
Jennifer

I’ve had nothing but trouble with constructing gl::Batches from VboMeshes in the past. The order of construction is important, and there’s some weirdness to do with who dictates certain attribute states that I can never quite remember, so I just don’t do it. Luckily, the awesome geom library can help here. Just create the Plane as you were, but inject a colour attribute by way of the >> operator, then just construct your Batch as normal. Your setup code becomes:

void ForumSandboxApp::setup()
{
    auto customShader = gl::getStockShader( gl::ShaderDef().color() );
    
    ci::geom::Plane plane = ci::geom::Plane().size( ci::vec2( 1 ) ).normal( ci::vec3( 0, 0, 1 ) );
    mTiltedTextureVBOBatch = ci::gl::Batch::create( plane >> geom::Constant ( geom::COLOR, ColorAf::white() ), customShader );
}

and then a slight modification to your ::draw method to retrieve the VboMesh differently.

void ForumSandboxApp::draw()
{
    gl::clear( Colorf::gray ( 0.2f ) );
    
    float testFloat = sin( ci::app::getElapsedSeconds() );
    auto mappedColorAttrib = mTiltedTextureVBOBatch->getVboMesh()->mapAttrib4f( ci::geom::Attrib::COLOR, true );
    
    for( int i = 0; i < mTiltedTextureVBOBatch->getNumVertices(); i++ ) {
        if( i == 0 ) {
            mappedColorAttrib->r = testFloat;
        }
        else if( i == 1 ) {
            mappedColorAttrib->g = testFloat;
        }
        else if( i == 2 ) {
            mappedColorAttrib->b = testFloat;
        }
        ++mappedColorAttrib;
    }
    
    mappedColorAttrib.unmap();
    
    gl::setMatrices( cam );
    gl::ScopedDepth depth { true };
    gl::ScopedColor color { Colorf::white() };
    mTiltedTextureVBOBatch->draw();
}

Hope this helps,

A

1 Like

Wow, thanks! That actually worked great! But now I’m having trouble getting the texture to draw. I’ve changed the stock shader to use textures, and I tried adding the TEX_COORD_0 attribute to the plane:

auto def = ci::gl::ShaderDef().texture().color();
ci::gl::GlslProgRef customShader = ci::gl::getStockShader( def );
ci::geom::Plane plane = ci::geom::Plane().size( ci::vec2( 1 ) ).normal( ci::vec3( 0, 0, 1 ) );
mTiltedTextureVBOBatch = ci::gl::Batch::create( plane >> ci::geom::Constant( ci::geom::TEX_COORD_0, ci::vec2() ) >> ci::geom::Constant( ci::geom::COLOR, ci::ColorAf::white() ), customShader );

Then when drawing, I bind the texture (along with some other transformations, which are working):

ci::gl::ScopedTextureBind texBind( mTiltedImageTex );
mTiltedTextureVBOBatch->draw();

Any additional advice is greatly appreciated. Thanks!

Brief update: I actually got this to render properly by assigning the texture coordinates to the vertices after creating the batch:

	mTiltedTextureVBOBatch = ci::gl::Batch::create( plane >> ci::geom::Constant( ci::geom::TEX_COORD_0, ci::vec2( 0, 1 ) ) >> ci::geom::Constant( ci::geom::COLOR, ci::ColorAf::white() ), customShader );

	//	assign vertex texture coordinates
	auto mappedCoordAttrib = mTiltedTextureVBOBatch->getVboMesh()->mapAttrib2f( ci::geom::Attrib::TEX_COORD_0, false );

	for( int i = 0; i < mTiltedTextureVBOBatch->getVboMesh()->getNumVertices(); i++ ) {
		ci::vec2& coord = *mappedCoordAttrib;

		if( i == 0 ) {
			mappedCoordAttrib->x = 0.f;
			mappedCoordAttrib->y = 0.f;
		}
		else if( i == 1 ) {
			mappedCoordAttrib->x = 0.f;
			mappedCoordAttrib->y = 1.f;
		}
		else if( i == 2 ) {
			mappedCoordAttrib->x = 1.f;
			mappedCoordAttrib->y = 0.f;
		}
		else if( i == 3 ) {
			mappedCoordAttrib->x = 1.f;
			mappedCoordAttrib->y = 1.f;
		}

		++mappedCoordAttrib;
	}

	mappedCoordAttrib.unmap();

This feels a little clunky, although I do this only once during the setup. But is there a better way to do this? Thanks again!

The plane should come with UVs out of the box.

void ForumSandboxApp::setup()
{
    auto customShader = gl::getStockShader( gl::ShaderDef().color().texture() );
    
    mTexture = gl::Texture::create( ip::checkerboard( 256, 256 ) );
    
    ci::geom::Plane plane = ci::geom::Plane().size( ci::vec2( 1 ) ).normal( ci::vec3( 0, 0, 1 ) );
    mTiltedTextureVBOBatch = ci::gl::Batch::create( plane >> geom::Constant ( geom::COLOR, ColorAf::white() ), customShader );
    
    cam = CameraPersp ( getWindowWidth(), getWindowHeight(), 60.0f, 0.1f, 1000.0f );
    cam.lookAt ( vec3 ( 0, 0, 5 ), vec3 ( 0 ) );
    
    camUi = CameraUi ( &cam, getWindow() );
}

void ForumSandboxApp::draw()
{
    gl::clear( Colorf::gray ( 0.2f ) );
    
    float testFloat = sin( ci::app::getElapsedSeconds() );
    auto mappedColorAttrib = mTiltedTextureVBOBatch->getVboMesh()->mapAttrib4f( ci::geom::Attrib::COLOR, true );
    
    for( int i = 0; i < mTiltedTextureVBOBatch->getNumVertices(); i++ ) {
        if( i == 0 ) {
            mappedColorAttrib->r = testFloat;
        }
        else if( i == 1 ) {
            mappedColorAttrib->g = testFloat;
        }
        else if( i == 2 ) {
            mappedColorAttrib->b = testFloat;
        }
        ++mappedColorAttrib;
    }
    
    mappedColorAttrib.unmap();
    
    gl::setMatrices( cam );
    gl::ScopedDepth depth { true };
    gl::ScopedColor color { Colorf::white() };
    gl::ScopedTextureBind tex0 { mTexture, 0 };
    mTiltedTextureVBOBatch->draw();
}

plane-test

Thanks, that’s working, and I appreciate your help!