OpenGL rotation and translation done correctly

CSharpie picture CSharpie · Dec 29, 2012 · Viewed 15.5k times · Source

Im a bit stuck when it comes to rotation and translation in OpenGL.

I got 3 Matrices, projection, view and model.

My VertexShader:

gl_Position =  projection * model * view * vec4(vertexData, 1);

What is the best way to translate and rotate objects?

Either multiply my model matrix with a translation and or rotation matrix, or pass data (rotation and translation) to the shader and to the math there?

Also I need to know "the final object position" for my mousepicking implementation.

What I did so far was something like this:

object.Transformation = Matrix.CreateTransLation(x,y,z) * Matrix.CreateRotation(x,y,z);

...

ForEach object to Draw
{
    modelMatrix.Push();

    modelMatrix.Mult(object.Transformation); // this also updates the matrix for the shader

    object.Draw();

    modelMatrix.Pop();
}

This works, but it doesnt feel right. What the best way to do this?

Answer

datenwolf picture datenwolf · Dec 29, 2012

This

gl_Position =  projection * model * view * vec4(vertexData, 1);

is wrong. Matrix multiplication is not commutative, i.e. the order of operations matters. The transformations on a vertex' position, in order are:

  • model
  • view
  • projection

Matrix multiplication for column vectors as used by OpenGL is left associative, i.e. goes from right to left. Hence the expression in the R side of the statement should be

gl_Position =  projection * view * model * vec4(vertexPosition, 1);

However you can contract view and model transform into a compound modelview (first model, then view) transform. This saves a full matrix multiplication

gl_Position =  projection * modelview * vec4(vertexPosition, 1);

The projection should be kept separate as other shading steps may require the eye space position of the vertex which is the result of modelview * position without projection applied.

BTW: You're transforming the vertex position, not the data. A vertex consists a larger number of attributes (not just the position) hence calling it "Data" is semantically wrong.

What is the best way to translate and rotate objects?

Those are part of the modelview transform. You should create a transformation matrix exactly one time on the CPU and pass it to the GPU. Doing this in the shader would force the GPU to redo the whole calculation for each and every vertex. You don't want to do this.

Update due to comment

Let's say you're using my →linmath.h. Then in your drawing function you'd have set up the scaffolding for your scene, i.e. set the viewport, built projection and view matrices

#include <linmath.h>

/* ... */

void display(void)
{
     mat4x4 projection;
     mat4x4 view;

     glClear(…),
     glViewport(…);

     mat4x4_frustum(projection, …);

     // linmath.h doesn't have a look_at function... yet
     // I'll add it soon
     mat4x4_look_at(view, …);

Then for each object you have a position and a orientation (translation and rotation). Orientations are stored most conveniently in a quaternion, but for processing vectors a matrix representation works better. So we iterate over the objects in the scene

     for(int i_object = 0; i_object < scene->n_objects; i++) {
         Object * const obj = scene->objects + i;

         mat4x4 translation, orientation, model_view;

         mat4x4_translate(translation, obj->pos.x, obj->pos.y, obj->pos.z);
         mat4x4_from_quat(orientation, obj->orientation);
         mat4x4_mul(model_view, translation, orientation);

model_view now contains the model matrix. Next we multiply the view matrix on it. Remember, matrix multiplication is right to left (mat4x4_mul can output onto one of its input operands).

         mat4x4_mul(model_view, view, model_view);

Now model_view contains the full compount model orientation and translation and view matrix. All we need to do now is binding the shader program used for the object

         glUseProgram(obj->shader->program);

Set the uniforms

         glUniformMatrix4f(obj->shader->location.projection, 1, GL_FALSE, projection);
         glUniformMatrix4f(obj->shader->location.modelview,  1, GL_FALSE, model_view);
         // and a few others...

And draw the object

        object_draw(obj);
    }

/* ... */

}