Instanced Rendering with an array of ofMatrix4x4

hi, im currently trying to draw lots instances of a mesh box with different matrix transformations for each mesh.

i have looked at vboMeshDrawInstancedExample and thats almost what i need but in my case, for each mesh, im calculating the matrix transformations in update and in draw i need pass this array of matrices to my shader using a vbo (i think)

have read about this possible solution,

  1. Create a VBO containing the vertexes for one cube.
    vbo1 = [v1] [v2] [v3] ... [v36]

  2. Create another VBO with the view matrix and color for each cube, and use an attribute divisor of 1. (You can use the same vbo, but I would use a separate one.)
    vbo2 = [cube 1 mat, color] [cube 2 mat, color] ... [cube N mat, color]

  3. Call glDrawElementsInstanced() or glDrawArraysInstanced(). This will draw the cube over and over again.

there is also this tutorial,
http://ogldev.atspace.co.uk/www/tutorial33/tutorial33.html

i guess im stuck on not knowing how to pass the matrices array into vbo as custom data that i can read in my shader?

anyone deal with this before?

im aware there is another similar post (link below), but i believe this is a different approach.

cheers!

Perhaps the ofxUbo addon might help with this?

Hi @julapy. How about this?
.h

#pragma once

#include "ofMain.h"

class NodeEx : public ofNode
{
public:
    float height;
    ofFloatColor col;
    ofPoint origPos;
    float origHeight;
};

class ofApp : public ofBaseApp
{
public:
    void setup();
    void update();
    void draw();
    
    void keyPressed(int key);
    void keyReleased(int key);
    void mouseMoved(int x, int y);
    void mouseDragged(int x, int y, int button);
    void mousePressed(int x, int y, int button);
    void mouseReleased(int x, int y, int button);
    void windowResized(int w, int h);
    void dragEvent(ofDragInfo dragInfo);
    void gotMessage(ofMessage msg);
    
private:
    
    const int numPts = 100;
    vector<NodeEx> nodes;
    ofVboMesh vbo;
    ofShader shader;
    
    GLuint matrixID;
    GLuint colorID;
    GLuint heightID;
};

in .cpp

#include "ofApp.h"

