Best way to draw an array of objects

hello,

I made an app which is drawing a bunch of circles moving according to the mouse position.

This is working fine but for now i am only dealing with a hundred of circles. I tried to push it and i quickly faced the limit as number of circle grows.

I think the limit is the big for loop and position calculations of the circles.

I was wondering how i could make this app deal with a lot more circles.
What is the best way ?

Also, shader or VBO could be useful to compute circles center positions, but how would i draw the circles ? maybe compute positions in the shader then back to the app and draw the cirlces at these positions ?

thank you for your help

using VBO ? shaders ? or any other technique ?

if you use OF_PRIMITIVE_TRIANGLES in the vboMesh, you can append the circles directly in the vbo and draw all of them at once, using mesh.append(circleMesh)

the fastest way though is by using instanced drawing and a custom attribute in the vbo to pass the position.

there’s a couple of examples in 0.9 i think the most similar to what you want to do is textureBufferInstancedExample although you wouldn’t need the texture buffer, something like:

//.h
ofVboMesh circleMesh;
vector<ofVec3f> posVector;

//setup
//create 1 circle in circleMesh
for(int i=0;i<numPositions;i++){
    posVector.push_back(newPosition);
}
circleMesh.getVbo().setAttributeData(shader.getLocation("circlePosition"), posVector.data(), 3, posVector.size());
circleMesh.setAttributeDivisor(shader.getLocation("circlePosition"),1); // this tells that the circlePosition attribute is passed once per instance instead of once per vertex

//update
for(auto & pos: posVector){
    pos = ...;
}
circleMesh.getVbo().updateAttributeData(shader.getLocation("circlePosition"), posVector.data(), posVector.size());

// draw
shader.begin();
circleMesh.drawInstanced(OF_MESH_FILL, posVector.size() );
shader.end();

where shader should have the usual attributes for position, color, texcoord (or whatever you are using) and a vec3 circlePosition attribute that allows to position the circle.

1 Like

thanks a lot @arturo ! i will have a look at your suggestion.

Just for my personal knowing : i first thought about putting my point array into an ofFbo and them pass it to a shader to compute positions. Then return the new positions and draw the circles. Would it work like this ? if not why ?

thanks a lot

no an fbo is not meant to be used like that, you should be using a buffer object (ofBufferObject) or a texture buffer object using an ofTexture the way it’s demo’d in the textureBufferInstancedExample.

Also downloading data form the graphics card is slow and problematic so it’s better to do those calculations in the same shader that draws the circles. With instanced drawing you can also just pass the geometry of one circle and calculate the position for every circle depending on an id you get in the vertex shader

OK.

Well all those vbo, fbo, shaders and other opengl related things are still quite fuzzy to me. I must confess that i don’t really understand your example :wink:
But i will dig into it.

Note that every circle can have its own color.

So basically, what are the major steps ?

What i understand is :

  • create points
  • add points to a vbo
  • pass the vbo to a shader
  • compute position and draw the circle in the vertex shader (pass mouse position to the shader for the interactivity)
  • color is applied by a fragment shader ?

am i right ?

