一杯清酒邀明月
天下本无事,庸人扰之而烦耳。

QT中使用opengl

.pro文件中添加

QT += opengl


1、使用指定版本的OpenGL
如下使用opengl4.5调用方法,使用指定版本的接口,必须设备图形显示设备支持对应OpenGL版本才可。
Q:什么是CoreProfile和Compatibility Profile?
A:在OpenGL的发展历程中,总是兼顾向下兼容的特性,但是到了一定的程度之后,这些旧有的OpenGLAPI不再适应时代的需要,还有一些扩展并不是驱动一定要实现的扩展,这些被统一划入可选的CompatibilityProfile;而由OpenGL规范规定必须支持的扩展,则是Core Profile,想要支持先进的OpenGL,相应的CoreProfile扩展必须被实现。所以如果使用#include < QOpenGLFunctions > 或者 #include <QOpenGLFunctions_4_5_Core>时,会发现如glDrawPixels等接口找不到的问题。所以我们使用Cmpatibility版本。所有opengl的接口函数都可以使用。

 1 #include <QOpenGLFunctions_4_5_Cmpatibility>
 2 //QOpenGLFunctions类提供了跨平台访问的OpenGL ES 2.0 API,QOpenGLFunctions提供了一个在所有OpenGL系统上都可用的保证API,并在需要它的系统上负责功能解析。使用QOpenGLFunctions的推荐方法是直接继承:
 3 class Widget : public QOpenGLWidget, protected QOpenGLFunctions_4_5_Cmpatibility
 4 {
 5 public:
 6     Widget(QWidget *parent = 0);
 7     ~Widget();
 8     void initializeGL();                ///< 初始化
 9     void resizeGL(int w, int h);        ///< 当窗口发生变化时重新初始化
10     void paintGL();                     ///< 绘制
11 }
 1 void Widget::initializeGL()
 2 {
 3     //获取上下文
 4     //QOpenGLFunctions_4_5_Compatibility* compatibility= QOpenGLContext::currentContext()->versionFunctions<QOpenGLFunctions_4_5_Compatibility>();
 5     //compatibility->initializeOpenGLFunctions();
 6     
 7     /* 0. 初始化函数,使得函数可以使用 */
 8     initializeOpenGLFunctions();
 9 
10     const GLubyte* name = glGetString(GL_VENDOR); //返回负责当前OpenGL实现厂商的名字
11     const GLubyte* biaoshifu = glGetString(GL_RENDERER); //返回一个渲染器标识符,通常是个硬件平台
12     const GLubyte* OpenGLVersion =glGetString(GL_VERSION); //返回当前OpenGL实现的版本号
13 //    const GLubyte* OpenGLExensions =glGetString(GL_EXTENSIONS); //
14 
15     QString str;
16     str.sprintf("%s | %s | %s",name,biaoshifu,OpenGLVersion);
17 
18     qDebug()<<str;
19 }

 

