使用 GLM(旋转)为 OpenGL 创建变换矩阵

Posted

技术标签:

【中文标题】使用 GLM(旋转)为 OpenGL 创建变换矩阵【英文标题】:Creating a transformation matrix for OpenGL with GLM (Rotations) 【发布时间】:2020-04-24 05:34:00 【问题描述】:

原问题:

问题

我有一个unit cube,我想将其转换为连接两个点。我是 OpenGL 新手,只知道线性代数的最基本部分。我试图模仿类似于极坐标的东西来连接这些点。当 Z 和另一个轴发生变化时,我当前的实现不起作用。我也试过mat = glm::lookAt(center, terminal, y_axis);,但没有成功。

代码

这来自位于 schedule_edge_update() 的 for 循环体。

auto const initial = p1;
auto const terminal = p2;
auto const distance = glm::distance(initial, terminal);
auto const length = distance * 0.5f;
auto const center = (initial + terminal) / 2.f;
auto const rejection = terminal - initial;
auto const delta = glm::normalize(rejection);

auto mat = glm::mat4(1);

// translate
mat = glm::translate(mat, center);

// rotate
auto const phi_hyp = glm::length(glm::vec2(delta.x, delta.z));
if (phi_hyp != 0.0f) 
    auto phi = acosf(delta.x / phi_hyp);
    mat = glm::rotate(mat, phi, y_axis);


auto const theta_hyp = glm::length(glm::vec2(delta.x, delta.y));
if (theta_hyp != 0.0f) 
    auto theta = acosf(delta.x / theta_hyp);
    theta *= delta.x > 0 ? -1.0f : 1.0f;
    mat = glm::rotate(mat, theta, z_axis);


// scale
edges->add_matrix(glm::scale(mat, glm::vec3(length, 0.05f, 0.01f)));

当矩阵被添加到edges 时,它会排队等待缓冲以进行实例渲染。

远方

这是我的测试点和我制作的一个大立方体。

特写

这是一个不工作的例子。起点标记为 p1,终点标记为 p2。不连接任何点的线应该连接 p1 和 p2。

不同的特写

这是另一个例子,但是这个例子标有 p1 和 p2 的坐标。 p1 和 p2 的区别在于 Y 和 Z 的变化。但是,我的代码将立方体(在平移后)围绕 y 轴旋转 90 度。然后是缩放它。您可以说它是旋转的,因为它在一个轴(旋转前的 y 轴)上更宽。

坐标的完整列表

// Test points
auto const A = glm::vec3(-10.0f, -10.0f, -20.0f);
auto const B = glm::vec3(+10.0f, -10.0f, -20.0f);
auto const C = glm::vec3(+10.0f, +10.0f, -20.0f);
auto const D = glm::vec3(+00.0f, +10.0f, -20.0f);
auto const E = glm::vec3(+05.0f, +05.0f, -20.0f);
auto const F = glm::vec3(+00.0f, +00.0f, -30.0f);
auto const G = glm::vec3(-10.0f, -10.0f, -30.0f);
auto const H = glm::vec3(+55.0f, -15.0f, -60.0f);
auto const I = glm::vec3(+55.0f, -05.0f, -70.0f);

get_nodes().emplace_back(A);
get_nodes().emplace_back(B);
get_nodes().emplace_back(C);
get_nodes().emplace_back(D);
get_nodes().emplace_back(E);
get_nodes().emplace_back(F);
get_nodes().emplace_back(G);
get_nodes().emplace_back(H);
get_nodes().emplace_back(I);

get_edges().emplace_back(A, B);
get_edges().emplace_back(B, C);
get_edges().emplace_back(C, D);
get_edges().emplace_back(D, E);
get_edges().emplace_back(E, F);
get_edges().emplace_back(F, G);
get_edges().emplace_back(G, H);
get_edges().emplace_back(H, I);