void ofApp::setup()
{
    ofEnableDepthTest();
    
    shader.load("shaders/box.vert", "shaders/box.frag");
    
    ofBoxPrimitive box;
    box.set(30.0);
    vbo = box.getMesh();
    
    for (int i = 0; i < numPts; i++)
    {
        NodeEx node;
        ofPoint p = ofPoint(ofRandomWidth(), ofRandomHeight(), ofRandom(-1000, 0));
        node.setPosition(p);
        node.origPos = p;
        node.col = ofFloatColor(ofRandomuf(), ofRandomuf(), ofRandomuf());
        node.height = ofRandom(10, 50);
        node.origHeight = node.height;
        nodes.push_back(node);
    }
    
    vector<float> tMats;
    vector<float> cols;
    vector<float> heights;

    for (auto n: nodes)
    {
        ofMatrix4x4 m = n.getGlobalTransformMatrix();
        for (int j = 0; j < 16; j++)
        {
            tMats.push_back(m.getPtr()[j]);
        }
        
        cols.push_back(n.col.r);
        cols.push_back(n.col.g);
        cols.push_back(n.col.b);

        heights.push_back(n.height);
    }
    
//    int locTMat = shader.getAttributeLocation("transformmatrix");
//    vbo.getVbo().setAttributeData(locTMat,
//                                  &tMats[0],
//                                  16,
//                                  tMats.size(),
//                                  GL_DYNAMIC_DRAW,
//                                  sizeof(ofMatrix4x4));
//    
//    int locCol = shader.getAttributeLocation("color");
//    vbo.getVbo().setAttributeData(locCol,
//                                  &cols[0],
//                                  3,
//                                  cols.size(),
//                                  GL_DYNAMIC_DRAW,
//                                  sizeof(ofVec3f));
//    
//    int locHeight = shader.getAttributeLocation("height");
//    vbo.getVbo().setAttributeData(locHeight,
//                                  &heights[0],
//                                  1,
//                                  heights.size(),
//                                  GL_DYNAMIC_DRAW,
//                                  sizeof(float));
    
    glGenBuffers(1, &matrixID);
    int pos = glGetAttribLocation(shader.getProgram(), "transformmatrix");
    int pos1 = pos+0;
    int pos2 = pos+1;
    int pos3 = pos+2;
    int pos4 = pos+3;
    glEnableVertexAttribArray(pos1);
    glEnableVertexAttribArray(pos2);
    glEnableVertexAttribArray(pos3);
    glEnableVertexAttribArray(pos4);
    glBindBuffer(GL_ARRAY_BUFFER, matrixID);
    glVertexAttribPointer(pos1, 4, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 4 * 4, (void*)(0));
    glVertexAttribPointer(pos2, 4, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 4 * 4, (void*)(sizeof(float) * 4));
    glVertexAttribPointer(pos3, 4, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 4 * 4, (void*)(sizeof(float) * 8));
    glVertexAttribPointer(pos4, 4, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 4 * 4, (void*)(sizeof(float) * 12));
    glVertexAttribDivisor(pos1, 1);
    glVertexAttribDivisor(pos2, 1);
    glVertexAttribDivisor(pos3, 1);
    glVertexAttribDivisor(pos4, 1);
    
    glGenBuffers(1, &colorID);
    int poscol = glGetAttribLocation(shader.getProgram(), "color");
    glEnableVertexAttribArray(poscol);
    glBindBuffer(GL_ARRAY_BUFFER, colorID);
    glVertexAttribPointer(poscol, 3, GL_FLOAT, GL_FALSE, 0, NULL);
    glVertexAttribDivisor(poscol, 1);
    
    glGenBuffers(1, &heightID);
    int posHeight = glGetAttribLocation(shader.getProgram(), "height");
    glEnableVertexAttribArray(posHeight);
    glBindBuffer(GL_ARRAY_BUFFER, heightID);
    glVertexAttribPointer(posHeight, 1, GL_FLOAT, GL_FALSE, 0, NULL);
    glVertexAttribDivisor(posHeight, 1);
    
    
    glBindBuffer(GL_ARRAY_BUFFER, matrixID);
    glBufferData(GL_ARRAY_BUFFER, tMats.size()*sizeof(float), &tMats[0], GL_DYNAMIC_DRAW);
    
    glBindBuffer(GL_ARRAY_BUFFER, colorID);
    glBufferData(GL_ARRAY_BUFFER, cols.size()*sizeof(float), &cols[0], GL_DYNAMIC_DRAW);
    
    glBindBuffer(GL_ARRAY_BUFFER, heightID);
    glBufferData(GL_ARRAY_BUFFER, heights.size()*sizeof(float), &heights[0], GL_DYNAMIC_DRAW);
}

void ofApp::update()
{
    int i = 0;
    for (auto &n: nodes)
    {
        float noise = ofSignedNoise(ofGetElapsedTimef() * i / 100) * 100;

        ofPoint p = n.origPos;
        p += noise;
        n.setPosition(p);
        
        float height = n.origHeight;
        height += noise;
        n.height = height;
        
        i++;
    }
    
    vector<float> tMats;
    vector<float> cols;
    vector<float> heights;
    
    for (auto n: nodes)
    {
        ofMatrix4x4 m = n.getGlobalTransformMatrix();
        for (int j = 0; j < 16; j++)
        {
            tMats.push_back(m.getPtr()[j]);
        }
        
        cols.push_back(n.col.r);
        cols.push_back(n.col.g);
        cols.push_back(n.col.b);
        
        heights.push_back(n.height);
    }
    
    glBindBuffer(GL_ARRAY_BUFFER, matrixID);
    glBufferData(GL_ARRAY_BUFFER, tMats.size()*sizeof(float), &tMats[0], GL_DYNAMIC_DRAW);
    
    glBindBuffer(GL_ARRAY_BUFFER, colorID);
    glBufferData(GL_ARRAY_BUFFER, cols.size()*sizeof(float), &cols[0], GL_DYNAMIC_DRAW);
    
    glBindBuffer(GL_ARRAY_BUFFER, heightID);
    glBufferData(GL_ARRAY_BUFFER, heights.size()*sizeof(float), &heights[0], GL_DYNAMIC_DRAW);
}

