opengl 如何加阴影_OpenGL + Qt: 3 - 旋转动画和键盘操纵

前三篇链接:

OpenGL + Qt: 0 - 三角形绘制

OpenGL + Qt: 1 - 用下拉框选颜色

OpenGL + Qt: 2 - 走向3D,画正四面体

这一周笔者经历了漫长的洲际飞行和昏天黑地的倒时差,所以本篇内容相对少一些,侧重 Qt 而不是 OpenGL。在上一篇中,我们绘制了一个正四面体,然而正四面体的一个特点是无论你从哪个角度看,同时至多只能看到三个面。为了能更好地观察绘制效果,我们尝试变换观察的镜头位置来看被隐藏的面。这一篇中我们将实现动画效果,通过不断旋转被观察的四面体来看到它的四个面。同时我们再通过键盘操控相机位置,从而更好地展示不同相机位置导致的绘制效果变化。

QElapsedTimer 计时

如果要做动画效果,首先我们需要知道程序运行到了什么时间,然后根据时间计算出此时的图像信息,从而进行渲染。不同的操作系统和平台有各种各样的计时 API,但是 Qt 提供了一个简单方便的工具 QElapsedTimer 来完成这件事。其功能非常简单,就是给出从它的 start() 方法执行之后到现在经过了多久的时间。

首先我们先在 PaintingWidget 的声明中添加这个东西,然后在构造函数中初始化它,并启动它。已有的代码我这里直接忽略,请参考前面几篇文章。

class PaintingWidget : public QOpenGLWidget
{......
private:......QElapsedTimer *m_timer;
};PaintingWidget::PaintingWidget(QWidget* parent){......m_timer = new QElapsedTimer;m_timer->start();
}

接下来在 paintGL() 中,我们只需要调用 m_timer->elapsed() 就可以得到从 start() 到绘图程序执行时刻,经过了多少时间,单位为毫秒。

旋转矩阵

对于一个刚体而言,我们知道其旋转变换是一个线性变换,通过一个旋转矩阵确定。在二维中,图形的旋转是围绕着一个点进行的。众所周知,二维图形围绕原点旋转

角的旋转矩阵是

但是在三维世界里,旋转变得复杂很多,旋转中心不再是一个点,而是一个轴。考虑到图形学中常用的是齐次坐标系,还需要处理第四维的情况。不过 Qt 的 QMatrix4x4 提供了简单的接口帮我们完成这些复杂的数学计算:

void QMatrix4x4::rotate(float angle, const QVector3D &vector);

rotate()函数的第一个参数是旋转的角度,以度数,为单位,而第二个参数是旋转轴,必须以 QVector3D 类型传入。我们在上一篇提到过,MVP矩阵是 P * V * M,物体本身的旋转、放缩等变换就是编码在 M 矩阵中,因此我们在绘图函数中计算 MVP 矩阵的地方,添加旋转代码:

void PaintingWidget::paintGL()
{QOpenGLFunctions *f = this->context()->functions();f->glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);f->glClearColor(0.0f, 0.2f, 0.0f, 1.0f);m_vao->bind();m_shader->bind();QMatrix4x4 mvp;mvp.perspective(45.0f, this->aspectRatio, 0.1f, 100.0f);mvp.lookAt(QVector3D(0.0f, 3.0f, 0.0f), QVector3D(0.0f, 0.0f, 0.0f), QVector3D(1.0f, 0.0f, 0.0f));float time_in_second = (float)m_timer->elapsed() / 1000;mvp.rotate(30.0f * time_in_second, QVector3D(0.7f, 0.5f, 0.2f));m_shader->setUniformValue(m_shader->uniformLocation("MVP"), mvp);f->glDrawArrays(GL_TRIANGLES, 0, 4 * 3);m_shader->release();m_vao->release();this->update();
}

这里我们通过 m_timer 获得程序开始运行到此刻的时间,然后以每秒 30° 的速度围绕轴 (0.7, 0.5, 0.2) 进行旋转。这个轴是我随便选的,为的是能够看到四面体的四个面。这段代码的最后一行调用了 update() 方法。在前面我们提到过,这个方法是发出一个绘图请求,但并不立即执行,而是等到下一个处理时间节点再执行。另一个要求重新绘图的方法是 repaint(),这个方法会立即触发绘图,但是 repaint() 会调用 paintGL() 来完成绘图,因此如果在 paintGL() 中调用 repaint() 会导致无穷递归而使程序崩溃。

至此,旋转动画效果就实现了,效果如下图。

4c1298287780e76aa3077718b0651988.png

键盘控制相机位置

在大多数 OpenGL 教程中,都会介绍如何使用各种工具库(如 GLUT)实现用键盘和鼠标操纵图像。这对于 Qt 来说则更是小菜一碟,毕竟 Qt 的老本行就是做用户界面,有非常完善的各种输入设备的响应机制。

与一般控件的响应机制不同,Qt 中键盘事件的响应机制反倒是很简单,直接通过重载 QWidget 上的各种事件处理器来实现,并不需要使用信号-槽机制。最基本的键盘事件有两种:key press 和 key release,分别由 QWidget::keyPressEvent() 和 QWidget::keyReleaseEvent() 两个事件处理方法进行处理。

这里我仅仅简单实现用方向键控制相机的位置。默认情况下,我们的相机位置在四面体正上方(0, 3, 0)的位置,为了简单起见,我们设置相机观察的中心点为相机位置到 y=0 平面的投影点,用方向键控制前后左右的位置,即相机的 x 坐标和 z 坐标;用正负号键控制相机高度。首先我们需要在 PaintingWidget 的声明部分添加 keyPressEvent() 和相机位置变量 camera_pos:

class PaintingWidget : public QOpenGLWidget
{......
protected:......void keyPressEvent(QKeyEvent *keyEvent);
private:......QVector3D camera_pos;
}

在构造函数中,将相机坐标初始化为默认位置,并且还需要使用 setFocusPolicy() 设置这个 Widget 获取焦点的方式,从而确保它能够得到焦点

PaintingWidget::PaintingWidget(QWidget* parent):QOpenGLWidget (parent), camera_pos(0.0f, 3.0f, 0.0f), ......{......setFocusPolicy(Qt::StrongFocus);
}

在绘图的时候,我们需要计算出相机观察的中心点,同样取x正方向为上方向。

    QVector3D center(camera_pos);center.setY(0);mvp.lookAt(camera_pos, center, QVector3D(1.0f, 0.0f, 0.0f));

这样在重载 keyPressEvent() 的代码中,我们就将右方向键设置为x坐标加一个量,因为 OpenGL 是一个右手系,右方向键就变为z坐标加一个量,最后别忘了请求绘图,代码如下:

void PaintingWidget::keyPressEvent(QKeyEvent *keyEvent){switch (keyEvent->key()){case Qt::Key_Right:camera_pos.setZ(camera_pos.z() + 0.1f);break;case Qt::Key_Left:camera_pos.setZ(camera_pos.z() - 0.1f);break;case Qt::Key_Up:camera_pos.setX(camera_pos.x() + 0.1f);break;case Qt::Key_Down:camera_pos.setX(camera_pos.x() - 0.1f);break;case Qt::Key_Plus:camera_pos.setY(camera_pos.y() + 0.1f);break;case Qt::Key_Minus:camera_pos.setY(camera_pos.y() - 0.1f);break;}update();
}

大功告成,现在这个 demo 开始变得丰富起来,我们可以通过键盘看到相机在不同位置观察物体得到的效果,也更有 3D 的感觉了!

本期代码链接:https://github.com/linmx0130/QGLDemo/tree/ch3

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/486359.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

mybatis java类注解式_Spring整合Mybatis注解方式

Spring整合Mybatis(注解方式)环境准备jar包:Spring所需依赖:spring-context、spring-aspects、aspectjrt、aspectjweaver、spring-tx、spring-jdbcmybatis所需要:mybatis、mybatis-spring、mysql-connector-java、(druid、c3p0、HikariCP)辅助…

2020十大新兴技术揭晓!每一项都可能颠覆我们的生活

转自丨科技日报作者丨张佳欣编辑丨翟丽影全世界都在竞相研发新冠肺炎疫苗,前景令人鼓舞,我们可能会在破纪录的时间内研发出疫苗。但在未来新冠疫情仍在蔓延的情况下,技术能帮助我们更快地实现目标吗?世界经济论坛和《科学美国人》杂志本月10…

c#_导出table功能

一:第一张导出方法,简单快捷 请注意:一般表格都有真分页,查询数据时候注意把分页条件去掉#region 此处是获取的list数组 然后转table再调用ExportExcel var list"你的list数据库源"DataTable dt new DataTable();dt.…

java 协议处理器_协议处理器urlstreamhandler及contenthandler

先看段打开网页的代码:URL urlnew URL("http://souljava.blog.163.com/");URLConnection connectionurl.openConnection();connection.getInputStream();问题1:客户端浏览器怎么判断接受到的是什么数据类型?回答:java的…

自己做一个小项目python_【Python】第一个微信小项目

一、微信好友数据分析功能介绍1.爬取好友列表,显示好友昵称、性别和地域和签名, 文件保存为 xlsx 格式2.统计好友的地域分布,并且做成词云和可视化展示在地图上3.获取所有好友的头像,合并成一张大图二、需要的库1、Pyecharts:一个用于生成ech…

RISC-V的自定义CPU悖论

来源:内容编译自「eejournal」,谢谢。随着这些年的发展,RISC-V的受重视程度与与日俱增。这主要因为它是免费的、灵活的,并且速度很快。这使RISC-V成为许多开发人员的安全便捷选择。但是您会认为RISC-V是通用RISC处理器还是定制的随…

java计算加速减速_Javascript加速运动与减速运动

加速运动,即一个物体运动时速度越来越快;减速运动,即一个物体运动时速度越来越慢。现在用Javascript来模拟这两个效果,原理就是用setInterval或setTimeout动态改变一个元素与另外一个元素的距离,如xxx.style.left或xxx…

python 运维包_基础入门_Python-模块和包.运维开发中__import__动态导入最佳实践?

常规导入:import module_name[,module1,...]from module_name import [*|child[,child1,...]from module_name import [*|child[,child1,...] as alias_name注意: 导入语句可出现在程序任意位置,自定义包要实现from module_name import *的效果则此模块必须在__init__.py实现__…

华为宣布出售荣耀,声明来了

来源:澎湃新闻综合华为官网、《深圳特区报》今天,华为发表声明,整体出售荣耀业务资产。对于交割后的荣耀,华为不占有任何股份,也不参与经营管理与决策。11月17日一早,荣耀最终的出售方案公布。多家企业在《…

Node.js教程-mysql模块

概述 在Node.js中,mysql模块是实现MySQL协议的JavaScript客户端工具。Node.js程序通过与MySQL建立链接,然后可对数据进行增、删、改、查等操作。 安装 由于mysql模块不是Node.js内置模块,需手动安装 npm i mysql注意:若MySQL服…

java 几种引用类型_Java 四种引用类型总结-Fun言

1. Java引用介绍Java从1.2版本开始引入了4种引用,这4种引用的级别由高到低依次为:强引用 > 软引用 > 弱引用 > 虚引用⑴ 强引用(Strong Reference)强引用是使用最普遍的引用,也是赋值的默认引用。如果一个对象具有强引用&…

JS运算符类型

一、运算符类型 1、算术运算符&#xff1a;用于各类数值运算&#xff0c;包括加()、减(-)、乘(*)、除(/)、求余(或称模运算&#xff0c;%)、自增()、自减(--)共七种。2、关系运算符&#xff1a;用于比较运算。包括大于&#xff08;>&#xff09;、小于&#xff08;<&…

python关于字符串的内置函数_Python 字符串内置函数(二)

# 2.格式化相关# ljust(width) 函数 获取固定长度&#xff0c;左对齐&#xff0c;右边不够用空格补齐# rjust(width) 函数 获取固定长度&#xff0c;右对齐&#xff0c;左边不够用空格补齐# center(width) 函数 获取固定长度&#xff0c;中间对齐&#xff0c;两边不够用空格补齐…

边缘计算4.0正急速驶来,你做好准备了吗?

物联网智库 整理发布导 读近十年间&#xff0c;各行各业的企业都经历着数字化技术和产品对传统生产经营模式的冲击。人工智能、大数据、区块链、自动化等技术的日渐完善&#xff0c;让企业的生产效率和业务模式发生了翻天覆地的变化。

java中for的常规用法_Java for循环的几种用法详解

本文非常适合初学Java的程序员&#xff0c;主要是来了解一下Java中的几种for循环用法&#xff0c;分析得十分详细&#xff0c;一起来看看。J2SE 1.5提供了另一种形式的for循环。借助这种形式的for循环&#xff0c;可以用更简单地方式来遍历数组和Collection等类型的对象。本文介…

运维-替换-修改kibana徽标

作为一名纯运维人员&#xff0c;想更改kibana的徽标。 并不能像开发一样去看源码并修改源码。 所以我们可以替换徽标。 先来一个效果图。我的版本是5.5.1。 具体的修改过程&#xff1a; 在kibana安装路径下面。 optimize/bundles/ 0cebf3d61338c454670b1c5bdf5d6d8d.svg 这…

python中的变量的作用_Python中的变量作用域

python中变量作用域包括&#xff1a;L (Local) 局部作用域&#xff0c;函数内部声明但没有使用global的变量E (Enclosing) 闭包函数外的函数中&#xff0c;def或者lambda的本地作用域G (Global) 全局作用域&#xff0c;函数中使用global声明的变量或在模块层声明的变量B (Built…

欧拉公式——真正的宇宙第一公式

来源&#xff1a;数学中国欧拉公式是数学里最令人着迷的公式之一&#xff0c;它将数学里最重要的几个常数联系到了一起&#xff1a;两个超越数&#xff1a;自然对数的底e&#xff0c;圆周率π&#xff1b;两个单位&#xff1a;虚数单位i和自然数的单位1&#xff0c;以及数学里常…

SpringBoot入门篇--整合mybatis+generator自动生成代码+druid连接池+PageHelper分页插件

我们这一一篇博客讲的是如何整合Springboot和Mybatis框架&#xff0c;然后使用generator自动生成mapper&#xff0c;pojo等文件。然后再使用阿里巴巴提供的开源连接池druid&#xff0c;这个连接池的好处我就不说了&#xff0c;集合了所有连接池的好处&#xff0c;并且还提供了监…

python提取部分字符串三参数_python3 字符串属性(三)

maketrans 和 translate的用法(配合使用)下面是python的英文用法解释maketrans(x, yNone, zNone, /)Return a translation table usable for str.translate().If there is only one argument, it must be a dictionary mapping Unicodeordinals (integers) or characters to Un…