
Введение
OpenSceneGraph (далее OSG) — открытый кроссплатформенный фреймворк, написанный на C++ и представляющий собой графический движок, предоставляющий программисту объектный интерфейс к OpenGL. В нашей стране этот движок не особенно популярен, даже на Хабре я видел только одну более-менее приличную публикацию о нем. OSG применяется за рубежом много где, например он является основой для свободного авиасимулятора FlightGear, существует открытая реализация игры Morrowind, называемая OpenMW разработка которой так же перенесена на OSG с движка Ogre. Русскоязычной документации по нему исчезающе мало, а среди англоязычной можно отметить лишь серию книг от разработчиков: OpenSceneGraph 3.0. Beginner’s Guide и OpenSceneGraph 3. Cookbook.
Тем не менее, движок достаточно интересен по следующим причинам:
- Открытая кроссплатформенная реализация на C++.
- Модульная архитектура.
- Расширяемость за счет встроенной системы плагинов.
- Возможность многопоточной обработки графических данных и встроенный инструментарий для её реализации
- Управление динамической памятью через механизм умных указателей
Думаю, что читателям Хабра будет интересно более подробно ознакомится с этим проектом. Не лишним будет и пополнение русскоязычной базы знаний по OSG. Все материалы, которые будут публиковаться мной по данной теме основаны на книге OpenSceneGraph 3.0. Beginner’s Guide, но являются не её переводом, а скорее творческой переработкой изложенного там материала. Если вам интересна данная тема, прошу под кат
Единственным верным способом получить самую свежую версию OSG на своей машине — собрать библиотеку из исходных текстов. Существующий бинарный инсталлятор для Windows ориентируется на компилятор MS Visual C++. Мне же, для своих проектов необходимо использование компилятора GCC, вернее его варианта MinGW32, входящего в поставку средств разработки фреймворка Qt. Таким образом нам понадобится:
- Установленный и настроенный фреймворк Qt с компилятором MinGW32 версии 5.3 и IDE QtCreator
- Клиент Git для Windows
- Утилита сmake для Windows
Предполагается, что читатель знаком с IDE QtCreator и системой сборки qmake, используемой в проектах Qt. Кроме того, предполагается, что читатель владеет основами использования системы контроля версий Git и имеет ненулевые навыки в программировании в принципе.
1. Получение исходных текстов OpenSceneGraph
Создаем на своем жестком диске каталог, где будем производить сборку OSG, например по пути D:\OSG

Перейдем в этот каталог и стянем туда исходники с официального репозитория OSG на Github
D:\OSG> git clone https://github.com/openscenegraph/OpenSceneGraph.git
Длительность процесса скачивания зависит от того, насколько широк ваш канал доступа в Интернет. Рано или поздно мы получим у себя локальную копию репозитория OSG.
Скачав исходники создадим рядом каталог build-win32-debug

В этом каталоге мы будем осуществлять сборку отладочного комплекта OSG. Но прежде
2. Настройка cmake
Для корректной работы cmake нам следует отредактировать файл путь-установки-cmake\share\cmake-3.13\Modules\CMakeMinGWFindMake.cmake. По-умолчанию он выглядит так
# Distributed under the OSI-approved BSD 3-Clause License. See accompanying
# file Copyright.txt or https://cmake.org/licensing for details.
find_program(CMAKE_MAKE_PROGRAM mingw32-make.exe PATHS
"[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\MinGW;InstallLocation]/bin"
c:/MinGW/bin /MinGW/bin
"[HKEY_CURRENT_USER\\Software\\CodeBlocks;Path]/MinGW/bin"
)
find_program(CMAKE_SH sh.exe )
if(CMAKE_SH)
message(FATAL_ERROR "sh.exe was found in your PATH, here:\n${CMAKE_SH}\nFor MinGW make to work correctly sh.exe must NOT be in your path.\nRun cmake from a shell that does not have sh.exe in your PATH.\nIf you want to use a UNIX shell, then use MSYS Makefiles.\n")
set(CMAKE_MAKE_PROGRAM NOTFOUND)
endif()
mark_as_advanced(CMAKE_MAKE_PROGRAM CMAKE_SH)
Закоментируем в нем несколько строк, дабы утилита не пыталась искать в нашей системе юниксовый шелл, и не найдя, завершалась с ошибкой
# Distributed under the OSI-approved BSD 3-Clause License. See accompanying
# file Copyright.txt or https://cmake.org/licensing for details.
find_program(CMAKE_MAKE_PROGRAM mingw32-make.exe PATHS
"[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\MinGW;InstallLocation]/bin"
c:/MinGW/bin /MinGW/bin
"[HKEY_CURRENT_USER\\Software\\CodeBlocks;Path]/MinGW/bin"
)
#find_program(CMAKE_SH sh.exe )
#if(CMAKE_SH)
# message(FATAL_ERROR "sh.exe was found in your PATH, here:\n${CMAKE_SH}\nFor MinGW make to work correctly sh.exe must NOT be in your path.\nRun cmake from a shell that does not have sh.exe in your PATH.\nIf you want to use a UNIX shell, then use MSYS Makefiles.\n")
# set(CMAKE_MAKE_PROGRAM NOTFOUND)
#endif()
mark_as_advanced(CMAKE_MAKE_PROGRAM CMAKE_SH)
3. Сборка и установка отладочной и релизной версий движка
Теперь запускаем командный интерпретатор cmd, ярлык на который находится по пути Пуск->Программы->Qt->Qt 5.11.2->Qt 5.11.2 for Desktop (MinGW 5.3.0 32bit)

Запущенный сеанс командной строки настраивает всё окружение, необходимое для работы средств сборки mingw32. Переходим в каталог с исходниками OSG
C:\Qt\Qt5.11.2\5.11.2\mingw53_32>D:
D:\> cd OSG\build-win32-debug
Даем команду
D:\OSG\build-win32-debug>cmake -G "MinGW Makefiles" -DCMAKE_INSTALL_PREFIX=E:\Apps\OSG -DCMAKE_BUILD_TYPE=DEBUG ../OpenSceneGraph
Разберем смысл параметров подробнее:
- -G "MinGW Makefiles" — указывает, что необходимо сгенерировать Makefile для утилиты mingw32-make
- -DCMAKE_INSTALL_PREFIX=E:\Apps\OSG — устанавливаем путь, по которому будет установлен OSG
- -DCMAKE_BUILD_TYPE=DEBUG — указывает, что следует собирать отладочную версию движка.
Выполнение команды проверяет готовность окружения для сборки, генерирует сценарий сборки и следующий выхлоп
Выхлоп cmake при настройке сборки OSG
-- The C compiler identification is GNU 5.3.0
-- The CXX compiler identification is GNU 5.3.0
-- Check for working C compiler: C:/Qt/Qt5.11.2/Tools/mingw530_32/bin/gcc.exe
-- Check for working C compiler: C:/Qt/Qt5.11.2/Tools/mingw530_32/bin/gcc.exe -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: C:/Qt/Qt5.11.2/Tools/mingw530_32/bin/g++.exe
-- Check for working CXX compiler: C:/Qt/Qt5.11.2/Tools/mingw530_32/bin/g++.exe -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Looking for pthread.h
-- Looking for pthread.h - found
-- Looking for pthread_create
-- Looking for pthread_create - found
-- Found Threads: TRUE
-- Found OpenGL: opengl32
-- Could NOT find EGL (missing: EGL_INCLUDE_DIR)
-- Checking windows version...
-- Performing Test GL_HEADER_HAS_GLINT64
-- Performing Test GL_HEADER_HAS_GLINT64 - Failed
-- Performing Test GL_HEADER_HAS_GLUINT64
-- Performing Test GL_HEADER_HAS_GLUINT64 - Failed
-- 32 bit architecture detected
-- Could NOT find Freetype (missing: FREETYPE_LIBRARY FREETYPE_INCLUDE_DIRS)
-- Could NOT find JPEG (missing: JPEG_LIBRARY JPEG_INCLUDE_DIR)
-- Could NOT find Jasper (missing: JASPER_LIBRARIES JASPER_INCLUDE_DIR JPEG_LIBRARIES)
-- Could NOT find LibXml2 (missing: LIBXML2_LIBRARY LIBXML2_INCLUDE_DIR)
-- Could NOT find ZLIB (missing: ZLIB_INCLUDE_DIR)
-- Could NOT find ZLIB (missing: ZLIB_INCLUDE_DIR)
-- Could NOT find GDAL (missing: GDAL_LIBRARY GDAL_INCLUDE_DIR)
-- Could NOT find PkgConfig (missing: PKG_CONFIG_EXECUTABLE)
-- Could NOT find CURL (missing: CURL_LIBRARY CURL_INCLUDE_DIR)
-- Trying to find DCMTK expecting DCMTKConfig.cmake
-- Trying to find DCMTK expecting DCMTKConfig.cmake - failed
-- Trying to find DCMTK relying on FindDCMTK.cmake
-- Please set DCMTK_DIR and re-run configure (missing: DCMTK_config_INCLUDE_DIR DCMTK_dcmdata_INCLUDE_DIR DCMTK_dcmimage_INCLUDE_DIR DCMTK_dcmimgle_INCLUDE_DIR DCMTK_dcmjpeg_INCLUDE_DIR DCMTK_dcmjpls_INCLUDE_DIR DCMTK_dcmnet_INCLUDE_DIR DCMTK_dcmpstat_INCLUDE_DIR DCMTK_dcmqrdb_INCLUDE_DIR DCMTK_dcmsign_INCLUDE_DIR DCMTK_dcmsr_INCLUDE_DIR DCMTK_dcmtls_INCLUDE_DIR DCMTK_ofstd_INCLUDE_DIR DCMTK_oflog_INCLUDE_DIR)
-- Could NOT find PkgConfig (missing: PKG_CONFIG_EXECUTABLE)
-- Could NOT find GStreamer (missing: GSTREAMER_INCLUDE_DIRS GSTREAMER_LIBRARIES GSTREAMER_VERSION GSTREAMER_BASE_INCLUDE_DIRS GSTREAMER_BASE_LIBRARIES GSTREAMER_APP_INCLUDE_DIRS GSTREAMER_APP_LIBRARIES GSTREAMER_PBUTILS_INCLUDE_DIRS GSTREAMER_PBUTILS_LIBRARIES) (found version "")
-- Could NOT find SDL2 (missing: SDL2_LIBRARY SDL2_INCLUDE_DIR)
-- Could NOT find SDL (missing: SDL_LIBRARY SDL_INCLUDE_DIR)
-- Could NOT find PkgConfig (missing: PKG_CONFIG_EXECUTABLE)
-- Could NOT find PkgConfig (missing: PKG_CONFIG_EXECUTABLE)
-- Could NOT find PkgConfig (missing: PKG_CONFIG_EXECUTABLE)
-- Could NOT find JPEG (missing: JPEG_LIBRARY JPEG_INCLUDE_DIR)
-- Could NOT find ZLIB (missing: ZLIB_INCLUDE_DIR)
-- Could NOT find PNG (missing: PNG_LIBRARY PNG_PNG_INCLUDE_DIR)
-- Could NOT find TIFF (missing: TIFF_LIBRARY TIFF_INCLUDE_DIR)
-- g++ version 5.3.0
-- Performing Test _OPENTHREADS_ATOMIC_USE_GCC_BUILTINS
-- Performing Test _OPENTHREADS_ATOMIC_USE_GCC_BUILTINS - Success
-- Performing Test _OPENTHREADS_ATOMIC_USE_MIPOSPRO_BUILTINS
-- Performing Test _OPENTHREADS_ATOMIC_USE_MIPOSPRO_BUILTINS - Failed
-- Performing Test _OPENTHREADS_ATOMIC_USE_SUN
-- Performing Test _OPENTHREADS_ATOMIC_USE_SUN - Failed
-- Performing Test _OPENTHREADS_ATOMIC_USE_WIN32_INTERLOCKED
-- Performing Test _OPENTHREADS_ATOMIC_USE_WIN32_INTERLOCKED - Success
-- Performing Test _OPENTHREADS_ATOMIC_USE_BSD_ATOMIC
-- Performing Test _OPENTHREADS_ATOMIC_USE_BSD_ATOMIC - Failed
-- Configuring done
-- Generating done
-- Build files have been written to: D:/OSG/build-win32-debug
говорит нам о том, что можно приступать к сборке. Даем команду
D:\OSG\build-win32-debug>mingw32-make -j9
можно, как в моем примере, указать число потоков сборки, если у вас многоядерный процессор (ключ -j). Начнется процесс сборки, занимающий на моем компьютере около восьми минут

