【问题标题】:OpenGL cube rotation using quaternions使用四元数的OpenGL立方体旋转
【发布时间】:2013-07-03 10:57:33
【问题描述】:

我一直在尝试使用在 SDL 全屏窗口中运行的 OpenGL 旋转立方体。我用glRotatef() 成功地做到了这一点,但是我遇到了“万向节锁定”和其他与欧拉角相关的问题。为了改进我的程序,我研究了四元数。我按照this page 上的指示编写了一个四元数类,并尝试使用它来使用glMultMatrixf() 旋转我的立方体,但是当围绕一个以上的轴旋转时,立方体会扭曲,角度不是90 度的倍数。我检查了我的四元数到矩阵的转换和我的四元数乘法代码,但我找不到任何错误。

这是问题的照片:

这是显示这些立方体的完整程序(需要 SDL 和 OpenGL)

//==============================================================================
#include <cmath>

#include <SDL/SDL.h>
#include <SDL/SDL_opengl.h>

namespace game_lib
{
    struct vec3
    {
        vec3() : x(0), y(0), z(0) { }
        vec3(float x, float y, float z) : x(x), y(y), z(z) { }

        vec3 normalize();
        inline float lenSqr() { return x*x + y*y + z*z; }
        float len();

        inline vec3 operator+(vec3 v) { v.x += x; v.y += y; v.z += z; return v; }
        inline vec3 operator-(vec3 v) { v.x = x - v.x; v.y = y - v.y; v.z = z - v.z; return v; }
        inline vec3 operator*(float f) { return vec3(f*x, f*y, f*z); }
        inline vec3 operator/(float f) { return vec3(x/f, y/f, z/f); }


        bool operator==(vec3 v);

        float x, y, z;

        enum faces { FRONT, BACK, LEFT, RIGHT, TOP, BOTTOM };
    };
    inline vec3 operator*(float f, vec3 v) { return v*f; }

    struct quaternion
    {
        quaternion() : w(1), x(0), y(0), z(0) { }
        quaternion(float w, float x, float y, float z) : w(w), x(x), y(y), z(z) { }
        quaternion(float angle, vec3 axis);

        quaternion normalize();
        inline float lenSqr() { return w*w + x*x + y*y + z*z; }
        float len();

        quaternion operator*(quaternion);

        float w, x, y, z;
    };

    void DrawGLCuboid(vec3 centre, vec3 dimensions, quaternion rotation, const vec3 colours[6]);
}

const int EPSILON = 0.001;

inline bool feq(float f1, float f2)
{
    const float diff = f1 - f2;
    return (diff > -EPSILON) && (diff < EPSILON);
}

//{=================== vec3 methods =================
game_lib::vec3 game_lib::vec3::normalize()
{
    const float lengthSqr = lenSqr();
    if (lengthSqr > 1-EPSILON*EPSILON and lengthSqr < 1+EPSILON*EPSILON) // Optimisation to not re-normalize a normalized vector
        return *this;
    const float length = std::sqrt(lengthSqr);
    return game_lib::vec3(x/length, y/length, z/length);
}

float game_lib::vec3::len() { return std::sqrt(lenSqr()); }

bool game_lib::vec3::operator==(vec3 v) { return feq(v.x,x) and feq(v.y, y) and feq(v.z, z); }
//}==================================================


//{================ quaternion methods ==============
game_lib::quaternion::quaternion(float angle, vec3 axis)
{
    const vec3 axisN = axis.normalize();
    const float sin_a_2 = std::sin(angle*M_PI/360);
    w = std::cos(angle*M_PI/360);
    x = axisN.x*sin_a_2;
    y = axisN.y*sin_a_2;
    z = axisN.z*sin_a_2;
}

game_lib::quaternion game_lib::quaternion::normalize()
{
    const float lengthSqr = lenSqr();
    if (lengthSqr > 1-EPSILON*EPSILON and lengthSqr < 1+EPSILON*EPSILON) // Optimisation to not re-normalize a normalized quaternion
        return *this;
    const float length = std::sqrt(lengthSqr);
    return game_lib::quaternion(w/length, x/length, y/length, z/length);
}

