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

Матрица модели служит для описания расположения объекта в 3D-мире. Она осуществляет преобразование вершин из локальной системы координат объекта в мировую систему координат. С слову, все системы координат в OSG являются правовинтовыми.

Следующим шагом является преобразование мировых координат в пространство вида, выполняемое с помощью матрицы вида. Предположим, что мы имеем камеру, расположенную в начале отсчета мировой системы координат. Матрица, обратная матрице преобразования камеры фактически и используется как матрица вида. В правовинтовой системе координат OpenGL, по-умолчанию, всегда определяет камеру расположенной в точке (0, 0, 0) глобальной системы координат и направленной вдоль отрицательного направления оси Z.

Замечу, что в OpenGL не разделяют понятия матрица модели и матрица вида. Однако, там определяется матрица модель-вид, выполняющая преобразование локальных координат объекта в координаты видового пространства. Эта матрица, по сути, является произвдением матрицы модели и матрицы вида. Таким образом, преобразование вершины V из локальных координат в пространство вида можно условно записать как произведение

Ve = V * modelViewMatrix

Следующей важной задачей является определить, как 3D-объекты будут проецироваться в плоскость экрана и вычислить так называемую пирамиду отсечения - область пространства, содержащую объекты, подлежащие отображению на экране. Матрица проекции используется для задания пирамиды отсечения, заданной в мировом пространстве шестью плоскостями: левой, правой, нижней, верхней, ближеней и дальней. OpenGL предоставляет функцию gluPerapective(), позволяющую задать пирамиду отсечения и способ проецирования трехмерного мира на плоскость.

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

Vs = V * modelViewMatrix * projectionMatrix * windowMatrix

или

Vs = V * MVPW

где MVPW - эквивалентная матрица преобразования, равная произведению трех матриц: матрицы модель-вид, матрицы проекции и матрицы окна.

Vs в этой ситуации является трехмерным вектором, который определяет положение 2D-пикселя со значением глубины. Обратив операцию преобразования координат мы получим линию в трехмерном пространстве. Поэтому 2D-точку можно рассматривать как две точки - одну на ближней (Zs = 0), другую - на дальней плоскости отсечения (Zs = 1). Координаты этих точек в трехмерном пространстве

V0 = (Xs, Ys, 0) * invMVPW
V1 = (Xs, Ys, 1) * invMVPW

где invMVPW - матрица, обратная MVPW.

Класс osg::Camera

Разработчики, использующие OpenGL, очень часто любят применять glTranslate() и glRotate() для перемещения сцены, а gluLookAt для перемещения камеры, хотя эти операции могут быть заменены на glMultMatrix(). Фактически, описанные операции сводятся к преобразованиям данных из мирового пространства в пространство вида.

Аналогичным образом OSG предосталвет класс osg::Transform для перемещения модели при её размещении в графе сцены, но мы всегда стараемся использовать osg::MatrixTransform и osg::PositionAttitudeTransform для работы с матрицей модели и класс osg::Camera для работы с матрицей вида.

Класс osg::Camera является одним из наиболее важных классов в ядре OSG. Он может использоваться как групповой узел графа сцены, но функционально являет собой нечто большее, чем просто узел. Его функции можно разделить на несколько категорий.

Во-первых, класс камеры ссылается на видовую матрицу, матрицу проектции и вьюпорт, которые применяются ко всем дочерним нодам камеры и проецируются её на экран. Связанные с этими функциями методы:

  1. setViewMatrix() и setViewMatrixAsLookAt() - задают видовую матрицу, передаваемую как параметр osg::Matrix или стандартные переменные eye/center/up
  2. setProjectionMatrix() - принимает параметр osg::MAtrix, определяющий матрицу проекции.
  3. setProjectionMatrixAsFrustum(), setProjectionMatrixAsOrtho(), setProjectionMatrixAsOrtho2D() и setProjectionMatrixAsPerspective() - используются для задания матрицы перспективной или ортогональной проекции с различными параметрами пирамиды отсечения. Они работают как соотвествующие функции OpenGL (glOrtho(), gluPerspective() и т.д.)
  4. setViewport() - задает прямоугольую область отрисовки в окне приложения через параметр типа osg::Viewport.

Следующий код демонстрирует процесс задания матрицы проекции и вьпорта для области с диагональю (x, y) - (x + w, x + h)

camera->setViewMatrix(viewMAtrix);
camera->setProjectionMatrix(projectionMatrix);
camera->setViewport(new osg::Viewport(x, y, w, h));

Всегда можно получить текщуие видовую матрицу, матрицу проекции и вьпорт из объекта камеры используя методы get*(), например

osg::Matrix viewMAtrix = camera->getViewMatrix();

Для получения позиции и ориентации видовой матрицы можно использовать такой код

osg::Vec3 eye, center, up;
camera->getViewMatrixAsLookAt(eye, center, up);

Во-вторых, класс камеры инкапсулирует такие функции OpenGL как glClear(), glClearColor() и glClearDepth(), очищает кадровые буферы и устанавлявает их параметры при перерисовке сцены на каждом кадре. С эими функциями связаны методы

  1. setClearMask() - устанавлявает механизм очистки буфера. По-умочанию установлена маска GL_COLOR_BUFFER_BIT GL_DEPTH_BUFFER_BIT
  2. setClearColor() - устанавливает цевет в формате RGBA, которым будет залит фуфер кадра.

Аналогично, представлены методы setClearDepth(), setClearStencil() и setClearAccum() а так же соответсвующие методы get*() для получения текущих параметров с камеры.

Третья группа методов включает в себя управление графическим контекстом openGL, о чем мы поговорим, когда будем рассматривать интегрцию OSG в сторонний графический интерфейс.

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

Управление порядком рендеринга

Существует как минимум одна основная камера в любом графе сцены. Она создается и управляется классом osgViewer::Viewer и можетбыть прочитана методом getCamera(). Она автоматически добавляет корневую ноду и все её дочерние ноды при запуске вьювера. По-умолчанию, любые другие камеры, явно и неявно добавляемые к корневому узлу, разделяют графический контекст с основной камерой, и рисуют свои подсцены в то же самое графическое окно, что и основная камера.

Коасс osg::Camera предоставляет метод setRenderOrder() для управления порядком рендеринга камерами. Это задается перечислителями, задающими порядок рендеринга и опциональным числовым параметром. Первый параметр-перечислитель может принимать значения PRE_RENDER и POST_RENDER, задающий общий порядок отрисовки. Второй параметр сортирует камеры одного типа в порядке возрастания своего значения. По-умолчанию он равен нулю.

Например

camera1->setRenderOrder(osg::Camera::PRE_RENDER);
camera2->setRenderOrder(osg::Camera::PRE_RENDER, 5);
camera3->setRenderOrder(osg::Camera::POST_RENDER);

Если задан порядок PRE_RENDER, то результат её работы будет очищаться и перекрываться результатами рендеринга следующей камеры. Это особенно полезно при рендеринге в текстуру, так как мы хотим, чтобы данная подсцена была скрыта, не мешая обновлению основной сцены.

Если задан порядок POST_RENDER, то камера может затереть текущий буфер кадра. Мы можем избежать этого, задав соответсвующие параметры для setClearMask(). Типичным примером может служить реализация HUD-дисплея поверх основной сцены.

Создание HUD-дисплея

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

main.h

#ifndef		MAIN_H
#define		MAIN_H

#include    <osg/Camera>
#include    <osgDB/ReadFile>
#include    <osgViewer/Viewer>

#endif

main.cpp

#include	"main.h"

int main(int argc, char *argv[])
{
    (void) argc; (void) argv;

    osg::ref_ptr<osg::Node> model = osgDB::readNodeFile("../data/lz.osg");
    osg::ref_ptr<osg::Node> hud_model = osgDB::readNodeFile("../data/glider.osg");

    osg::ref_ptr<osg::Camera> camera = new osg::Camera;
    camera->setClearMask(GL_DEPTH_BUFFER_BIT);
    camera->setRenderOrder(osg::Camera::POST_RENDER);

    camera->setReferenceFrame(osg::Camera::ABSOLUTE_RF);
    camera->setViewMatrixAsLookAt(osg::Vec3(0.0f, -5.0f, 5.0f),
                                  osg::Vec3(),
                                  osg::Vec3(0.0f, 1.0f, 1.0f));

    camera->addChild(hud_model.get());

    osg::ref_ptr<osg::Group> root = new osg::Group;
    root->addChild(model.get());
    root->addChild(camera.get());

    osgViewer::Viewer viewer;
    viewer.setSceneData(root.get());
    
    return viewer.run();
}

Загружаем две модели с диска - тестовый ландшафт lz.osg и модель дельтаплана glider.osg, которая будет рисваться поверх всей сцены HUD-камерой

osg::ref_ptr<osg::Node> model = osgDB::readNodeFile("../data/lz.osg");
osg::ref_ptr<osg::Node> hud_model = osgDB::readNodeFile("../data/glider.osg");

Создаем HUD-камеру. Эта камера должна рендерить изображение поверх основной сцены. Она перекрывает все ранее отрисованные данные, независимо от их расположения и глубины. Мы используем маску GL_DEPTH_BUFFER_BIT для очистки буфера глубины. GL_COLOR_BUFFER_BIT мы не устанавливаем, чтобы гарантировать сохрание данных буфера цвета

osg::ref_ptr<osg::Camera> camera = new osg::Camera;
camera->setClearMask(GL_DEPTH_BUFFER_BIT);
camera->setRenderOrder(osg::Camera::POST_RENDER);

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

camera->setReferenceFrame(osg::Camera::ABSOLUTE_RF);
camera->setViewMatrixAsLookAt(osg::Vec3(0.0f, -5.0f, 5.0f),
                              osg::Vec3(),
                              osg::Vec3(0.0f, 1.0f, 1.0f));

camera->addChild(hud_model.get());

Создаем корневую ноду и добавляем в неё в качестве дочерней камеру HUD и основную сцену в виде ландшафта.

osg::ref_ptr<osg::Group> root = new osg::Group;
root->addChild(model.get());
root->addChild(camera.get());

Как обычно запускаем сцену

osgViewer::Viewer viewer;
viewer.setSceneData(root.get());

return viewer.run();

При запуске программы манипуляции камеры влияют на модель ландшафта, однако дельтаплан не меняет свое положение вне зависимости от пользовательского ввода