0
$\begingroup$

I was able to write the following working code to draw any number of "sprites" (defined as 2 tringles, 6 vertices)

    Matrix.setIdentityM(mModelMatrix, 0);

    Matrix.multiplyMM(mMVPMatrix, 0, getCamera().mViewMatrix, 0, mModelMatrix, 0);
    GLES20.glUniformMatrix4fv(mShader.getMVMatrixHandle(), 1, false, mMVPMatrix, 0);

    Matrix.multiplyMM(mMVPMatrix, 0, getCamera().projMatrix, 0, mMVPMatrix, 0);
    GLES20.glUniformMatrix4fv(mShader.getMVPMatrixHandle(), 1, false, mMVPMatrix, 0);

    mPositions.position(0);
    mColors.position(0);
    mNormals.position(0);
    mTextureCoordinates.position(0);

    GLES20.glVertexAttribPointer(mShader.getPositionHandle(), mPositionDataSize, GLES20.GL_FLOAT, false, 0, mPositions);
    GLES20.glVertexAttribPointer(mShader.getColorHandle(), mColorDataSize, GLES20.GL_FLOAT, false, 0, mColors);
    GLES20.glVertexAttribPointer(mShader.getNormalHandle(), mNormalDataSize, GLES20.GL_FLOAT, false, 0, mNormals);
    GLES20.glVertexAttribPointer(mShader.getTextureCoordinateHandle(), mTextureCoordinateDataSize, GLES20.GL_FLOAT, false, 0, mTextureCoordinates);

    GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 6*numSprites);

Using a sprite atlas this code displays many different sprite textures in one fell swoop, which is very fast.

However, I would also like to draw multiple copies of a 3D .obj file. Now I've loaded in the necessary FloatBuffers of positions, normals, texture coordinates, etc., so I can easily add these buffers to the mPositions, mTextureCoordinates buffers above, etc., no problem.

But these instances of the .obj model I'm trying to display need to be in different x/y locations.

Is there an efficient way this can be achieved in OpenGL? Maybe one of the following:

  1. I could displace the .obj files _positions buffer before adding to the global mPositions buffer to draw, but this seems rather inefficient.

  2. One solution is to run a separate instance of the above code for each .obj 3D object, including an OpenGL translation for each instance. But I've heard this is costly if you have many 3D objects. (This is the reason I use a sprite atlas for 2D sprites in the above code)

  3. "Another" approach that seems to work is to add the following example code, where _positions etc are the buffers for the `.obj. file, and I draw 5 of the same object file in a row using a translation. It's very similar to (2) but without having to repeat the OpenGL initialisation code every time.

    for (int c1 = 0; c1 < 5; c1++) {
    
        _positions.position(0);
        _normals.position(0);
        _textureCoordinates.position(0);
    
        Matrix.setIdentityM(mModelMatrix, 0);
        Matrix.translateM(mModelMatrix, 0, cx + dx * c1 * 4, cy, 0);
        Matrix.scaleM(mModelMatrix, 0, 0.1f, 0.1f, 0.03f);
        Matrix.rotateM(mModelMatrix, 0, -90, 1, 0, 0);
    
        Matrix.multiplyMM(mMVPMatrix, 0, getCamera().mViewMatrix, 0, mModelMatrix, 0);
    
        GLES20.glUniformMatrix4fv(mShader.getMVMatrixHandle(), 1, false, mMVPMatrix, 0);
        Matrix.multiplyMM(mMVPMatrix, 0, getCamera().projMatrix, 0, mMVPMatrix, 0);
        GLES20.glUniformMatrix4fv(mShader.getMVPMatrixHandle(), 1, false, mMVPMatrix, 0);
        GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, numFaces);
    }
    

But I'm not sure if this isn't just as inefficient as the (2) ?

  1. Maybe there's no way round it? Perhaps (2)/(3) is the only way and I'm just being over cautious because of my "bad" experience without using a sprite atlas for 2D sprites? After all, I was originally drawing 1000's of 2D sprites using method (1) originally, but I will only be drawing around 10-20 3D objects. In the 2D case, switching to a sprite atlas greatly speeded things up !

(I'm interested in OpenGL ES 2.0 primarily)

$\endgroup$

1 Answer 1

2
$\begingroup$

What you are looking for here is instanced rendering. Instanced rendering coupled with attribute divisors is a very powerful tool to render multiple copies of the same object using only one drawing call.

Using instanced rendering you only have to specify the geometry of an object once on the GPU. Then, you could pass along an additional buffer of matrices, one matrix for each instance of the object you are rendering. Using attribute divisors you can tell the GPU that you will only need one single matrix per a number of vertices. Attribute divisors tell the GPU when and how to advance an index in a attribute buffer. This allows you to only define a single matrix per instance instead of passing along a single matrix for every vertex in a single model.

Here, is an excellent tutorial on using instanced rendering and attribute divisors.

$\endgroup$
3
  • $\begingroup$ thanks, think this will work in OpenGL ES 3 but not 2. I forgot to mention I'm interested in the latter. Perhaps i may have to switch to OpenGL ES 3 though. $\endgroup$
    – user9851
    Commented Jan 18, 2019 at 20:32
  • $\begingroup$ On the other hand ES 3.1 only covers 50% of the market, so i will probably look for an alternative way. $\endgroup$
    – user9851
    Commented Jan 18, 2019 at 21:00
  • $\begingroup$ Might be possible in ES 2: khronos.org/registry/OpenGL/extensions/NV/NV_draw_instanced.txt $\endgroup$
    – user9851
    Commented Jan 18, 2019 at 22:12

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.