Не используйте углы Эйлера для этого, поскольку у них есть много вопросов, как тот, который вы получили. Вместо этого используйте кумулятивные матрицы преобразования. Похоже, этот вопрос задают снова и снова ... в течение некоторого времени. Поэтому я решил сделать пример, как сделать это с чистым OpenGL 1.0 no GLM или смешные вещи.
Определение
Позволяет иметь игрок управление, в состоянии объекта под названием obj
и камеры eye
. Каждый из них должен быть представлен отдельной матрицей преобразования 4x4
. OpenGL хранит их как 1D-массивы.Для получения дополнительной информации см
Мы хотим контролировать obj
в своей локальной системе координат, независимой от камеры. Если вы используете как матрицы, так и объекты, умноженные вместе в GL_MODELVIEW
, чтобы избежать злоупотребления матрицей GL_PROJECTION
, вы быстро поймете, что это не разрешимо просто glRotate/glTranslate
звонков обычным способом.
Из-за этого многие люди переключаются на углы Эйлера, которые могут справиться с этим легко, но поднимают целую кучу других проблем (многие современные игры все еще используют их там, где они не должны, и есть множество ошибок и проблем из-за Это).
Так добавьте в ваш проект:
GLfloat mobj[16],meye[16];
Используя GL для наших матриц
Это просто просто сделать это:
glMatrixMode(GL_MODELVIEW); // set matrix target we want to work with does not matter really which)
glPushMatrix(); // store original matrix so we do not mess something up
glLoadMatrixf(mobj); // load our matrix into GL
//here do your stuff like glRotatef(10.0,0.0,1.0,0.0);
glGetFloatv(GL_MODELVIEW_MATRIX,mobj); // get our updated matrix from GL
glPopMatrix(); // restore original state
С этим мы можем использовать GL вызывает наши матрицы вне цикла рендеринга. (например, в обработчике клавиатуры или в некотором таймере).
Rendering матрицы петли
Теперь, если мы хотим, чтобы сделать наш объект с нашими матрицами, то мы должны установить матрицы GL правильно. Пусть предполагается, что матрица проекции задана, а только вопрос модели. Видовая матрица должна быть:
GL_MODELVIEW = Inverse(meye) * mobj
Но OpenGL не имеет матрицы обратной функции. Так что это единственное, что нам нужно закодировать. Поскольку матрица всегда 4x4
, тогда это не так сложно.
Я положил все это вместе в этом простом примере GL/C++/VCL:
//---------------------------------------------------------------------------
#include <vcl.h> // you can ignore this
#include <gl/gl.h>
#include <gl/glu.h>
#pragma hdrstop // you can ignore this
#include "Unit1.h" // you can ignore this
//---------------------------------------------------------------------------
// you can ignore this
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
// here some global variables
int xs,ys; // window resolution
HDC hdc; // device context
HGLRC hrc; // rendering context
// 4x4 transform matrices
GLfloat mobj[16]; // object transform matrix
GLfloat meye[16]; // camera transform matrix
// key codes for controling (Arrows + Space)
WORD key_left =37;
WORD key_right=39;
WORD key_up =38;
WORD key_down =40;
WORD key_forw =32;
// key pressed state
bool _left =false;
bool _right=false;
bool _up =false;
bool _down =false;
bool _forw =false;
bool _shift=false;
// sceene need repaint?
bool _redraw=true;
//---------------------------------------------------------------------------
// here inverse matrix computation
GLfloat matrix_subdet ( GLfloat *a,int r,int s)
{
GLfloat c,q[9];
int i,j,k;
k=0; // q = sub matrix
for (j=0;j<4;j++)
if (j!=s)
for (i=0;i<4;i++)
if (i!=r)
{
q[k]=a[i+(j<<2)];
k++;
}
c=0;
c+=q[0]*q[4]*q[8];
c+=q[1]*q[5]*q[6];
c+=q[2]*q[3]*q[7];
c-=q[0]*q[5]*q[7];
c-=q[1]*q[3]*q[8];
c-=q[2]*q[4]*q[6];
if (int((r+s)&1)) c=-c; // add signum
return c;
}
void matrix_subdet (GLfloat *c,GLfloat *a)
{
GLfloat q[16];
int i,j;
for (i=0;i<4;i++)
for (j=0;j<4;j++)
q[j+(i<<2)]=matrix_subdet(a,i,j);
for (i=0;i<16;i++) c[i]=q[i];
}
GLfloat matrix_det ( GLfloat *a)
{
GLfloat c=0;
c+=a[ 0]*matrix_subdet(a,0,0);
c+=a[ 4]*matrix_subdet(a,0,1);
c+=a[ 8]*matrix_subdet(a,0,2);
c+=a[12]*matrix_subdet(a,0,3);
return c;
}
GLfloat matrix_det ( GLfloat *a,GLfloat *b)
{
GLfloat c=0;
c+=a[ 0]*b[ 0];
c+=a[ 4]*b[ 1];
c+=a[ 8]*b[ 2];
c+=a[12]*b[ 3];
return c;
}
void matrix_inv (GLfloat *c,GLfloat *a)
{
GLfloat d[16],D;
matrix_subdet(d,a);
D=matrix_det(a,d);
if (D) D=1.0/D;
for (int i=0;i<16;i++) c[i]=d[i]*D;
}
//---------------------------------------------------------------------------
// here OpenGL stuff
//---------------------------------------------------------------------------
int TForm1::ogl_init()
{
// just init OpenGL
if (ogl_inicialized) return 1;
hdc = GetDC(Form1->Handle); // get device context
PIXELFORMATDESCRIPTOR pfd;
ZeroMemory(&pfd, sizeof(pfd)); // set the pixel format for the DC
pfd.nSize = sizeof(pfd);
pfd.nVersion = 1;
pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
pfd.iPixelType = PFD_TYPE_RGBA;
pfd.cColorBits = 24;
pfd.cDepthBits = 24;
pfd.iLayerType = PFD_MAIN_PLANE;
SetPixelFormat(hdc,ChoosePixelFormat(hdc, &pfd),&pfd);
hrc = wglCreateContext(hdc); // create current rendering context
if(hrc == NULL)
{
ShowMessage("Could not initialize OpenGL Rendering context !!!");
ogl_inicialized=0;
return 0;
}
if(wglMakeCurrent(hdc, hrc) == false)
{
ShowMessage("Could not make current OpenGL Rendering context !!!");
wglDeleteContext(hrc); // destroy rendering context
ogl_inicialized=0;
return 0;
}
ogl_resize();
glEnable(GL_DEPTH_TEST);
glDisable(GL_CULL_FACE);
glDisable(GL_TEXTURE_2D);
glDisable(GL_BLEND);
glShadeModel(GL_SMOOTH);
ogl_inicialized=1;
return 1;
}
//---------------------------------------------------------------------------
void TForm1::ogl_exit()
{
// just exit from OpneGL
if (!ogl_inicialized) return;
wglMakeCurrent(NULL, NULL); // release current rendering context
wglDeleteContext(hrc); // destroy rendering context
ogl_inicialized=0;
}
//---------------------------------------------------------------------------
void TForm1::ogl_draw()
{
// rendering routine
_redraw=false;
// here the whole rendering
glClearColor(0.0f, 0.0f, 0.0f, 1.0f); // background color
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
GLfloat ieye[16]; // inverse camera transform matrix
matrix_inv(ieye,meye);
glMatrixMode(GL_MODELVIEW);
glLoadMatrixf(ieye);
glMultMatrixf(mobj);
// render player controlable object
// centered by (0,0,0)
// +z forward, +x right, +y up
float x=0.5,y=0.1,z=0.7; // half sizes of object
glColor3f(0.7,0.7,0.7);
glBegin(GL_TRIANGLE_FAN);
glVertex3f(0.0,0.0,+z);
glVertex3f(-x,-y,-z);
glVertex3f(+x,-y,-z);
glVertex3f(0.0,+y,-z);
glVertex3f(-x,-y,-z);
glEnd();
glColor3f(0.5,0.5,0.5);
glBegin(GL_TRIANGLES);
glVertex3f(-x,-y,-z);
glVertex3f(+x,-y,-z);
glVertex3f(0.0,+y,-z);
glEnd();
// render x,y,z axises as r,g,b lines
glBegin(GL_LINES);
glColor3f(1.0,0.0,0.0); glVertex3f(0.0,0.0,0.0); glVertex3f(1.0,0.0,0.0);
glColor3f(0.0,1.0,0.0); glVertex3f(0.0,0.0,0.0); glVertex3f(0.0,1.0,0.0);
glColor3f(0.0,0.0,1.0); glVertex3f(0.0,0.0,0.0); glVertex3f(0.0,0.0,1.0);
glEnd();
glFlush();
SwapBuffers(hdc);
}
//---------------------------------------------------------------------------
void TForm1::ogl_resize()
{
xs=ClientWidth;
ys=ClientHeight;
if (xs<=0) xs = 1; // Prevent a divide by zero
if (ys<=0) ys = 1;
if (!ogl_inicialized) return;
glViewport(0,0,xs,ys); // Set Viewport to window dimensions
glMatrixMode(GL_PROJECTION); // use projection matrix
glLoadIdentity(); // set it to unit matrix
gluPerspective(30,float(xs)/float(ys),0.1,100.0); // perspective projection 30 degrees FOV and 0.1 focal length view depth 100-0.1
glMatrixMode(GL_TEXTURE); // use texture matrix
glLoadIdentity(); // set it to unit matrix
glMatrixMode(GL_MODELVIEW); // use modelview marix
glLoadIdentity(); // set it to unit matrix
}
//---------------------------------------------------------------------------
// here window stuff
//---------------------------------------------------------------------------
// window constructor
__fastcall TForm1::TForm1(TComponent* Owner):TForm(Owner)
{
ogl_inicialized=0;
hdc=NULL;
hrc=NULL;
ogl_init();
// init matrices
glMatrixMode(GL_MODELVIEW);
// object is at (0,0,0) rotatet so Z+ is pointing to screen
glLoadIdentity();
glRotatef(180.0,0.0,1.0,0.0);
glGetFloatv(GL_MODELVIEW_MATRIX,mobj);
// camera is behind object looking at object
glLoadIdentity();
glTranslatef(0.0,0.0,+20.0);
glGetFloatv(GL_MODELVIEW_MATRIX,meye);
}
//---------------------------------------------------------------------------
// window destructor
void __fastcall TForm1::FormDestroy(TObject *Sender)
{
ogl_exit();
}
//---------------------------------------------------------------------------
// common window events
//---------------------------------------------------------------------------
void __fastcall TForm1::FormResize(TObject *Sender)
{
ogl_resize();
_redraw=true;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormPaint(TObject *Sender)
{
_redraw=true;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::tim_updateTimer(TObject *Sender)
{
// here movement and repaint timer handler (I have 20ms interval)
GLfloat da=5.0; // angular turn speed in [deg/timer_iteration]
GLfloat dp=0.1; // movement speed in [world_units/timer_iteration]
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
if (_shift) // if Shift pressed control camera
{
// copy meye to GL
glLoadMatrixf(meye);
// handle keyboard with GL functions
if (_left) { _redraw=true; glRotatef(+da,0.0,1.0,0.0); }
if (_right) { _redraw=true; glRotatef(-da,0.0,1.0,0.0); }
if (_up ) { _redraw=true; glRotatef(-da,1.0,0.0,0.0); }
if (_down) { _redraw=true; glRotatef(+da,1.0,0.0,0.0); }
// obtain meye from GL
glGetFloatv(GL_MODELVIEW_MATRIX,meye);
}
else{ // else control object
// copy meye to GL
glLoadMatrixf(mobj);
// handle keyboard with GL functions
if (_left) { _redraw=true; glRotatef(+da,0.0,1.0,0.0); }
if (_right) { _redraw=true; glRotatef(-da,0.0,1.0,0.0); }
if (_up ) { _redraw=true; glRotatef(-da,1.0,0.0,0.0); }
if (_down) { _redraw=true; glRotatef(+da,1.0,0.0,0.0); }
// obtain mobj from GL
glGetFloatv(GL_MODELVIEW_MATRIX,mobj);
}
glPopMatrix();
// handle keyboard directly
if (_forw)
{
_redraw=true;
mobj[12]+=dp*mobj[8]; // mobj[12,13,14] is object position
mobj[13]+=dp*mobj[9]; // mobj[8,9,10] is object Z axis direction vector
mobj[14]+=dp*mobj[10]; // if not glScale is used then it is unit in size
}
// render if needed
if (_redraw) ogl_draw();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormMouseWheelDown(TObject *Sender, TShiftState Shift, TPoint &MousePos, bool &Handled)
{
// move camera matrix forward
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
glLoadMatrixf(meye);
glTranslatef(0,0,+2.0);
glGetFloatv(GL_MODELVIEW_MATRIX,meye);
glPopMatrix();
Handled=true;
_redraw=true;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormMouseWheelUp(TObject *Sender, TShiftState Shift, TPoint &MousePos, bool &Handled)
{
// move camera matrix backward
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
glLoadMatrixf(meye);
glTranslatef(0,0,-2.0);
glGetFloatv(GL_MODELVIEW_MATRIX,meye);
glPopMatrix();
Handled=true;
_redraw=true;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormKeyDown(TObject *Sender, WORD &Key,TShiftState Shift)
{
_shift=Shift.Contains(ssShift);
// on key down event
if (Key==key_left) _left =true;
if (Key==key_right) _right=true;
if (Key==key_up ) _up =true;
if (Key==key_down) _down =true;
if (Key==key_forw) _forw =true;
Key=0; // key is handled
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormKeyUp(TObject *Sender, WORD &Key, TShiftState Shift)
{
_shift=Shift.Contains(ssShift);
// on key release event
if (Key==key_left) _left =false;
if (Key==key_right) _right=false;
if (Key==key_up ) _up =false;
if (Key==key_down) _down =false;
if (Key==key_forw) _forw =false;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormActivate(TObject *Sender)
{
_left =false; // clear key flags after focus change
_right=false; // just to avoid constantly "pressed" keys
_up =false; // after window focus swaping during key press
_down =false; // many games are ignoring this and you need to
_forw =false; // press&release the stuck key again to stop movement ...
}
//---------------------------------------------------------------------------
Это просто одно приложение форм VCL с одним 20ms таймера в нем. Поэтому переносите события в код стиля среды. Вы можете игнорировать прагмы VCL. Этот пример управляется стрелками. Если сдвиг нажат, стрелки поворачивают камеру в противном случае. Пространство перемещает объект вперед.
Здесь компилируется Win32 автономный демо:
Этот подход имеет один недостаток
С сложении преобразований вы теряете точность. Чтобы исправить это, вы можете использовать векторное умножение (перекрестное произведение).Просто подсчитайте количество операций, выполняемых на такой матрице, и если достигнут порог, нормализуйте матрицу и сбросьте счетчик.
По нормализации я имею в виду обеспечение того, чтобы все оси были единичными и перпендикулярными друг другу. Оставляя направление основной оси (обычно вперед или вид), как есть. Перекрестное произведение двух векторов возвращает перпендикулярный вектор каждому. Так, например, если вы извлечь X,Y,Z
осях (места описаны в ссылке в # 1) и Z
является главной осью, то:
X = Y x Z
Y = Z x X
Z = Z/|Z|
X = X/|X|
Y = Y/|Y|
Где:
// cross product: W = U x V
W.x=(U.y*V.z)-(U.z*V.y)
W.y=(U.z*V.x)-(U.x*V.z)
W.z=(U.x*V.y)-(U.y*V.x)
// dot product: a = (U.V)
a=U.x*V.x+U.y*V.y+U.z*V.z
// abs of vector a = |U|
a=sqrt((U.x*U.x)+(U.y*U.y)+(U.z*U.z))
В случае вашей координате система не выводится из единичной матрицы, тогда вам нужно свести на нет какую-либо ось или изменить порядок операндов с поперечным продуктом, чтобы гарантировать, что направление ваших осей остается таким же, как должно.
Для получения дополнительной информации посмотрите на:
Это очень запутанным. Где в этом коде ваша ротация должна произойти? Почему вы помещаете матрицу вида в стек матрицы проекции? Почему вы умножаете представление и прогноз в этом порядке? Что там делает 'glOrtho'? – derhass
Чтобы повернуть, я изменяю переменную lookat (x, y и z), сохраняя при этом позицию (x, y, z). –
Но как вы его меняете? Это решающий момент всего вопроса. – derhass