here is the result so far (without using shader or vbo :

if you don’t understand things very well yet you should probably try it out by just using an ofVboMesh, it should be fast enough for what you want to do i think but if it isn’t then you can try using shaders…

just add the circles to 1 ofVboMesh and drwa that. you can add colors for every circle by having color coordinates in the circle mesh. the easiest is to have a mesh with 1 circle and append as many as you want to the vbo mesh that will be drawn:

//.h
ofMesh circleMesh;
ofVboMesh vboMesh;

//setup
// create a circle in circleMesh

//update
vboMesh.clear();
for(int i=0;i<numCircles;i++){
    // compute circle position
    // compute circle color
    for(auto & vertex: circleMesh.getVertices()){
        vboMesh.addVertex(vertex + circlePosition);
        vboMesh.addColor(circleColor);
    }
}


//draw
vboMesh.draw();

if the colors of the circles won’t change then you might want to add them in setup and only clear and re-add the vertices on every update.

Yes i set a matrix of circle just at the beginning and set circles color according to an image i read.
each circle is an object from a ball class i made.

// Matrix setup
void ofApp::setupMatrix() {
    if (balls.size() != 0) {
        balls.clear();
    }
    
    for (int y = 0; y < nbY; y++) {
        for (int x = 0; x < nbX; x++) {
            balls.push_back(ball());
            float posx = x * ballRadius * 2;
            float posy = y * ballRadius * 2;
            ofColor c = image.getColor(x, y);
            balls.back().setup(posx , posy, ballRadius, c);
        }
    }
}

then i update position of circles with some basic physics (spring effect)

    // In update()
    for (int i = 0; i < balls.size(); i++) {
    
    dist = balls[i].pin - balls[i].position;
    attraction = ((dist * stiffness) - (damping * dist))/mass;
    balls[i].velocity = (balls[i].velocity + attraction) * friction;
    balls[i].position = balls[i].position + balls[i].velocity;
            
    if (dist.length() < limit) {
        if (bTwinkle) {
            balls[i].position = balls[i].pin + ofPoint(ofRandom(twinkle), ofRandom(twinkle));
        }
        else {
            balls[i].position = balls[i].pin;
        }
    }
    balls[i].update();
}

positions are affected by dragging the mouse

void ofApp::mouseDragged(int x, int y, int button){
    for (int i = 0; i < balls.size(); i++) {
        if (balls[i].inArea(x, y)) {
            balls[i].position.set(x, y);
        }
    }
}

in draw() i make some color variation for each circle according to their position around their original position
something like a screen color blend

ofPushMatrix();
ofColor c = ofColor(ofMap(dist.x + dist.y, -10, 10, 20, 220, true));

// formula for color SCREEN blend mode
ofSetColor(ofColor(255) - (((ofColor(255) - c)*(ofColor(255) - color))/ofColor(contrast)));

ofCircle(position, radius);
ofPopMatrix();

it seems like there are heavy for loops and computations. I don’t really know if i am making it right, i think this could be done in a much better way so it could be lighter in ressources but that was the way it seemed logical for me (and my knowledge)

Now i am going to give the ofVboMesh a try, but i think it would be a good exercice for me to dig into shaders for that project…

any support is welcome !

thanks a lot @arturo

drawing individual circles and setting the global color + push/pop matrix on each object is way slower than having all the geometry in only one vbo

well, good news.
let’s get cracking !

Just some more questions before i dig into it :

  • why use ofMesh and ofVboMesh ? i mean what is the difference and why not just use one or another instead of both ?

  • i still don’t get how to draw curved meshes like circles. Is it a base shape or do i have to create one using points and fill the shape with color ?

ofMesh is only stored in RAM in the computer’s memory while ofVboMesh is also uploaded to the graphics card memory as a vbo, since circleMesh is not going to be drawn it doesn’t need a vbo while for the mesh that it’s going to be rendered at the end it’s more efficient to have a vboMesh so it’s directly stored in the graphics card.

You can’t draw curves in openGL what you do is just draw lots of small lines so they look like a curve. An easy way to create a circle is to just use an ofPolyline to create one and then put all those points in the ofMesh but since the ofMesh needs triangles to be drawn you’ll need to go through the center for every 2 points, something like:

ofPolyline circle;
circle.arc(0,0,radius,radius,0,360);
for(int i=0; i<circle.getVertices().size(); i+=2){
    circleMesh.addVertex(circle[i]);
    circleMesh.addVertex(circle[i+1]);
    circleMesh.addVertex(ofVec3f(0,0));
}

you can also just use an ofPath to get the circle already decomposed in triangles but the resulting mesh might have more triangles than necesary:

ofPath circle;
circle.arc(0,0,radius,radius,0,360);
circleMesh = cricle.getTessellation();

alsot take a look at the code in ofPolyline::arc to see how to draw a circle just using sin and cos

all those information are really helpful !
thanks a lot @arturo

i am trying the simplest way for start. But i am getting a weird shape.
I know that in openGL it is important to take care of drawing modes and such. Or maybe something dealing with vertex index ?

radius = 150;

path.arc(0, 0, radius, radius, 0, 360);

mesh = path.getTessellation();

ofPoint center(ofGetWidth() / 2, ofGetHeight() / 2);

for (int i = 0; i < mesh.getVertices().size(); i++) {
     vboMesh.addVertex(mesh.getVertex(i) + center);
}

Humm… despite all my efforts, i can’t find the way to draw the circle properly…

any help appreciated.

thanks

so the way ofPath creates a mesh for a shape is by using vertices but also indices, i didn’t realized about that when i posted my example. there’s ways to do it by using ofPath but also using a polyline will work.

also take a look at this tutorial which explains all the basics of how the different objects that you are trying to use here work:

http://openframeworks.cc/tutorials/graphics/opengl.html

yes i have already read the ofPath, ofPolyline, ofVboMesh and opendGL doc.
I tried with indices. I also tried your example :

ofPolyline circle;
circle.arc(0,0,radius,radius,0,360);
for(int i=0; i<circle.getVertices().size(); i+=2){
    circleMesh.addVertex(circle[i]);
    circleMesh.addVertex(circle[i+1]);
    circleMesh.addVertex(ofVec3f(0,0));
}

but no luck so far. I must be doing something wrong.

i wonder what are the vertex indices getTesselation() is using
I can’t figure out how to reorder them (if this is the issue i am facing)

there was an error in the code i posted for the polyline it should be:

        ofPolyline circle;
        circle.arc(0,0,radius,radius,0,360);
        for(int i=0; i<circle.getVertices().size()-1; i++){
            mesh.addVertex(circle[i]+pos);
            mesh.addVertex(circle[i+1]+pos);
            mesh.addVertex(ofVec3f(0,0)+pos);
        }

the indices the tessellation is creating are just the order in which the triangles are drawn. if you use mesh.append(otherMesh) it’ll do the right thing and reorder the indices properly but since you want to modify the vertices before adding the mesh (to add the position) probably the easiest is to just use the polyline variation

yes i just also spotted the problem :wink:

now this is working

poly.arc(0, 0, radius, radius, 0, 360);

for(int i=0; i<poly.getVertices().size(); i++){
    mesh.addVertex(poly[i]);
    mesh.addVertex(poly[i+1]);
    mesh.addVertex(ofVec3f(0,0));
}

Now for my personal knowledge, i wanted to know why this one wouldn’t work

path.arc(0, 0, radius, radius, 0, 360);
mesh = path.getTessellation();

seems like ofPolyline and ofPath create an arc the same way but i can’t find the way to “re-organize” the tessellation the way i want it to be drawn

the append method in ofMesh appends a mesh with indices into another, you can take a look into that code’s method to see how it’s done

i mean does getTesselation() does it a certain way i can’t draw it correctly or is it me lacking some basics ?