ПрограммированиеСтатьиГрафика

Камера в OpenGL

Внимание! Этот документ ещё не опубликован.

Автор:

Введение
Теория
Матрица преобразования в OpenGL
Класс камеры
Список литературы
Приложение

Введение


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

Теория

"В OpenGL вершины всегда задаются четырьмя координатами, что связано с использованием так называемых однородных координат, основное удобство применения которых заключается в том, что с их помощью все виды преобразований координат могут быть представлены в единой форме. Они были введены в геометрии и впоследствии использованы в графике. В однородных координатах положение точки Р(х, у, z) записывается как P(W•x, W•y, W•z, W) или P(X, Y, Z, W) для любого масштабного множителя, причем трехмерные декартовы координаты легко определяются:

x = X/W; у = Y/W; z = Z/W

То есть привычная нам ортогональная система координат получается как проекция однородной системы на плоскость W = 1.

В матричной форме для обозначения координат точки в некотором трехмерном пространстве с использованием однородных координат применяется запись |Х У Z W|.

Преобразования однородных координат описываются соотношениями
eq1 | Камера в OpenGL
и
eq2 | Камера в OpenGL
где Т — некоторая матрица преобразования.

Рассмотрим систему уравнений:


eq3 | Камера в OpenGL

Запишем эту систему уравнений в матричной форме:


eq4 | Камера в OpenGL

или в более удобной для будущего использования форме


eq5 | Камера в OpenGL

Рассмотрим теперь изменение масштаба. В этом случае

eq6 | Камера в OpenGL

или в матричной форме


eq7 | Камера в OpenGL

Что получится, если объединить два рассмотренных преобразования? В этом случае получаем композицию двух преобразований, которая в матричной форме записывается в виде:


eq7 | Камера в OpenGL

В общем виде обобщённая 4х4-матрица преобразования для трёхмерных однородных координат имеет вид:


eq8 | Камера в OpenGL

T может быть представлена в виде блочной матрицы:


eq9 | Камера в OpenGL

Подматрица


eq10 | Камера в OpenGL

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


eq11 | Камера в OpenGL

производит перенос, вектор


eq12 | Камера в OpenGL

- перспективное преобразование, а последний скалярный элемент s - общее изменение масштаба." [1]

Матрица преобразования в OpenGL

OpenGL хранит матрицу преобразования в следующем виде:


m | Камера в OpenGL

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

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

Матрицу преобразования можно рассматривать и в другом виде:


axis | Камера в OpenGL
3 набора элементов (m0, m1, m2), (m4, m5, m6), (m8, m9, m10) представляют собой координаты ортогонального базиса в некой глобальной системе координат:

Элементы верхней левой 3x3 подматрицы отвечают за поворот и масштабирование, то есть их изменяют команды glRotate() и glScale(). Подробнее почитать, как именно изменяются элементы матрицы при повороте, можно тут. Элементы m12, m13, m14 модифицируются командой gTranslate(). Элементы m3, m7, m11 являются тёмной материей и модифицировать их не стоит. [2]

Класс камеры

Код класса камеры портирован отсюда с С++ на С#/OpenTK

class Camera
{
    private int viewport_width;
    private int viewport_height;
    private float[] transform = null;

    public Camera(float x, float y, float z, int viewport_width, int viewport_height)
    {
        this.viewport_width = viewport_width;
        this.viewport_height = viewport_height;

        transform = new float[16];
        transform[0] = 1.0f;
        transform[5] = 1.0f;
        transform[10] = -1.0f;
        transform[15] = 1.0f;
        transform[12] = x; transform[13] = y; transform[14] = z;
    }
}

Первые 2 переменные хранят размеры области вывода и нужны для установки перспективной матрицы. Массив хранит модельно-видовую матрицу.

Конструктор принимает 3 координаты положения камеры в пространстве и размеры области вывода. Внутри конструктора после сохранения ширины и высоты области вывода мы устанавливаем верхнюю левую 3х3 подматрицу в единичную матрицу (кроме 10 элемента, так как ось oZ направлена не от наблюдателя, а в обратную сторону). Правый нижний элемент матрицы отвечает за масштаб и всегда равен 1. Элементы матрицы с 12 по 14 отвечают за координаты камеры и инициализируются входными параметрами.

