OpenGL – 旋转不起作用

背景

我目前有一个我创建的环境,我加载了一些形状(以供我参考)并且我试图让控件向前,向后移动并旋转我正在寻找的位置。

我使用我的观察点和摄像机位置点进行向前和向后计算。 我试图旋转360度,但似乎根据我到目前为止得到了一些奇怪的限制。 我正在输出我的xangle,看看我一直在哪里。

为了旋转,我改变了lookat变量(x,y和z),同时保持位置(x,y,z)相同。

问题

当连续增加角度时,我击中两个reflection点,其中出现的旋转改变方向。 无论出于何种原因,这些似乎发生在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度的reflection点。 对我来说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 

不要使用欧拉角,因为它们有许多问题,比如你得到的问题。 而是使用累积变换矩阵。 看起来这个问题一次又一次被问到……一段时间了。 所以我决定用纯OpenGL 1.0没有GLM或有趣的东西来做一个例子。

  1. 定义

    让玩家可控制的对象称为obj和相机eye 。 它们中的每一个都应该由单独的4x4变换矩阵表示。 OpenGL将它们存储为一维数组。 有关详情,请参阅

    • 理解4×4同质变换矩阵

    我们希望独立于摄像机视图控制其局部坐标系中的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存在问题。 modelview矩阵应该是:

     GL_MODELVIEW = Inverse(meye) * mobj 

    但是OpenGL没有任何矩阵反函数。 所以这是我们唯一需要编码的东西。 由于矩阵总是4x4 ,因此并不难。

我将所有这些放在这个简单的GL / C ++ / VCL示例中:

 //--------------------------------------------------------------------------- #include  // you can ignore this #include  #include  #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 ... } //--------------------------------------------------------------------------- 

它是简单的单一formsVCL应用程序,其中包含单个20ms计时器。 因此,将事件移植到您的环境样式代码中。 您可以忽略VCL编译指示和包含。 此示例由箭头驱动。 如果按下shift,则箭头正在转动相机,否则转动物体。 空间正在向前移动物体。

这里编译了Win32独立演示:

  • 编译演示

这种方法有一个缺点

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

归一化意味着确保所有轴都是单位的并且彼此垂直离开主轴的方向(通常是视图或物体的前方)。 2个向量的叉积返回每个向量的垂直向量。 因此,例如,如果提取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 Wx=(Uy*Vz)-(Uz*Vy) Wy=(Uz*Vx)-(Ux*Vz) Wz=(Ux*Vy)-(Uy*Vx) // dot product: a = (UV) a=Ux*V.x+Uy*V.y+Uz*Vz // abs of vector a = |U| a=sqrt((Ux*Ux)+(Uy*Uy)+(Uz*Uz)) 

如果您的坐标系不是从单位矩阵派生的,那么您需要取消某个轴或更改叉积操作数的顺序,以确保轴的方向保持不变。

欲了解更多信息,请查看:

  • 如何通过累积变换随时间保持精度(全伪逆矩阵)