// Big cube
auto const C0 = glm::vec3(-5.0f, -5.0f, -5.0f);
auto const C1 = glm::vec3(-5.0f, -5.0f, +5.0f);
auto const C2 = glm::vec3(-5.0f, +5.0f, -5.0f);
auto const C3 = glm::vec3(-5.0f, +5.0f, +5.0f);
auto const C4 = glm::vec3(+5.0f, -5.0f, -5.0f);
auto const C5 = glm::vec3(+5.0f, -5.0f, +5.0f);
auto const C6 = glm::vec3(+5.0f, +5.0f, -5.0f);
auto const C7 = glm::vec3(+5.0f, +5.0f, +5.0f);

get_nodes().emplace_back(C0);
get_nodes().emplace_back(C1);
get_nodes().emplace_back(C2);
get_nodes().emplace_back(C3);
get_nodes().emplace_back(C4);
get_nodes().emplace_back(C5);
get_nodes().emplace_back(C6);
get_nodes().emplace_back(C7);

get_edges().emplace_back(C0, C1);
get_edges().emplace_back(C0, C2);
get_edges().emplace_back(C0, C4);
get_edges().emplace_back(C1, C3);
get_edges().emplace_back(C1, C5);
get_edges().emplace_back(C2, C3);
get_edges().emplace_back(C2, C6);
get_edges().emplace_back(C3, C7);
get_edges().emplace_back(C4, C5);
get_edges().emplace_back(C4, C6);
get_edges().emplace_back(C5, C7);
get_edges().emplace_back(C6, C7);

schedule_node_update();
schedule_edge_update();

Spektre 使用 GLM 的解决方案

代码

auto constexpr A = vec3(-0.5f, 0.0f, 0.0f);
auto constexpr B = vec3(+0.5f, 0.0f, 0.0f);
auto const C = p1;
auto const D = p2;

auto M = mat4(1.0f);

// Translate
auto const center = 0.5 * (C + D);
M = translate(M, center);

// Rotate
auto constexpr p = B - A;
auto const q = D - C;
auto const n = cross(p, q);
if (n != vec3()) 
    auto const a = angle(normalize(p), normalize(q));
    M = rotate(M, a, n);


// Scale
auto constexpr thickness = 0.05f;
M = scale(M, vec3(0.5f * distance(C, D), thickness, thickness));

edges->add_matrix(M);

成功的结果

【问题讨论】:

不清楚你想达到什么目标!通过矩阵连接 2 个点是什么意思?哪些点?您是否正在构建类似于海龟图形的立方体?矩阵不加入任何它可以定位、定向和缩放某些几何图形的东西。我在您的示例中没有看到相关代码没有渲染我没有看到几何我没有看到控制代码(这可以解释你想要做什么)。请稍微编辑您的问题以使其更清楚...... @Spektre 我正在尝试从两个节点之间的原点移动一个单位立方体。然后旋转和缩放它以连接节点。 imgur.com/a/U2nxItB我可以链接repo,但是控制代码非常大。立方体和球体通过 .obj 文件加载。 所以您希望立方体的对角线在 2 点上开始/结束?你知道立方体的中心、大小、方向(最好是两个对角点的位置)吗?还有你想要立方体击中的 2 点? 所以轴对齐立方体为 1。以(0,0,0) 为中心,边长为12 或2。(0,0,0) 是一个对角点,(1,1,1) 是另一个?您的图片建议使用后者。 我设法完成编辑,请参阅我的答案。 【参考方案1】:

所以问题归结为:

我知道 4 个点 A,B,C,D,我想计算将 A,B 转换为 C,D 的变换矩阵。

可以这样完成。假设我们像这样转换点:

M * A = C
M * B = D

M 是我们要计算的变换矩阵。有无数种可能的解决方案(因为线 AB 可以在自己的轴上进行任何旋转)

