Как получить вертикальное вращение в 3D-пространстве с OpenGL? - PullRequest
3 голосов
/ 11 апреля 2019

У меня есть поле кубов, созданное в OpenGL, и я работаю, как ожидалось, и часть вращения «камеры» работает, пока я не попытаюсь посмотреть вверх или вниз.

У меня есть фрагмент кода, который работает:

if pressed[pygame.K_UP] or pressed[pygame.K_DOWN]:
            rotx = cos(rot/radian)
            rotz = sin(rot/radian)
            if pressed[pygame.K_UP]:
                glRotatef(speed / 2, -rotx, 0, rotz)
            if pressed[pygame.K_DOWN]:
                glRotatef(speed / 2, rotx, 0, -rotz)

, но он работает только тогда, когда rot равен 0. Поэтому, когда я впервые запускаю программу, я могу смотреть вверх и вниз, если ятолько двигайтесь из стороны в сторону и не смотрите влево или вправо или двигайтесь вперед и назад.

verticies = (
    (1, -1, -1),
    (1, 1, -1),
    (-1, 1, -1),
    (-1, -1, -1),
    (1, -1, 1),
    (1, 1, 1),
    (-1, -1, 1),
    (-1, 1, 1)
    )

edges = (
    (0,1),
    (0,3),
    (0,4),
    (2,1),
    (2,3),
    (2,7),
    (6,3),
    (6,4),
    (6,7),
    (5,1),
    (5,4),
    (5,7)
    )


def Cube(tX, tY, tZ):
    glBegin(GL_LINES)
    for edge in edges:
        for vertex in edge:
            glVertex3f(verticies[vertex][0] + tX, verticies[vertex][1] + tY, verticies[vertex][2] + tZ)
    glEnd()


def main():
    pygame.init()
    screenSize = (1500, 800)
    pygame.display.set_mode(screenSize, DOUBLEBUF|OPENGL)

    gluPerspective(45, (screenSize[0]/screenSize[1]), 0.1, 50.0)

    rot = 0
    speed = 3
    radian = 57.2958
    while True:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                quit()

        pressed = pygame.key.get_pressed()

        #==# Rotation with arrow keys #==#
        if pressed[pygame.K_LEFT]:
            glRotatef(speed / 2, 0, -1, 0)
            rot += 1
        if pressed[pygame.K_RIGHT]:
            glRotatef(speed / 2, 0, 1, 0)
            rot -= 1
        if pressed[pygame.K_UP] or pressed[pygame.K_DOWN]:
            rotx = cos(rot/radian)
            rotz = sin(rot/radian)
            if pressed[pygame.K_UP]:
                glRotatef(speed / 2, -rotx, 0, rotz)
            if pressed[pygame.K_DOWN]:
                glRotatef(speed / 2, rotx, 0, -rotz)

        #==# Walking with WASD #==#
        if pressed[pygame.K_w]:
            glTranslate(sin(rot/radian) / speed, 0, cos(rot/radian) / speed)
        if pressed[pygame.K_s]:
            glTranslate(-sin(rot/radian) / speed, 0, -cos(rot/radian) / speed)

        if pressed[pygame.K_a]:
            glTranslate(sin((rot + 90)/radian) / speed, 0, cos((rot + 90)/radian) / speed)
        if pressed[pygame.K_d]:
            glTranslate(-sin((rot + 90)/radian) / speed, 0, -cos((rot + 90)/radian) / speed)


        glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)

        for i in range(8):
            for j in range(8):
                Cube(-i*2.5, -4, -j*2.5)

        pygame.display.flip()
        pygame.time.wait(10)

main()

Я думал, что это будет работать как движение и камера в игре FPS, но это не так.

1 Ответ

1 голос
/ 11 апреля 2019

Все дело в порядке. OpenGL - это государственный двигатель. Каждая операция меняет состояние. Когда вы выполняете такую ​​операцию, как glTranslatef или glRotatef, текущая матрица в стеке матриц изменяется.

В OpenGL существуют разные матрицы, такие как матрица вида модели и матрица проекции. Первое, что вы должны сделать, это разделить матрицу проекции и модель с помощью матрицы. Это можно сделать, установив матричный режим (см. glMatrixMode):

