Reputation: 1355
The OpenGL maths library(GLM) uses the following algorithm to compute the translation matrix:
//taken from source code
template<typename T, qualifier Q>
GLM_FUNC_QUALIFIER mat<4, 4, T, Q> translate(mat<4, 4, T, Q> const& m, vec<3, T, Q> const& v)
{
mat<4, 4, T, Q> Result(m);
Result[3] = m[0] * v[0] + m[1] * v[1] + m[2] * v[2] + m[3];
return Result;
}
(Here the vector v is a 3 dimensional vector and the matrix m is a 4X4 matrix, since we're using homogeneous coordinates the vector v is also 4 dimensional).
The following is from Linear Algebra Theory:
Let m have the entries:
Now, suppose the matrix m gives some linear transformation, and is also a transformation matrix, and we'd like to add a translation of X, Y, and Z in the X, Y and Z dimensions respectively, if I'm not mistaken, the way we'd do that is by forming a composite matrix:
which gives something like:
Now, I'm not getting what this GLM function of translate does, because it does something like:
And the matrix with added transformation of translation, i.e. m becomes:
Now, these two matrices are not equal and hence they would result in different transformations, so I'm confused to which matrix does the actual translation and which is the correct one or if there is any other idea hidden behind the algorithm?
Note: Before reading the answer note that in column-major representation of a matrix, you access the entries of your matrix using: matrix[column-index][row-index].
The source code with which I perform transformation:
#include <iostream>
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <cmath>
#include <string.h>
#include "glm/glm.hpp"
#include "glm/gtc/matrix_transform.hpp"
#include "glm/gtc/type_ptr.hpp"
// Window Dimensions
const GLint WIDTH=800, HEIGHT=600;
GLuint VAO, VBO, shader;
GLint uniformModel {};
GLint uniformModelRot {};
GLfloat triOffset {};
float triMaxOffset = 0.7f;
bool direction = true;
const float toRadians = 3.14159265f/180.0f;
// vertex shader
static const char* vShader =
"#version 330\n"
"layout (location = 0) in vec3 pos;\n"
"uniform mat4 model;\n"
"void main(){\n"
" gl_Position = model * vec4(0.5*pos, 1.0);\n"
"}\n";
// fragment shader
static const char* fShader = ""
"#version 330\n"
"out vec4 color;\n"
"uniform mat4 model;\n"
"void main(){\n"
" color = model *vec4(1.0, 1.0, 0.0, 1.0);\n"
"}\n";
void AddShader(GLuint theProgram, const char* ShaderCode, GLenum shaderType, std::string info){
std::cerr <<"INFO: Adding "<<info<<" Shader"<<std::endl;
GLuint theShader = glCreateShader(shaderType);
const GLchar* theCode[1];
theCode[0] = ShaderCode;
GLint codeLength[1];
codeLength[0] = strlen(ShaderCode);
glShaderSource(theShader, 1, theCode, codeLength);
glCompileShader(theShader);
GLint result =0;
GLchar eLog[1024] ={0};
glGetShaderiv(theShader, GL_COMPILE_STATUS, &result);
if(!result){
glGetShaderInfoLog(shader, sizeof(eLog), NULL, eLog);
std::cerr<<"Error compiling program"<<std::endl;
return;
}
glAttachShader(theProgram, theShader);
}
void CompileShader(){
shader = glCreateProgram();
if(!shader){
std::cerr<<"Error creating shader"<<std::endl;
return;
}
AddShader(shader, vShader, GL_VERTEX_SHADER, "vertex");
AddShader(shader, fShader, GL_FRAGMENT_SHADER, "fragment");
GLint result =0;
GLchar eLog[1024] ={0};
glLinkProgram(shader);
glGetProgramiv(shader, GL_LINK_STATUS, &result);
if(!result){
glGetProgramInfoLog(shader, sizeof(eLog), NULL, eLog);
std::cerr<<"Error linking program"<<std::endl;
return;
}
glValidateProgram(shader);
glGetProgramiv(shader, GL_VALIDATE_STATUS, &result);
if(!result){
glGetProgramInfoLog(shader, sizeof(eLog), NULL, eLog);
std::cerr<<"Error Validating program"<<std::endl;
return;
}
uniformModel = glGetUniformLocation(shader,"model");
}
void CreateTriangles(){
GLfloat vertices[]={
-1.0f, -1.0f, 0.0f,
1.0f, -1.0f, 0.0f,
0.0f, 1.0f, 0.0f
};
glGenVertexArrays(1, &VAO);
glBindVertexArray(VAO);
glGenBuffers(1, &VBO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat)*9,vertices, GL_STATIC_DRAW);
glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,0,0);
glEnableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
}
int main(){
//initialize GLFW
if(!glfwInit()){
std::cerr << "GLFW initialization failed!" << std::endl;
glfwTerminate();
return 1;
}
//Setup GLFW window properties
//openGL version
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
// core profile = no backward compatibility
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
//allow forward compatibility
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
GLFWwindow *mainWindow = glfwCreateWindow(WIDTH, HEIGHT, "TEST WINDOW", NULL, NULL);
if(!mainWindow){
std::cerr << "GLFW Window creation failed" << std::endl;
glfwTerminate();
return 1;
}
// get Buffer size information
int bufferWidth, bufferHeight;
glfwGetFramebufferSize(mainWindow, &bufferWidth, &bufferHeight);
// set context for GLEW to use
glfwMakeContextCurrent(mainWindow);
// allow modern extension features
if(glewInit()!=GLEW_OK){
std::cerr << "GLEW initialization failed" << std::endl;
glfwDestroyWindow(mainWindow);
glfwTerminate();
return 1;
}
// setup viewport size
glViewport(0, 0, bufferWidth, bufferHeight);
CreateTriangles();
CompileShader();
while(!glfwWindowShouldClose(mainWindow)){
// get and handle user input events
glfwPollEvents();
glClearColor(1.0f, 0.0f, 0.0f, 1.0);
glClear(GL_COLOR_BUFFER_BIT);
if(direction){
triOffset += 0.05f;
}else{
triOffset -= 0.05f;
}
if(abs(triOffset) >= triMaxOffset){
direction = !direction;
}
glUseProgram(shader);
glm::mat4 modelMatrix(1.0f);
modelMatrix = glm::translate(modelMatrix, glm::vec3(triOffset, 0.0f, 0.0f));
glUniformMatrix4fv(uniformModel, 1, GL_FALSE,glm::value_ptr(modelMatrix));
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES,0,3);
glBindVertexArray(0);
glUseProgram(0);
// swap buffers
glfwSwapBuffers(mainWindow);
}
return 0;
}
Upvotes: 7
Views: 16309
Reputation: 1355
The technical details of as to how the math is done is magnificiently done in @Rabbid76's answer, but if anyone would like to understand why m*t
is computed instead of t*m
then here's the answer:
Computing the matrix tm
like this:
here, you're taking the standard basis as the basis vectors for linear combination, so, essentially you're transforming in world space coordinates. but
doing it the other way around and computing mt
means now you're essentially taking the basis as the m[0]
, m[1]
and m[2]
respectively, so you're transforming in the local space given by the basis, and since this is essentially a model matrix, we just call it model space.
That is probably one way to view it if you're only considering translation, but what if you're handling composite transformations like below:
M=glm::translate(M,T);
R=glm::rotate(M,angle,Rot_axis);
Here the model matrix is M
(initialized to identity at first), T
is the translation matrix, R
the rotation matrix and others are straightforward above.
So the transformation sequence that happens in the above code is:
M.T.R
and say this is applied to the vector v=[x, y, z, 1]
, the vector undergoes first a rotation, then a translation and then only the model transformation is done, if it helps, you may see it like this:
M.(T.R.v)
Upvotes: 1
Reputation: 211166
OpenGL Mathematics (GLM) is based on the OpenGL Shading Language (GLSL). What glm::translate
actually does is to set up a translation matrix and multiply the input matrix by the translation. It computes m*t
in the meaning of GLSL Vector and Matrix Operations:
mat<4, 4, T, Q> Result(m); Result[3] = m[0] * v[0] + m[1] * v[1] + m[2] * v[2] + m[3];
(In the following Result
is substituted by R
)
Note, m[0] * v[0]
multiplies each component of the column m[0]
by the scalar v[0]
. The result is the vector (m[0][0]*v[0], m[0][1]*v[0], m[0][2]*v[0], m[0][3]*v[0])
.
So R[3] = m[0]*v[0] + m[1]*v[1] + m[2]*v[2] + m[3]
is the same as
R[3][0] = m[0][0] * v[0] + m[1][0] * v[1] + m[2][0] * v[2] + m[3][0]
R[3][1] = m[0][1] * v[0] + m[1][1] * v[1] + m[2][1] * v[2] + m[3][1]
R[3][2] = m[0][2] * v[0] + m[1][2] * v[1] + m[2][2] * v[2] + m[3][2]
R[3][3] = m[0][3] * v[0] + m[1][3] * v[1] + m[2][3] * v[2] + m[3][3]
glm::translate
actually calculates:
vh = (v[0], v[1], v[2], 1)
R = m
R[3][0] = dot( (m[0][0], m[1][0], m[2][0], m[3][0]), vh )
R[3][1] = dot( (m[0][1], m[1][1], m[2][1], m[3][1]), vh )
R[3][2] = dot( (m[0][2], m[1][2], m[2][2], m[3][2]), vh )
R[3][3] = dot( (m[0][3], m[1][3], m[2][3], m[3][3]), vh )
The code above computes the Dot product of the rows from m
, by vh
. vh
is the 4th column of the translation t
. Note the translation matrix t
is defined as:
c0 c1 c2 c3
---------------------
r0: 1 0 0 v[0]
r1: 0 1 0 v[1]
r2: 0 0 0 v[2]
r3: 0 0 0 1
A concatenation of 4x4 matrices (R = m*t
) is the Dot product of the rows of m
and the columns of t
and can be expressed as:
(See OpenGL Shading Language 4.60 Specification - 5.10. Vector and Matrix Operations)
for i from 0 to 3
for j fro 0 to 3
R[i][j] = dot( (m[0][j], m[1][j], m[2][j], m[3][j]), t[i] )
Where dot(a, b) == a[0]*b[0] + a[1]*b[1] + a[2]*b[2] + a[3]*b[3]
,
(m[0][j], m[1][j], m[2][j], m[3][j])
is the j-th row of m
and
t[i]
is i-th column of t
.
For glm::translate
it is sufficient to copy R[0]
, R[1]
and R[2]
from m[0]
, m[1]
and m[2]
.
e.g. for (i=0
, j=0
):
R[0][0] = dot( (m[0][0], m[1][0], m[2][0], m[3][0]), t[0] )
R[0][0] = dot( (m[0][0], m[1][0], m[2][0], m[3][0]), (1, 0, 0, 0) )
R[0][0] = m[0][0] * 1 + m[1][0] * 0 + m[2][0] * 0 + m[3][0]) * 0
R[0][0] = m[0][0]
GLM matrices (as OpenGL matrices) are stored in column major order. If you investigate matrices in the debugger that may lead to confusions.
If you have the matrix
c0 c1 c2 c3
-------------------
r0: Xx Yx Zx Tx
r1: Xy Yy Zy Ty
r2: Xz Yz Zz Tz
r3: 0 0 0 1
then the memory image of a 4*4 OpenGL matrix looks like this:
Xx, Xy, Xz, 0, Yx, Yy, Yz, 0, Zx, Zy, Zz, 0, Tx, Ty, Tz, 1
If you investigate it in a debugger, it may look like:
[ [ Xx, Xy, Xz, 0 ],
[ Yx, Yy, Yz, 0 ],
[ Zx, Zy, Zz, 0 ],
[ Tx, Ty, Tz, 1 ] ]
Upvotes: 5