📜  通过Python了解OpenGL

📅  最后修改于: 2020-09-06 08:35:39             🧑  作者: Mango

介绍

在穆罕默德·朱奈德·哈立德(Muhammad Junaid Khalid)撰写了这篇文章之后,他在其中解释了基本的OpenGL概念和设置,现在我们将研究如何制作更复杂的对象以及如何对其进行动画处理

OpenGL很老,您不会在网上找到很多关于如何正确使用它和理解它的教程,因为所有顶级公司都已经深陷于新技术的困境。

要了解现代OpenGL代码,您必须首先了解明智的玛雅游戏开发人员在石碑上写的古老概念。

在本文中,我们将跳入您需要了解的几个基本主题:

  • 基本矩阵运算
  • 复合转换
  • 涉及引荐点的转换
  • 建模示范

在最后一节,我们将看看如何实际使用Python库使用OpenGL pygame的PyOpenGL

基本矩阵运算

为了正确使用OpenGL中的许多功能,我们需要一些几何形状。

空间中的每个都可以用笛卡尔坐标表示。坐标通过定义XYZ值来表示任何给定点的位置。

实际上,我们将它们用作1×3矩阵,或者更确切地说是3维向量(稍后将在矩阵上详细介绍)。

以下是一些坐标的示例:

a=(5,3,4) 

b=(9,1,2)

ab是空间中的点,它们的x坐标分别是59,y坐标分别是31,依此类推。

在计算机图形学中,通常会使用齐次坐标代替常规的旧笛卡尔坐标。它们基本上是同一件事,只是带有一个附加的实用程序参数,为简单起见,我们将其始终称为1

所以如果经常坐标a(5,3,4),相应的齐次坐标会(5,3,4,1)。这背后有很多几何理论,但对于本文而言,它并不是必需的。

接下来,表示几何变换的基本工具是矩阵。矩阵基本上是二维数组(在这种情况下,大小为n * n,对于它们而言,具有相同数量的行和列非常重要)。

现在,矩阵运算通常非常简单明了,例如加法,减法等。但是,当然,最重要的运算必须是最复杂的运算-乘法。让我们看一下基本的矩阵运算示例:

现在,随着所有数学趋向于发展,当您实际想要一些实用的东西时,它变得相对复杂。

矩阵乘法的公式如下:

c作为所得到的矩阵,a并且b作为被乘数和乘数。

实际上,此公式有一个简单的解释。每个元素都可以通过将i-th行和j-th列中所有元素的乘积相加来构造。这就是为什么in a[i,k]中的i是固定的,并且k使用来迭代相应行的元素的原因。相同的原理可以应用于b[k,j]

知道了这一点,要使我们能够使用矩阵乘法,还需要满足一个附加条件。如果我们想乘以矩阵ABa*bc*d。第一矩阵(b)中单行中的元素数必须与第二矩阵(c)中一列中的元素数相同,以便可以正确使用上述公式。

可视化此概念的一种很好的方法是突出显示将在给定元素的乘法中利用谁的元素的行和列。想象两条高亮显示的线条彼此重叠,就像它们在同一矩阵中一样。

他们截取的元素是其乘积总和的结果元素的位置:

矩阵乘法是如此重要,因为如果我们想用简单的术语来解释以下表达式:A*B(A和B是矩阵),我们会说:

我们正在使用B转换A。

这就是矩阵乘法是通常用于在OpenGL或几何图形中转换任何对象的典型工具的原因。

关于矩阵乘法,您需要了解的最后一件事是它具有中性值。这意味着存在一个唯一元素(在这种情况下为矩阵)E,当与任何其他元素相乘时,该元素A不会改变A的值,即:

感叹号与“ exists”符号一起表示:存在一个唯一元素E,该元素E …

如果与普通整数相乘,E则其值为1。在矩阵的情况下,E在法线直角坐标(E 1)和齐次坐标(E 2)中分别具有以下值:

每个单一的几何变换都有自己的独特变换矩阵,该矩阵具有某种模式,其中最重要的是:

  • Translation
  • Scaling
  • Reflection
  • Rotation
  • Sheering

平移

平移是按照设置的向量从字面上移动对象的行为。受变换影响的对象不会以任何方式改变其形状,也不会改变其方向-它只是在空间中移动(这就是为什么平移归类为运动变换)。

可以用以下矩阵形式描述平移:

t-s表示由对象有多大的xyz位置值将被改变。

因此,在用平移矩阵变换任何坐标之后T,我们得到

:

 

使用以下OpenGL函数实现平移: 

void glTranslatef(GLfloat tx, GLfloat ty, GLfloat tz);

 

如您所见,如果我们知道转换矩阵的形式,那么了解OpenGL函数非常简单,所有OpenGL转换都是如此。