internal void MoveLoc(float x, float y, float z, float distance)
{
    float dx = x * transform[0] + y * transform[4] + z * transform[8];
    float dy = x * transform[1] + y * transform[5] + z * transform[9];
    float dz = x * transform[2] + y * transform[6] + z * transform[10];
    transform[12] += dx * distance;
    transform[13] += dy * distance;
    transform[14] += dz * distance;
}

Первые три параметра определяют расстояние, на которое будет передвинута камера в локальной системе координат вдоль каждой из осей. Четвёртый параметр является масштабным множителем, для первых трёх. Его назначение в этом методе неясно, но я не стал менять код, а просто всегда передаю 1 в качестве его значения. После перемножения входных координат на локальную матрицу преобразования 3х3 (в верхнем левом углу) результаты записываются в глобальную матрицу преобразования в качестве глобальных координат камеры. Визуальным эффектом от вызова метода MoveLoc() может быть, к примеру, движение камеры вперёд, для чего нужно просто передать в метод отрицательное смещение по оси oZ.

internal void MoveGlob(float x, float y, float z, float distance)
{
    transform[12] += x * distance;
    transform[13] += y * distance;
    transform[14] += z * distance;
}

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

internal void RotateLoc(float deg, float x, float y, float z)
{
    GL.MatrixMode(MatrixMode.Modelview);
    GL.PushMatrix();
    GL.LoadMatrix(transform);
    GL.Rotate(deg, x, y, z);
    GL.GetFloat(GetPName.ModelviewMatrix, transform);
    GL.PopMatrix();
}

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

internal void RotateGlob(float deg, float x, float y, float z)
{
    float dx = x * transform[0] + y * transform[1] + z * transform[2];
    float dy = x * transform[4] + y * transform[5] + z * transform[6];
    float dz = x * transform[8] + y * transform[9] + z * transform[10];
    GL.MatrixMode(MatrixMode.Modelview);
    GL.PushMatrix();
    GL.LoadMatrix(transform);
    GL.Rotate(deg, dx, dy, dz);
    GL.GetFloat(GetPName.ModelviewMatrix, transform);
    GL.PopMatrix();
}

Метод RotateGlob отличается от предыдущего тем, что предварительно вектор, относительно которого происходит поворот, переводится локальные координаты.

internal void SetView()
{
    GL.MatrixMode(MatrixMode.Projection);

    Matrix4 mat = Matrix4.CreateOrthographicOffCenter(-this.viewport_width / 2,
                                                       this.viewport_width / 2,
                                                      -this.viewport_height / 2,
                                                       this.viewport_height / 2, -1, 1);
    GL.LoadMatrix(ref mat);

    GL.MatrixMode(MatrixMode.Modelview);

    float[] viewmatrix = new float[]{//Remove the three - for non-inverted z-axis
          transform[0], transform[4], -transform[8], 0,
          transform[1], transform[5], -transform[9], 0,
          transform[2], transform[6], -transform[10], 0,

          -(transform[0]*transform[12] +
          transform[1]*transform[13] +
          transform[2]*transform[14]),

          -(transform[4]*transform[12] +
          transform[5]*transform[13] +
          transform[6]*transform[14]),

          //add a - like above for non-inverted z-axis
          (transform[8]*transform[12] +
          transform[9]*transform[13] +
          transform[10]*transform[14]), 1};

    GL.LoadMatrix(viewmatrix);
}

Ключевой метод SetView(), который нужно вызывать каждый кадр перед отрисовкой всех объектов, устанавливает из матрицы transform значения для верхней левой подматрицы 3х3 простым копированием соответствующих элементов (с учётом инвертированной оси oZ), а вектор положения камеры в пространстве предварительно умножается на базисные векторы, чтобы получить соответствующие координаты камеры в новой системе координат.

Список литературы


1. Тихомиров Ю.В. OpenGL. Программирование трёхмерной графики. - 2-е изд. - СПб.: БВЧ-Петербург, 2002. - 304 с.: ил., с. 120-123.
2. OpenGL Transformation
Страницы: 1 2 Следующая »

#OpenGL, #камера

25 августа 2011 (Обновление: 20 сен 2012)

Комментарии [9]