void ofApp::draw()
{
    shader.begin();
    vbo.drawInstanced(OF_MESH_FILL, nodes.size());
    shader.end();
}

void ofApp::keyPressed(int key){}
void ofApp::keyReleased(int key){}
void ofApp::mouseMoved(int x, int y){}
void ofApp::mouseDragged(int x, int y, int button){}
void ofApp::mousePressed(int x, int y, int button){}
void ofApp::mouseReleased(int x, int y, int button){}
void ofApp::windowResized(int w, int h){}
void ofApp::gotMessage(ofMessage msg){}
void ofApp::dragEvent(ofDragInfo dragInfo){}

.vert

attribute mat4 transformmatrix;
attribute vec3 color;
attribute float height;

varying vec3 vertex_color;

void main(void)
{
    vertex_color = color.rgb;
    
    vec4 vPos = gl_Vertex;
    vPos.z *= height;
    gl_Position = gl_ProjectionMatrix * gl_ModelViewMatrix * transformmatrix * vPos;
}

.frag

varying vec3 vertex_color;

void main(void)
{
    gl_FragColor = vec4(vertex_color, 1.0);
}

This might be tedious, but works using glVertexAttribDivisor to set ofMatrix4x4 to shader mat4 attribute.

Hope this helps!

hi @Akira_At_Asia!

thanks for your help.
i actually managed to figure it out just as you posted! :smile:

here is how i went about it,
i load the extra data into a ofBufferObject, so it is pretty much what you are doing but using more the OF core objects.

i wonder if this is a common enough problem for OF to support in the core somehow? cc @arturo @tgfrerer
the current approach is very tedious…

the below code draws a number of circles.
i work out the matrix transformations prior in the update loop.

shader.begin();

int circleRes = 20;
float circleRadius = 1.0;
ofPolyline circlePolyline;
circlePolyline.arc(0, 0, 0, circleRadius, circleRadius, 0, 360, circleRes);
const vector<ofVec3f> & points = circlePolyline.getVertices();
int numOfPoints = points.size();

vector<ofMatrix4x4> matrices;
int numOfCircles = circles.size();
for(int i=0; i<numOfCircles; i++) {
    matrices.push_back(circles[i]->mat);
}

ofBufferObject buffer;
buffer.allocate();
buffer.setData(sizeof(ofMatrix4x4) * numOfCircles, matrices[0].getPtr(), GL_DYNAMIC_DRAW);

ofVbo vbo;
vbo.setVertexData(&points[0], numOfPoints, GL_DYNAMIC_DRAW);
vbo.bind();

buffer.bind(GL_ARRAY_BUFFER);
for (unsigned int i = 0; i < 4 ; i++) {
    glEnableVertexAttribArray(TRANSFORM_ATTRIBUTE_LOCATION + i);
    glVertexAttribPointer(TRANSFORM_ATTRIBUTE_LOCATION + i, 4, GL_FLOAT, GL_FALSE, sizeof(ofMatrix4x4), (const GLvoid*)(sizeof(GLfloat) * i * 4));
    glVertexAttribDivisor(TRANSFORM_ATTRIBUTE_LOCATION + i, 1);
}
buffer.unbind(GL_ARRAY_BUFFER);

ofSetColor(ofColor::white);
glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, numOfPoints, numOfCircles);

ofSetColor(ofColor::black);
glDrawArraysInstanced(GL_LINE_LOOP, 0, numOfPoints, numOfCircles);

vbo.unbind();

shader.end();

vert shader,

#version 150

uniform mat4 projectionMatrix;
uniform mat4 modelViewMatrix;
uniform mat4 modelViewProjectionMatrix;
uniform vec4 globalColor = vec4(1.0);

in vec4 position;
in vec2 texcoord;
in vec4 color_coord;
in vec3 normal;
in mat4 transform;

out vec4 colorVarying;		// we use flat colors, and, more importantly, flat normals, since we want crisp, flat shaded surfaces.
out vec2 texCoordVarying;

void main()
{
    colorVarying = globalColor;
	gl_Position = projectionMatrix * modelViewMatrix * transform * position;
}