不必介意GLfloat,它只是OpenGL在多种平台上均可使用的一种聪明的数据类型,您可以这样看:

typedef float GLfloat;
typedef double GLdouble;
typedef someType GLsomeType;

这是一项必要的措施,因为例如,并非所有系统都具有相同的存储空间char

 

旋转

旋转是更复杂的变换,因为简单的事实取决于两个因素:

  • 枢轴:我们将围绕3D空间中的哪条线(或2D空间中的点)旋转
  • 数量:我们将旋转多少(度或弧度)

因此,我们首先需要定义2D空间中的旋转,为此我们需要一点三角函数。

快速参考:

这些三角函数只能在直角三角形内使用(其中一个角度必须为90度)。

用于围绕顶点(0,0)将2D空间中的对象旋转角度的基本旋转矩阵A如下:

 

同样,第三行和第三列只是在万一我们希望将转换转换堆叠在其他转换之上的情况下(我们将在OpenGL中使用),如果您不完全了解它们为什么现在在那里,也可以。在复合转换示例中应该清除所有问题。

一切都在2D空间中,现在让我们进入3D空间。在3D空间中,我们需要定义一个矩阵,该矩阵可以围绕任何直线旋转对象。

正如一个聪明人曾经说过的:“保持简单而愚蠢!” 幸运的是,数学魔术师曾经使它变得简单而愚蠢。

围绕一条线的每一个旋转可以分解为以下几种转换:

  • 绕x轴旋转
  • 绕y轴旋转
  • 绕z轴旋转
  • 实用程序翻译(稍后会涉及)

所以,只有三件事情,我们需要构建任何3D旋转是代表围绕旋转矩阵xy以及z通过角轴线A

3D旋转是通过以下OpenGL功能实现的:

void glRotatef(GLfloat angle, GLfloat x, GLfloat y, GLfloat z);
  • angle:旋转角度,以度为单位(0-360)
  • x,y,z:围绕其执行旋转的向量

缩放

缩放是将目标对象的任意维度乘以标量的行为。此标量可以是<1我们要缩小对象时的标量,也可以是>1我们要扩大对象时的标量。

缩放可以用以下矩阵形式描述:

sx,sy,sz是与目标对象的x,y和z值相乘的标量.

用缩放矩阵转换任何坐标后,S我们得到:

当通过系数k缩放对象时,此变换特别有用(这意味着生成的对象大两倍),这可以通过设置x = y = z = k来实现

 

缩放的一种特殊情况称为反射。可以通过将xyz设置为来实现-1。这只是意味着我们反转了对象坐标之一的符号。

简单来说,我们把对象的另一侧xyz轴。

可以修改此转换以使其适用于任何反射,但是我们现在确实不需要它。

void glScalef(GLfloat sx, GLfloat sy, GLfloat sz);

复合转换

复合转换是由多个(以上列出)基本转换组成的转换。变换AB由矩阵乘以相应的变换矩阵组合M_aM_b

