Examples to build manual cinder meshes using indices, positions and colors. VboMesh & TriMesh

Hi all!

Here learning about Cinder and OpenGL meshes. Would love to fully understand how them works. I’ve been doing some good readings here: deprecated-vbomesh and here how-to-create-mesh-and-draw-it-on-the-screen about VboMeshes but I’ve missed a simple and updated example showing how to build vboMesh manually without using cinder surfaces.

Then I’ve found this blog from drewish showing it and doing nice comparisons about triMesh and VboMesh so I’ve started to update it to 0.9.0. Right now I’ve a vboMesh working out but still not with triMesh. Attached visual results for vboMesh and triMesh.

Also guess I should use Batch to increase performance. Would be nice to receive some hints.

// Updated drewish demo (to Cinder 0.9.0 ) about VBOMesh vs TriMesh. Building cinder manual meshes using indices, positions and colors.
// by Carles Gutiérrez
// Original code from
// Simple demo to compare drawing 2D triangles with Cinder's VBO and TriMesh wrappers.
// https://drewish.com/2014/08/16/comparing-the-trimesh-and-vbomesh-in-cinder/


//TODO 
// fix triMesh uses
// try textures coords


#include "cinder/app/App.h"
#include "cinder/app/RendererGl.h"
#include "cinder/gl/gl.h"
#include "cinder/gl/Vbo.h"
#include "cinder/Trimesh.h"
#include "cinder/params/Params.h"
// sqrt(3) / 2
#define M_SQRT_3_2 0.8660254037844386

using namespace ci;
using namespace ci::app;
using namespace std;

class ComparingTriMeshAndVboMeshApp : public App {
  public:
	void setup() override;
	void mouseDown( MouseEvent event ) override;
	void update() override;
	void draw() override;

	//void prepareSettings(Settings *settings) override;//0.9.0 use lambda at CINDER_APP
	void recalculate();
	void buildVBOMesh();
	void buildTriMesh();

	gl::VboMeshRef	mVboMesh;	//gl::VboMesh mMesh;

	TriMesh mTriangles;
	params::InterfaceGl mParams;

	int  mCols = 10;
	int  mRows = 10;
	int  mScale = 30;
	bool mWireframe = false;
	bool useTriMesh = false;
	bool bUpdateColorsFbo = false;
};

void ComparingTriMeshAndVboMeshApp::setup()
{
	mParams = params::InterfaceGl("Parameters", vec2(220, 170));
	mParams.addParam("Scale", &mScale)
		.keyIncr("s").keyDecr("S")
		.min(1).max(100).step(1)
		.updateFn([this] { recalculate(); });
	mParams.addParam("Columns", &mCols)
		.keyIncr("d").keyDecr("D")
		.min(1).max(100).step(1)
		.updateFn([this] { recalculate(); });
	mParams.addParam("Rows", &mRows)
		.keyIncr("f").keyDecr("F")
		.min(1).max(100).step(1)
		.updateFn([this] { recalculate(); });

	mParams.addParam("Wireframe", &mWireframe)
		.key("w");
	mParams.addParam("VBO/TriMesh", &useTriMesh)
		.updateFn([this] { recalculate(); })
		.key("t");
	mParams.addParam("update VBO Colors", &bUpdateColorsFbo)
		.key("c");

	recalculate();
}

void ComparingTriMeshAndVboMeshApp::mouseDown( MouseEvent event )
{
}

void ComparingTriMeshAndVboMeshApp::update()
{
	if(!useTriMesh && bUpdateColorsFbo){
		//update colors playing with position x
		float offset = getElapsedSeconds() * 4.0f;
		auto mappedPosAttrib = mVboMesh->mapAttrib3f(geom::Attrib::POSITION, false);
		auto mappedColorAttrib = mVboMesh->mapAttrib3f(geom::Attrib::COLOR, false);
		for (int i = 0; i < mVboMesh->getNumVertices(); i++) {
			vec3 &pos = *mappedPosAttrib;
			vec3 &color = *mappedColorAttrib;
			mappedColorAttrib->y = sinf(pos.x - offset) *0.75f;
			++mappedColorAttrib;
			++mappedPosAttrib;
		}
		mappedColorAttrib.unmap();
		mappedPosAttrib.unmap();
	}
	
}