frag shader,

#version 150

in vec4 colorVarying;
in vec2 texCoordVarying;

out vec4 fragColor;

void main(){
	fragColor = colorVarying;
}
1 Like

didn’t knew that you can upload mat4 like that to an attribute, that is ugly : )

we could add one more method setAttributeMatrixData(…) with it’s corresponding updateData, setBuffer and updateBuffer and then have one more parameter in each function to have the posibility of setting a divisor but that seems like too many parameters and methods already. there’s surely more special cases that would require more methods and even worse more parameters per method.

probably the best would be to make the internal ofVbo classes VertexAttribute, IndexAttribute and a new MatrixAttribute public and add to them a divisor. then we could have a setVertexAttribute(…) and setMatrixAttribute method in ofVbo which would allow to pass one of this classes. that way the api is way more flexible and we can add more special cases just by adding methods to the specific attribute classes while keeping ofVbo simple enough

1 Like

@arturo sounds great!

also if this case does make it to the core, please give me a shout to create an example if needed.

I did this using a 1D float SamplerBuffer and sampling into it in the shader using an offset of gl_InstanceID * 16 in the past. It’s superfast.

To me, passing matrices as attributes seems a bit like bending the pipeline.

That said, having a way to specify the glVertexAttribDivisor in ofVbo::VertexAttribute could be super useful! =)

you can also use a shader storage buffer as it’s used in the compute shader particles example but that’s only supported since 4.3 or through the corresponding extension and not in GLES

awesome, thanks for all the tips.
seems like theres many different ways of skinning a cat in glsl land.
i found the gl examples really useful when learning how to use the new programmable pipeline.
more examples like using 1D float textures to upload data would be great!

Hey @julapy, did you see the latest examples James Acres, Arturo and I used for our workshop at the resonate festival? If not, here they are: res15-opengl =)

@tgfrerer uh very cool!
downloading them now.
are all these for 084?

some will probably work on 0.8.4 but we were using the nightly builds

@julapy sorry for the late response. for zippier performance, i ended up breaking up the matrices into their 16 components and sending them over as a texture and then rebuilding the matrix in the vertex shader. it sounds like the approach was very similar to @tgfrerer’s, but tim’s use of SampleBuffers makes more sense. i can share code but, heads up, it’s ugly!!

hi @mantissa, yeah sure if you can post something that would be great!

@julapy, here’s a cleaned-up gist for ya. https://gist.github.com/mantissa/8f390d497b52c601b9ce

Using sample buffers is the modern way to go, but I haven’t made it there yet.

Let me know if you have any questions!

1 Like

just ran the your example @mantissa,
very clear and exactly what i was after!
thanks for putting it together.

@tgfrerer ive been looking through your res15 examples… really great resource!
but can’t seem to find an example of using SamplerBuffer to retrieve ofMatrix4x4 data or something similar… let me know if im just being blind…

cheers!

i don’t think there was nothing like that in the res15 examples. a way to make the uploading of the matrices faster in the example @mantissa posted is to use an ofTexture instead of an ofImage and have a vector of matrices that get updated on the for loop instead of creating them in the loop and copying them to the image. then you can upload the whole thing in one call directly to the texture like:

//setup
tex.allocate(1,matrices.size()*16,GL_RGBA32F);

//update
tex.loadData(matrices[0].getPtr(),1,matrices.size()*16,GL_RGBA)

i’ve just done a small change in ofTexture that allows to use ofBufferObjects as texture buffers easily and uploaded an example to show how to upload matrices using a texture buffer.

in any case uploading a matrix or anything that is not image data in a texture or texture buffer is kind of hacky since a texture only allows image formats. shader storage buffers as demoed in the compute shader examples, allow to pass any type of data to the shader and read it like that type which is more clear than a texture for this kind of usage. the main advantage of using texture buffers is probably that they’ll be supported by older drivers and hardware.

@arturo indeed copying the matrix data as a memory block would be faster again.

i have had a look at the computeShaderParticlesExample and computeShaderTextureExample
but anything that is using openGL 4.3 won’t run on OSX.
OSX only supports up to openGL 4.1 (i think) at the moment.