float game_lib::quaternion::len() { return std::sqrt(lenSqr()); }

game_lib::quaternion game_lib::quaternion::operator*(game_lib::quaternion q)
{
    return game_lib::quaternion(w*q.w - x*q.x - y*q.y - z*q.z, w*q.x + x*q.w + y*q.z - z*q.y, w*q.y - x*q.z + y*q.w + z*q.x, w*q.z + x*q.y - y*q.x + z*q.w);
}
//}==================================================


void game_lib::DrawGLCuboid(vec3 cen, vec3 dim, quaternion rot, const vec3 col[6])
{
    glPushMatrix();
    glTranslatef(cen.x, cen.y, cen.z);
    vec3 dim_2 = 1/2*dim;
    const quaternion r_norm = rot.normalize();

    // Quaternion to matrix
    const float x_x = r_norm.x*r_norm.x,   y_y = r_norm.y*r_norm.y,   z_z = r_norm.z*r_norm.z;
    const float w_x = r_norm.w*r_norm.x,   w_y = r_norm.w*r_norm.y,   w_z = r_norm.w*r_norm.z;
    const float x_y = r_norm.x*r_norm.y,   x_z = r_norm.x*r_norm.z,   y_z = r_norm.y*r_norm.z;

    GLfloat matrix[16];

    // Column 1                  // Column 2                  // Column 3                  // Column 4
    matrix[0] = 1-2*(y_y+z_z);   matrix[4] = 2*(x_y-w_z);     matrix[8] = 2*(x_z+w_y);     matrix[12] = 0;
    matrix[1] = 2*(x_y+w_z);     matrix[5] = 1-2*(x_x+z_z);   matrix[9] = 2*(y_z+w_x);     matrix[13] = 0;
    matrix[2] = 2*(x_z-w_y);     matrix[6] = 2*(y_z-w_x);     matrix[10] = 1-2*(x_x+y_y);  matrix[14] = 0;
    matrix[3] = 0;               matrix[7] = 0;               matrix[11] = 0;              matrix[15] = 1;

    /* From http://www.cprogramming.com/tutorial/3d/quaternions.html
    1-2y2-2z2   2xy-2wz     2xz+2wy     0

    2xy+2wz     1-2x2-2z2   2yz+2wx     0

    2xz-2wy     2yz-2wx     1-2x2-2y2   0

    0           0           0           1
    */

    glMultMatrixf(matrix);

    glBegin(GL_QUADS);
        int i = vec3::FRONT;
        glColor3f(col[i].x, col[i].y, col[i].z);

        glVertex3f(-1, 1, 1);
        glVertex3f(1, 1, 1);
        glVertex3f(1, -1, 1);
        glVertex3f(-1, -1, 1);


        i = vec3::BACK;
        glColor3f(col[i].x, col[i].y, col[i].z);

        glVertex3f(-1, 1, -1);
        glVertex3f(1, 1, -1);
        glVertex3f(1, -1, -1);
        glVertex3f(-1, -1, -1);


        i = vec3::LEFT;
        glColor3f(col[i].x, col[i].y, col[i].z);

        glVertex3f(-1, 1, 1);
        glVertex3f(-1, -1, 1);
        glVertex3f(-1, -1, -1);
        glVertex3f(-1, 1, -1);


        i = vec3::RIGHT;
        glColor3f(col[i].x, col[i].y, col[i].z);

        glVertex3f(1, 1, 1);
        glVertex3f(1, -1, 1);
        glVertex3f(1, -1, -1);
        glVertex3f(1, 1, -1);


        i = vec3::BOTTOM;
        glColor3f(col[i].x, col[i].y, col[i].z);

        glVertex3f(-1, -1, 1);
        glVertex3f(1, -1, 1);
        glVertex3f(1, -1, -1);
        glVertex3f(-1, -1, -1);


        i = vec3::TOP;
        glColor3f(col[i].x, col[i].y, col[i].z);

        glVertex3f(-1, 1, 1);
        glVertex3f(1, 1, 1);
        glVertex3f(1, 1, -1);
        glVertex3f(-1, 1, -1);

        glColor3f(1, 1, 1);

        // Following three quads are axes to help determine rotational correctness...
        // x-axis
        glVertex3f(-2, 0.05, 0.05);
        glVertex3f(2, 0.05, 0.05);
        glVertex3f(2, -0.05, -0.05);
        glVertex3f(-2, -0.05, -0.05);

        // y-axis
        glVertex3f(0.05, -2, 0.05);
        glVertex3f(0.05, 2, 0.05);
        glVertex3f(-0.05, 2, -0.05);
        glVertex3f(-0.05, -2, -0.05);

        // z-axis
        glVertex3f(0.05, 0.05, -2);
        glVertex3f(0.05, 0.05, 2);
        glVertex3f(-0.05, -0.05, 2);
        glVertex3f(-0.05, -0.05, -2);
    glEnd();
    glPopMatrix();
}