void ComparingTriMeshAndVboMeshApp::draw()
{
	gl::pushModelView();
	gl::clear(Color::black());

	if (mWireframe) gl::enableWireframe();

	if (useTriMesh)
		gl::draw(mTriangles);
	else
		gl::draw(mVboMesh);

	if (mWireframe) gl::disableWireframe();

	gl::popModelView();

	mParams.draw();
}


void ComparingTriMeshAndVboMeshApp::recalculate()
{
	if (useTriMesh) buildTriMesh();
	else buildVBOMesh();
}

void ComparingTriMeshAndVboMeshApp::buildVBOMesh()
{

	vector<gl::VboMesh::Layout> bufferLayout = {
		gl::VboMesh::Layout().usage(GL_STATIC_DRAW).attrib(geom::Attrib::POSITION, 3),
		gl::VboMesh::Layout().usage(GL_DYNAMIC_DRAW).attrib(geom::Attrib::COLOR, 3),
		gl::VboMesh::Layout().usage(GL_STATIC_DRAW).attrib(geom::Attrib::TEX_COORD_0, 2)
	};

	mVboMesh = gl::VboMesh::create(mCols * mRows * 3, GL_TRIANGLES, bufferLayout);//mMesh = gl::VboMesh(mCols * mRows * 3, mCols * mRows * 3, layout, GL_TRIANGLES);
	vector<uint32_t> indices;
	vector<vec3> positions;
	vector<Color> colors;

	float w = M_SQRT_3_2 * mScale;
	float h = 1.0 * mScale;
	int index = -1;
	Color8u color;

	for (int col = 0; col < mCols; col++) {
		float x = col * 2 * w;
		int direction = (col % 2) ? 1 : -1;
		for (int row = 0; row < mRows; row++) {
			direction *= -1;
			float y = row * h;

			positions.push_back(vec3(x - w * direction, y - h, 0));
			positions.push_back(vec3(x + w * direction, y + 0, 0));
			positions.push_back(vec3(x - w * direction, y + h, 0));

			// #69D2E7 and #A7DBD8
			color = (direction > 0) ? Color8u(105, 210, 231) : Color8u(224, 228, 204);
			colors.push_back(color);
			colors.push_back(color);
			colors.push_back(color);

			index += 3;
			indices.push_back(index - 2);
			indices.push_back(index - 1);
			indices.push_back(index);
		}
	}
	mVboMesh->bufferIndices(indices.size(), &indices);//mVboMesh->bufferIndices(indices);
	mVboMesh->bufferAttrib(geom::POSITION, positions.size() * sizeof(vec3), positions.data());//mMesh.bufferPositions(positions);
	mVboMesh->bufferAttrib(geom::COLOR, colors.size() * sizeof(Color), colors.data());//mMesh.bufferColorsRGB(colors);
	


}

void ComparingTriMeshAndVboMeshApp::buildTriMesh()
{
	float w = M_SQRT_3_2 * mScale;
	float h = 1.0 * mScale;
	int index = -1;
	Color color;

	mTriangles.clear();

	for (int col = 0; col < mCols; col++) {
		float x = col * 2 * w;
		int direction = (col % 2) ? 1 : -1;
		for (int row = 0; row < mRows; row++) {
			direction *= -1;
			float y = row * h;

			mTriangles.appendPosition(vec3(x - w * direction, y - h, 0));//mTriangles.appendVertex(vec3(x - w * direction, y - h, 0));
			mTriangles.appendPosition(vec3(x + w * direction, y + 0, 0));
			mTriangles.appendPosition(vec3(x - w * direction, y + h, 0));

			// #69D2E7 and #A7DBD8
			color = (direction > 0) ? Color8u(250, 105, 0) : Color8u(105, 210, 231);
			mTriangles.appendColorRgb(color);
			mTriangles.appendColorRgb(color);
			mTriangles.appendColorRgb(color);

			index += 3;
			mTriangles.appendTriangle(index - 2, index - 1, index);
		}
	}
}

