【问题标题】:how does GLM handle translationGLM 如何处理翻译
【发布时间】:2020-04-01 00:25:18
【问题描述】:

OpenGL 数学库 (GLM) 使用以下算法来计算平移矩阵:

//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;
}

(这里的向量 v 是一个 3 维向量,矩阵 m 是一个 4X4 矩阵,因为我们使用齐次坐标,所以向量 v 也是 4 维的)。

以下来自线性代数理论:

m 有条目:

现在,假设矩阵 m 给出了一些线性变换,并且也是一个变换矩阵,我们想在 X、Y 和Z维度,如果我没记错的话,我们这样做的方式是形成一个复合矩阵:

这给出了类似的东西:

现在,我不明白 translate 的 GLM 函数是做什么的,因为它的作用类似于:

加上平移变换的矩阵,即m变为:

现在,这两个矩阵不相等,因此它们会导致不同的转换,所以我很困惑哪个矩阵进行实际转换,哪个是正确的,或者算法背后是否隐藏着任何其他想法?

注意:在阅读答案之前,请注意,在矩阵的列优先表示中,您可以使用:matrix[column-index][row-index] 访问矩阵的条目。。 p>

编辑

我执行转换的源代码:

#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;
}

【问题讨论】:

  • @NicolBolas 是否有明确的原因为什么实施不只是翻译每个 x,y,z 值,似乎涉及某种点积? glm 是否假设您将在某个空间进行翻译?谢谢
  • @jutzcode 如果您说要通过向量 (0,1,0) 移动三角形,这是否意味着将模型在模型坐标系中上移 1 个单位,或者在模型坐标系中上移 1 个单位世界坐标系?
  • @juztcode translate 实际上做的是建立一个平移矩阵,并将输入矩阵乘以平移。它计算 m*t 而不是 t*m
  • @Rabbid76,为什么将结果放在R[3]中(在上面的源代码中)?
  • @juztcode GLM 矩阵(作为 OpenGL 矩阵)按列主要顺序存储。 R[3] 是矩阵的第 4 列。

标签: c++ opengl matrix glsl glm-math


【解决方案1】:

OpenGL Mathematics (GLM) 基于OpenGL Shading Language (GLSL)glm::translate 实际上所做的就是建立一个平移矩阵,并将输入矩阵乘以平移。它计算m*t 含义中的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];

(以下Result替换为R

注意,m[0] * v[0] 将列 m[0] 的每个组件乘以标量 v[0]。结果是向量(m[0][0]*v[0], m[0][1]*v[0], m[0][2]*v[0], m[0][3]*v[0])

所以R[3] = m[0]*v[0] + m[1]*v[1] + m[2]*v[2] + m[3] 是一样的

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实际计算:

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 )

上面的代码通过vh计算来自m的行的Dot productvh 是翻译 t 的第 4 列。注意翻译矩阵t定义为:

     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  

4x4 矩阵 (R = m*t) 的串联是Dot product 的行mt 的列,可以表示为: (见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] )

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])m 的第 j 行,
t[i]i-th 列t.

对于glm::translate,从m[0]m[1]m[2] 复制R[0]R[1]R[2] 就足够了。

例如对于(i=0j=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 矩阵(作为 OpenGL 矩阵)按列主要顺序存储。如果您在调试器中调查可能导致混淆的矩阵。

如果你有矩阵

     c0  c1  c2  c3 
-------------------  
r0:  Xx  Yx  Zx  Tx  
r1:  Xy  Yy  Zy  Ty 
r2:  Xz  Yz  Zz  Tz  
r3:   0   0   0   1  

那么 4*4 OpenGL 矩阵的内存图像如下所示:

Xx, Xy, Xz, 0, Yx, Yy, Yz, 0, Zx, Zy, Zz, 0, Tx, Ty, Tz, 1

如果您在调试器中对其进行调查,它可能如下所示:

[ [ Xx, Xy, Xz, 0 ],
  [ Yx, Yy, Yz, 0 ],
  [ Zx, Zy, Zz, 0 ],
  [ Tx, Ty, Tz, 1 ] ]

【讨论】:

【解决方案2】:

关于如何完成数学运算的技术细节在@Rabbid76 的回答中得到了详尽的说明,但如果有人想了解为什么计算m*t 而不是t*m,那么答案如下: 像这样计算矩阵tm

在这里,您将标准基作为线性组合的基向量,因此,本质上您是在世界空间坐标中进行变换。 但是

反过来计算mt 意味着现在您基本上将基础分别作为m[0]m[1]m[2],因此您正在本地空间中进行转换 由基给定,由于这本质上是一个模型矩阵,所以我们称之为 模型空间

如果您只考虑翻译,这可能是一种查看方式,但如果您正在处理如下复合转换怎么办:

M=glm::translate(M,T);
R=glm::rotate(M,angle,Rot_axis);  

这里的模型矩阵是M(首先初始化为恒等式),T是平移矩阵,R是旋转矩阵,其他的都在上面直截了当。

所以上面代码中发生的转换顺序是:
M.T.R
并说这应用于向量v=[x, y, z, 1],向量首先经过旋转,然后是平移,然后只完成模型转换,如果有帮助,您可能会看到这样:
M.(T.R.v)

【讨论】:

  • @Rabbid76 ,如果你有时间请看一下我的回答,专业的眼光可能会发现我的错误。 ;-)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-02-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-08-02
相关资源
最近更新 更多