using namespace game_lib;

struct SDL_Surface;
union SDL_Event;

class CApp {
    private:
        bool m_running, m_init;
        SDL_Surface* m_screen;
        float depth;

    public:
        CApp();
        ~CApp();
        bool init();
        int execute();
        void cleanup();

    private:
        //void processEvent(SDL_Event* Event); // Usually I have this, but it's big and irrelevant (
        void render();
};


//==============================================================================
CApp::CApp()
: m_running(true), m_init(false), m_screen(NULL), depth(-6) { }

CApp::~CApp()
{
    if (m_init) cleanup();
}

//------------------------------------------------------------------------------

bool CApp::init()
{
    if(SDL_Init(SDL_INIT_EVERYTHING) < 0)
        return false;


    SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);

    SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
    SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
    SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
    SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8);

    SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16);
    SDL_GL_SetAttribute(SDL_GL_BUFFER_SIZE, 32);

    SDL_GL_SetAttribute(SDL_GL_ACCUM_RED_SIZE, 8);
    SDL_GL_SetAttribute(SDL_GL_ACCUM_GREEN_SIZE, 8);
    SDL_GL_SetAttribute(SDL_GL_ACCUM_BLUE_SIZE, 8);
    SDL_GL_SetAttribute(SDL_GL_ACCUM_ALPHA_SIZE, 8);

    SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
    SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 2);

    const SDL_VideoInfo* inf = SDL_GetVideoInfo();

    if((m_screen = SDL_SetVideoMode(inf->current_w, inf->current_h, 0, SDL_OPENGL | SDL_FULLSCREEN)) == NULL)
        return false;

    glClearColor(0, 0, 0, 0);

    glClearDepth(1);
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LEQUAL);

    glViewport(0, 0, inf->current_w, inf->current_h);

    glMatrixMode(GL_PROJECTION); // Camera space
    glLoadIdentity();

    gluPerspective(45.0f, 1024.0f/600.0f, 0.1f, 100.0f);

    glEnable(GL_TEXTURE_2D);
    glMatrixMode(GL_MODELVIEW); // Model space
    glLoadIdentity();

    m_init = true;
    return true;
}

int CApp::execute()
{
    if(init() == false)
        return -1;
    SDL_Event event;
    while(m_running)
    {
        //while(SDL_PollEvent(&event))
            // process events removed to save space
        render();
        SDL_Delay(10);
    }
    cleanup();
    return 0;
}

void CApp::cleanup()
{
    if (m_screen)
    {
        SDL_FreeSurface(m_screen);
        m_screen = NULL;
    }
    SDL_Quit();
    m_init = false;
}

