一、现象

在OpenGL中先移动后旋转与先旋转后移动的最终效果是并不一定相同的,也就是说在Opengl中如果调用函数glTranslatef和函数glRotatef的次序不同,即使参数一样,效果也可能会不同。下面我们通过两段程序说明该问题。

二、代码

如下所示,在vs2015中新建Win32程序:

输入以下代码:

#include <windows.h>
#include <gl/GL.h>
#include <gl/GLU.h>#pragma comment(lib,"opengl32.lib")
#pragma comment(lib,"glu32.lib")//窗口处理函数(回调函数)
LRESULT CALLBACK GLWindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM IParam)
{switch (msg){case WM_CLOSE:{PostQuitMessage(0); //该函数向系统表明有个线程有终止请求。如果没有该语句,鼠标点击程序窗口右上方的关闭按钮时会无法关闭。return 0;}}return DefWindowProc(hwnd, msg, wParam, IParam); //该函数确保每一个消息得到处理。如果没有该语句,窗口会卡死。
}void Init()
{glMatrixMode(GL_PROJECTION); //将当前矩阵指定为投影矩阵,声明我们接下来会对投影进行相关的操作。也就是把物体投影到一个平面上,就像我们照相一样,把3维物体投到2维的平面上。这样,接下来的语句可以是跟透视相关的函数,比如glFrustum()或gluPerspective()gluPerspective(50.0f, 800.0f / 600.0f, 0.1f, 1000.0f); //根据所设置的参数设置当前矩阵。设置垂直方向的视角为50度,画布宽高比为800.0f / 600.0f,最近可以看到的距离为0.1f,最远可以看到的距离为1000.0fglMatrixMode(GL_MODELVIEW);  //切换到模型视图矩阵,这样才能正确画图glLoadIdentity(); //对当前矩阵进行初始化。无论以前进行了多少次矩阵变换,在该命令执行后,当前矩阵均恢复成一个单位矩阵,即相当于没有进行任何矩阵变换状态
}void Draw()
{glClearColor(30.0f / 255.0f, 30.0f / 255.0f, 30.0f / 255.0f, 1.0f); //指定刷新颜色缓冲区时所用的颜色,设置窗口背景颜色为R:0%,G:0%,B:0%,A:100%。切记:此函数仅仅设定颜色,并不执行清除工作glClear(GL_COLOR_BUFFER_BIT);         //清除颜色缓冲。实际完成了把整个窗口清除glClearColor函数设置的颜色的任务glLoadIdentity();                     //对当前矩阵进行初始化glTranslatef(0.0f, 0.0f, -5.0f);      //修改当前的坐标系统,使接下来绘制的所有图形都沿X轴正方向平移0个单位,沿Y轴正方向平移0个单位,沿Z轴正方向平移-5个单位glRotatef(30.0f, 0.0f, 1.0f, 0.0f);   //修改当前的坐标系统(注意,修改的是局部坐标系统而不是世界坐标系统),使接下来绘制的所有图形都沿Y轴旋转30度glBegin(GL_TRIANGLES);                //绘制一个三角形glColor4ub(255, 0, 0, 255); glVertex3f(-0.5f, -0.25f, 0.0f);glColor4ub(0, 0, 255, 255); glVertex3f(0.5f, -0.25f, 0.0f);glColor4ub(0, 255, 0, 255); glVertex3f(0.0f, 0.5f, 0.0f);glEnd();                              //表示此次绘图完成了
}INT WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nCmdShow)
{WNDCLASSEX wndclass;                             //定义窗口类结构体变量wndclasswndclass.cbClsExtra = 0;                         //为窗口类的额外信息做记录,初始化为0wndclass.cbSize = sizeof(WNDCLASSEX);            //WNDCLASSEX 的大小wndclass.cbWndExtra = 0;                         //记录窗口实例的额外信息,系统初始为0wndclass.hbrBackground = NULL;                   //窗口类的背景刷,为背景刷句柄wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);  //窗口类的鼠标样式,为鼠标样式资源的句柄wndclass.hIcon = NULL;                           //窗口类的图标,为资源句柄,如果设置为NULL,系统将为窗口提供一个默认的图标wndclass.hIconSm = NULL;                         //小图标的句柄,在任务栏显示的图标wndclass.hInstance = hInstance;                  //本模块的事例句柄wndclass.lpfnWndProc = GLWindowProc;             //指向窗口处理函数(回调函数)。处理窗口事件,像单击鼠标会怎样,右击鼠标会怎样,都是由此函数控制的。wndclass.lpszClassName = L"GLWindow";            //注册窗口使用的窗口名称wndclass.lpszMenuName = NULL;                    //菜单名称,为NULL则为没有菜单wndclass.style = CS_VREDRAW | CS_HREDRAW;        //窗口更新时的重绘方式ATOM atom = RegisterClassEx(&wndclass);          //注册窗口wndclassif (!atom)                                       //如果注册失败,显示提示框{MessageBox(NULL, L"Register Fail", L"Error", MB_OK);return 0;}RECT rect;rect.left = 0;     //指定矩形框左上角的x坐标rect.right = 800;  //指定矩形框右下角的x坐标rect.top = 0;      //指定矩形框左上角的y坐标rect.bottom = 600; //指定矩形框右下角的y坐标AdjustWindowRect(&rect,WS_OVERLAPPEDWINDOW,NULL); //用于创建一个客户区所需大小的窗口int windowWidth = rect.right - rect.left;         //得到客户区的宽度int windowHeight = rect.bottom - rect.top;        //得到客户区的高度HWND hwnd = CreateWindowEx(NULL, L"GLWindow", L"OpenGL Window", WS_OVERLAPPEDWINDOW, 100, 100, windowWidth, windowHeight, NULL, NULL, hInstance, NULL);HDC dc = GetDC(hwnd);      //为一个指定窗口的客户端区域或者整个屏幕从一个设备上下文(DC)中提取一个句柄。返回值为指定窗口客户端区域的DC的句柄PIXELFORMATDESCRIPTOR pfd; //像素格式结构变量memset(&pfd, 0, sizeof(PIXELFORMATDESCRIPTOR));      //将pfd的所有成员变量清0pfd.nVersion = 1;                                    //这里固定设置为1,不要管为什么,微软也没有解释为什么。pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR);           //pfd的大小pfd.cColorBits = 32;                                 //32位的颜色深度。即一个像素占4个字节,R占8位,G占8位,B占8位,A占8位pfd.cDepthBits = 24;                                 //深度缓存设置为24位,这个缓存能解决三维场景的消隐问题pfd.cStencilBits = 8;                                //模板缓冲区占用8位pfd.iPixelType = PFD_TYPE_RGBA;                      //当前采用RGBA颜色模式pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; //支持windows,支持opengl,支持双缓冲区int pixelFormat = ChoosePixelFormat(dc, &pfd);       //选择一个像素格式,并将像素格式索引号返回给pixelFormat变量SetPixelFormat(dc, pixelFormat, &pfd);               //设置像素格式HGLRC rc = wglCreateContext(dc);                     //创建一个新的OpenGL渲染描述表wglMakeCurrent(dc, rc);                              //使一个指定的OpenGL渲染上下文调用线程的当前呈现上下文Init();ShowWindow(hwnd, SW_SHOW); //该函数设置指定窗口的显示状态。如果没有该语句,执行程序时窗口不会显示UpdateWindow(hwnd);        //绕过消息队列(不进队),直接向窗口客户区发送WM_PAINT消息,使得窗口立即更新MSG msg;while (true){if (PeekMessage(&msg, NULL, NULL, NULL, PM_REMOVE)) //该函数为一个消息检查线程消息队列,并将该消息(如果存在)放于指定的结构{if (msg.message == WM_QUIT) //如果关闭消息循环{break;                  //如果没有该语句,鼠标点击程序窗口右上方的关闭按钮时会无法关闭。}TranslateMessage(&msg); //将虚拟键消息转换成字符消息DispatchMessage(&msg);  //该函数分发一个消息给窗口程序。如果没有该语句,无法拖动窗口和通过鼠标对窗口进行放大,最小化,关闭等操作。}Draw();          //用于画图元。在while(true)的循环中每一次循环都调用Draw函数绘制一次场景。该函数绘制是绘制在后面的缓冲区上的,所以绘制完后,得调用函数SwapBuffers将它交换到前面的缓冲区,这样用户才能看到绘制的东西。SwapBuffers(dc); //交换opengl前后的两个缓冲区。一个对应的是前面的屏幕的缓存,一个对应的是后面的缓存,之所以用交换的方式,是内部进行了指针的交换,如此速度很快}return 0;
}

上述程序的作用是先将坐标系统(局部坐标系统)沿Z轴负方向平移5个单位,然后沿Y轴旋转30度,再绘制一个三角形。编译运行,我们可以得到三角形如下所示:

我们将vs中的代码换成代码如下所示:

#include <windows.h>
#include <gl/GL.h>
#include <gl/GLU.h>#pragma comment(lib,"opengl32.lib")
#pragma comment(lib,"glu32.lib")//窗口处理函数(回调函数)
LRESULT CALLBACK GLWindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM IParam)
{switch (msg){case WM_CLOSE:{PostQuitMessage(0); //该函数向系统表明有个线程有终止请求。如果没有该语句,鼠标点击程序窗口右上方的关闭按钮时会无法关闭。return 0;}}return DefWindowProc(hwnd, msg, wParam, IParam); //该函数确保每一个消息得到处理。如果没有该语句,窗口会卡死。
}void Init()
{glMatrixMode(GL_PROJECTION); //将当前矩阵指定为投影矩阵,声明我们接下来会对投影进行相关的操作。也就是把物体投影到一个平面上,就像我们照相一样,把3维物体投到2维的平面上。这样,接下来的语句可以是跟透视相关的函数,比如glFrustum()或gluPerspective()gluPerspective(50.0f, 800.0f / 600.0f, 0.1f, 1000.0f); //根据所设置的参数设置当前矩阵。设置垂直方向的视角为50度,画布宽高比为800.0f / 600.0f,最近可以看到的距离为0.1f,最远可以看到的距离为1000.0fglMatrixMode(GL_MODELVIEW);  //切换到模型视图矩阵,这样才能正确画图glLoadIdentity(); //对当前矩阵进行初始化。无论以前进行了多少次矩阵变换,在该命令执行后,当前矩阵均恢复成一个单位矩阵,即相当于没有进行任何矩阵变换状态
}void Draw()
{glClearColor(30.0f / 255.0f, 30.0f / 255.0f, 30.0f / 255.0f, 1.0f); //指定刷新颜色缓冲区时所用的颜色,设置窗口背景颜色为R:0%,G:0%,B:0%,A:100%。切记:此函数仅仅设定颜色,并不执行清除工作glClear(GL_COLOR_BUFFER_BIT);         //清除颜色缓冲。实际完成了把整个窗口清除glClearColor函数设置的颜色的任务glLoadIdentity();                     //对当前矩阵进行初始化glRotatef(30.0f, 0.0f, 1.0f, 0.0f);   //修改当前的坐标系统(注意,修改的是局部坐标系统而不是世界坐标系统),使接下来绘制的所有图形都沿Y轴旋转30度                                   glTranslatef(0.0f, 0.0f, -5.0f);      //修改当前的坐标系统,使接下来绘制的所有图形都沿X轴正方向平移0个单位,沿Y轴正方向平移0个单位,沿Z轴正方向平移-5个单位glBegin(GL_TRIANGLES);                //绘制一个三角形glColor4ub(255, 0, 0, 255); glVertex3f(-0.5f, -0.25f, 0.0f);glColor4ub(0, 0, 255, 255); glVertex3f(0.5f, -0.25f, 0.0f);glColor4ub(0, 255, 0, 255); glVertex3f(0.0f, 0.5f, 0.0f);glEnd();                              //表示此次绘图完成了
}INT WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nCmdShow)
{WNDCLASSEX wndclass;                             //定义窗口类结构体变量wndclasswndclass.cbClsExtra = 0;                         //为窗口类的额外信息做记录,初始化为0wndclass.cbSize = sizeof(WNDCLASSEX);            //WNDCLASSEX 的大小wndclass.cbWndExtra = 0;                         //记录窗口实例的额外信息,系统初始为0wndclass.hbrBackground = NULL;                   //窗口类的背景刷,为背景刷句柄wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);  //窗口类的鼠标样式,为鼠标样式资源的句柄wndclass.hIcon = NULL;                           //窗口类的图标,为资源句柄,如果设置为NULL,系统将为窗口提供一个默认的图标wndclass.hIconSm = NULL;                         //小图标的句柄,在任务栏显示的图标wndclass.hInstance = hInstance;                  //本模块的事例句柄wndclass.lpfnWndProc = GLWindowProc;             //指向窗口处理函数(回调函数)。处理窗口事件,像单击鼠标会怎样,右击鼠标会怎样,都是由此函数控制的。wndclass.lpszClassName = L"GLWindow";            //注册窗口使用的窗口名称wndclass.lpszMenuName = NULL;                    //菜单名称,为NULL则为没有菜单wndclass.style = CS_VREDRAW | CS_HREDRAW;        //窗口更新时的重绘方式ATOM atom = RegisterClassEx(&wndclass);          //注册窗口wndclassif (!atom)                                       //如果注册失败,显示提示框{MessageBox(NULL, L"Register Fail", L"Error", MB_OK);return 0;}RECT rect;rect.left = 0;     //指定矩形框左上角的x坐标rect.right = 800;  //指定矩形框右下角的x坐标rect.top = 0;      //指定矩形框左上角的y坐标rect.bottom = 600; //指定矩形框右下角的y坐标AdjustWindowRect(&rect,WS_OVERLAPPEDWINDOW,NULL); //用于创建一个客户区所需大小的窗口int windowWidth = rect.right - rect.left;         //得到客户区的宽度int windowHeight = rect.bottom - rect.top;        //得到客户区的高度HWND hwnd = CreateWindowEx(NULL, L"GLWindow", L"OpenGL Window", WS_OVERLAPPEDWINDOW, 100, 100, windowWidth, windowHeight, NULL, NULL, hInstance, NULL);HDC dc = GetDC(hwnd);      //为一个指定窗口的客户端区域或者整个屏幕从一个设备上下文(DC)中提取一个句柄。返回值为指定窗口客户端区域的DC的句柄PIXELFORMATDESCRIPTOR pfd; //像素格式结构变量memset(&pfd, 0, sizeof(PIXELFORMATDESCRIPTOR));      //将pfd的所有成员变量清0pfd.nVersion = 1;                                    //这里固定设置为1,不要管为什么,微软也没有解释为什么。pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR);           //pfd的大小pfd.cColorBits = 32;                                 //32位的颜色深度。即一个像素占4个字节,R占8位,G占8位,B占8位,A占8位pfd.cDepthBits = 24;                                 //深度缓存设置为24位,这个缓存能解决三维场景的消隐问题pfd.cStencilBits = 8;                                //模板缓冲区占用8位pfd.iPixelType = PFD_TYPE_RGBA;                      //当前采用RGBA颜色模式pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; //支持windows,支持opengl,支持双缓冲区int pixelFormat = ChoosePixelFormat(dc, &pfd);       //选择一个像素格式,并将像素格式索引号返回给pixelFormat变量SetPixelFormat(dc, pixelFormat, &pfd);               //设置像素格式HGLRC rc = wglCreateContext(dc);                     //创建一个新的OpenGL渲染描述表wglMakeCurrent(dc, rc);                              //使一个指定的OpenGL渲染上下文调用线程的当前呈现上下文Init();ShowWindow(hwnd, SW_SHOW); //该函数设置指定窗口的显示状态。如果没有该语句,执行程序时窗口不会显示UpdateWindow(hwnd);        //绕过消息队列(不进队),直接向窗口客户区发送WM_PAINT消息,使得窗口立即更新MSG msg;while (true){if (PeekMessage(&msg, NULL, NULL, NULL, PM_REMOVE)) //该函数为一个消息检查线程消息队列,并将该消息(如果存在)放于指定的结构{if (msg.message == WM_QUIT) //如果关闭消息循环{break;                  //如果没有该语句,鼠标点击程序窗口右上方的关闭按钮时会无法关闭。}TranslateMessage(&msg); //将虚拟键消息转换成字符消息DispatchMessage(&msg);  //该函数分发一个消息给窗口程序。如果没有该语句,无法拖动窗口和通过鼠标对窗口进行放大,最小化,关闭等操作。}Draw();          //用于画图元。在while(true)的循环中每一次循环都调用Draw函数绘制一次场景。该函数绘制是绘制在后面的缓冲区上的,所以绘制完后,得调用函数SwapBuffers将它交换到前面的缓冲区,这样用户才能看到绘制的东西。SwapBuffers(dc); //交换opengl前后的两个缓冲区。一个对应的是前面的屏幕的缓存,一个对应的是后面的缓存,之所以用交换的方式,是内部进行了指针的交换,如此速度很快}return 0;
}

上述程序的作用是先将坐标系统(局部坐标系统)沿Y轴旋转30度,然后沿Z轴负方向平移5个单位,再绘制一个三角形。编译运行,我们得到三角形如下所示:

三、原因分析

通过对比,我们可以发现两段代码运行得到的三角形不同。两段代码中不同的地方只有平移和旋转的顺序,其它地方都是相同的,但是得到的三角形却是不同。造成该现象的原因是调用函数glTranslatef和函数glRotatef,会令局部坐标系也跟着平移和旋转,而不仅仅只是平移和旋转物体,平移和旋转针对的都是局部坐标系的操作。从矩阵变换的角度去考虑这个问题就是两个矩阵交换位置相乘结果不一样,从而导致了该现象。从数学的角度去分析,我们可以参考链接:https://jingyan.baidu.com/article/414eccf617a9c66b421f0a5e.html

四、代码下载

本博文演示所用的代码和vs工程可以从该链接下载https://download.csdn.net/download/u014552102/10993374

关于在Opengl中先平移后旋转和先旋转后平移的效果不一样的原因相关推荐

  1. OpenGL中的平移旋转缩放

    1.opengl中缩放使用的函数是glScalef 其原型为void glScalef(GLfloat  x,  GLfloat  y,  GLfloat  z); 该函数表示模型在各轴上进行扩大和缩 ...

  2. OpenGL中关于矩阵缩放、平移、旋转的讲解

    1.矩阵缩放 对一个向量进行缩放(Scaling)就是对向量的长度进行缩放,而保持它的方向不变.由于我们进行的是2维或3维操作,我们可以分别定义一个有2或3个缩放变量的向量,每个变量缩放一个轴(x.y ...

  3. OpenGL中平移函数glTranslatef()、旋转函数glRotatef()的理解

    https://blog.csdn.net/tan_handsome/article/details/50614146 void glTranslatef(GLfloat x,GLfloat y,GL ...

  4. OpenGL中平移函数glTranslatef()、旋转函数glRotatef()的理解(非常好的文章)

    void glTranslatef(GLfloat x,GLfloat y,GLfloat z); 函数功能:沿X轴正方向平移x个单位(x是有符号数)   沿Y轴正方向平移y个单位(y是有符号数)   ...

  5. python图片旋转脚本_封装了深度学习中几个图片数据增强的脚本-python平移、旋转与调整大小...

    文章目录 平移 代码 演示 删除 代码 演示 旋转 代码 演示 平移 代码 可以调整以下的代码,只进行某一方向的平移:也可修改divisor调整平移的比例因子. import cv2 import n ...

  6. iOS开发中UIImageView逆时针旋转,并得到旋转后的图片

    很多小伙伴会用系统的动画旋转,但都是顺时针的,但是开发中有些场景需要用到逆时针旋转效果更好,比方说tableView的 展开/收起 指示箭头方向的变换,如果是顺时针复位,就会显得特别别扭.以下一段代码 ...

  7. OpenGL中各种坐标系的理解

    OPENGL坐标系可分为:世界坐标系和当前绘图坐标系. 世界坐标系:在OpenGL中,世界坐标系是以屏幕中心为原点(0, 0, 0),且是始终不变的.你面对 屏幕,你的右边是x正轴,上面是y正轴,屏幕 ...

  8. openGL中的坐标系

    openGL中使用的是右手坐标系 右手坐标系:伸开右手,大拇指指向X轴正方向,食指指向Y轴正方向,其他三个手指指向Z轴正方向 左手坐标系:伸开左手,大拇指指向X轴正方向,食指指向Y轴正方向,其他三个手 ...

  9. OpenGL中的glLoadIdentity、glTranslatef、glRotatef原理【转帖】

    OpenGL中的glLoadIdentity.glTranslatef.glRotatef原理 单位矩阵 对角线上都是1,其余元素皆为0的矩阵. 在矩阵的乘法中,有一种矩阵起着特殊的作用,如同数的乘法 ...

最新文章

  1. 在mysql 服务器上安装sysbench-0.4.12,报错。
  2. RDKit | 基于最大公共子结构(MCS)的分子比对
  3. mysql distinct、group_concat
  4. 开始JBoss BPM流程的3种基本方法
  5. 中国捆矛行业市场供需与战略研究报告
  6. Linux命令——lsb_release
  7. adsl密码查看器,宽带密码查看器,无广告,绿色版
  8. 抽象代数学习笔记(抽象代数的历史、运算)
  9. Microsoft Office Word、Excel 和 PowerPoint 文件格式兼容包
  10. smartdrv.exe的使用及简单说明和相关程序下载
  11. Vue最全知识点集合
  12. Avalonia的Snoop
  13. 软件开发模型优点缺点
  14. 结构化、半结构化、非结构化数据
  15. 检索策略(抓取策略)
  16. 图形学中走样(Aliasing)和反走样(Antialiasing)
  17. 通过网页控制嵌入式设备
  18. USACO 3.4 Raucous Rockers (rockers)
  19. 2021-04-22
  20. Liunx学习笔记 - 07 - 02 正则表达式与文件格式化处理

热门文章

  1. 前端解决web端 125%,150%缩放,1366*768分辨率兼容问题
  2. PostgreSQL的MVCC
  3. Docker安装java环境并部署jar包运行
  4. 东方国信 Java一面
  5. 使用国标流媒体服务器查看监控摄像头视频流如何正确使用UDP及TCP协议?
  6. 【转载】基于Office Online Server 2016 的office在线编辑
  7. 瑞士轮 pascal
  8. 文泰 单笔划 字 教程
  9. 第4章 学习Shader所需的数学基础(上)(坐标系、点和矢量)
  10. Python多子图总标题title