Введение
Мы уже говорили о том, что класс 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 графического окна. Можно варьировать следующие параметры:
- setDoubleBuffer() — включение/выключение двойной буферизации. По-умолчанию включено.
- setDepthBuffer() — включить/выключить буфер глубины. По-умолчанию включен.
- Установить разрядность альфа-буфера (alpha buffer), буфера трафарета (stencil buffer), накопительного буфера (accumulation buffer) путем использования методов типа setMinimumNumAlphaBits(). По-умолчанию все параметры равны 0.
- Разрешение использования сглаживание и его глубина, при помощи метода setNumMultiSamples(). По-умолчанию — 0.
- Включение стерео-режима. Выключен по-умолчанию.
Рассмотрим использование данного синглтона на примере сглаживания
Пример использования синглтона osg::DisplaySettings
main.h
main.cpp
#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
main.cpp
#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
Чтобы создать окно с заданными характеристиками, необходимо проделать следующие шаги:
- Настроить объект типа osg::GraphicsContext::Traits
- Создать графический контекст окна
- Связать этот графический контекст с камерой
- Сделать камеру основной для вьювера
Пример traits
main.h
main.cpp
#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 с другими настройками, то заголовок будет на своем месте.
Продолжение следует...