29 January 2019

OpenSceneGraph: Управление окнами и режимами отображения

ProgrammingWorking with 3D-graphicsDevelopment for LinuxCGIDevelopment for Windows
Tutorial
image

Введение


Мы уже говорили о том, что класс osg::Camera управляет связанным с ним графическим контекстом OpenGL. Графический контекст инкапсулирует информацию о том, как и куда происходит отрисовка объектов и какие атрибуты состояния к ним применяются. Под контекстом понимают графическое окно, вернее его клиентскую область, или пиксельный буфер OpenGL, который хранит данные пикселей без передачи их в кадровый буфер.

OSG использует класс osg::GraphicsContext для представления абстрактного графического контекста, и класс osg::GraphicsWindow, для представления абстрактного графического окна. Последний имеет метод getEventQueue() для управления событиями от элементов GUI. Вообще говоря графический контекст есть платформоспецифичное понятие, поэтому большую часть работы по созданию окна и связыванию его контекста с контекстом OpenGL, OSG берет на себя. При вызове метода createGraphicsContext() класса osg::GraphicsContext() требуемый код (а его не мало, поверьте!) будет сгенерирован препроцессором автоматически, в зависимости от платформы. От нас лишь требуется передать этому методу аргумент типа osg::GraphicsContex::Traits, содержащий описание того, какое окно мы хотим получить.

1. Класс osg::DisplaySettings


OSG позволяет разработчику управлять глобальными настройками отображения, на основе которых работают камеры, вьюверы и рендерятся элементы сцены. Для этого используется паттерн синглтон, то есть уникальный объект, содержащий эти настройки, реализованный в виде класса osg::DisplaySettings, к которому можно получить доступ из любой точки программы. Следовательно, из нашего приложения, мы можем изменить эти настройки в любой момент

osg::DisplaySettings *ds = osg::DisplaySettings::instance();

Синглтон osg::DisplaySettings содержит настройки, применяемые к вновь создаваемым устройствам рендеринга, контексту OpenGL графического окна. Можно варьировать следующие параметры:

  1. setDoubleBuffer() — включение/выключение двойной буферизации. По-умолчанию включено.
  2. setDepthBuffer() — включить/выключить буфер глубины. По-умолчанию включен.
  3. Установить разрядность альфа-буфера (alpha buffer), буфера трафарета (stencil buffer), накопительного буфера (accumulation buffer) путем использования методов типа setMinimumNumAlphaBits(). По-умолчанию все параметры равны 0.
  4. Разрешение использования сглаживание и его глубина, при помощи метода setNumMultiSamples(). По-умолчанию — 0.
  5. Включение стерео-режима. Выключен по-умолчанию.

Рассмотрим использование данного синглтона на примере сглаживания

Пример использования синглтона osg::DisplaySettings
main.h


#ifndef		MAIN_H
#define		MAIN_H

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

#endif

main.cpp

#include	"main.h"

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

    osg::DisplaySettings::instance()->setNumMultiSamples(6);

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

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

    return viewer.run();
}


Существенным здесь является лишь один вызов

osg::DisplaySettings::instance()->setNumMultiSamples(6);

— задание параметра сглаживания, который может принимать значения 2, 4 и 6 в зависимости от используемого графического устройства. Обратим внимание, как выглядит лопасть винта цессны без применения сглаживания



и после его применения



2. Переключение в оконный режим


Класс osgViewer::Viewer может быть очень быстро перенастроен на отображение в оконном режиме. Как вы заметили, все предыдущие наши примеры отображались в полноэкранном режиме. Для переключения вьювера в оконный режим существует метод setUpViewInWindow(), который принимает в качестве параметров координаты левого верхнего угла окна, его ширину и высоту в пикселях

viewer.setUpViewInWindow(50, 50, 800, 600);

Опционально этот метод принимает пятый параметр — номер экрана, на который следует выводить окно, для того случая, если у вас не один монитор. Наверняка работая с несколькими мониторами в Windows вы наблюдали, что сцена расползается на все мониторы в полноэкранном режиме (в Linux такое не наблюдается).

Кроме того, в настройках проекта можно задать переменную окружения OSG_WINDOW таким образом



что будет эквивалентно вызову setUpViewInWindow(), который в таком случае можно и не выполнять.



Для явного указания экрана, на который следует выводить вьювер в полноэкранном режиме можно воспользоваться методом setUpViewOnSingleScreen() указав в качестве параметра номер экрана (по-умолчанию 0).

OSG поддерживает также демонстрационные сферические дисплеи. Для настройки отображения на таком дисплее можно использовать метод setUpViewFor3DSphericalDisplay().

3. Композитный вьювер


Класс osgViewer::Viewer управляет одним видом, отображающим один граф сцены. Кроме него существует класс osgViewer::CompositeViewer который поддерживает несколько видов и несколько сцен. Он имеет те же методы run(), frame() и done() для управления процессом рендеринга, но при этом позволяет добавлять и удалять независимые виды с помощью методов addView() и removeView(), а также получение видов по их индексу методом getView(). Объект вида описывается классом osgViewer::View.

Класс osgViewer::View является базовым для класса osgViewer::Viewer. Он позволяет добавлять корневой узел с данными сцены, манипулятор камеры и обработчики событий. Основное отличие этого класса (вид) от класса вьювера — он не позволяет рендерить сцену вызовами run() или frame(). Типичный сценарий добавления вида выглядит так

osgViewer::CompositeViewer multiviewer;
multiviewer.addView( view );

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

Пример composite
main.h


#ifndef		MAIN_H
#define		MAIN_H

#include    <osgDB/ReadFile>
#include    <osgViewer/CompositeViewer>

#endif

main.cpp

#include	"main.h"

//------------------------------------------------------------------------------
//
//------------------------------------------------------------------------------
osgViewer::View *createView(int x, int y, int w, int h,
                            osg::Node *scene)
{
    osg::ref_ptr<osgViewer::View> view = new osgViewer::View;
    view->setSceneData(scene);
    view->setUpViewInWindow(x, y, w, h);

    return view.release();
}

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

    osg::ref_ptr<osg::Node> model1 = osgDB::readNodeFile("../data/cessna.osg");
    osg::ref_ptr<osg::Node> model2 = osgDB::readNodeFile("../data/cow.osg");
    osg::ref_ptr<osg::Node> model3 = osgDB::readNodeFile("../data/glider.osg");

    osgViewer::View *view1 = createView(50, 50, 320, 240, model1);
    osgViewer::View *view2 = createView(380, 50, 320, 240, model2);
    osgViewer::View *view3 = createView(185, 330, 320, 240, model3);

    osgViewer::CompositeViewer viewer;
    viewer.addView(view1);
    viewer.addView(view2);
    viewer.addView(view3);
    
    return viewer.run();
}


Создание отдельного вида поместим в функцию, принимающую в качестве параметров положение и размеры окна, а также сцену в виде указателя на её корневой узел

osgViewer::View *createView(int x, int y, int w, int h,
                            osg::Node *scene)
{
    osg::ref_ptr<osgViewer::View> view = new osgViewer::View;
    view->setSceneData(scene);
    view->setUpViewInWindow(x, y, w, h);

    return view.release();
}

Здесь мы создаем вид, управляемый умным указателем на объект osgViewer::View

osg::ref_ptr<osgViewer::View> view = new osgViewer::View;

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

view->setSceneData(scene);
view->setUpViewInWindow(x, y, w, h);

Вид возвращаем из функции по правилам возврата умных указателей

return view.release();

Теперь в основной программе загружаем три разных модели

osgViewer::View *view1 = createView(50, 50, 320, 240, model1);
osgViewer::View *view2 = createView(380, 50, 320, 240, model2);
osgViewer::View *view3 = createView(185, 330, 320, 240, model3);

создаем три разных вида

osgViewer::View *view1 = createView(50, 50, 320, 240, model1);
osgViewer::View *view2 = createView(380, 50, 320, 240, model2);
osgViewer::View *view3 = createView(185, 330, 320, 240, model3);

создаем композитный вьювер и добавляем в него созданные ранее виды

osgViewer::CompositeViewer viewer;
viewer.addView(view1);
viewer.addView(view2);
viewer.addView(view3);

и запускаем рендеринг абсолютно аналогично тому, как мы это делали в случае с одной сценой

return viewer.run();

Всё! При запуске программы мы получим три разный окна. Содержимым каждого окна можно управлять независимо. Любое из окон можно закрыть стандартным способом, а из приложения целиком выйти по нажатию Esc.



3. Класс osg::GraphicsContext::Traits


Слово "traits" в переводе с английского означает "черты". Так вот, вышеупомянутый класс описывает черты будущего окна, и содержит все свойства для описания графического контекста. Он отличается от класса osg::DisplaySettings, который управляет характеристиками всех графических контекстов для вновь созданных камер. Основные публичные свойства этого класса перечислены в таблице ниже

Атрибут класса Тип Значение по-умолчанию Описание
x int 0 Начальная горизонтальная позиция окна
y int 0 Начальная вертикальная позиция окна
width int 0 Ширина окна
height int 0 Высота окна
windowName std::string "" Заголовок окна
windowDecoration bool false Флаг отображения заголовка окна
red unsigned int 8 Число бит красного в буфере цвета OpenGL
green unsigned int 8 Число бит зеленого в буфере цвета OpenGL
blue unsigned int 8 Число бит синего в буфере цвета OpenGL
alpha unsigned int 8 Число бит в альфа-буфере OpenGL
depth unsigned int 24 Число бит в буфере глубины OpenGL
stencil unsigned int 0 Число бит в буфере трафарета OpenGL
doubleBuffer bool false Использовать двойной буфер
samples unsigned int 0 Число примитивом сглаживания
quadBufferStereo bool false Использовать счетверенный стерео-буфер (для оборудования NVidia)
inheritedWindowData osg::ref_ptr NULL Описатель данных, ассоциированных с окном

Для инициализации объекта Traits необходимо выполнить следующий код

osg::ref_ptr<osg::GraphicsContext::Traits> traits = new osg::GraphicsContext::Traits;

traits->x = 50;
traits->y = 100;
...

4. Настраиваем окно приложения OSG


Чтобы создать окно с заданными характеристиками, необходимо проделать следующие шаги:

  1. Настроить объект типа osg::GraphicsContext::Traits
  2. Создать графический контекст окна
  3. Связать этот графический контекст с камерой
  4. Сделать камеру основной для вьювера

Пример traits
main.h

#ifndef		MAIN_H
#define		MAIN_H

#include    <osg/GraphicsContext>
#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::GraphicsContext::Traits> traits = new osg::GraphicsContext::Traits;
    traits->x = 50;
    traits->y = 50;
    traits->width = 800;
    traits->height = 600;
    traits->windowName = "OSG application";
    traits->windowDecoration = true;
    traits->doubleBuffer = true;
    traits->samples = 4;

    osg::ref_ptr<osg::GraphicsContext> gc = osg::GraphicsContext::createGraphicsContext(traits.get());

    osg::ref_ptr<osg::Camera> camera = new osg::Camera;
    camera->setGraphicsContext(gc);
    camera->setViewport( new osg::Viewport(0, 0, traits->width, traits->height) );
    camera->setClearMask(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
    camera->setClearColor( osg::Vec4(0.2f, 0.2f, 0.4f, 1.0f) );
    double aspect = static_cast<double>(traits->width) / static_cast<double>(traits->height);
    camera->setProjectionMatrixAsPerspective(30.0, aspect, 1.0, 1000.0);
    camera->getOrCreateStateSet()->setMode(GL_DEPTH_TEST, osg::StateAttribute::ON);

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

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


Для задания настроек окна создаем экземпляр класса osg::GraphicsContext::Traits и инициализируем его необходимыми нам параметрами

osg::ref_ptr<osg::GraphicsContext::Traits> traits = new osg::GraphicsContext::Traits;
traits->x = 50;
traits->y = 50;
traits->width = 800;
traits->height = 600;
traits->windowName = "OSG application";
traits->windowDecoration = true;
traits->doubleBuffer = true;
traits->samples = 4;

После этого создаем графический контекст, передавая в качестве настроек указатель на traits

osg::ref_ptr<osg::GraphicsContext> gc = osg::GraphicsContext::createGraphicsContext(traits.get());

Создаем камеру

osg::ref_ptr<osg::Camera> camera = new osg::Camera;

Связываем камеру с созданным графическим контекстом

camera->setGraphicsContext(gc);

Настраиваем вьюпорт, задаем маску очистки буферов, задаем цвет очистки

camera->setViewport( new osg::Viewport(0, 0, traits->width, traits->height) );
camera->setClearMask(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
camera->setClearColor( osg::Vec4(0.2f, 0.2f, 0.4f, 1.0f) );

Настраиваем матрицу перспективной проекции

double aspect = static_cast<double>(traits->width) / static_cast<double>(traits->height);
camera->setProjectionMatrixAsPerspective(30.0, aspect, 1.0, 1000.0);

Не забываем включить тест глубины, для корректного отображения граней

camera->getOrCreateStateSet()->setMode(GL_DEPTH_TEST, osg::StateAttribute::ON);

Загружаем модель самолета

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

Настраиваем и запускаем вьювер, указав настроенную нами камеру в качетве основной камеры

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

return viewer.run();

На выходе имеем окно с требуемыми параметрами



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

Продолжение следует...
Tags:3d-графикаграфический движокopenscenegraphc++
Hubs: Programming Working with 3D-graphics Development for Linux CGI Development for Windows
+9
1.4k 25
Leave a comment