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\$
  • 2
    \$\begingroup\$ Why not use a modelMatrix? \$\endgroup\$ – Bálint Apr 15 '16 at 17:50
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\$
  • \$\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 Apr 16 '16 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 Apr 16 '16 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 Apr 16 '16 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 Apr 18 '16 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 Apr 18 '16 at 14:19

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.