2、使用QOpenGLFunctions
QOpenGLFunctions类提供跨平台访问的OpenGL ES 2.0 API,QOpenGLFunctions提供了一个在所有OpenGL系统上都可用的保证API, 并在需要它的系统上负责功能解析。使用QOpenGLFunctions的推荐方法是直接继承,同时在初始化函数中void initializeGL() 调用此接口initializeOpenGLFunctions() 进行初始化。如下:
OpenGL ES相对OpenGL删减了一切低效能的操作方式,有高性能的决不留低效能的,即只求效能不求兼容性(和苹果的作风类似)。也就是说很多opengl函数无法使用,如glDrawPixels等。
典型:
1.没有double型数据类型,但加入了高性能的定点小数数据类型。
2.没有glBegin/glEnd/glVertex,只能用glDrawArrays/glDraw…
3.没有实时将非压缩图片数据转成压缩贴图的功能,程序必须直接提供压缩好的贴图
数据类型:
1: i GLint 整数型
2: f GLfixed 定点小数
3: x GLclampx 限定型定点小数
删除的功能:
1.glBegin/glEnd
2.glArrayElement
3.显示列表
4.求值器
5.索引色模式
6.自定义裁剪平面
7.glRect
8.图像处理(这个一般显卡也没有,FireGL/Quadro显卡有)
9.反馈缓冲
10.选择缓冲
11.累积缓冲
12.边界标志
13.glPolygonMode
14.GL_QUADS,GL_QUAD_STRIP,GL_POLYGON
15.glPushAttrib,glPopAttrib,glPushClientAttrib,glPopClientAttrib
15.TEXTURE_1D、TEXTURE_3D、TEXTURE_RECT、TEXTURE_CUBE_MAP
16.GL_COMBINE
17.自动纹理坐标生成
18.纹理边界
19.GL_CLAMP、GL_CLAMP_TO_BORDER
20.消失纹理代表
21.纹理LOD限定
22.纹理偏好限定
23.纹理自动压缩、解压缩
24.glDrawPixels,glPixelTransfer,glPixelZoom
25.glReadBuffer,glDrawBuffer,glCopyPixels
其它注意事项:
1.glDrawArrays等函数中数据必须紧密排列,即间隔为0
2.各种数据的堆栈深度较低
参考代码:

 1 #ifndef WIDGET_H
 2 #define WIDGET_H
 3 
 4 #include <QOpenGLWidget>
 5 #include <QOpenGLFunctions>
 6 #include <QOpenGLShaderProgram>
 7 
 8 //QOpenGLFunctions类提供了跨平台访问的OpenGL ES 2.0 API,QOpenGLFunctions提供了一个在所有OpenGL系统上都可用的保证API,并在需要它的系统上负责功能解析。使用QOpenGLFunctions的推荐方法是直接继承:
 9 class Widget : public QOpenGLWidget, protected QOpenGLFunctions