CINDER_APP(ComparingTriMeshAndVboMeshApp, RendererGl, [](App::Settings *settings) {
	settings->setHighDensityDisplayEnabled();//settings->enableHighDensityDisplay();
	settings->setMultiTouchEnabled(false);//settings->enableMultiTouch(false);
})

Maybe this could be helpful:

https://app.gitbook.com/@interwebjill/s/getting-started-with-cinder-0-9/

2 Likes

Thank you Jill, this tutorial looks awesome. I will follow it.

Hello all,
I’ve did some cleanings, added normals and Batch for the VboMesh and a CameraUI just for fun.
But:

  • VboMesh works but if I use Batch, then no colors are visible.
  • Same for TriMesh. No colors visibles at vertices, just a white wireframe.
  • Playing with mesh size does changes in the color indices order ( check “update Vbo colors” and modify the “Scale” of the Mesh)

Should I add lighting in order to see such colors in the TriMesh or VboMesh with Batch?

Here the code:

    // Updated drewish demo (to Cinder 0.9.0 ) about VBOMesh vs TriMesh. Building cinder manual meshes using indices, positions and colors.
    // by Carles Gutiérrez
    // Original code from
    // Simple demo to compare drawing 2D triangles with Cinder's VBO and TriMesh wrappers.
    // https://drewish.com/2014/08/16/comparing-the-trimesh-and-vbomesh-in-cinder/

    #include "cinder/app/App.h"
    #include "cinder/app/RendererGl.h"
    #include "cinder/gl/gl.h"
    #include "cinder/gl/Vbo.h"
    #include "cinder/Trimesh.h"
    #include "cinder/params/Params.h"
    #include "cinder/CameraUi.h"
    // sqrt(3) / 2
    #define M_SQRT_3_2 0.8660254037844386

    using namespace ci;
    using namespace ci::app;
    using namespace std;

    class ComparingTriMeshAndVboMeshApp : public App {
      public:
    	void setup() override;
    	void mouseDown( MouseEvent event ) override;
    	void update() override;
    	void draw() override;

    	void recalculate();
    	void buildVBOMesh();
    	void buildTriMesh();

    	gl::VboMeshRef	mVboMesh;	//gl::VboMesh mMesh;
    	gl::BatchRef    mBatch;
    	gl::GlslProgRef mShader;

    	TriMesh mTriangles;

    	params::InterfaceGl mParams;

    	int  mCols = 10;
    	int  mRows = 10;
    	int  mScale = 33;//30;
    	bool mWireframe = false;
    	bool useTriMesh = false;
    	bool bUpdateColorsFbo = false;
    	int mFps = 0;
    	bool bBatch = false;
    	bool bCamera = false;

    	CameraPersp             mCam;
    	CameraUi        mCamUi;
    };

    void ComparingTriMeshAndVboMeshApp::setup()
    {


    	mParams = params::InterfaceGl("Parameters", vec2(220, 190));
    	mParams.addParam("FPS", &mFps)
    		.keyIncr("+").keyDecr("-")
    		.min(1).max(1000).step(1)
    		.updateFn([this] {});
    	mParams.addParam("Scale", &mScale)
    		.keyIncr("s").keyDecr("S")
    		.min(1).max(100).step(1)
    		.updateFn([this] { recalculate(); });
    	mParams.addParam("Columns", &mCols)
    		.keyIncr("d").keyDecr("D")
    		.min(1).max(100).step(1)
    		.updateFn([this] { recalculate(); });
    	mParams.addParam("Rows", &mRows)
    		.keyIncr("f").keyDecr("F")
    		.min(1).max(100).step(1)
    		.updateFn([this] { recalculate(); });

    	mParams.addParam("Wireframe", &mWireframe)
    		.key("w");
    	mParams.addParam("VBO/TriMesh", &useTriMesh)
    		.updateFn([this] { recalculate(); })
    		.key("t")
    		.updateFn([this] { recalculate(); });
    	mParams.addParam("update VBO Colors", &bUpdateColorsFbo)
    		.key("c")
    		.updateFn([this] { recalculate(); });
    	mParams.addParam("bBatch", &bBatch)
    		.key("b")
    		.updateFn([this] { recalculate(); });
    	mParams.addParam("bCamera", &bCamera)
    		.key("u")
    		.updateFn([this] { recalculate(); });
    	

    	recalculate();

    	mCam.lookAt(vec3(250, 0, 400), vec3(250,0,0));
    	mCamUi = CameraUi(&mCam, getWindow());
    }

    void ComparingTriMeshAndVboMeshApp::mouseDown( MouseEvent event )
    {
    }

    void ComparingTriMeshAndVboMeshApp::update()
    {
    	if(!useTriMesh && bUpdateColorsFbo){
    		//updating colors by position x
    		float offset = getElapsedSeconds() * 4.0f;
    		auto mappedPosAttrib = mVboMesh->mapAttrib3f(geom::Attrib::POSITION, false);
    		auto mappedColorAttrib = mVboMesh->mapAttrib3f(geom::Attrib::COLOR, false);
    		for (int i = 0; i < mVboMesh->getNumVertices(); i++) {
    			vec3 &pos = *mappedPosAttrib;
    			vec3 &color = *mappedColorAttrib;
    			mappedColorAttrib->y = sinf(pos.x - offset) *0.75f;
    			++mappedColorAttrib;
    			++mappedPosAttrib;
    		}
    		mappedColorAttrib.unmap();
    		mappedPosAttrib.unmap();
    	}

    	mFps = getAverageFps();
    	
    }

    void ComparingTriMeshAndVboMeshApp::draw()
    {
    	gl::clear(Color::black());

    	gl::pushModelView();
    		if (bCamera) {
    			gl::setMatrices(mCam);
    			gl::enableDepthRead();
    			gl::enableDepthWrite();
    		}
    		if (mWireframe) gl::enableWireframe();

    		if (useTriMesh)
    			gl::draw(mTriangles);
    		else {
    			if (bBatch) {
    				//gl::color(Color(1.0, 1.0, 1.0));
    				mBatch->draw();
    			}
    			else gl::draw(mVboMesh);
    		}

    		if (mWireframe) gl::disableWireframe();
    	gl::popModelView();

    	gl::setMatricesWindow(getWindowSize());
    	mParams.draw();

    }


    void ComparingTriMeshAndVboMeshApp::recalculate()
    {
    	if (useTriMesh) buildTriMesh();
    	else buildVBOMesh();
    }

    void ComparingTriMeshAndVboMeshApp::buildVBOMesh()
    {

    	vector<gl::VboMesh::Layout> bufferLayout = {
    		gl::VboMesh::Layout().usage(GL_STATIC_DRAW).attrib(geom::Attrib::POSITION, 3),
    		gl::VboMesh::Layout().usage(GL_DYNAMIC_DRAW).attrib(geom::Attrib::COLOR, 3),
    		gl::VboMesh::Layout().usage(GL_STATIC_DRAW).attrib(geom::Attrib::TEX_COORD_0, 2),
    		gl::VboMesh::Layout().usage(GL_STATIC_DRAW).attrib(geom::Attrib::NORMAL , 3)
    	};

    	mVboMesh = gl::VboMesh::create(mCols * mRows * 3, GL_TRIANGLES, bufferLayout);//mMesh = gl::VboMesh(mCols * mRows * 3, mCols * mRows * 3, layout, GL_TRIANGLES);
    	vector<uint32_t> indices;
    	vector<vec3> positions;
    	vector<Color> colors;
    	vector <vec3> normals;

    	float w = M_SQRT_3_2 * mScale;
    	float h = 1.0 * mScale;
    	int index = -1;
    	Color8u color;

    	for (int col = 0; col < mCols; col++) {
    		float x = col * 2 * w;
    		int direction = (col % 2) ? 1 : -1;
    		for (int row = 0; row < mRows; row++) {
    			direction *= -1;
    			float y = row * h;

    			positions.push_back(vec3(x - w * direction, y - h, 0));
    			positions.push_back(vec3(x + w * direction, y + 0, 0));
    			positions.push_back(vec3(x - w * direction, y + h, 0));

    			auto normal = vec3(0, 0, 1);
    			normals.push_back(normal);
    			normals.push_back(normal);
    			normals.push_back(normal);

    			// #69D2E7 and #A7DBD8
    			color = (direction > 0) ? Color8u(105, 210, 231) : Color8u(224, 228, 204);
    			colors.push_back(color);
    			colors.push_back(color);
    			colors.push_back(color);

    			index += 3;
    			indices.push_back(index - 2);
    			indices.push_back(index - 1);
    			indices.push_back(index);
    		}
    	}
    	mVboMesh->bufferIndices(indices.size() * sizeof(uint32_t), indices.data());
    	mVboMesh->bufferAttrib(geom::POSITION, positions);//mVboMesh->bufferAttrib(geom::POSITION, positions.size() * sizeof(vec3), positions.data());
    	mVboMesh->bufferAttrib(geom::COLOR, colors);//colors.size() * sizeof(Color), colors.data()
    	mVboMesh->bufferAttrib(geom::Attrib::NORMAL, normals);

    	if (bBatch) {
    		mShader = gl::getStockShader(gl::ShaderDef());// using a Cinder generic shader, since we always need one
    		mBatch = gl::Batch::create(mVboMesh, mShader);
    	}

    }

    void ComparingTriMeshAndVboMeshApp::buildTriMesh()
    {
    	float w = M_SQRT_3_2 * mScale;
    	float h = 1.0 * mScale;
    	int index = -1;
    	Color color;

    	mTriangles.clear();

    	for (int col = 0; col < mCols; col++) {
    		float x = col * 2 * w;
    		int direction = (col % 2) ? 1 : -1;
    		for (int row = 0; row < mRows; row++) {
    			direction *= -1;
    			float y = row * h;

    			mTriangles.appendPosition(vec3(x - w * direction, y - h, 0));//mTriangles.appendVertex(vec3(x - w * direction, y - h, 0));
    			mTriangles.appendPosition(vec3(x + w * direction, y + 0, 0));
    			mTriangles.appendPosition(vec3(x - w * direction, y + h, 0));

    			// #69D2E7 and #A7DBD8
    			color = (direction > 0) ? Color8u(250, 105, 0) : Color8u(105, 210, 231);
    			mTriangles.appendColorRgb(color);
    			mTriangles.appendColorRgb(color);
    			mTriangles.appendColorRgb(color);

    			index += 3;
    			mTriangles.appendTriangle(index - 2, index - 1, index);
    		}
    	}
    }

    CINDER_APP(ComparingTriMeshAndVboMeshApp, RendererGl, [](App::Settings *settings) {
    	settings->setHighDensityDisplayEnabled();//settings->enableHighDensityDisplay();
    	settings->setMultiTouchEnabled(false);//settings->enableMultiTouch(false);
    })

After a quick glance at your code, I think you should explicitly add support for colors to the stock shader:

mShader = gl::getStockShader(gl::ShaderDef().color());

To add very basic lighting, you could try this:

mShader = gl::getStockShader(gl::ShaderDef().lambert());

Note that the stock shaders are very limited and basically only there to have at least something showing up on screen. My advise is to learn some basic shader coding. The Cinder samples can help you with that.

~Paul

Thanks Paul, I forgot to add .color() to the shader for VboMesh Batch, now does perfect.
With triMesh I’m still missing how to add the colors properly ( first without Batch and later with ). I will figure it out learning more about triMesh.

I will keep working on shader coding and their samples. Thanks for the advice.