OpenGL - вращение не работает
Фон
В настоящее время у меня есть среда, в которую я создал несколько форм (для справки о том, где я нахожусь), и я пытаюсь заставить элементы управления двигаться вперед, назад и вращаться, куда я смотрю.
Я работал вперед и назад, используя свою точку обзора и точку положения камеры. Я пытаюсь повернуть на 360 градусов, но, похоже, получаю странные ограничения в зависимости от того, что у меня есть. Я вывожу свой xangle, чтобы увидеть, где я нахожусь всегда..
Чтобы повернуть, я изменяю переменную lookat (x,y и z), сохраняя положение (x,y,z) одинаковым.
проблема
Постоянно увеличивая угол, я попадаю в две точки отражения, где появившееся вращение меняет направление. По какой-то причине это может происходить при 60 и 300 градусах, как показано ниже:
Очевидно, что эти углы не верны. Я оценил поведение, которое должно происходить, когда происходит вращение, и вычисленные декартовы координаты кажутся правильными, но отображение угла - это то, что отключено.
Моя настройка setupviewport sub:
Private Sub SetupViewport()
Dim w As Integer = GLcontrol1.Width
Dim h As Integer = GLcontrol1.Height
Dim perspective1 As Matrix4 = cam.GetViewMatrix() * Matrix4.CreatePerspectiveFieldOfView(1.3F, GLcontrol1.Width / CSng(GLcontrol1.Height), 0.1F, 2000.0F)
GL.MatrixMode(MatrixMode.Projection)
GL.LoadIdentity()
GL.Ortho(0, w, h, 0, -1, 1)
GL.LoadMatrix(perspective1)
GL.MatrixMode(MatrixMode.Modelview)
GL.LoadIdentity()
GL.Viewport(0, 0, w, h)
GL.Enable(EnableCap.DepthTest)
GL.DepthFunc(DepthFunction.Less)
End Sub
Мой класс камеры:
Class Camera
Public Position As Vector3 = Vector3.Zero
Public Orientation As New Vector3(0.0F, 0.0F, 0.0F)
Public MoveSpeed As Single = 0.2F
Public MouseSensitivity As Single = 0.01F
Public lookat As New Vector3()
Public manual_lookat As Boolean = False
Public invert_y As Boolean = False
Public Function aim_at_origin()
Position.X = 0
Position.Y = 0
Position.Z = 2
If invert_y = False Then
Return Matrix4.LookAt(Position, Position + lookat, Vector3.UnitY)
Else
Return Matrix4.LookAt(Position, Position + lookat, -Vector3.UnitY)
End If
End Function
Public Function GetViewMatrix() As Matrix4
If invert_y = False Then
Return Matrix4.LookAt(Position, lookat, Vector3.UnitY)
Else
Return Matrix4.LookAt(Position, lookat, -Vector3.UnitY)
End If
End Function
End Class
Класс камеры устанавливает матрицу для умножения на текущую. Умножение происходит каждый кадр, когда вызывается setupviewport.
Я не могу понять, почему у него есть точки отражения на 300 и 60 градусов. Для меня 180 градусов или 360 будет иметь смысл. Похоже, что площадь поворота составляет всего 45 градусов от визуально.
Я отмечаю, что это MATH, C# и VB .NET, поскольку ответы могут быть приемлемы в большинстве языков программирования.
Чтобы повернуть, я называю этот класс:
Private Sub rotate_view(ByVal delta_camanglex As Single, ByVal delta_camangley As Single)
Dim curdistance As Single = 1
curdistance = Math.Sqrt((cam.Position.X - cam.lookat.X) ^ 2 + (cam.Position.Y - cam.lookat.Y) ^ 2 + (cam.Position.Z - cam.lookat.Z) ^ 2)
Dim invertx As Boolean = False
Dim inverty As Boolean = False
camanglex = camanglex + delta_camanglex
camangley = camangley + delta_camangley
If camanglex >= 360 Then
camanglex = camanglex - 360
End If
If camangley >= 360 Then
camangley = camangley - 360
End If
If camanglex < 0 Then
camanglex = camanglex + 360
End If
If camangley < 0 Then
camangley = camangley + 360
End If
cam.manual_lookat = True
Dim sigma As Single = camanglex
Dim theda As Single = camangley
lookatx = curdistance * Sin(sigma * (PI / 180)) * Cos((theda) * (PI / 180))
lookaty = curdistance * Sin((sigma) * (PI / 180)) * Sin((theda) * (PI / 180))
lookatz = curdistance * Cos((sigma) * (PI / 180))
cam.lookat.X = lookatx
cam.lookat.Y = lookaty
cam.lookat.Z = lookatz
End Sub
1 ответ
Не используйте для этого углы Эйлера, так как у них много проблем, подобных той, которую вы получили. Вместо этого используйте кумулятивные матрицы преобразования. Похоже, этот вопрос задают снова и снова... в течение некоторого времени. Поэтому я решил сделать пример, как сделать это с чистым OpenGL 1.0 без GLM или с забавными вещами.
Определения
Позволяет иметь управляемый игроком объект под названием
obj
и камераeye
, Каждый из них должен быть представлен отдельным4x4
матрица преобразования. OpenGL хранит их как одномерные массивы. Для получения дополнительной информации см.Мы хотим контролировать
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 для наших матриц вне цикла рендеринга. (например, в обработчике клавиатуры или в каком-то таймере).
Рендеринг петлевых матриц
Теперь, если мы хотим визуализировать наш объект с нашими матрицами, нам нужно правильно установить матрицы 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 одной формы с одним таймером 20 мс. Так что перенесите события в ваш код стиля среды. Вы можете игнорировать прагмы VCL и включает в себя. Этот пример приводится в движение стрелками. Если нажать Shift, стрелки поворачивают камеру, в противном случае объект. Пространство движет объект вперед.
Вот скомпилированная 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))
Если ваша система координат не является производной от единичной матрицы, то вам нужно отменить некоторую ось или изменить порядок операндов перекрестного произведения, чтобы направление ваших осей оставалось как следует.
Для получения дополнительной информации взгляните на: