php+mysql网站开发全程实例.pdf,海外 推广网站,网站的建设可以起到什么作用是什么,优化营商环境心得体会个人伴随资源 目录 一、说明二、 PyQt4三、PyOpenGL四、OpenGL 管线五、Python集成开发环境5.1 Emacs配置5.2 pycharm环境 六、你好#xff0c;OpenGL#xff01;七、QGL控件八、平截头体.svg九、定义几何9.1 立即模式与保留模式9.2 使用 VBO 定义 Cube 十、渲染立方体十一、渲…伴随资源 目录 一、说明二、 PyQt4三、PyOpenGL四、OpenGL 管线五、Python集成开发环境5.1 Emacs配置5.2 pycharm环境 六、你好OpenGL七、QGL控件八、平截头体.svg九、定义几何9.1 立即模式与保留模式9.2 使用 VBO 定义 Cube 十、渲染立方体十一、渲染循环十二、添加旋转滑块 一、说明
在本教程中我们将编写一个小型 Python 脚本该脚本在 GUI 中呈现一个立方体并使用滑块来控制其旋转。这将基于其他教程即本教程但会更详细地解释该过程和一般 OpenGL 概念。您可以在此处下载完整的脚本。
二、 PyQt4
有许多不同的框架用于在 python 中创建 GUI - 内置选项是TkInter它提供了跨平台Tk GUI 工具包的包装器该工具包具有简单的学习曲线并且适用于小型应用程序但是流行的现代跨平台-平台解决方案是Qt。
Qt 的 python 端口称为 PyQt有两个最新版本 - PyQt4 和 PyQt5 - 可以安装在 python 2 或 3 中。由于已经存在许多使用 PyQt4 的教程而且核心库似乎很多版本之间相同我们将使用 PyQt4。
这个包可以安装在linux上
apt-get install python-qt4 如果您使用的是 Windows则与许多其他 python 包一样您可以使用从Unofficial Windows Binaries for Python Extension Packages pagepip install下载的 python 轮来安装 PyQt4 。如果您更喜欢从源代码构建请参阅本指南。
三、PyOpenGL
还有许多不同的库可用于在 python 中创建 3D 图形但最常见的跨平台解决方案是 OpenGL - 特别是使用PyOpenGL包装器。您应该能够pip install pyopengl在 Linux 上轻松安装它或者再次使用此处pip install下载的轮子进行安装。在 Ubuntu 16.04 上我发现我必须使用而不是 ‘’‘pip’‘’
apt-get install python-qt4-gl PyQt 在其QtOpenGL模块中使用与 PyOpenGL 相同的系统安装以提供特殊的 OpenGL QWidget从而可以轻松进行交互。下面详细介绍这一点。
四、OpenGL 管线
请注意我们将在此处使用的 OpenGL 功能主要是固定功能管道的一部分实际上在 OpenGL 3.0 之后已弃用转而使用可编程基于着色器管道该管道利用现代 GPU 并行性来高效渲染。不幸的是网络上的大多数教程都使用已弃用的管道而且它也恰好是我使用旧模拟器所习惯的管道。稍后我可能会尝试将所有内容切换到所谓的现代 OpenGL但现在这里有一篇很好的读物它用简单的术语解释了这些差异。
五、Python集成开发环境
在我们深入研究使用 Qt OpenGL 创建简单应用程序的示例之前您可能想知道使用哪些工具在 Python 中进行开发。有很多、很多、很多文章专门讨论这一点其细节比我所能提供的要详细得多但我想说的是通过一些工作好的 ol’ Emacs 可以变成一个很棒的轻量级 IDE因为所有涉及 Emacs 的东西都需要好处是如果您已经使用 Emacs 处理其他事情它可以很好地集成到您的工作流程中。下面我将详细介绍我的 Emacs 设置。
如果您要从 Matlab 过渡到 Python 并寻找类似的工具Spyder是一个不错的选择并且与一些 Python 发行版中的其他工具配合使用。在使用 JetBrains 的 CLion 进行 C/C 开发后我很想尝试PyCharm我听说过它很棒不过除非您是学生否则它不是免费的。
5.1 Emacs配置
Emacs 生态系统中有很多用于 Python 开发的优秀软件包但我最近对Elpy很感兴趣您可以在这里了解更多信息。要让 Elpy 使用 python3 而不是 python2请查看此线程以获取说明。
Elpy 内置了一些很棒的功能例如自动完成但您可能希望使用其他一些软件包来扩展它。要查看当前集成的内容请打开 Emacs 并运行M-x elpy-config这将为您提供一个小界面来检查已安装的内容。如果您想进一步进行设置您可以按照这个很棒的指南来启用 Flyspell 以获得更好的语法检查等。
非常重要的是Elpy 使用Flake8进行语法检查并且可以配置为编辑文件~/.flake8可能尚不存在如果不存在则创建它。例如Flake8 突出显示了许多令人烦恼的小语法错误因此您可以添加到此配置文件中
[flake8]
max-line-length 99
max-doc-length 79
ignore E2,E302,E41,E303我们还将行长度设置为 99文档字符串长度设置为 79正如python.org 所建议的那样尽管标准 python 库使用更严格的长度。
最后作为旁注如果您的 Emacs 设置中的任何内容要求您在“临时”缓冲区中计算 lisp 表达式请不要惊慌 - 请查看此页面以获取有关如何执行此操作的帮助。可以在 Emacs 中CTRL-X LEFTARROW从主缓冲区轻松访问“临时”缓冲区。
5.2 pycharm环境
如果您在windows下采用pycharm开发环境一切变得异常简单无需什么特殊配置只要安装好QT、OpenGL就一切就绪。
六、你好OpenGL
现在我们已经接触了 IDE 的设置和选择让我们开始导入必要的模块并了解它们各自提供的内容。
from PyQt4 import QtCore # core Qt functionality
from PyQt4 import QtGui # extends QtCore with GUI functionality
from PyQt4 import QtOpenGL # provides QGLWidget, a special OpenGL QWidgetimport OpenGL.GL as gl # python wrapping of OpenGL
from OpenGL import GLU # OpenGL Utility Library, extends OpenGL functionalityimport sys # well need this later to run our Qt application无需将其转变为 Qt 教程的介绍因为我们现在确实想专注于 OpenGL 集成任何应用程序的主窗口都是由派生自QtGui.QMainWindow.让我们创建主窗口类然后给它一个名称并在初始化程序中调整它的大小
class MainWindow(QtGui.QMainWindow):def __init__(self):QtGui.QMainWindow.__init__(self) # call the init for the parent classself.resize(300, 300)self.setWindowTitle(Hello OpenGL App)if __name__ __main__:app QtGui.QApplication(sys.argv)win MainWindow()win.show()sys.exit(app.exec_())如果您运行到目前为止的所有内容则应该打开一个指定大小、名称为“Hello OpenGL App”的 Qt 窗口并且可以退出。多么激动人心但是我们如何使用 OpenGL 在该窗口中渲染某些内容呢这就是QGLWidget进来的地方。 QGL控件 定义几何 立即模式与保留模式 使用 VBO 定义 Cube 渲染立方体 渲染循环 添加旋转滑块 设置 七、QGL控件
QGLWidget是一个 Qt 小部件旨在使用 OpenGL 轻松渲染图形。我们通过子类化 QGLWidget 并实现三个提供的虚拟函数来实现这一点这些函数在必要时由 Qt 自动调用
initializeGL在调用调整大小或绘制之前调用一次以设置 OpenGL 渲染上下文 resizeGL在创建窗口以及调整窗口大小以设置 OpenGL 视口和投影时调用一次 paintGL在更新小部件以渲染场景时调用 让我们继承 QGLWidget 并开始填充这些函数。
class GLWidget(QtOpenGL.QGLWidget):def __init__(self, parentNone):self.parent parentQtOpenGL.QGLWidget.__init__(self, parent)在派生类的初始化程序中我们调用父GLWidget类的初始化程序。
def initializeGL(self):self.qglClearColor(QtGui.QColor(0, 0, 255)) # initialize the screen to bluegl.glEnable(gl.GL_DEPTH_TEST) # enable depth testing在初始化函数中启用深度测试使 OpenGL 自动确保“片段”根据其在深度缓冲区中的值正确渲染。
def resizeGL(self, width, height):gl.glViewport(0, 0, width, height)gl.glMatrixMode(gl.GL_PROJECTION)gl.glLoadIdentity()aspect width / float(height)GLU.gluPerspective(45.0, aspect, 1.0, 100.0)gl.glMatrixMode(gl.GL_MODELVIEW)该resizeGL函数有更多的设置发生。首先glViewport(x,y,width,height)指定窗口的哪一部分用于绘图通常设置为使用窗口的完整宽度和高度传递给resizeGL。
接下来glMatrixMode(mode)将活动矩阵堆栈设置为投影堆栈其中包含用于定义查看体积的投影变换。为了定义这个变换我们首先使用 加载单位矩阵到投影堆栈glLoadIdentity()然后使用 定义视锥体gluPerspective(field_of_view, aspect_ratio, z_near, z_far)
八、平截头体.svg
透视投影创建的观察体积称为截锥体如上所示垂直视场 (FOV) 直接在第一个参数中指定而水平视场则根据垂直视场和纵横比定义近端和远端剪裁平面也是输入。
有一个更通用的函数glFrustum可用于创建离轴透视投影实际上在幕后gluPerspective调用。我们也可以使用正交投影来glFrustrum定义观看体积。glOrtho这会创建一个矩形棱柱的观看体积并且不切实际地导致位于不同深度z 值的相同高度的对象被绘制为相同的尺寸。我们将在这里坚持使用透视投影。
正交.svg
最后我们将矩阵模式设置回GL_MODELVIEW用于所有后续相机和模型转换的矩阵模式。一般来说我们不应该使用GL_PROJECTIONexcept 来定义观看量。
def paintGL(self):
gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT)# Add rendering code here!最后GLWidget要实现的虚拟函数是paintGL我们将在其中进行所有渲染。目前我们所做的只是使用glClear(bitmask)掩码的按位或来进行一些预渲染内务处理告诉 OpenGL 要清除哪些缓冲区。我们清除颜色和深度缓冲区以便每次都从干净的状态开始渲染步骤。
让我们GLWidget通过创建一个对象并将其设置为我们的中心小部件来使用我们新完成的类MainWindow如下所示。
class MainWindow(QtGui.QMainWindow):def __init__(self):QtGui.QMainWindow.__init__(self) # call the init for the parent classself.resize(300, 300)self.setWindowTitle(Hello OpenGL App)glWidget GLWidget(self)self.setCentralWidget(glWidget)所有QMainWindow对象都必须有一个中央小部件其他小部件可以停靠在该中央小部件上。运行我们到目前为止编写的所有内容您应该看到一个空白的蓝色窗口而不是一个空白的黑色窗口因为我们使用 OpenGL 在initializeGL.
hello_opengl_blue.svg
万岁现在我们已经完成了GLWidget设置让我们做一些更有趣的事情。
九、定义几何
9.1 立即模式与保留模式
就渲染简单几何体而言有两种模式。立即模式包括将绘图命令夹在glBegin和glEnd命令之间导致性能较差因为 GPU 必须等待glEnd然后立即渲染。保留模式是现代方法几何体不是在每个周期立即调用渲染而是使用所谓的顶点缓冲区对象 (VBO)定义这些对象被发送到并存储在 GPU 上进行渲染。除非需要更新VBO否则后续渲染不需要CPU和GPU之间的通信因为VBO数据存储在图形硬件上。
9.2 使用 VBO 定义 Cube
首先我们将导入 python OpenGL 包提供的 VBO 类以及numpy通用数组
from OpenGL.arrays import vbo
import numpy as np现在我们准备定义我们正在绘制的立方体的几何形状。让我们向我们的GLWidget类添加一个新函数initGeometry我们将在该类中定义立方体。
def initGeometry(self):self.cubeVtxArray np.array([[0.0, 0.0, 0.0],[1.0, 0.0, 0.0],[1.0, 1.0, 0.0],[0.0, 1.0, 0.0],[0.0, 0.0, 1.0],[1.0, 0.0, 1.0],[1.0, 1.0, 1.0],[0.0, 1.0, 1.0]])self.vertVBO vbo.VBO(np.reshape(self.cubeVtxArray,(1, -1)).astype(np.float32))self.vertVBO.bind()首先将以原点为中心的立方体顶点位置定义为 2D numpy 数组。然后创建顶点位置 VBO vertVBO注意将numpy 数组重塑为 1D 并将数组元素转换为np.float32因为 OpenGL 需要浮点数不是双精度数这是 python 的默认值。最后绑定VBO以供GPU使用。
self.cubeClrArray np.array([[0.0, 0.0, 0.0],[1.0, 0.0, 0.0],[1.0, 1.0, 0.0],[0.0, 1.0, 0.0],[0.0, 0.0, 1.0],[1.0, 0.0, 1.0],[1.0, 1.0, 1.0],[0.0, 1.0, 1.0 ]])
self.colorVBO vbo.VBO(np.reshape(self.cubeClrArray,(1, -1)).astype(np.float32))
self.colorVBO.bind()我们创建第二个 VBO 来存储每个顶点的颜色该颜色被任意设置为与顶点位置相同。 OpenGL 将使用这些顶点颜色为立方体面着色并具有良好的渐变效果。请注意我们实际上并不需要一个全新的 VBO 来处理颜色而是需要一个新的 VBO。相反我们可以通过在渲染时交错行并指定步长连续顶点或颜色数据值之间的字节数来将顶点位置和颜色数据存储在同一个 VBO 中。
self.cubeIdxArray np.array([0, 1, 2, 3,3, 2, 6, 7,1, 0, 4, 5,2, 1, 5, 6,0, 3, 7, 4,7, 6, 5, 4 ])最后我们指定组成一维数组中六个立方体面中每一个的顶点。所有这些几何设置都应该在 中调用initializeGL我们将其修改为
def initializeGL(self):self.qglClearColor(QtGui.QColor(0, 0, 255)) # initialize the screen to bluegl.glEnable(gl.GL_DEPTH_TEST) # enable depth testingself.initGeometry()十、渲染立方体
要渲染立方体我们需要向paintGL函数添加代码。将其更新为以下内容
def paintGL(self):gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT)gl.glPushMatrix() # push the current matrix to the current stackgl.glTranslate(0.0, 0.0, -50.0) # third, translate cube to specified depthgl.glScale(20.0, 20.0, 20.0) # second, scale cubegl.glTranslate(-0.5, -0.5, -0.5) # first, translate cube center to origingl.glEnableClientState(gl.GL_VERTEX_ARRAY)gl.glEnableClientState(gl.GL_COLOR_ARRAY)gl.glVertexPointer(3, gl.GL_FLOAT, 0, self.vertVBO)gl.glColorPointer(3, gl.GL_FLOAT, 0, self.colorVBO)gl.glDrawElements(gl.GL_QUADS, len(self.cubeIdxArray), gl.GL_UNSIGNED_INT, self.cubeIdxArray)gl.glDisableClientState(gl.GL_VERTEX_ARRAY)gl.glDisableClientState(gl.GL_COLOR_ARRAY)gl.glPopMatrix() # restore the previous modelview matrix让我们逐步看一下新的调用
gl.glPushMatrix() # push the current matrix to the current stackgl.glTranslate(0.0, 0.0, -50.0) # third, translate cube to specified depth
gl.glScale(20.0, 20.0, 20.0) # second, scale cube
gl.glTranslate(-0.5, -0.5, -0.5) # first, translate cube center to origin首先我们用来glPushMatrix()复制当前的变换矩阵因为我们没有做任何其他事情所以它是恒等式并将其推送到当前的矩阵堆栈回想一下这是在GL_MODELVIEW我们设置场景投影之后设置的。然后我们应用一系列变换以相反的顺序构建它们 - 因此我们将立方体中心平移到原点这在缩放或旋转之前是必要的然后缩放立方体然后将其平移到较大的深度以便我们实际上可以看到它。
但是等等……立方体在哪里渲染好吧我们实际上还没有渲染任何东西 - 我们只是设置将应用于立方体的转换它是通过以下方式渲染的
gl.glEnableClientState(gl.GL_VERTEX_ARRAY)
gl.glEnableClientState(gl.GL_COLOR_ARRAY)gl.glVertexPointer(3, gl.GL_FLOAT, 0, self.vertVBO)
gl.glColorPointer(3, gl.GL_FLOAT, 0, self.colorVBO)gl.glDrawElements(gl.GL_QUADS,len(self.cubeIdxArray),gl.GL_UNSIGNED_INT,self.cubeIdxArray)gl.glDisableClientState(gl.GL_VERTEX_ARRAY)
gl.glDisableClientState(gl.GL_COLOR_ARRAY)gl.glPopMatrix() # restore the previous modelview matrix为了渲染立方体我们首先启用顶点和颜色数组然后将指针设置为我们之前创建的顶点位置和颜色数组glVertexPointer(size, type, stride, pointer)glColorPointer(size, type, stride, pointer)以便 GPU 可以访问数据。
然后我们调用使用 中指定的面索引glDrawElements(mode, count, type, indices)来渲染立方体面。或者我们可以直接在顶点 VBO 中定义面并使用但这需要将重复的顶点发送到 GPU。在这种情况下使用索引列表会更有效。GL_QUADScubeIdxArrayglDrawArraysglDrawElements
最后我们禁用启用的客户端状态为了安全起见然后从模型视图堆栈中弹出当前矩阵将堆栈顶部重置为恒等变换。
十一、渲染循环
把它们放在一起会产生……什么也没有我们已经准备好所有代码但paintGL从未实际调用过。由我们决定渲染的时间/频率最简单的方法是设置一个计时器作为updateGL回调paintGL自动调用以便定期进行渲染。更新MainWindow类如下
class MainWindow(QtGui.QMainWindow):def __init__(self):QtGui.QMainWindow.__init__(self)self.resize(300, 300)self.setWindowTitle(Hello OpenGL App)glWidget GLWidget(self)self.setCentralWidget(glWidget)timer QtCore.QTimer(self)timer.setInterval(20) # period, in millisecondstimer.timeout.connect(glWidget.updateGL)timer.start()我们创建了一个QTimer对象并将其设置为每 20 毫秒 (50 Hz) 发出一个信号并将该信号连接到派生基类提供的槽函数。当我们开始添加滑块和其他 GUI 元素时我们将更多地利用 Qt 的信号和插槽。updateGLGLWidget
十二、添加旋转滑块 hello_opengl_cube.svg
现在我们的应用程序中渲染了一个立方体让我们添加一个滑块来更改其方向以便我们实际上可以看到它是三维的。这涉及到修改 MainWindow类我们在其中设置 GUI 结构。请注意创建更复杂的 GUI 可能需要 QtCreator用于 C 应用程序等设计工具但对于简单的 GUI我们可以手动添加元素。
class MainWindow(QtGui.QMainWindow):
def __init__(self):QtGui.QMainWindow.__init__(self) # call the init for the parent classself.resize(300, 300)self.setWindowTitle(Hello OpenGL App)self.glWidget GLWidget(self)
self.initGUI()timer QtCore.QTimer(self)timer.setInterval(20) # period, in millisecondstimer.timeout.connect(self.glWidget.updateGL)timer.start()def initGUI(self):central_widget QtGui.QWidget()gui_layout QtGui.QVBoxLayout()central_widget.setLayout(gui_layout)self.setCentralWidget(central_widget)gui_layout.addWidget(self.glWidget)sliderX QtGui.QSlider(QtCore.Qt.Horizontal)sliderX.valueChanged.connect(lambda val: self.glWidget.setRotX(val))sliderY QtGui.QSlider(QtCore.Qt.Horizontal)sliderY.valueChanged.connect(lambda val: self.glWidget.setRotY(val))sliderZ QtGui.QSlider(QtCore.Qt.Horizontal)sliderZ.valueChanged.connect(lambda val: self.glWidget.setRotZ(val))gui_layout.addWidget(sliderX)gui_layout.addWidget(sliderY)gui_layout.addWidget(sliderZ)我们添加了一个新initGUI函数该函数被调用来代替原始调用以设置glWidget为应用程序的中央小部件。该函数封装了所有GUI布局设置让我们一步一步地看一下。
def initGUI(self):central_widget QtGui.QWidget()gui_layout QtGui.QVBoxLayout()central_widget.setLayout(gui_layout)self.setCentralWidget(central_widget)我们可以通过以下方式构建 Qt GUI首先定义一个新的中央小部件为该小部件创建一个布局该布局定义添加到其中的小部件将如何组织垂直堆叠因为我们使用QVBoxLayout然后设置中央小部件的布局。最后我们将新的小部件设置为该类的中心小部件MainWindow。 gui_layout.addWidget(self.glWidget)sliderX QtGui.QSlider(QtCore.Qt.Horizontal)sliderX.valueChanged.connect(lambda val: self.glWidget.setRotX(val))sliderY QtGui.QSlider(QtCore.Qt.Horizontal)sliderY.valueChanged.connect(lambda val: self.glWidget.setRotY(val))sliderZ QtGui.QSlider(QtCore.Qt.Horizontal)sliderZ.valueChanged.connect(lambda val: self.glWidget.setRotZ(val))gui_layout.addWidget(sliderX)gui_layout.addWidget(sliderY)gui_layout.addWidget(sliderZ)接下来我们将小部件添加到中央小部件的布局中从glWidget顶部开始然后QSlider在其下面添加三个小部件。我们将valueChanged每个滑块的信号连接到相应的槽函数该函数捕获滑块值val并设置立方体绕轴的旋转。
我们将旋转角度作为glWidget属性添加到initializeGL函数中
def initializeGL(self):self.qglClearColor(QtGui.QColor(0, 0, 255)) # initialize the screen to bluegl.glEnable(gl.GL_DEPTH_TEST) # enable depth testingself.initGeometry()self.rotX 0.0self.rotY 0.0self.rotZ 0.0使用上述设置器从滑块更新
def setRotX(self, val):self.rotX valdef setRotY(self, val):self.rotY valdef setRotZ(self, val):self.rotZ val然后更新paintGL渲染代码以使用这些角度来执行立方体围绕局部轴的连续旋转我们将在以后的文章中详细介绍旋转但格式是轴角度 gl.glTranslate(0.0, 0.0, -50.0) # third, translate cube to specified depthgl.glScale(20.0, 20.0, 20.0) # second, scale cubegl.glRotate(self.rotX, 1.0, 0.0, 0.0)gl.glRotate(self.rotY, 0.0, 1.0, 0.0)gl.glRotate(self.rotZ, 0.0, 0.0, 1.0)gl.glTranslate(-0.5, -0.5, -0.5) # first, translate cube center to origin结果是相同的渲染立方体但其下方有三个滑块允许我们旋转它。您应该看到一些与下面的立方体几乎但不完全不同的东西。 立方体.gif
想知道为什么我的立方体看起来这么糟糕我使用这个漂亮的工具将屏幕直接录制为 GIF而 GIF 格式的调色板极其有限8 位即 256 种颜色这在尝试显示具有一堆颜色渐变的立方体时非常明显。我保证你的会看起来更好
hello_opengl_cube_rotated.svg
您可以在此处下载完整的脚本。