Рубрики
Без рубрики

Понимание OpenGL через Python

OpenGL стар, и в Интернете не так много литературы, чтобы учиться. Понимание основных концепций и того, как они работают, важно для всего, и особенно когда речь идет о графике. В этой статье мы начнем с самого низа и рассмотрим важные темы OpenGL в Python.

Автор оригинала: Vladimir Batoćanin.

Вступление

После этой статьи Мухаммеда Джунаида Халида, где были объяснены основные OpenGL концепции и настройки, теперь мы рассмотрим, как сделать более сложные объекты и как анимировать их.

OpenGL очень стар, и вы не найдете много учебников в Интернете о том, как правильно его использовать и понимать, потому что все лучшие собаки уже по колено в новых технологиях.

Чтобы понять современный код OpenGL, вы должны сначала понять древние концепции, которые были написаны на каменных табличках мудрыми разработчиками игр майя.

В этой статье мы перейдем к нескольким фундаментальным темам, которые вам нужно знать:

  • Основные Матричные Операции
  • Составные преобразования
  • Преобразования, включающие Реферальную точку
  • Демонстрация моделирования

В последнем разделе мы рассмотрим, как на самом деле использовать OpenGL с библиотеками Python PyGame и PyOpenGL .

В следующей статье мы более подробно рассмотрим, как использовать OpenGL с Python и упомянутыми выше библиотеками.

Основные Матричные Операции

Чтобы правильно использовать многие функции в OpenGL, нам понадобится некоторая геометрия.

Каждая отдельная точка в пространстве может быть представлена декартовыми координатами . Координаты представляют местоположение любой заданной точки, определяя ее значения X , Y и Z .

Мы будем практически использовать их как матрицы 1×3 , или, скорее, 3-мерные векторы (подробнее о матрицах позже).

Вот примеры некоторых координат:

a и b – точки в пространстве, их x-координаты 5 и 9 соответственно, y-координаты 3 и 1 и так далее.

В компьютерной графике чаще всего вместо обычных старых декартовых координат используются однородные координаты. В основном это одно и то же, только с дополнительным параметром полезности, который для простоты мы скажем всегда 1 .

Таким образом , если регулярные координаты a равны (5,3,4) , то соответствующие однородные координаты будут (5,3,4,1) . За этим стоит много геометрической теории, но в этой статье она на самом деле не нужна.

Далее, существенным инструментом для представления геометрических преобразований являются матрицы . Матрица-это в основном двумерный массив (в данном случае размера n*n для них очень важно иметь одинаковое количество строк и столбцов).

Теперь матричные операции чаще всего довольно просты, как сложение, вычитание и т. Д. Но, конечно, самая важная операция должна быть самой сложной – умножение. Давайте рассмотрим основные примеры матричных операций:

Теперь, как и вся математика, она становится относительно сложной, когда вы действительно хотите получить от нее что-то практическое.

Формула для умножения матриц выглядит следующим образом:

$$ c[i,j] =}^{n}a[i,k]*b[k,j] $$

c – результирующая матрица, a и b – множитель и множитель.

На самом деле есть простое объяснение этой формулы. Каждый элемент может быть построен путем суммирования произведений всех элементов в i -й строке и j -й колонке. Именно по этой причине в a[i,k] i фиксируется , а k используется для перебора элементов соответствующей строки. Тот же принцип может быть применен к b[k,j] .

Зная это, есть дополнительное условие, которое должно быть выполнено, чтобы мы могли использовать матричное умножение. Если мы хотим умножить матрицы A и B измерений a*b и c*d . Количество элементов в одной строке в первой матрице ( b ) должно быть таким же, как количество элементов в столбце во второй матрице ( c ), чтобы можно было правильно использовать приведенную выше формулу.

Очень хорошим способом визуализации этой концепции является выделение строк и столбцов, элементы которых будут использоваться при умножении для данного элемента. Представьте себе две выделенные линии друг над другом, как будто они находятся в одной и той же матрице.

Элемент, где перехват является положением результирующего элемента суммирования их произведений:

пересечение матриц

Матричное умножение так важно, потому что если мы хотим объяснить следующее выражение простыми словами: A*B (A и B-матрицы), мы бы сказали:

Мы преобразуем А с помощью Б.

Вот почему матричное умножение является квинтэссенцией инструмента для преобразования любого объекта в OpenGL или геометрии в целом.

Последнее, что вам нужно знать о матричном умножении, – это то, что оно имеет нейтраль . Это означает, что существует уникальный элемент (в данном случае матрица) E , который при умножении на любой другой элемент A не меняет значения A , то есть:

$$ (!\exists{E}\ \ \forall{A})\ $$

Восклицательный знак в сочетании с символом exists означает: Существует уникальный элемент E, который…

В случае умножения на нормальные целые числа E имеет значение 1 . В случае матриц E имеет следующие значения в нормальном декартовом (E 1 ) и однородные координаты (E 2 ) соответственно:

Каждое отдельное геометрическое преобразование имеет свою собственную уникальную матрицу преобразования, которая имеет некоторый шаблон, из которых наиболее важными являются:

  • Перевод
  • Пересчет
  • Отражение
  • Вращение
  • Стрижка

Перевод

Перевод-это акт буквального перемещения объекта по заданному вектору. Объект, на который воздействует трансформация, никак не меняет своей формы и не меняет своей ориентации – он просто перемещается в пространстве (поэтому перевод классифицируется как движение трансформация).

Перевод может быть описан в следующей матричной форме:

t -s представляет, насколько будут изменены значения местоположения объекта x , y и z .

Итак, после преобразования любых координат с помощью матрицы перевода T получаем:

$$ [x,y,z]*T=[t_x+x,t_y+y,t_z+z] $$

