2
\$\begingroup\$

I am working on a game which has a board of falling down blocks, similar to Tetris. Each block is represented with for vertices and two triangles, each vertex consisting out of its location and texture coordinations. I am using glDrawElements to render the blocks.

I would like to interpolate the location data when blocks fall down. I am wondering, what is the most efficient, elegant and easy way to interpolate such vertex data in OpenGL?

I obviously need two location states (x0 y0) and (x1 y1) and a time step t (0 < t < 1), then I can lerp in my vertex shader. How do I pass this information to the shader? For the timestep, uniforms came into my mind. But what about the locations?

  1. Pass both locations to my VBO at any time. This would work, but requires my VBO to be twice as big as it includes both location states, even if they are equal (= the block hasn't moved)! Blocks will move at times, but most of the time, they are static - wouldn't this approach be a wast of memory?
  2. Keep (x0 y0) in the VBO, transmit (x1 y1) via uniforms if necessary (= if the block has moved). This would save memory, but completely destroy my rendering pipeline.
  3. Anything else? Both of the approaches above seem silly to me. How do other games do this?

Thanks!

\$\endgroup\$
1
  • 2
    \$\begingroup\$ Why not use a modelMatrix? \$\endgroup\$
    – Bálint
    Commented Apr 15, 2016 at 17:50

1 Answer 1

3
\$\begingroup\$

I would implement a model matrix as suggested in the comments.

Calculate the new position, (x1 y1):

Vector2f newPosition = interpolatePosition(oldPosition);

Then apply that as a translation to the model matrix:

Matrix4f modelMatrix = new Matrix4f().translate(newPosition);

Then pass the model matrix into the shader as a uniform:

GLuint matrixUniform = glGetUniformLocation(shader, "modelMatrix");
glUniformMatrix4fv(matrixUniform, GL_FALSE, modelMatrix);

Finally, in your shader you can multiply the vertex positions by this model matrix:

in vec2 position;
uniform mat4 modelMatrix;

void main() {
    gl_Position = modelMatrix * vec4(position, 0, 1);
}

Check out the open.gl article on transformations for more details.


How to do the same thing with only a single draw call

Let's say we want to use the same VBO more than once but apply different transforms to each instance. We can utilize uniform buffers and glDrawElementsInstanced.

First lets define two model matrices with different translations:

Matrix4f modelMatrixOne = new Matrix4f().translate(-1, 0, 0);
Matrix4f modelMatrixTwo = new Matrix4f().translate(1, 0, 0);

Now we create the uniform buffer to store both matrices:

int ubo = glGenBuffers();
glBindBuffer(GL_UNIFORM_BUFFER, ubo);

int matrixSize = Float.BYTES * 16;
glBufferData(GL_UNIFORM_BUFFER, matrixSize * 2, GL_DYNAMIC_DRAW);
glBufferSubData(GL_UNIFORM_BUFFER, 0, floatBuffer(modelMatrixOne));
glBufferSubData(GL_UNIFORM_BUFFER, matrixSize, floatBuffer(modelMatrixTwo));

Attach the uniform buffer to a binding point:

int bindingPoint = 0;  // valid range: 0 to GL_MAX_UNIFORM_BUFFER_BINDINGS
glBindBufferBase(GL_UNIFORM_BUFFER, bindingPoint, ubo);

Then associate the shader's uniform block with that binding point:

int blockIndex = glGetUniformBlockIndex(shader, "ModelMatrices");
glUniformBlockBinding(shader, blockIndex, bindingPoint);

Now we update the draw call:

glDrawElementsInstanced(GL_TRIANGLES, elements, GL_UNSIGNED_INT, 0, 2);

This is the same as a normal glDrawElements call with an extra parameter, 2, which specifies how many instances to draw.

Finally we update our vertex shader to use the uniform block we setup and gl_InstanceID:

in vec2 position;
uniform ModelMatrices {
    mat4 modelMatrix[2];
};

void main() {
    gl_Position = modelMatrix[gl_InstanceID] * vec4(position, 0, 1);
}

NOTE: This method requires the shader to know exactly how many matrices will be used. If we're using one matrix per block then it essentially needs to know how many blocks

\$\endgroup\$
5
  • \$\begingroup\$ Thanks for the explanation. Doesn't this mean though I cannot use glDrawElements anymore? I would need to pass the translation matrix for each individual block via uniform and this would mean I cannot draw them in one call. Or am I overseeing something? \$\endgroup\$
    – Frithjof
    Commented Apr 16, 2016 at 10:37
  • \$\begingroup\$ You are correct that one draw call will not work with this method. You will need to transform the model matrix, send it to the shader, then call glDrawElements for each block. \$\endgroup\$
    – Exide
    Commented Apr 16, 2016 at 18:13
  • \$\begingroup\$ It donned on me that you could use glDrawElementsInstanced and the GL_UNIFORM_BUFFER to get a single draw call. I'll see if I can throw together some sample code and update my answer. \$\endgroup\$
    – Exide
    Commented Apr 16, 2016 at 19:55
  • \$\begingroup\$ @Frithjof After I added that last bit about glDrawElementsInstanced you came back and marked this answer unaccepted. Is there something else you need? \$\endgroup\$
    – Exide
    Commented Apr 18, 2016 at 5:51
  • \$\begingroup\$ Oh, I'm sorry about that. I guess I had already accepted it before and therefore accidentally unaccepted it. Thanks for letting me know, excellent work! \$\endgroup\$
    – Frithjof
    Commented Apr 18, 2016 at 14:19

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .