Opengl: Сохранение вектора камеры Arcball, выровненного с осью y

По сути, я пытаюсь имитировать вращение камеры в Maya. Шар арки в Maya всегда выровнен по оси y. Таким образом, независимо от того, куда указывает вектор вверх, он все равно вращается или регистрируется с помощью вектора вверх вдоль оси y.

Мне удалось реализовать arcball в OpenGL с использованием C ++ и Qt. Но я не могу понять, как сохранить его выравнивание по вектору вверх. Иногда мне удавалось выровнять его с помощью приведенного ниже кода:

void ArcCamera::setPos (Vector3 np)
{
    Vector3 up(0, 1, 0);

    Position = np;
    ViewDir = (ViewPoint - Position); ViewDir.normalize();

    RightVector =  ViewDir ^ up; RightVector.normalize();

    UpVector = RightVector ^ ViewDir;  UpVector.normalize();
}

Это работает до тех пор, пока положение не будет под углом 90 градусов, затем изменится правый вектор и все будет инвертировано.

Поэтому вместо этого я поддерживал полное вращение (в кватернионах) и вращал им исходные позиции (вверх, вправо, поз). Это лучше всего работает, чтобы все было согласовано, но теперь я просто не могу выровнять вектор вверх по оси y. Ниже представлена ​​функция поворота.

void CCamera::setRot (QQuaternion q)
{
    tot = tot * q;

    Position  = tot.rotatedVector(PositionOriginal);

    UpVector = tot.rotatedVector(UpVectorOriginal);
    UpVector.normalize();

    RightVector  = tot.rotatedVector(RightVectorOriginal);
    RightVector.normalize();
}

QQuaternion q создается из пары ось-угол, полученной при перетаскивании мышью. Я уверен, что все сделано правильно. Само вращение в порядке, оно просто не поддерживает выравнивание ориентации.

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


person Poken1151    schedule 14.05.2013    source источник
comment
Я не уверен, какой конечный результат вы хотите получить. Матрица обзора для камеры в заданной точке, смотрящей на точку фокусировки, вверх (0, 1, 0)? В этом случае QMatrix4x4 предлагает все, что вам нужно.   -  person peppe    schedule 18.05.2013


Ответы (2)


Причина, по которой это не работает, заключается в том, что манипуляции с камерой Maya во вьюпорте не используют интерфейс Arcball. Что вы хотите сделать, так это команда Maya для перестановки. Лучший источник, который я нашел для объяснения этого, - это этот документ из Класс профессора Орра по компьютерной графике.

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

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

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

translate(pivotPosition) * rotate(angleX, angleY, angleZ) * translate(-pivotPosition)

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

azimuthRotation = translate(pivotPosition) * rotateY(angleY) * translate(-pivotPosition)

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

elevationRotation = translate(worldToViewMatrix * pivotPosition) * rotateX(angleX) * translate(worldToViewMatrix * -pivotPosition)

Затем мы можем получить новую матрицу просмотра с помощью:

newWorldToViewMatrix = elevationRotation * worldToViewMatrix * azimuthRotation

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

newOrientation = transpose(mat3(newWorldToViewMatrix))
newPosition = -((newOrientation * newWorldToViewMatrix).column(3))

На этом этапе у нас есть разделенные элементы. Если ваша камера параметризована так, что вы сохраняете только кватернион для вашей ориентации, вам просто нужно выполнить преобразование матрицы вращения -> кватернион. Конечно, Maya будет преобразовывать в углы Эйлера для отображения в поле канала, что будет зависеть от порядка поворота камеры (обратите внимание, что математика для переворачивания не меняется при изменении порядка поворота, точно так же, как поворот матрица -> преобразование углов Эйлера выполнено).

Вот пример реализации на Python:

#!/usr/bin/env python

import numpy as np
from math import *

def translate(amount):
    'Make a translation matrix, to move by `amount`'
    t = np.matrix(np.eye(4))
    t[3] = amount.T
    t[3, 3] = 1
    return t.T

def rotateX(amount):
    'Make a rotation matrix, that rotates around the X axis by `amount` rads'
    c = cos(amount)
    s = sin(amount)

    return np.matrix([
        [1, 0, 0, 0],
        [0, c,-s, 0],
        [0, s, c, 0],
        [0, 0, 0, 1],
    ])

def rotateY(amount):
    'Make a rotation matrix, that rotates around the Y axis by `amount` rads'
    c = cos(amount)
    s = sin(amount)
    return np.matrix([
        [c, 0, s, 0],
        [0, 1, 0, 0],
       [-s, 0, c, 0],
        [0, 0, 0, 1],
    ])

def rotateZ(amount):
    'Make a rotation matrix, that rotates around the Z axis by `amount` rads'
    c = cos(amount)
    s = sin(amount)
    return np.matrix([
        [c,-s, 0, 0],
        [s, c, 0, 0],
        [0, 0, 1, 0],
        [0, 0, 0, 1],
    ])

def rotate(x, y, z, pivot):
    'Make a XYZ rotation matrix, with `pivot` as the center of the rotation'
    m = rotateX(x) * rotateY(y) * rotateZ(z)

    I = np.matrix(np.eye(4))
    t = (I-m) * pivot
    m[0, 3] = t[0, 0]
    m[1, 3] = t[1, 0]
    m[2, 3] = t[2, 0]

    return m

def eulerAnglesZYX(matrix):
    'Extract the Euler angles from an ZYX rotation matrix'
    x = atan2(-matrix[1, 2], matrix[2, 2])
    cy = sqrt(1 - matrix[0, 2]**2)
    y = atan2(matrix[0, 2], cy)
    sx = sin(x)
    cx = cos(x)
    sz = cx * matrix[1, 0] + sx * matrix[2, 0]
    cz = cx * matrix[1, 1] + sx * matrix[2, 1]
    z = atan2(sz, cz)
    return np.array((x, y, z),)

def eulerAnglesXYZ(matrix):
    'Extract the Euler angles from an XYZ rotation matrix'
    z = atan2(matrix[1, 0], matrix[0, 0])
    cy = sqrt(1 - matrix[2, 0]**2)
    y = atan2(-matrix[2, 0], cy)
    sz = sin(z)
    cz = cos(z)
    sx = sz * matrix[0, 2] - cz * matrix[1, 2]
    cx = cz * matrix[1, 1] - sz * matrix[0, 1]
    x = atan2(sx, cx)
    return np.array((x, y, z),)

class Camera(object):
    def __init__(self, worldPos, rx, ry, rz, coi):
        # Initialize the camera orientation.  In this case the original
        # orientation is built from XYZ Euler angles.  orientation is the top
        # 3x3 XYZ rotation matrix for the view-to-world matrix, and can more
        # easily be thought of as the world space orientation.
        self.orientation = \
            (rotateZ(rz) * rotateY(ry) * rotateX(rx))

        # position is a point in world space for the camera.
        self.position = worldPos

        # Construct the world-to-view matrix, which is the inverse of the
        # view-to-world matrix.
        self.view = self.orientation.T * translate(-self.position)

        # coi is the "center of interest".  It defines a point that is coi
        # units in front of the camera, which is the pivot for the tumble
        # operation.
        self.coi = coi

    def tumble(self, azimuth, elevation):
        '''Tumble the camera around the center of interest.

        Azimuth is the number of radians to rotate around the world-space Y axis.
        Elevation is the number of radians to rotate around the view-space X axis.
        '''
        # Find the world space pivot point.  This is the view position in world
        # space minus the view direction vector scaled by the center of
        # interest distance.
        pivotPos = self.position - (self.coi * self.orientation.T[2]).T

        # Construct the azimuth and elevation transformation matrices
        azimuthMatrix = rotate(0, -azimuth, 0, pivotPos) 
        elevationMatrix = rotate(elevation, 0, 0, self.view * pivotPos)

        # Get the new view matrix
        self.view = elevationMatrix * self.view * azimuthMatrix

        # Extract the orientation from the new view matrix
        self.orientation = np.matrix(self.view).T
        self.orientation.T[3] = [0, 0, 0, 1]

        # Now extract the new view position
        negEye = self.orientation * self.view
        self.position = -(negEye.T[3]).T
        self.position[3, 0] = 1

np.set_printoptions(precision=3)

pos = np.matrix([[5.321, 5.866, 4.383, 1]]).T
orientation = radians(-60), radians(40), 0
coi = 1

camera = Camera(pos, *orientation, coi=coi)
print 'Initial attributes:'
print np.round(np.degrees(eulerAnglesXYZ(camera.orientation)), 3)
print np.round(camera.position, 3)
print 'Attributes after tumbling:'
camera.tumble(azimuth=radians(-40), elevation=radians(-60))
print np.round(np.degrees(eulerAnglesXYZ(camera.orientation)), 3)
print np.round(camera.position, 3)
person Chris    schedule 04.01.2016
comment
Если вам интересно узнать о функциях извлечения углов Эйлера, ознакомьтесь со статьей Майка Дэя о том, как сделать это без ветвей: d3cw3dd2w32x2b.cloudfront.net/wp-content/uploads/2012/07/ Или посмотрите мое описание на gamedev.stackexchange.com/questions/50963#112271 - person Chris; 04.01.2016

Отслеживайте с самого начала ваш вид и правильные векторы и обновляйте их с помощью матрицы вращения. Затем вычислите свой вектор вверх.

person Trax    schedule 14.05.2013
comment
Спасибо за ответ ... но я не понимаю, как это отвечает на мой вопрос. Как я уже сказал в посте, я занимался арбаром. Вращения не проблема, я хочу, чтобы вектор вверх был выровнен с осью Y сцены. Я могу ограничить (повернуть) ось без проблем, но я тоже не это имею в виду, я хочу сохранить ориентацию вектора вверх камеры, но заставить его оставаться на одной линии с осью y. Например, в Maya. - person Poken1151; 14.05.2013
comment
Я полагаю, что in-line означает camera-up-vector == world-up-vector. Это правильно? - person Trax; 14.05.2013
comment
Ну не совсем равны. Но по сути да. Я отредактировал свой пост, чтобы показать первый сработавший пример. Но при +/- 90 градусов все переворачивается. Я видел несколько других подобных сообщений, но ни одного с четким решением. Я считаю, что отслеживал свои векторы вверх / вправо и вид. В моем втором испытании я пытаюсь повернуть их все, поэтому я не уверен, что мне не хватает, чтобы правильно зафиксировать вектор вверх. - person Poken1151; 14.05.2013
comment
Ну не совсем равны. Но по сути да. что это должно значить? если вы хотите, чтобы ваша камера оставалась открытой, тогда они оба одинаковы, потому что оба выражаются в мировых координатах. Если все переворачивается, вы можете сохранить все эти 3 вектора (на самом деле вам нужно только right и view dir, поскольку up всегда (0, 1, 0)) и применять небольшие вращения для каждого кадра (с помощью мыши или вашего ввода). Если Maya не всегда держит камеру в положении (0, 1, 0), извините, я не понимаю (у меня нет Maya). - person Trax; 14.05.2013
comment
Чтобы уточнить, нет, они не совпадают. Восходящий вектор камеры вычисляется в каждом кадре, когда камера вращается вокруг начала координат. Рядный означал, что он не катится. - person Poken1151; 14.05.2013