void CApp::render()
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glLoadIdentity();

    glTranslatef(0.0f, 0.0f, depth);

    //glRotatef(63, 0, 1, 0); // How I used to do rotation
    //glRotatef(47, 1, 0, 0);

    const vec3 c1colours[] = {vec3(1, 0, 0), vec3(0, 1, 0), vec3(1, 0.2, 1), vec3(0, 0, 1), vec3(1, 1, 0), vec3(1, 0.5, 1)};
    const vec3 c2colours[] = {vec3(1, 0, 0), vec3(0, 1, 1), vec3(1, 1, 0), vec3(0, 1, 0), vec3(1, 0.5, 0), vec3(0.5, 0, 0)};

    // New rotation method, but doesn't work...
    const quaternion c1Rotation = quaternion(63, vec3(0, 1, 0)) * quaternion(47, vec3(1, 0, 0));

    DrawGLCuboid(vec3(-2, 0, -2), vec3(2, 2, 2), c1Rotation, c1colours);
    DrawGLCuboid(vec3(2.5, 0.3, -1.2), vec3(2, 2, 2), quaternion(72, vec3(0, 0, 1)), c2colours);

    SDL_GL_SwapBuffers();
}




int main(int argc, char* argv[])
{
    CApp theApp;
    return theApp.execute();
}

【问题讨论】:

  • 你确实意识到 OpenGL 带有一个(嗯,有一个~官方~)数学库?它被称为glm。
  • 那是很多代码。看起来你正在用两个四元数的乘积绘制左边的立方体,而右边的立方体只有一个四元数。左边的看起来很乱,但右边的看起来还不错。是这样吗?
  • @GraphicsMuncher:是的。左边的立方体显示旋转是如何不起作用的,右边的立方体(正确绘制)在那里显示一次围绕 1 个轴的旋转是有效的。我会在我的问题中包含这个。

标签: c++ opengl rotation quaternions sdl-opengl


【解决方案1】:

如前所述,您最好使用已经为您计算过的库。

这里的问题是您在matrix[6] 和matrix[9] 上用w_x 交换了符号。

相关行应如此解读:

matrix[1] = 2*(x_y+w_z);     matrix[5] = 1-2*(x_x+z_z);   matrix[9] = 2*(y_z-w_x);     matrix[13] = 0;
matrix[2] = 2*(x_z-w_y);     matrix[6] = 2*(y_z+w_x);     matrix[10] = 1-2*(x_x+y_y);  matrix[14] = 0;

【讨论】:

  • 是的,这行得通。我得告诉辅导员here。
  • +1 用于实际发现错误并使其成为您应该使用库的一个很好的例子。另外,thusly.这个词的使用也很巧妙
【解决方案2】:
  1. 使用 GLM 进行数学运算。有人已经很好地完成了专门为 OpenGL 制作一个漂亮的矢量库的繁重工作——它被设置为匹配 GLSL 以启动。

  2. 不要使用glBegin 或glVertex* 或他们的任何朋友。自 3.0 以来,它们已被弃用。使用 VBO,您的 GPU 会感谢您。

  3. 云台锁是完全可以避免的。在旋转对象时旋转坐标轴以保持原生坐标系(每隔一段时间用 gram-schmidt 重新正交化)。这更加直观,尤其是对于相机而言。查看Frenet frame for a good visual idea - 向下滚动查看一堆精美的 GIF。旋转矩阵的 X、Y 和 Z 列是您的右、前和上向量,作为一个有用的提示。

【讨论】:

  • 如何旋转我的轴?我想这就是我之前想要做的。我设法用glRotatef(angle, 0, 1, 0); glRotatef(angle2, std::cos(angle*M_PI/180), 0, std::sin(angle*M_PI/180)); 让它围绕x 轴和y 轴旋转,这是我通过在纸上画几个立方体想出的,但我无法计算出带有z 轴旋转的方程。添加 z 轴使其过于复杂,无法凭经验计算。
  • 另外,如果通过直观的系统可以轻松避免云台锁定,为什么每个人都说四元数或矩阵是解决云台锁定的唯一方法?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-06-23
  • 2013-03-13
  • 1970-01-01
  • 2023-04-06
相关资源
最近更新 更多