10 {
11 public:
12     Widget(QWidget *parent = 0);
13     ~Widget();
14     void initializeGL();                ///< 初始化
15     void resizeGL(int w, int h);        ///< 当窗口发生变化时重新初始化
16     void paintGL();                     ///< 绘制
17 
18     void initVbo();                     ///< 初始化Vbo
19     void loadTextures();                ///< 加载纹理
20     void keyPressEvent(QKeyEvent * e);  ///< 键盘事件
21 private:
22     /* [1] 需要定点着色器和片段着色器,不然做不了任何渲染 */
23     /*   这里定义了一个着色器[顶点着色器、片段着色器]编译对象 */
24     QOpenGLShaderProgram * program;
25     ///< 可以根据此id,利用glGetUniformLocation等方法获取shader里面的属性
26     GLuint programid;
27 
28     ///< 其实还有视图矩阵、投影矩阵、MVP矩阵,这里简单的用下,不区分特别细!
29     ///< 分三个这样的矩阵,分别是模型矩阵、视图矩阵、透视矩阵,这样以后灵活性更强:
30     ///  1. 比如单独控制模型灯光跟随,shader可能需要传入除了mvp矩阵外的模型矩阵*视图矩阵这样一个矩阵
31     QMatrix4x4 m_projection;
32 
33     ///< 矩阵、顶点、颜色在着色器里面的位置
34     GLuint matrixLocation, vertexLocation, textureLocation,
35            samplerLocation;
36 
37     ///< 顶点、索引、颜色->buffer的标识
38     GLuint verVbo, v_indexVbo, textureVbo;
39     GLuint texture;
40 
41     int vVerticesLen;   ///< 顶点数组长度
42     int tri_indexLen;   ///< 索引数组长度
43     int textureCoordLen;///< 纹理坐标数组长度
44 };
45 
46 #endif // WIDGET_H
  1 #include "widget.h"
  2 #include<QKeyEvent>
  3 #include<QGLWidget>
  4 #include<QOpenGLFunctions_3_2_Core>
  5 
  6 Widget::Widget(QWidget *parent) : QOpenGLWidget(parent)
  7 {
  8     ///< 官方文档有这样设置,具体还没细细看,但是意思吧,就是告诉渲染属性,使用版本等;有空可以深入研究,光看效果不一定能看出什么区别!
  9     ///< 这个有些是放到main.cpp中去了,通过new widget.setFormat(format)那样去设置,不清楚...我觉得本质是一样,都是给该widget设置属性。
 10 //    QSurfaceFormat format;
 11 //    format.setDepthBufferSize(24);
 12 //    format.setStencilBufferSize(8);
 13 //    format.setVersion(3, 2);
 14 //    format.setProfile(QSurfaceFormat::CoreProfile);
 15 //    setFormat(format);
 16 }
 17 
 18 Widget::~Widget()
 19 {
 20     glDeleteBuffers(1, &verVbo);
 21     glDeleteBuffers(1, &v_indexVbo);
 22     glDeleteProgram(programid);
 23     glDeleteTextures(1, &texture);
 24 }
 25 
 26 /* 1.1 着色器代码 */
 27 /* *********************************************
 28  *   顶点着色器定义一个输入,它是 4 个成员的矢量 vPosition。
 29  *   主函数声明着色器宣布着色器开始执行。着色器主体非常简单,
 30  *   它复制输入 vPosition 属性到 gl_Position 输出变量中。
 31  *   每个顶点着色器必须输出位置值到 gl_Position 变量中,
 32  *   这个变量传入到管线的下一个阶段中。
 33  *   matrix主要是模型视图矩阵,控制位置和旋转等
 34  * ******************************************** */
 35 /* 顶点着色器 */
 36 static const char *vertexShaderSourceCore =
 37     "attribute vec4 vPosition;\n"
 38     "uniform highp mat4 matrix;\n"
 39     "attribute vec2 TexCoord;\n"
 40     "varying vec2 TexCoord0;\n"
 41     "void main() {\n"
 42     "   TexCoord0 = TexCoord;\n"
 43     "   gl_Position = matrix * vPosition;\n"
 44     "}\n";
 45 
 46 /* *********************************************
 47  *   gl_FragColor,gl_FragColor是片段着色器最终的输出值,
 48  *   本例中输出值来自外部传入的颜色数组。
 49  * ******************************************** */
 50 
 51 /* 片段着色器 */
 52 static const char *fragmentShaderSourceCore =
 53     "varying vec2 TexCoord0;\n"
 54     "uniform sampler2D gSampler;\n"
 55     "void main() {\n"
 56     "   gl_FragColor = texture2D(gSampler, TexCoord0.st);\n"
 57     "}\n";
 58 
 59 
 60 ///* 2.1 三角形顶点的坐标 */
 61 //GLfloat vVertices[] = {0.0f, 0.5f, 0.0f,
 62 //                       -0.5f, -0.5f, 0.0f,
 63 //                       0.5f, -0.5f, 0.0f};
 64 ///* 2.2 三角形顶点的索引 */
 65 //GLuint tri_index[] = {0, 1, 2};
 66 ///* 2.3 顶点颜色数组 */
 67 //GLfloat colors[] = {1.0f, 0.0f, 0.0f,0.5f,
 68 //                    0.0f, 1.0f, 0.0f,0.5f,
 69 //                    0.0f, 0.0f, 1.0f,0.5f};
 70 
 71 /* 2.1 正方体顶点的坐标 */
 72 GLfloat vVertices[] = {-0.5f, -0.5f, 0.5f,
 73                        0.5f, -0.5f, 0.5f,
 74                        -0.5f,  0.5f, 0.5f,
 75                        0.5f,  0.5f, 0.5f,
 76 
 77                        -0.5f, -0.5f,  -0.5f,
 78                        0.5f, -0.5f,  -0.5f,
 79                        -0.5f,  0.5f,  -0.5f,
 80                        0.5f,  0.5f,  -0.5f,
 81 
 82                        -0.5f, -0.5f,  -0.5f,
 83                        -0.5f, -0.5f,  0.5f,
 84                        -0.5f,  0.5f,  -0.5f,
 85                        -0.5f,  0.5f,  0.5f,
 86 
 87                        0.5f, -0.5f,  -0.5f,
 88                        0.5f, -0.5f,  0.5f,
 89                        0.5f,  0.5f,  -0.5f,
 90                        0.5f,  0.5f,  0.5f,
 91 
 92                        -0.5f, 0.5f,  -0.5f,
 93                        -0.5f, 0.5f,  0.5f,
 94                        0.5f,  0.5f,  -0.5f,
 95                        0.5f,  0.5f,  0.5f,
 96 
 97                        -0.5f, -0.5f,  -0.5f,
 98                        -0.5f, -0.5f,  0.5f,
 99                        0.5f,  -0.5f,  -0.5f,
100                        0.5f,  -0.5f,  0.5f};
101 /* 2.2 正方体顶点的索引 */
102 GLuint tri_index[] = {0, 3, 2,
103                       0, 1, 3,
104                       4, 7, 6,
105                       4, 5, 7,
106                       8, 11, 10,
107                       8, 9, 11,
108                       12, 15, 14,
109                       12, 13, 15,
110                       16, 19, 18,
111                       16, 17, 19,
112                       20, 23, 22,
113                       20, 21, 23};
114 
115 ///< 纹理点 6个面 每个面四个纹理坐标映射???好像不对呀,绘制出来花花的...
116 ///< 还得好好思考下纹理坐标和现在的顶点索引坐标如何对应!!!
117 /// 下面这个先注释掉,上面的说法:六个面,每个面四个纹理坐标映射好像不对....
118 //float texCoords[] =
119 //{
120 //    0.0f, 0.0f,
121 //    1.0f, 0.0f,
122 //    0.0f, 1.0f,
123 //    1.0f, 1.0f,
124 
125 //    0.0f, 0.0f,
126 //    1.0f, 0.0f,
127 //    0.0f, 1.0f,
128 //    1.0f, 1.0f,
129 
130 //    0.0f, 0.0f,
131 //    1.0f, 0.0f,
132 //    0.0f, 1.0f,
133 //    1.0f, 1.0f,
134 //    0.0f, 0.0f,
135 //    1.0f, 0.0f,
136 //    0.0f, 1.0f,
137 //    1.0f, 1.0f,
138 //    0.0f, 0.0f,
139 //    1.0f, 0.0f,
140 //    0.0f, 1.0f,
141 //    1.0f, 1.0f,
142 //    0.0f, 0.0f,
143 //    1.0f, 0.0f,
144 //    0.0f, 1.0f,
145 //    1.0f, 1.0f
146 //};
147 ///< 我们就来手动修改下,直到不花为止,然后回过头去细细分析下,具体原因?
148 ///  这样学习起来更快,毕竟现有感觉,带着感觉去更加有激情和感悟...
149 ///  不过按照自己认为的坐标去对应,始终不对(上下两面还是花)...哎,缓一缓,先仔细研究下再回过头来修改...
150 float texCoords[] =
151 {
152     0.0f, 0.0f,
153     1.0f, 0.0f,
154     0.0f, 1.0f,
155     1.0f, 1.0f,
156 
157     0.0f, 0.0f,
158     1.0f, 0.0f,
159     0.0f, 1.0f,
160     1.0f, 1.0f,
161 
162     0.0f, 0.0f,
163     1.0f, 0.0f,
164     0.0f, 1.0f,
165     1.0f, 1.0f,
166 
167     0.0f, 0.0f,
168     1.0f, 0.0f,
169     0.0f, 1.0f,
170     1.0f, 1.0f,
171 
172     0.0f, 0.0f,
173     1.0f, 0.0f,
174     0.0f, 1.0f,
175     1.0f, 1.0f,
176 
177     0.0f, 0.0f,
178     1.0f, 0.0f,
179     0.0f, 1.0f,
180     1.0f, 1.0f
181 };
182 
183 /**
184  * @brief 初始化模型信息vbo【显存】
185  */
186 void Widget::initVbo()
187 {
188     ///< 计算获得数组长度,之后会用到该变量,这样只需要改动这里即可!如果用链表,直接.size()即可求出!
189     vVerticesLen = sizeof(vVertices)/sizeof(GLfloat);
190     tri_indexLen = sizeof(tri_index)/sizeof(GLuint);
191     textureCoordLen = sizeof(texCoords)/sizeof(GLfloat);
192 
193     qDebug() << vVerticesLen;
194     qDebug() << tri_indexLen;
195 
196     ///< 初始化顶点buffer并装载数据到显存
197     glGenBuffers(1, &verVbo);
198     glBindBuffer(GL_ARRAY_BUFFER, verVbo);
199     glBufferData(GL_ARRAY_BUFFER, vVerticesLen * sizeof(GLfloat), vVertices, GL_STATIC_DRAW);
200 
201     ///< 初始化索引buffer并装载数据到显存
202     glGenBuffers(1, &v_indexVbo);
203     glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, v_indexVbo);
204     glBufferData(GL_ELEMENT_ARRAY_BUFFER, tri_indexLen * sizeof(GLuint), tri_index, GL_STATIC_DRAW);
205 
206     ///< 初始化纹理坐标buffer并装载到显存
207     glGenBuffers(1, &textureVbo);
208     glBindBuffer(GL_ARRAY_BUFFER, textureVbo);
209     glBufferData(GL_ARRAY_BUFFER, textureCoordLen * sizeof(GLfloat), texCoords, GL_STATIC_DRAW);
210 }
211 
212 /**
213  * @brief 装载纹理,具体纹理知识可以参考网友资料:
214  * http://www.cnblogs.com/tornadomeet/archive/2012/08/24/2654719.html
215  */
216 void Widget::loadTextures()
217 {
218     QImage tex, buf;
219     if (!buf.load("../2012082420060914.jpg"))
220     {
221         qWarning("annot open the image...");
222         QImage dummy(128, 128, QImage::Format_RGB32);
223         dummy.fill(Qt::green);
224         buf = dummy;
225     }
226 
227     ///< 转换为OpenGL支持的格式
228     tex = QGLWidget::convertToGLFormat(buf);
229 
230     ///< 开辟一个纹理内存,内存指向texture
231     glGenTextures(1, &texture);
232     ///< 将创建的纹理内存指向的内容绑定到纹理对象GL_TEXTURE_2D上,
233     ///  经过这句代码后,以后对GL_TEXTURE_2D的操作的任何操作都同时对应与它所绑定的纹理对象
234     glBindTexture(GL_TEXTURE_2D, texture);
235     ///< 开始真正创建纹理数据
236     glTexImage2D(GL_TEXTURE_2D, 0, 3, tex.width(), tex.height(),
237                  0, GL_RGBA, GL_UNSIGNED_BYTE, tex.bits());
238 
239     ///< 当所显示的纹理比加载进来的纹理小时,采用GL_LINEAR的方法来处理
240     glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
241     ///< 当所显示的纹理比加载进来的纹理大时,采用GL_LINEAR的方法来处理
242     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
243 }
244 
245 void Widget::initializeGL()
246 {
247     qDebug("+++ initializeGL +++");
248     /* 0. 初始化函数,使得函数可以使用 */
249     initializeOpenGLFunctions();
250 
251     /* 创建项目对象链接着色器 */
252     /* 1. 初始化最大的任务是装载顶点和片段着色器 */
253     program = new QOpenGLShaderProgram(this);
254     /* 一旦应用程序已经创建了顶点、片段着色器对象,
255        * 它需要去创建项目对象,项目是最终的链接对象,
256        * 每个着色器在被绘制前都应该联系到项目或者项目对象。
257        * ***************************************** */
258     /* 1.2 加载 */
259     if(!program->addShaderFromSourceCode(QOpenGLShader::Vertex, vertexShaderSourceCore))
260     {
261         return;
262     }
263     if(!program->addShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShaderSourceCore))
264     {
265         return;
266     }
267 
268     /* 1.3 设置属性位置,将vPosition属性设置为位置0, vertex为位置1
269            这里我就让程序自动分配,当然你也可以手动; 我在后面通过代码获取到了!
270     */
271     //program->bindAttributeLocation("vertex", 1);
272     //program->bindAttributeLocation("vPosition", 0);
273     //program->bindAttributeLocation("a_color", 1);
274     //program->bindAttributeLocation("matrix", 2);
275 
276     /* 1.4 链接项目检查错误 */
277     if( !program->link() )
278     {
279         return;
280     }
281 
282     if( !program->bind() ){
283         return ;
284     }
285 
286     ///< 获取shaderprogram的id号,然后可以通过id号获取一些属性...
287     programid = program->programId();
288 
289     ///< 从shaderprogram里面获取变量标识,总共用到两种方式,看你喜好!倾向第一种
290     matrixLocation = glGetUniformLocation(programid, "matrix");
291     vertexLocation = glGetAttribLocation(programid, "vPosition");
292     textureLocation = program->attributeLocation("TexCoord");
293     samplerLocation = program->uniformLocation("gSampler");
294 
295     ///< 初始化vbo,对于实时变化的数据,可能需要在paintGL()里面每次调用!
296     initVbo();
297 
298     ///< 装载纹理
299     loadTextures();
300 
301     ///< 允许采用2D纹理技术
302     glEnable(GL_TEXTURE_2D);
303     ///< 设置背景颜色
304     glClearColor(0.5f, 0.5f, 0.5f, 0.0f);
305     ///< 开启深度测试,避免颜色相互透过,具体需要自己深入学习的哦!
306     glEnable(GL_DEPTH_TEST);
307     ///< 设置深度测试类型 - 不设置也会默认
308     glDepthFunc(GL_LEQUAL);
309 
310     ///< 这个地方先于resizeGL运行,所以这里设置无效!我一开始犯了这个错误!!!
311     //m_projection.translate(0.0f, 0.0f, -1.0f);
312 }
313 
314 void Widget::resizeGL(int w, int h)
315 {
316     /* 2.1 viewport 设定窗口的原点 origin (x, y)、宽度和高度 */
317     glViewport(0, 0, w, h);
318 
319     ///< 模型矩阵重置
320     m_projection.setToIdentity();
321     ///< 透视投影【做了简单容错】
322     qreal aspect = qreal(w) / qreal(h ? h : 1);
323     m_projection.perspective(60.0f, aspect, 1.0f, 100.0f);
324     ///< 增加了模型矩阵,需要做一定偏移量,保证物体刚开始渲染出来时可以被看到!
325     m_projection.translate(0.0f, 0.0f, -2.0f);
326 }
327 
328 void Widget::paintGL()
329 {
330     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
331 
332     ///< shader传入模型视图矩阵
333     glUniformMatrix4fv(matrixLocation, 1, GL_FALSE, m_projection.data());
334 
335     ///< shader绑定并启用顶点数组buffer
336     glBindBuffer(GL_ARRAY_BUFFER, verVbo);
337     glEnableVertexAttribArray(vertexLocation);
338     ///< 顶点xyz坐标,所以每三个作为一个顶点值
339     glVertexAttribPointer( vertexLocation, 3, GL_FLOAT, GL_FALSE, 0, 0);
340 
341     ///< shader绑定并顶点索引数组buffer - 索引无需启用
342     glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, v_indexVbo);
343 
344     ///< 绑定纹理
345     glUniform1i(samplerLocation, 0);
346     glActiveTexture(GL_TEXTURE_2D);
347     glBindTexture(GL_TEXTURE_2D, texture);
348     glBindBuffer(GL_ARRAY_BUFFER, textureVbo);
349     glEnableVertexAttribArray(textureLocation);
350     ///< 2是标识两个float为一个纹理坐标,从varying vec2 TexCoord0也可以看出!
351     glVertexAttribPointer(textureLocation, 2, GL_FLOAT, GL_FALSE, 0, 0);
352 
353     glDrawElements(GL_TRIANGLES, tri_indexLen, GL_UNSIGNED_INT, 0);
354 
355     ///< 解绑buffer、关闭启用顶点、颜色数组、解绑纹理
356     glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
357     glBindBuffer(GL_ARRAY_BUFFER, 0);
358     glBindTexture(GL_TEXTURE_2D, 0);
359     glBindBuffer(GL_ARRAY_BUFFER, 0);
360     glDisableVertexAttribArray(textureLocation);
361     glDisableVertexAttribArray(vertexLocation);
362 }
363 
364 /**
365  * @brief 写了键盘监听事件,可以控制模型旋转,方便预览
366  * @param k
367  */
368 void Widget::keyPressEvent(QKeyEvent * k)
369 {
370     qDebug("+++ keyPressEvent +++");
371     if(k->key() == Qt::Key_A)
372     {
373         m_projection.rotate(4, 0, 1, 0);
374     }
375     else if(k->key() == Qt::Key_D)
376     {
377         m_projection.rotate(-4, 0, 1, 0);
378     }
379     else if(k->key() == Qt::Key_W)
380     {
381         m_projection.rotate(4, 1, 0, 0);
382     }
383     else if(k->key() == Qt::Key_S)
384     {
385         m_projection.rotate(-4, 1, 0, 0);
386     }
387     update();
388 }
 1 #include "widget.h"
 2 #include <QApplication>
 3 
 4 int main(int argc, char *argv[])
 5 {
 6     QApplication a(argc, argv);
 7 
 8     Widget widget(0);
 9     widget.show();
10 
11     return a.exec();
12 }

 

 

 

 

 

 

posted on 2020-01-19 13:56  一杯清酒邀明月  阅读(10354)  评论(0编辑  收藏  举报