这看似很简单的逻辑,但是有些事情可能会令人困惑。例如:

  • 矩阵乘法不可交换:
  • 这些转换中的每个转换都有一个逆转换。逆变换是一种抵消原始变换的变换:
  • 当我们想对复合变换进行逆运算时,我们必须更改所使用元素的顺序:
    • 关键是-矩阵利用的拓扑顺序非常重要,就像上升到建筑物的某个楼层一样。

      如果您位于一楼,并且想要到达四楼,则首先需要去三楼,然后到四楼。

      但是,如果要下降到第二层,则必须先转到第三层,然后再到第二层(以相反的拓扑顺序)。

     

    涉及点的转换

    正如前面提到的,当一个转型具有相对做一个空间特定点,例如绕着转介点是A=(a,b,c)在三维空间中,而不是起源O=(0,0,0),我们必须把这一转诊点AO由翻译一切T(-a,-b,-c)

    然后,我们可以进行所需的任何转换,完成后,将所有内容转换回去T(a,b,c),以使原始原点O再次具有坐标(0,0,0)

    此示例的矩阵形式为:

    M我们希望对对象进行的变换在哪里。

    学习这些矩阵运算的重点是,您可以完全了解OpenGL的工作方式。

    建模示范

    完所有这些工作后,让我们看一个简单的建模演示。

    为了通过Python使用OpenGL做任何事情,我们将使用两个模块-PyGame和PyOpenGL:

    $ python3 -m pip install -U pygame --user
    $ python3 -m pip install PyOpenGL PyOpenGL_accelerate

    由于您自己需要卸载3本价值图形理论的书籍,因此我们将使用PyGame库。从本质上讲,它只会缩短从项目初始化到实际建模和动画制作的过程。

    首先,我们需要从OpenGL和PyGame导入所有必需的东西:

    import pygame as pg
    from pygame.locals import *
    
    from OpenGL.GL import *
    from OpenGL.GLU import *

    在下面的示例中,我们可以看到,要对非常规对象建模,我们只需要知道如何将复杂对象分解为更小和更简单的部分即可。

    因为我们仍然不知道其中某些功能的作用,所以我将在代码本身中提供一些表面级别的定义,以便您了解如何使用OpenGL。在下一篇文章中,所有这些都将详细介绍-这只是让您基本了解OpenGL的工作方式:

    def draw_gun():
        # Setting up materials, ambient, diffuse, specular and shininess properties are all
        # different properties of how a material will react in low/high/direct light for
        # example.
        ambient_coeffsGray = [0.3, 0.3, 0.3, 1]
        diffuse_coeffsGray = [0.5, 0.5, 0.5, 1]
        specular_coeffsGray = [0, 0, 0, 1]
        glMaterialfv(GL_FRONT, GL_AMBIENT, ambient_coeffsGray)
        glMaterialfv(GL_FRONT, GL_DIFFUSE, diffuse_coeffsGray)
        glMaterialfv(GL_FRONT, GL_SPECULAR, specular_coeffsGray)
        glMateriali(GL_FRONT, GL_SHININESS, 1)
    
        # OpenGL is very finicky when it comes to transformations, for all of them are global,
        # so it's good to seperate the transformations which are used to generate the object
        # from the actual global transformations like animation, movement and such.
        # The glPushMatrix() ----code----- glPopMatrix() just means that the code in between
        # these two functions calls is isolated from the rest of your project.
        # Even inside this push-pop (pp for short) block, we can use nested pp blocks,
        # which are used to further isolate code in it's entirety.
        glPushMatrix()
    
        glPushMatrix()
        glTranslatef(3.1, 0, 1.75)
        glRotatef(90, 0, 1, 0)
        glScalef(1, 1, 5)
        glScalef(0.2, 0.2, 0.2)
        glutSolidTorus(0.2, 1, 10, 10)
        glPopMatrix()
    
        glPushMatrix()
        glTranslatef(2.5, 0, 1.75)
        glScalef(0.1, 0.1, 1)
        glutSolidCube(1)
        glPopMatrix()
    
        glPushMatrix()
        glTranslatef(1, 0, 1)
        glRotatef(10, 0, 1, 0)
        glScalef(0.1, 0.1, 1)
        glutSolidCube(1)
    
        glPopMatrix()
    
        glPushMatrix()
        glTranslatef(0.8, 0, 0.8)
        glRotatef(90, 1, 0, 0)
        glScalef(0.5, 0.5, 0.5)
        glutSolidTorus(0.2, 1, 10, 10)
        glPopMatrix()
    
        glPushMatrix()
        glTranslatef(1, 0, 1.5)
        glRotatef(90, 0, 1, 0)
        glScalef(1, 1, 4)
        glutSolidCube(1)
        glPopMatrix()
    
        glPushMatrix()
        glRotatef(8, 0, 1, 0)
        glScalef(1.1, 0.8, 3)
        glutSolidCube(1)
        glPopMatrix()
    
        glPopMatrix()
    
    def main():
        # Initialization of PyGame modules
        pg.init()
        # Initialization of Glut library
        glutInit(sys.argv)
        # Setting up the viewport, camera, backgroud and display mode
        display = (800,600)
        pg.display.set_mode(display, DOUBLEBUF|OPENGL)
        glClearColor(0.1,0.1,0.1,0.3)
        gluPerspective(45, (display[0]/display[1]), 0.1, 50.0)
        gluLookAt(5,5,3,0,0,0,0,0,1)
    
        glTranslatef(0.0,0.0, -5)
        while True:
            # Listener for exit command
            for event in pg.event.get():
                if event.type == pg.QUIT:
                    pg.quit()
                    quit()
    
            # Clears the screen for the next frame to be drawn over
            glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)
            ############## INSERT CODE FOR GENERATING OBJECTS ##################
            draw_gun()
            ####################################################################
            # Function used to advance to the next frame essentially
            pg.display.flip()
            pg.time.wait(10)

    这些完整的代码可以使我们:

    结论

    OpenGL很老,您不会在网上找到很多关于如何正确使用它和理解它的教程,因为所有顶级公司都已经深陷于新技术的困境。

    为了正确使用OpenGL,需要掌握一些基本概念,以便通过OpenGL功能理解实现。

    在本文中,我们介绍了基本的矩阵运算(平移,旋转和缩放)以及复合转换和涉及引用点的转换。