По окончании сборки устанавливаем библиотеку
D:\OSG\build-win32-debug> mingw32-make install
после выполнения команды обнаруживаем библиотеку установленной по заранее заданному нами пути

Теперь соберем релизную версию движка, создав другой каталог сборки
D:\OSG\build-win32-debug>cd ..
D:\OSG> mkdir build-win32-release
D:\OSG>cd build-win32-release
D:\OSG\build-win32-release> cmake -G "MinGW Makefiles" -DCMAKE_INSTALL_PREFIX=E:\Apps\OSG ../OpenSceneGraph
D:\OSG\build-win32-release> mingw32-make -j9
D:\OSG\build-win32-release> mingw32-make install
4. Настройка переменных окружения
Расположение библиотек OSG после инсталляции может быть любым — определяется это пожеланиями конкретного пользователя и его возможностями для размещения файлов на компьютере. При этом, при настройке конкретного проекта, использующего данные библиотеке требует стремится к некой унификации, абстрагируясь от конкретного расположения библиотек.
Создадим несколько системных переменных окружения, указывающих пути к библиотекам, заголовочным файлам и плагинам OSG. В приведенном мной примере это будет выглядеть так

Необходимо создать переменные, имена которых обведены на скриншоте красным. После создания переменных, для того, чтобы они были видны средствами разработки, в частности QtCreator-ом нужно как минимум перелогинится в системе (выйти и зайти от имени текущего пользователя) или, возможно, перезагрузить систему (это же Windows!)
После этого процедуру установки OSG на наш компьютер можно считать оконченной.
5. Пишем Hello World в QtCreator
Знакомство с графическим движком OpenSceneGraph начнем с простейшего примера, как это обычно принято в программировании с некоего "Hello world!".
Прежде чем писать какой-либо код, настроим наш проект для системы сборки qmake, которую мы будем использовать на протяжении всего цикла статей. В интересующем нас месте файловой системы создадим следующую структуру каталогов
OSG-lessons/
|-data/
|-OSG-lessons/
|
|-hello/
|-include/
|-src/
В каталоге hello создадим файл hello.pro следующего содержания
Полный текст hello.pro
Разберем эти письме подробнее
TEMPLATE = app
TARGET = hello
DESTDIR = ../../bin
win32 {
OSG_LIB_DIRECTORY = $$(OSG_BIN_PATH)
OSG_INCLUDE_DIRECTORY = $$(OSG_INCLUDE_PATH)
CONFIG(debug, debug|release) {
TARGET = $$join(TARGET,,,_d)
LIBS += -L$$OSG_LIB_DIRECTORY -losgd
LIBS += -L$$OSG_LIB_DIRECTORY -losgViewerd
LIBS += -L$$OSG_LIB_DIRECTORY -losgDBd
LIBS += -L$$OSG_LIB_DIRECTORY -lOpenThreadsd
} else {
LIBS += -L$$OSG_LIB_DIRECTORY -losg
LIBS += -L$$OSG_LIB_DIRECTORY -losgViewer
LIBS += -L$$OSG_LIB_DIRECTORY -losgDB
LIBS += -L$$OSG_LIB_DIRECTORY -lOpenThreads
}
INCLUDEPATH += $$OSG_INCLUDE_DIRECTORY
}
unix {
CONFIG(debug, debug|release) {
TARGET = $$join(TARGET,,,_d)
LIBS += -losgd
LIBS += -losgViewerd
LIBS += -losgDBd
LIBS += -lOpenThreadsd
} else {
LIBS += -losg
LIBS += -losgViewer
LIBS += -losgDB
LIBS += -lOpenThreads
}
}
INCLUDEPATH += ./include
HEADERS += $$files(./include/*.h)
SOURCES += $$files(./src/*.cpp)
Разберем эти письме подробнее
TEMPLATE = app
TARGET = hello
DESTDIR = ../../bin
Переменные задают шаблон проекта (app — приложение), имя исполняемого файла (hello) и каталог, куда исполняемый файл помещается после сборки.
win32 {
OSG_LIB_DIRECTORY = $$(OSG_BIN_PATH)
OSG_INCLUDE_DIRECTORY = $$(OSG_INCLUDE_PATH)
CONFIG(debug, debug|release) {
TARGET = $$join(TARGET,,,_d)
LIBS += -L$$OSG_LIB_DIRECTORY -losgd
LIBS += -L$$OSG_LIB_DIRECTORY -losgViewerd
LIBS += -L$$OSG_LIB_DIRECTORY -losgDBd
LIBS += -L$$OSG_LIB_DIRECTORY -lOpenThreadsd
} else {
LIBS += -L$$OSG_LIB_DIRECTORY -losg
LIBS += -L$$OSG_LIB_DIRECTORY -losgViewer
LIBS += -L$$OSG_LIB_DIRECTORY -losgDB
LIBS += -L$$OSG_LIB_DIRECTORY -lOpenThreads
}
INCLUDEPATH += $$OSG_INCLUDE_DIRECTORY
}
В зависимости от ОС, где собирается проект, определяем переменные, указывающие пути к каталогам библиотек и заголовочных файлов OSG. Вот тут-то нам и пригодились переменные окружения OSG_BIN_PATH и OSG_INCLUDE_PATH — теперь неважно, где установлена библиотека OSG. Любой желающий работать с этим проектом на своем компьютере просто пропишет соответствующие переменные окружения в своей системе, не редактируя сценарий сборки.
CONFIG(debug, debug|release) {
TARGET = $$join(TARGET,,,_d)
LIBS += -L$$OSG_LIB_DIRECTORY -losgd
LIBS += -L$$OSG_LIB_DIRECTORY -losgViewerd
LIBS += -L$$OSG_LIB_DIRECTORY -losgDBd
LIBS += -L$$OSG_LIB_DIRECTORY -lOpenThreadsd
} else {
LIBS += -L$$OSG_LIB_DIRECTORY -losg
LIBS += -L$$OSG_LIB_DIRECTORY -losgViewer
LIBS += -L$$OSG_LIB_DIRECTORY -losgDB
LIBS += -L$$OSG_LIB_DIRECTORY -lOpenThreads
}
Пишем сценарий для сборки в unix-подобных ОС
unix {
CONFIG(debug, debug|release) {
TARGET = $$join(TARGET,,,_d)
LIBS += -losgd
LIBS += -losgViewerd
LIBS += -losgDBd
LIBS += -lOpenThreadsd
} else {
LIBS += -losg
LIBS += -losgViewer
LIBS += -losgDB
LIBS += -lOpenThreads
}
}
Здесь мы настраиваем имя исполняемого файла и указываем библиотеки, которые следует компоновать с нашей программой для различных вариантов сборки: как отладочной так и релизной. Отладочные библиотеки OSG имеют суффикс "d" после имени файла. Суффикс "_d" мы добавим так же и к исполняемому файлу проекта, дабы отличать отладочный вариант от релизного.
INCLUDEPATH += $$OSG_INCLUDE_DIRECTORY
INCLUDEPATH += ./include
HEADERS += $$files(./include/*.h)
SOURCES += $$files(./src/*.cpp)
Ну и наконец определяем пути поиска заголовочных файлов и файлы, включаемые в дерево проекта. Создаем в каталоге include/ пустой файл main.h, а в кталоге src/ — файл main.cpp. Открываем этот проект в QtCreator и настраиваем его так, как показано на скриншоте

После открытия проекта увидим следующую картину

Напишем такой код в файле 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::ref_ptr<osg::Node> root = osgDB::readNodeFile("../data/cessna.osg");
osgViewer::Viewer viewer;
viewer.setSceneData(root.get());
return viewer.run();
}
Файл с моделькой самолета необходимо скопировать в каталог data/. Этот файл, а так же многое из того, что будет использовано в данном цикле статей, можно скачать из репозитория OpenSceneGraph-Data
После компиляции и запуска мы получим что-то вроде этого

Первые две строчки нашего кода
(void) argc;
(void) argv;
помечают входные параметры функции main() как неиспользуемые, дабы избежать предупреждения компилятора. Далее создается корневая нода сцены, в качестве которой выступает модель самолета, загружаемая из файла cessna.osg
osg::ref_ptr<osg::Node> root = osgDB::readNodeFile("../data/cessna.osg");
Потом создается экземпляр класса osgViewer::Viewer — так называемый "вьювер" — объект управляющий отображением сцены на экране. Вьюверу передаются данные сцены
viewer.setSceneData(root.get());
и запускается цикл отрисовки сцены
return viewer.run();
В этом простейшем коде уже содержится ряд базовых концепций, используемых в OSG. Но разговор о них пойдет чуть попозже.
Заключение
Полный исходный код примеров, описываемых здесь и далее можно получить здесь. Надеюсь, что эта публикация заинтересовала читателей, продолжение следует...