glMatrixMode(GL_PROJECTION)
gluPerspective(45, (screenSize[0]/screenSize[1]), 0.1, 50.0)
glMatrixMode(GL_MODELVIEW)

# [...]

Когда матричная операция применяется к стеку матриц, тогда текущая матрица умножается на новую (дополнительную) матрицу, которая определяется операцией. Это означает glRotatef, за которым следует glTranslatef dies:

current_matrix = current_matrix * rotation_matrix * translation_matrix

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

current_matrix = rotation_matrix * translation_matrix * current_matrix

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

  • прочитать текущую модель просмотра матрицы по glGetFloatv(GL_MODELVIEW_MATRIX, ...)
  • инициализация текущей матрицы по единичной матрице glLoadIdentity
  • оценить входные данные и выполнить новые матричные операции (glTranslatef / glRotatef)
  • умножить прочитанную модель с матрицы до текущего matirx на glMultMatrix
current_mv_mat = (GLfloat * 16)() 
glGetFloatv(GL_MODELVIEW_MATRIX, current_mv_mat)
glLoadIdentity()

# [...] glTranslatef, glRotatef

glMultMatrixf(mv)

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

current_matrix = rotation_matrix * translation_matrix * current_matrix
mdel_view_matrix = roate_updown * current_matrix

К счастью, текущая матрица управляется в стеке и может быть нажата и извлечена с помощью glPushMatrix / glPopMatrix. Поворот вверх и вниз должен быть суммирован и окончательно применен к представлению:

glPushMatrix()

current_mv_mat = (GLfloat * 16)()
glGetFloatv(GL_MODELVIEW_MATRIX, current_mv_mat)
glLoadIdentity()
glRotatef(sum_rot_updown, 1, 0, 0)
glMultMatrixf(mv)

# [...] draw all the objects of the scene

glPopMatrix()

См. Пример, где я применил предложения к вашему исходному коду:

def main():
    pygame.init()
    screenSize = (1500, 800)
    pygame.display.set_mode(screenSize, DOUBLEBUF|OPENGL)

    glMatrixMode(GL_PROJECTION)
    gluPerspective(45, (screenSize[0]/screenSize[1]), 0.1, 50.0)
    glMatrixMode(GL_MODELVIEW)

    rot = 0
    speed = 3
    radian = 57.2958
    sum_rot_updown = 0
    current_mv_mat = (GLfloat * 16)()
    while True:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                quit()

        pressed = pygame.key.get_pressed()

        glGetFloatv(GL_MODELVIEW_MATRIX, current_mv_mat)
        glLoadIdentity()

        #==# Rotation left and right with arrow keys #==#
        if pressed[pygame.K_LEFT]:
            glRotatef(speed / 2, 0, -1, 0)
            rot += 1
        if pressed[pygame.K_RIGHT]:
            glRotatef(speed / 2, 0, 1, 0)
            rot -= 1

        #==# Walking with WASD #==#
        if pressed[pygame.K_w]:
            glTranslate(0, 0, 1/speed)
        if pressed[pygame.K_s]:
            glTranslate(0, 0, -1/speed)
        if pressed[pygame.K_a]:
            glTranslate(1/speed, 0, 0)
        if pressed[pygame.K_d]:
            glTranslate(-1/speed, 0, 0)

        glMultMatrixf(current_mv_mat)

        #==# Rotation up and down with arrow keys #==# 
        if pressed[pygame.K_UP]:
            sum_rot_updown -= speed / 2
        if pressed[pygame.K_DOWN]:
            sum_rot_updown += speed / 2

        glPushMatrix()

        glGetFloatv(GL_MODELVIEW_MATRIX, current_mv_mat)
        glLoadIdentity()
        glRotatef(sum_rot_updown, 1, 0, 0)
        glMultMatrixf(current_mv_mat)

        glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)

        for i in range(8):
            for j in range(8):
                Cube(-i*2.5, -4, -j*2.5)

        glPopMatrix()

        pygame.display.flip()
        pygame.time.wait(10)

main()
...