OpenGL - вращение не работает

Фон

В настоящее время у меня есть среда, которую я создал, в которую я загружаю некоторые фигуры (для ссылки на то, где я нахожусь), и пытаюсь заставить элементы управления двигаться вперед, назад и вращаться там, где я смотрю.

У меня есть направление вперед и назад, используя мою точку обзора и точку положения камеры. Я пытаюсь повернуть на 360 градусов, но, похоже, у меня есть некоторые странные ограничения, основанные на том, что у меня есть до сих пор. Я вывожу свой xangle, чтобы всегда видеть, где я нахожусь.

Чтобы повернуть, я меняю переменную просмотра (x, y и z), сохраняя при этом положение (x, y, z) прежним.

Проблема

При постоянном увеличении угла я попал в две точки отражения, где появившееся вращение меняет направление. По какой-то причине это происходит при 60 градусах и 300 градусах, как показано ниже:

введите описание изображения здесь

Очевидно, что эти углы неверны. Я оценил поведение, которое должно происходить при вращении, и вычисленные декартовы координаты кажутся правильными, но отображение угла не работает.

Мой саб setupviewport:

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

person Eric F    schedule 17.10.2016    source источник
comment
Это очень сбивает с толку. Где в этом коде должна происходить ваша ротация? Почему вы помещаете матрицу вида в стек матрицы проекции? Почему вы умножаете вид и проекцию именно в таком порядке? Что этот glOrtho там делает?   -  person derhass    schedule 17.10.2016
comment
Чтобы повернуть, я меняю переменную просмотра (x, y и z), сохраняя при этом положение (x, y, z) прежним.   -  person Eric F    schedule 17.10.2016
comment
Но как его изменить? Это ключевой момент всего вопроса.   -  person derhass    schedule 17.10.2016
comment
@derhass Я обновил свой вопрос, включив в него свой класс ротации. Он просто вычисляет, где должен быть взгляд   -  person Eric F    schedule 17.10.2016


Ответы (1)


Не используйте для этого углы Эйлера, так как у них много проблем, подобных той, что у вас есть. Вместо этого используйте кумулятивные матрицы преобразования. Похоже, этот вопрос задают снова и снова... в течение некоторого времени. Поэтому я решил сделать пример, как это сделать с помощью чистого OpenGL 1.0 без GLM или всякой всячины.

  1. Определения

    Давайте возьмем управляемый игроком объект с именем obj и камеру eye. Каждый из них должен быть представлен отдельной матрицей преобразования 4x4. OpenGL хранит их как одномерные массивы. Для получения дополнительной информации см.

    Мы хотим управлять obj в его локальной системе координат независимо от вида камеры. Если вы привыкли перемножать матрицы камеры и объекта в GL_MODELVIEW, чтобы избежать злоупотребления GL_PROJECTION матрицей, то вы быстро понимаете, что это не решается простыми вызовами glRotate/glTranslate обычным способом.

    Из-за этого многие люди переключаются на углы Эйлера, которые легко справляются с этим, но вызывают целую кучу других проблем (многие современные игры все еще используют их там, где не должны, и из-за этого возникает множество ошибок и проблем).

    Итак, добавьте это в свой проект:

    GLfloat mobj[16],meye[16];
    
  2. Использование 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 для наших матриц вне цикла рендеринга. (например, в обработчике клавиатуры или в каком-то таймере).

  3. Матрицы циклов рендеринга

    Теперь, если мы хотим визуализировать наш объект с нашими матрицами, нам нужно правильно установить матрицы GL. Предположим, что матрица проекции установлена, тогда речь идет только о Modelview. Матрица представления модели должна быть:

    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. Этот пример управляется стрелками. Если нажат шифт, то стрелки поворачивают камеру, иначе объект. Пространство перемещает объект вперед.

Здесь скомпилирована автономная демонстрация 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))

Если ваша система координат не получена из единичной матрицы, вам необходимо инвертировать некоторые оси или изменить порядок операндов перекрестного произведения, чтобы направление ваших осей оставалось правильным.

Для получения дополнительной информации взгляните на:

person Spektre    schedule 18.10.2016
comment
Я понимаю концепции, которые вы показываете, но мне действительно трудно преобразовать их в C# или VB .NET, поскольку вы написали их на C++. Ненавижу спрашивать, так как вы приложили много усилий к этому ответу, но можете ли вы сделать пример на любом из этих языков? - person Eric F; 18.10.2016
comment
@EricF Я не пишу код ни в одном из них, так что нет. Вам нужна только обратная матрица и события, которые, я думаю, достаточно просты. Код C# должен быть почти идентичен, за исключением того, что он обрабатывает массивы немного по-другому. Важные вещи это ogl_draw() и tim_updateTimer(TObject *Sender) - person Spektre; 18.10.2016