how does GLM handle translation

两盒软妹~` 提交于 2020-01-02 19:23:46

问题


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?

I'd really appreciate any help. Thanks in advance.

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]

EDIT: 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;
}

回答1:


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 ] ]



回答2:


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.

You can verify that this is true by doing a rotation and then a translation, which will move the primitive relative to it's model space.



来源:https://stackoverflow.com/questions/59222806/how-does-glm-handle-translation

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!