如果您dissect the M a bit 只是知道位置、方向和比例的问题。

    规模是最简单的

    它只是变换前后线长的比例。

    scale = |CD|/|AB|
    

    方向

    它由单位基向量表示。我们可以利用 AB 和 CD 只有一次旋转这一事实(所有其他的只是产生无限数量的解)所以我们可以将 AB 旋转 AB,CD 围绕垂直于两者的轴之间的角度 @ 987654338@,CD。我们可以通过平行于AB,CD的单位向量之间的点积的acos得到角度。唯一的问题是它不会给我们旋转方向,所以我们需要测试两种可能性(CW,CCW)。

    所以:

     axis  = cross(B-A,D-C)
     angle = +/- acos(dot(B-A,D-C) / |B-A|*|D-C|)
    

    翻译

    这很简单,我们只需将A 转换为M 而不进行翻译,我们称之为A',然后将结果位置更正,使其变为C

    M_origin += C-A'
    

    注意应该直接设置翻译,而不是应用翻译矩阵。那些通常在局部坐标系[LCS] 中转换,这涉及首先将差异转换为它。在这种情况下使用

    translate(Inverse(M)*(C-A'))
    

    translate(M*(C-A'))
    

    取决于使用的符号。

这里是C++/VCL/old GL小例子:

//---------------------------------------------------------------------------
#include <vcl.h>
#include <math.h>
#pragma hdrstop
#include "Unit1.h"
#include "gl_simple.h"
#include "OpenGLrep4d_double.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
double arot=0.0;                // just animation angle
//---------------------------------------------------------------------------
const int pnts=8;
double pnt[pnts*3]=             // Vertexes for 10x10x10 cube centered at (0,0,0)
    
    -5.0,-5.0,-5.0,
    -5.0,+5.0,-5.0,
    +5.0,+5.0,-5.0,
    +5.0,-5.0,-5.0,
    -5.0,-5.0,+5.0,
    -5.0,+5.0,+5.0,
    +5.0,+5.0,+5.0,
    +5.0,-5.0,+5.0,
    ;
const int lins=12;
int lin[lins*2]=                // lines (index of point used) no winding rule
    
    0,1,1,2,2,3,3,0,
    4,5,5,6,6,7,7,4,
    0,4,1,5,2,6,3,7,
    ;
double A[3]=-5.0,-5.0,-5.0;   // cube diagonal
double B[3]=+5.0,+5.0,+5.0;
double C[3]=-4.5, 2.0, 0.0;   // wanted cube diagonal
double D[3]=+4.5, 5.0, 0.0;
double M[16];                   // our transform matrix
//---------------------------------------------------------------------------
void compute_M()
    
    double scale,p[3],q[3],n[3],a;
    const double deg=180.0/M_PI;
    const double rad=M_PI/180.0;
    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();

    // scale
    vector_sub(p,B,A);                      // p=B-A
    vector_sub(q,D,C);                      // q=D-C
    scale=vector_len(q)/vector_len(p);      //  =|q|/|p|

    // rotation between AB and CD
    vector_mul(n,p,q);                      // n = (p x q) ... cross product
    vector_one(p,p);                        // p = p/|p|
    vector_one(q,q);                        // q = q/|q|
    a=acos(vector_mul(p,q));                // angle between AB and CD in [rad]

    glLoadIdentity();                       // unit matrix
    glRotated(+a*deg,n[0],n[1],n[2]);       // rotate by angle around normal to AB,CD
    glScaled(scale,scale,scale);            // apply scale
    glGetDoublev(GL_MODELVIEW_MATRIX,M);    // get the M from OpenGL

    // translation
    matrix_mul_vector(p,M,A);               // p = M*A
    vector_sub(p,C,p);                      // p = C-p
    M[12]=p[0];
    M[13]=p[1];
    M[14]=p[2];
    M[15]=1.0;

    // verify
    matrix_mul_vector(p,M,B);               // p = M*B
    vector_sub(p,p,D);                      // p = p-C
    if (vector_len(p)>1e-3)                 // if |p| too big use other direction to rotate
        
        glLoadIdentity();                       // unit matrix
        glRotated(-a*deg,n[0],n[1],n[2]);       // rotate by angle around normal to AB,CD
        glScaled(scale,scale,scale);            // apply scale
        glGetDoublev(GL_MODELVIEW_MATRIX,M);    // get the M from OpenGL
        

    glPopMatrix();
    
//---------------------------------------------------------------------------
void gl_draw()      // main rendering code
    
    int i;
    double m0[16],m1[16],m[16],x[3],y[3],z[3],t2[3][3];

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glDisable(GL_CULL_FACE);
    glEnable(GL_DEPTH_TEST);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glTranslated(0.0,0.0,-50.0);
    glRotated(15.0,1.0,0.0,0.0);
    glRotated(arot,0.0,1.0,0.0);

    glBegin(GL_LINES);
    glColor3f(1.0,0.0,0.0); for (i=0;i<lins*2;i++) glVertex3dv(pnt+(lin[i]*3)); // render original cube
    glColor3f(0.0,1.0,0.0); glVertex3dv(A); glVertex3dv(B);                     // render original diagonal AB
    glColor3f(1.0,1.0,0.0); glVertex3dv(C); glVertex3dv(D);                     // render wanted diagonal CD
    glEnd();

    // render transformed cube
    glMatrixMode(GL_MODELVIEW);
    glMultMatrixd(M);
    glBegin(GL_LINES);
    glColor3f(0.0,0.0,1.0); for (i=0;i<lins*2;i++) glVertex3dv(pnt+(lin[i]*3)); // render transformed cube
    glEnd();


    glFlush();
    SwapBuffers(hdc);
    
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner):TForm(Owner)
    
    // application init
    gl_init(Handle);
    compute_M();
    
//---------------------------------------------------------------------------
void __fastcall TForm1::FormDestroy(TObject *Sender)
    
    // application exit
    gl_exit();
    
//---------------------------------------------------------------------------
void __fastcall TForm1::FormResize(TObject *Sender)
    
    // window resize
    gl_resize(ClientWidth,ClientHeight);
    
//---------------------------------------------------------------------------
void __fastcall TForm1::FormPaint(TObject *Sender)
    
    // window repaint
    gl_draw();
    
//---------------------------------------------------------------------------
void __fastcall TForm1::Timer1Timer(TObject *Sender)
    
    arot+=1.5; if (arot>=360.0) arot-=360.0;
    gl_draw();
    
//---------------------------------------------------------------------------

忽略 VCL 相关的东西。您可以在此处找到 GL 支持功能:

complete GL+GLSL+VAO/VBO C++ example

这里唯一重要的是 compute_M() 以及全局变量。

如果您需要实现,您可以在上面链接的 QA 中找到向量数学函数(因此您可以将其转换为 GLM)。基本上需要。为简单起见,我使用了 GL 原生旋转(注意它们是度数而不是弧度)。

这里预览:

red 是原始立方体 green 是原始对角线 AB blueM 转换成立方体 yellow 需要对角线 CD

你可以看到它匹配。

如果您需要对齐的不仅仅是一条线,您还需要添加更多对齐信息(例如 2 条线(3 点)等)。有关更多信息,请参阅:

Problem superimposing and aligning 3D triangles

【讨论】:

以上是关于使用 GLM(旋转)为 OpenGL 创建变换矩阵的主要内容,如果未能解决你的问题,请参考以下文章

旋转矩阵的问题(openGL/glm)[关闭]

如何使用现代 OpenGL 的 GLM 或 JOML 矩阵正确转换(平移、旋转、缩放)对象

现代 OpenGL:VBO、GLM 和矩阵堆栈

opengl旋转的对象不居中

OpenGL/GLSL/GLM - Skybox 像第三人称一样旋转

GLM::Rotate 似乎会导致错误的旋转?