且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

OpenGL-旋转不起作用

更新时间:2023-11-08 12:01:10

请勿为此使用Euler角,因为它们有很多问题,例如您遇到的问题.而是使用累积变换矩阵.好像这个问题被问了一遍又一遍...一段时间了.因此,我决定举一个例子说明如何使用纯 OpenGL 1.0 而不使用 GLM 或有趣的东西.

Do not use Euler angles for this as they have many issues like the one you got. Instead use cumulative transform matrices. It looks like this question is asked again and again... for some time now. So I decided to make an example how to do it with pure OpenGL 1.0 no GLM or funny stuff.

  1. 定义

让我们有名为obj的播放器可控制对象和摄像机eye.它们中的每一个都应由单独的4x4变换矩阵表示. OpenGL将它们存储为一维数组.有关更多信息,请参见

Lets have Player control-able object called obj and camera eye. Each of them should be represented by separate 4x4 transform matrix. The OpenGL stores them as 1D arrays. For more info see

我们要在其局部坐标系中独立于摄像机视图来控制obj.如果您习惯将相机和对象矩阵在GL_MODELVIEW中相乘以避免GL_PROJECTION矩阵滥用,那么您很快就会意识到,仅通过通常方式下的glRotate/glTranslate调用就无法解决此问题.

We want to control obj in its local coordinate system independent on camera view. If you are used to have both camera and object matrices multiplied together in GL_MODELVIEW to avoid GL_PROJECTION matrix abuse then you quickly realize that this is not solvable by simply glRotate/glTranslate calls in usual manner.

由于许多人切换到Euler角度可以轻松解决此问题,但又带来了许多其他问题(如今,许多游戏仍在不应该使用它们的地方使用它们,因此存在大量错误和问题).

Because of that many people switch to Euler angles which can handle this easily but brings up a whole bunch of other problems (many nowadays games are still using them where they should not and there are a tons of bugs and issues because of it).

因此,将其添加到您的项目中:

So add this to your project:

GLfloat mobj[16],meye[16];

  • 在我们的矩阵中使用GL

    这很简单,只需执行以下操作即可:

    This is simple just do this:

    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调用. (例如,在键盘处理程序或某些计时器中).

    With this we can use GL calls for our matrices outside the rendering loop. (for example in keyboard handler or in some timer).

    渲染循环矩阵

    现在,如果要使用矩阵渲染对象,则需要正确设置GL矩阵.假设设置了投影矩阵,则仅考虑Modelview.模型视图矩阵应为:

    Now if we want to render our object with our matrices then we need to set the GL matrices properly. Let assume Projection matrix is set then just Modelview is in question. The modelview matrix should be:

    GL_MODELVIEW = Inverse(meye) * mobj
    

    但是 OpenGL 没有任何矩阵求逆函数.因此,这是我们唯一需要编写的代码.由于矩阵始终为4x4,所以并不难.

    But OpenGL does not have any matrix inverse function. So this is the only thing we need to code. As the matrix is always 4x4 then it is not that hard.

    我将所有这些放到了这个简单的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 编译指示和包含.此示例由箭头驱动.如果按下Shift键,则箭头会转向摄像机,否则会转向目标.空间正在将物体向前移动.

    It is simple single form VCL application with single 20ms timer in it. So port the events to your environment style code. You can ignore the VCL pragmas and includes. This example is driven by arrows. If shift is pressed then the arrows are turning camera otherwise the object. Space is moving object forward.

    此处已编译Win32独立演示:

    这种方法有一个缺点

    通过累积转换,您将失去精度.为了弥补这一点,您可以利用向量乘法(叉积).只需计算在此矩阵上执行的操作数,如果达到阈值就可以对矩阵进行归一化并重置计数器.

    With cumulating the transforms you are loosing precision. To remedy that you can exploit vector multiplication (cross product). Simply count the number of operations performed on such matrix and if threshold is reached normalize the matrix and reset the counter.

    通过归一化,我的意思是确保所有轴都是单位且彼此垂直,而保持主轴方向(通常在视图或物体的前面)不变. 2个向量的叉积返回正交向量.因此,例如,如果您提取X,Y,Z轴(位置在#1 中的链接中进行了描述),并且Z是主轴,则:

    By normalization I mean ensuring all the axises are unit and perpendicular to each other Leaving the direction of main axis (usually forward of view or object) as is. Cross product of 2 vectors returns perpendicular vector to each. So for example if you extract the X,Y,Z axises (locations are described in the link in #1) and Z is the main axis then:

    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))
    

    如果坐标系不是从单位矩阵派生的,那么您需要取反某些轴或更改叉积操作数的顺序,以确保轴的方向保持应有的状态.

    In case your coordinate system is not derived from unit matrix then you need to negate some axis or change the order of cross product operands to ensure the direction of your axises stays as should.

    有关更多信息,请查看:

    For more info take a look at: