В OSG класс osgViewer::Viewer поддерживает единственный вьювер для просмотра единственной сцены. Он предоставляет метод setSceneData() для задания содержимого этой сцены и метод run() для рендеринга сцены кадр за кадром. Кроме того, вьювер по умолчанию имеет объект osg::Camera в качестве основной камеры. Матрица вида камеры управляется внутренним объектом osgGA::GUIEventHandelr, обрабатывающим пользовательский ввод. Однако вьювер можно настроить на отображение в полноэкранном режиме, в окне, заставить генерировать стерео изображение, о чем мы и поговорим в последующих статьях.

Главный цикл симуляции run()

Цикл симуляции, определяемый методом run() выполняет три типа задач

  1. Задает манипулятор основной камеры.
  2. Настраивает связанный графический контекст.
  3. Рендеринг кадров в цикле.

Манипулятор читает события клавиатуры и мыши и изменяет матрицу вида для перемещения камеры по графу сцены. Он устанавлявается с помощью метода setCameraManipulator() параметром которого служит объект osgGA::CameraManipulator, например

viewer.setCameraManipulator(new osgGS::TrackballManipulator);

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

Класс манипулятора Описание Базовое использование
DriveManipulator Манипулятор, имитирующий езду по дороге Движение мышью: поворот вида. Перетаскивание мышь: левой кнопкой - ускоряет, правой - замедляет процесс перемещения
FlightManipulator Манипулятор имитирующий полет Движение мышью: изменяет положение и оринтацию вида
KeySwitchMatrixManipulator Декоратор, позволяющий переключать разные манипуляторы Используя addMatrixManipulator() добавляем манипулятор и переключаемся между манипуляторами нажатием клавиши
NodeTrackerManipulator Манипулятор, отслеживающий узел Отслеживаемй узел задается методом setTrackNode()
SphericalManipulator Манипулятор для осмотра сферических объектов Левая кнопка мыши: поворот вида, средняя кнопка: смещение вида, правая кнопка: масштабирование вида
TerrainManipulator Продвинутый трэк-болл манипулятор для просмотра ландшафтов Левая кнопка мыши: поворот вида, средняя кнопка: смещение вида, правая кнопка: масштабирование вида
TrackballManipulator Трэк-болл манипулятор по умолчанию Левая кнопка мыши: поворот вида, средняя кнопка: смещение вида, правая кнопка: масштабирование вида

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

LIBS += -Lпуть к библиотеке -losgGA

Графический контекст вьювера, все потоки и ресурсы инициализируеются методом realize(). Он автоматически вызывается перед рендерингом первого кадра.

После этого рендер входит в цикл. Для рендеринга одного кадра используется метод frame(), а проверка завершения рендеринга осуществляется вызовом метода done(). Таким образом, главный цикл может быть представлен в виде такого кода

while ( !viewer.done() )
{
	viewer.frame();
}

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

viewer.setRunFrameScheme( osgViewer::Viewer::ON_DEMAND );

Теперь frame() будет выполнятся только тогда, когда происходит изменение графа сцены, обносление процессов или пользовательских событий ввода, пока настройки рендеринга не будут изменены на CONTINUOS. Кроме того, OSG позволяет ограничить частоту обновления кадров заданием параметра через метод setRunFrameRate().

Модифицируем цикл симуляции

По предыдущим примерам мы хорошо знакомы с методом run(). Однако, возникает вопрос, а можно ли вмешаться в этот цикл, например чтобы добавить некоторые действия как до, так и после отрисовки кадра? Конечно можно, что мы и попытаемся сделать в следующем примере. При этом следует помнить о том, что на модифицированный цикл никак не влияют настройки обновления кадра по требованию а так же ограничение частоты кадров - чтобы использовать эти возможности следует использовать стандартный run().

main.h

#ifndef		MAIN_H
#define		MAIN_H

#include    <osgDB/ReadFile>
#include    <osgGA/TrackballManipulator>
#include    <osgViewer/Viewer>
#include    <iostream>

#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");

    osgViewer::Viewer viewer;

    viewer.setSceneData(model.get());
    viewer.setCameraManipulator(new osgGA::TrackballManipulator);

    while (!viewer.done())
    {
        viewer.frame();
        std::cout << "Frame number: " << viewer.getFrameStamp()->getFrameNumber() << std::endl;
    }
    
    return 0;
}

Загрузку данных сцены выполняем обычным образом

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

Создаем вьювер и передаем ему данные о сцене

osgViewer::Viewer viewer;

viewer.setSceneData(model.get());

Устанавливаем манипултор камеры

viewer.setCameraManipulator(new osgGA::TrackballManipulator);

А вот цикл run() разворачивам вручную

while (!viewer.done())
{
    viewer.frame();
    std::cout << "Frame number: " 
    		  << viewer.getFrameStamp()->getFrameNumber() 
    		  << std::endl;
}

Завершение цикла контролируем по значению, возвращаемому методом done(). Внутри цикла выполняем отрисовку кадра

viewer.frame();

и доплнительно выводим в консоль информацию о текущем кадре, в виде его номера

std::cout << "Frame number: " 
    	  << viewer.getFrameStamp()->getFrameNumber() 
    	  << std::endl;

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

и наблюдаем вывод в консоль номеров отрисованных кадров

Frame number: 196
Frame number: 197
Frame number: 198
Frame number: 199
Frame number: 200
Frame number: 201
Frame number: 202
Frame number: 203
Frame number: 204
Frame number: 205
Frame number: 206
Frame number: 207
Frame number: 208
Frame number: 209
Frame number: 210
Frame number: 211
Frame number: 212
Frame number: 213
Frame number: 214

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