Перевод осуществляется с помощью следующей функции OpenGL:

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

Как вы можете видеть, если мы знаем форму матрицы перевода, понимание функции OpenGL очень просто, это относится ко всем преобразованиям OpenGL.

Не обращайте внимания на GLfloat , это просто умный тип данных для OpenGL для работы на нескольких платформах, вы можете посмотреть на него так:

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

Это необходимая мера , потому что не все системы имеют одинаковое пространство хранения для char , например.

Вращение

Вращение-это немного более сложное преобразование, потому что оно зависит от 2 факторов:

  • Pivot: Вокруг какой линии в 3D-пространстве (или точки в 2D-пространстве) мы будем вращаться
  • Количество: На сколько (в градусах или радианах) мы будем вращаться

Из-за этого нам сначала нужно определить вращение в двумерном пространстве, а для этого нам нужно немного тригонометрии.

Вот краткая справка:

Эти тригонометрические функции могут быть использованы только внутри прямоугольного треугольника (один из углов должен быть 90 градусов).

Базовая матрица вращения для вращения объекта в 2D пространстве вокруг вершины ( 0,0 ) по углу А идет следующим образом:

Опять же, 3-я строка и 3-й столбец-это просто на случай, если мы захотим сложить преобразования перевода поверх других преобразований (что мы и сделаем в OpenGL), это нормально, если вы не до конца понимаете, почему они существуют прямо сейчас. Все должно проясниться в примере составного преобразования.

Все это было в 2D-пространстве, теперь давайте перейдем к 3D-пространству. В трехмерном пространстве нам нужно определить матрицу, которая может вращать объект вокруг любой линии.

Как сказал однажды один мудрец: “Будь проще и глупее!” К счастью, математические маги в кои-то веки сохранили его простым и глупым.

Каждое вращение вокруг линии может быть разбито на несколько преобразований:

  • Вращение вокруг оси x
  • Вращение вокруг оси y
  • Вращение вокруг оси z
  • Утилитарные переводы (которые будут затронуты позже)

Итак, единственные три вещи, которые нам нужно построить для любого 3D-вращения, – это матрицы, представляющие вращение вокруг оси x , y и z на угол A :

3D вращение реализовано с помощью следующей функции OpenGL:

void glRotatef(GLfloat angle, GLfloat x, GLfloat y, GLfloat z);
  • угол : угол поворота в градусах (0-360)
  • x,y,z : вектор, вокруг которого выполняется вращение

Пересчет

Масштабирование-это акт умножения любого измерения целевого объекта на скаляр . Этот скаляр может быть <1 , если мы хотим уменьшить объект, и он может быть >1 , если мы хотим увеличить объект.

Масштабирование можно описать следующей матричной формой:

s x , s y , s z – это скаляры, которые умножаются на значения x , y и z целевого объекта.

После преобразования любых координат с помощью масштабирующей матрицы S получаем:

Это преобразование особенно полезно при масштабировании объекта по коэффициенту k (это означает, что результирующий объект в два раза больше), это достигается установкой s x = s y = s z = k :

Частный случай масштабирования известен как отражение . Это достигается установкой либо s x , s y , либо s z в -1 . Это просто означает, что мы инвертируем знак одной из координат объекта.

Проще говоря, мы помещаем объект по другую сторону оси x , y или z .

Эта трансформация может быть изменена, чтобы работать для любой равнины отражения, но сейчас она нам действительно не нужна.

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

Составные преобразования

Составные преобразования-это преобразования, состоящие из более чем 1 базового преобразования (перечисленного выше). Преобразования A и B объединяются матричным умножением соответствующих матриц преобразования M_a и M_b .

Это может показаться очень простой логикой, однако есть некоторые вещи, которые могут сбить с толку. Например:

  • Матричное умножение не является коммутируемым:
  • Каждое из этих преобразований имеет обратное преобразование. Обратное преобразование-это преобразование, которое отменяет исходное:
  • Когда мы хотим сделать инверсию составного преобразования, мы должны изменить порядок используемых элементов:

Дело в том, что топологический порядок использования матрицы очень важен, как и подъем на определенный этаж здания.

Если вы находитесь на первом этаже и хотите попасть на четвертый этаж, сначала вам нужно подняться на третий этаж, а затем на четвертый.

Но если вы хотите спуститься обратно на второй этаж, вам придется подняться на третий этаж, а затем на второй этаж (в обратном топологическом порядке).

Преобразования, включающие Реферальную точку

Как уже упоминалось ранее, когда преобразование должно быть сделано относительно определенной точки в пространстве, например,вращаясь вокруг реферальной точки A=(a,b, c) в трехмерном пространстве , а не в начале координат O=(0,0,0) , нам нужно превратить эту реферальную точку A в O , переведя все на T(-a,-b,-c) .

Затем мы можем сделать любое преобразование, которое нам нужно сделать, и когда мы закончим,переведем все обратно на T(a,b , c) , так что исходное начало координат O снова будет иметь координаты (0,0,0) .

Матричная форма этого примера такова:

Где M – преобразование, которое мы хотим произвести над объектом.

Весь смысл изучения этих матричных операций заключается в том, чтобы вы могли полностью понять, как работает OpenGL.

Демонстрация моделирования

После всего этого давайте взглянем на простую демонстрацию моделирования.

Чтобы сделать что – либо с OpenGL через Python, мы будем использовать два модуля-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.

В этой статье мы рассмотрели основные матричные операции (перевод, поворот и масштабирование), а также составные преобразования и преобразования, включающие реферальную точку.

В следующей статье мы будем использовать PyGame и PyOpenGL для инициализации проекта , рисования объектов, их анимации и т. Д.!