Вращение Куба OpenGL с использованием кватернионов
Я пытался повернуть куб с помощью OpenGL, работающего в полноэкранном окне SDL. Мне удалось сделать это довольно успешно с glRotatef()
, однако я испытал "карданный замок" и другие проблемы, связанные с углами Эйлера. Желая улучшить свою программу, я исследовал кватернионы. Я закодировал класс кватернионов, следуя указаниям на этой странице , и попытался использовать его для вращения моего куба с помощью glMultMatrixf()
, однако куб деформируется при вращении вокруг более чем 1 оси с помощью углы, которые не кратны 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();
}
2 ответа:
Как уже было сказано, вам, вероятно, лучше всего использовать библиотеку, которая уже делает математику за вас.
Проблема здесь в том, что вы поменяли знакиw_x
наmatrix[6]
иmatrix[9]
.Соответствующие строки следует читать так:
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;
Используйте GLM для математики. Кто - то уже хорошо выполнил работу по созданию хорошей векторной библиотеки специально для OpenGL-она настроена на соответствие GLSL для загрузки.
Не используйте
glBegin
илиglVertex*
или кого-либо из их друзей. Они устарели с 3.0 года. Используйте VBO, ваш GPU будет Вам благодарен.Карданного замка можно полностью избежать. Поворачивайте оси так же, как вы поворачиваете объект, чтобы сохранить собственную систему координат (переортогонализация с помощью gram-schmidt every так часто). Это гораздо более интуитивно, особенно для камеры. Проверьте фреймFrenet для хорошей визуальной идеи - прокрутите вниз для группы хороших gif. Столбцы X, Y и Z вашей матрицы вращения-это ваши векторы вправо, вперед и вверх, просто как полезный совет.