2 Построение Графа

advertisement
Введение в OpenSceneGraph
2 Построение Графа
Сцены
Эта глава покажет вам как писать код, который строит OSG граф сцены. Тут раскрываются
как внешние, так и внутренние детали построения графа сцены, освещается механизм OSG
для загрузки всего графа сцены из файлов с 3D моделями.
Первый раздел посвящен управлению памятью. Графы сцены и их данные обычно
потребляют большие объемы памяти, и в этом разделе обсуждается OSG система управления
этой памятью, для предотвращения висячих указателей и утечек памяти.
Простейший граф сцены состоит из одного листового узла, содержащего некоторую
геометрию и состояние. Раздел 2.2 Geodes и Geometry описывает как определить
геометрию, нормали и цвет. Далее вы изучите как контролировать отображение геометрии,
определяя атрибуты состояния OSG и режимы.
Тем не менее реальным приложениям требуется граф сцены сложнее чем один узел. В этой
главе так же представлено OSG семейство узлов group, обладающих широкими
возможностями, используемыми в большинстве библиотек графов сцен.
Большинству приложений необходимо считывать данные геометрии из файлов 3D моделей.
В этой главе описывается простой OSG интерфейс загрузки файлов, который поддерживает
множество популярных форматов файлов 3D моделей.
В конце этой главы показано как добавить текст в ваше приложение. В NodeKits
сосредоточено большое количество продвинутой функциональности OSG. В этой главе
подробно рассмотрен один NodeKit, osgText.
2.1 Memory Management
Прежде чем вы начнете строить граф сцены, вам необходимо понять, как OSG управляет
памятью, занятой узлами графа сцены и данными. Твердое понимание этой концепции
делает проще для вас написание чистого кода, который избегает утечек памяти и висящих
указателей.
В предыдущей главе была показана диаграмма некоторого довольно простого графа сцены,
во главе которого стоял корневой узел. В довольно простом сценарии использования,
приложение хранит указатель на корневой узел, но не на другие узлы графа сцены. Корневой
узел, явно или не явно, ссылается на все узлы в графе сцены. Рисунок 2-1 показывает этот
типичный сценарий.
Когда ваше приложение заканчивает использование этого графа сцены, память, занимаемая
каждым узлом, должна быть освобождена во избежание утечек памяти. Написание кода для
Построение Графа Сцены
обхода всего графа сцены и удаление каждого узла (и его данных) была бы скучна подвержена
ошибкам.
К счастью в OSG представлена система автоматического сбора мусора, которая использует
подсчет ссылок памяти. Все узлы графа сцены OSG ведут подсчет ссылок, и когда их счетчик
ссылок уменьшается до нуля, объект удаляет себя сам. В результате для удаления графа сцены
изображенного на Рисунке 2-1, вашему приложению нужно просто удалить указатель на
корневой узел. Это вызовет каскадный эффект который удалит все узлы и данные в графе
сцены, как показано на Рисунке 2-2.
В системе сбора мусора присутствует два компонента:

OSG узлы и классы данных графа сцены унаследованы от общего предка,
osg::Referenced, в который содержит счетчик ссылок целого типа, и методы для его
увеличения и уменьшения.

В OSG определен шаблонный класс умного указателя osg::ref_ptr<> которым вы
пользуетесь как обычным C++ указателем. Используйте osg::ref_ptr<> переменные
для хранения адресов на OSG узлы и данные графа сцены, которые располагаются в
куче(heap). Когда в коде происходит назначение адреса Referenced объекта ref_ptr<>
переменной, счетчик ссылок автоматически увеличивается.
Рисунок 2-1
Ссылка на граф сцены
Обычно приложение ссылается на граф сцены с помощью одного указателя, в
котором хранится адрес на корневой узел. Приложению не нужно хранить
указатели на другие узлы графа сцены. Все другие узлы ссылаются, прямо или
косвенно, на корневой узел.
Введение в OpenSceneGraph
Рисунок 2-2
Каскадное удаление графа сцены
Система управления памятью удалит весь граф сцены, когда последний
указатель на корневой узел удален.
Любой код, который сохраняет указатель на объект, унаследованный от Referenced, должен
хранить его в ref_ptr<>, а не в обычной переменной указателе C++. Если весь код
придерживается этого правила, память автоматически освобождается, когда ссылка на
последний ref_ptr<> выходит из области действия.
ref_ptr<> использует перегрузку операторов, поэтому переменные ref_ptr<> имеют схожую
функциональность с обычными переменными указателями C++. Например ref_ptr<>
перегружает operator->() и operator*() для разыменования адреса указателя.
Если вы создали какой-нибудь узел графа сцены или данные, унаследованные от Referenced,
код вашего приложения не может явно удалить эту память. С очень небольшими
исключениями, все дочерние классы имеют защищенный деструктор. Это гарантирует что
объект, унаследованный от Referenced, может быть удален только тогда, когда его счетчик
ссылок достигнет 0.
Далее следует более подробное описание Referenced и ref_ptr<>, и показаны некоторые
примеры кода.
Предупреждение
Никогда не используйте переменные с обычными указателями С++ для
длительного хранения указателей на объекты унаследованные от Referenced. Как
исключение, вы можете воспользоваться переменной с обычным С++ указателем
для промежуточных операций, обычно адресуемая в куче память сохраняется в
ref_ptr<>. Тем не менее использование ref_ptr<> всегда более безопасный
подход.
2.1.1 Класс Referenced
В Referenced (пространство имен: osg) реализован подсчет количества ссылок на блок
памяти. Все узлы OSG и данные графа сцены, включая информацию о состоянии, а так же
массивы вершин, нормалей, и текстурных координат, унаследованы от Referenced. Как
результат, на всю память графа сцены OSG ведется подсчет количества ссылок.
Класс Referenced состоит из трех главных компонентов:
Построение Графа Сцены

В него включен защищенная переменная член _refCount, целого типа, для учета
количества ссылок, которая инициализируется нулем в конструкторе.

В него включены методы ref() и unref(), которые увеличивают или уменьшают
_refCount. unref() освобождает занимаемую объектом память при достижении
_refCount нуля.

Деструктор класса объявлен защищенным и виртуальным. Создание в стеке и явный
вызов delete не возможен, поскольку деструктор защищен, а объявление его
виртуальным позволяет дочерним классам выполнять код своих деструкторов.

Главное правило, ваш код никогда не должен вызывать напрямую методы ref() и unref().
Вместо этого позвольте ref_ptr<> позаботиться об этом самому.
2.1.2 Шаблонный Класс ref_ptr<>
В ref_ptr<> (пространство имен: osg) реализован умный указатель на объект типа
Referenced, и управление им счетчиком ссылок. Объект Referenced гарантирует, что удалит
себя, когда ссылка на последний ref_ptr<> выйдет из области видимости. ref_ptr<>
упрощает освобождение памяти графа сцены, и гарантирует удаление объекта при раскрутке
стека в результате возникновения исключения.
Шаблонный класс ref_ptr<> состоит из трех главных компонент:
 Он состоит из защищенного указателя, _ptr, для хранения адреса на управляемую
память.

Он состоит из нескольких методов, которые позволяют вашему коду использовать
ref_ptr<> как обычный С++ указатель, таких как operator->() и operator=().

Метод valid() возвращает true если ref_ptr<> не равен NULL.
Когда в коде адрес назначается переменной ref_ptr<>, оператор присваивания ref_ptr<>,
operator=(), полагает что адрес указывает на объект унаследованный от Referenced, и
автоматически увеличивает счетчик ссылок с помощью вызова Referenced::ref().
В двух случаях переменная ref_ptr<> уменьшает счетчик ссылок, во время удаления
ref_ptr<> (в деструкторе класса) и во время присваивания (в operator=()). В обоих случаях
ref_ptr<> уменьшает счетчик ссылок с помощью вызова Referenced::unref().
2.1.3 Примеры Управления Памятью
Следующие примеры используют классы osg::Geode и osg::Group. Geode это листовой узел
OSG, который содержит в себе геометрию для отображения; смотрите 2.2 Geodes и
Geometry для более подробной информации. Group это узел который может содержать
множество потомков; смотрите 2.3 Узлы Group для более подробной информации. Оба
класса унаследованы от Referenced.
Первый пример показывает, как объявлять переменную ref_ptr<>, назначать ей значение, и
проверять, что переменная ref_ptr<> содержит правильное значение.
Введение в OpenSceneGraph
#include <osg/Geode>
#include <osg::ref_ptr>
...
osg::ref_ptr<osg::Geode> geode = new osg::Geode;
if (!geode.valid())
// ref_ptr<> не правильна. Сгенерировать исключение
// или отобразить ошибку.
Как и в любом другом шаблоне, при объявлении укажите тип переменной между угловыми
скобками. Пример выше создает переменную ref_ptr<> которая хранит адрес на osg::Geode.
Учтите, что вы назначаете адрес так, как будто переменная была обычным C++ указателем.
В обычном сценарии использования, вы создаете узел и добавляете его как потомка к другому
узлу графа сцены:
#include <osg/Geode>
#include <osg/Group>
#include <osg/ref_ptr>
...
{
// Создать новый объект osg::Geode. Назначение переменной
// ref_ptr<> увеличивает счетчик ссылок до 1.
osg::ref_ptr<Geode> geode = new osg::Geode;
// Предполагается что ‘grp’ является указателем на узел osg::Group. Group
// использует ref_ptr<> что бы указывать на своих детей, поэтому addChild()
// снова увеличивает счетчик ссылок до 2.
grp->addChild( geode.get() );
} // ref_ptr<> переменной ‘geode’ выходит из области видимости здесь. Это
// вызывает уменьшение счетчика ссылок обратно до 1.
Вообще то ref_ptr<> в этом случае не требуется, поскольку ваш код не хранит указатель на
geode долгое время. Фактически в этом простом случае, показанном выше, ref_ptr<> просто
добавляет ненужное действие в процесс создания. Простого C++ указателя тут достаточно,
поскольку внутренний ref_ptr<> родительского узла osg::Group управляет занимаемой
памятью нового osg::Geode.
// Create a new osg::Geode object. Don’t increment its reference
// count.
osg::Geode* geode = new osg::Geode;
// The internal ref_ptr<> in Group increments the child Geode
// reference count to 1.
grp->addChild( geode );
Use caution when using regular C++ pointers for Referenced objects. For OSG’s
memory management system to work correctly, the address of the Referenced object
must be assigned to a ref_ptr<> variable. In the code above, that assignment happens
in the osg::Group::addChild() method. If the Referenced object is never assigned to
a ref_ptr<> variable, its memory leaks.
Построение Графа Сцены
{
osg::Geode* geode = new osg::Geode;
} // Don’t do this! Memory leak!
As stated previously, you can’t explicitly delete an object derived from Referenced or
create one on the stack. The following code generates compile errors:
osg::Geode* geode1 = new osg::Geode;
delete geode1; // Compile error: destructor is protected.
{
osg::Geode geode2;
} // Compile error: destructor is protected.
Variables of type ref_ptr<> can point only to objects derived from Referenced (or
objects that support the same interface as Referenced).
// OK, because Geode derives from Referenced:
osg::ref_ptr<Geode> geode = new osg::Geode;
int i;
osg::ref_ptr<int> rpi = &i; // NOT okay! ‘int’ isn’t derived
// from Referenced and doesn’t support the Referenced
// interface.
As discussed earlier in this chapter, OSG’s memory management feature facilitates
cascading deletion of entire scene graph trees. When the sole ref_ptr<> to the root
node is deleted, the root node reference count drops to zero, and the root node
destructor deletes both the root node and the ref_ptr<> pointers to its children. The
following code doesn’t leak the child Geode memory:
{
// ‘top’ increments the Group count to 1.
osg::ref_ptr<Group> top = new osg::Group;
// addChild() increments the Geode count to 1.
top->addChild( new osg::Geode );
} // The ‘top’ ref_ptr goes out of scope, deleting both the Group
// and Geode memory.
Be careful when returning the address of a object from a function. If you do this
incorrectly, the ref_ptr<> storing the memory address could go out of scope before the
address is placed on the return stack.
// DON’T do this. It stores the address as the return value on
// the call stack, but when the grp ref_ptr<> goes out of
// scope, the reference count goes to zero and the memory is
// deleted. The calling function is left with a dangling
// pointer.
osg::Group* createGroup()
{
// Allocate a new Group node.
osg::ref_ptr<osg::Group> grp = new osg::Group;
Введение в OpenSceneGraph
// Return the new Group’s address.
return *grp;
}
There are multiple solutions to the dilemma of how to return a Referenced object
address. The method employed in this book’s example code is to return a ref_ptr<>
storing the address, as the code below illustrates.
osg::ref_ptr<osg::Group> createGroup()
{
osg::ref_ptr<osg::Group> grp = new osg::Group;
// Return the new Group’s address. This stores the Group
// address in a ref_ptr<> and places the ref_ptr<> on the
// call stack as the return value.
return grp.get();
}
In summary:
Assigning an object derived from Referenced to a ref_ptr<> variable
automatically calls Referenced::ref() to increment the reference count.
If a ref_ptr<> variable is made to point to something else or is deleted, it calls
Referenced::unref() to decrement the reference count. If the count reaches
zero, unref() deletes the memory occupied by the object.
When allocating an object of type Referenced, always ensure it is assigned to
a ref_ptr<> to allow OSG’s memory management to operate correctly.
This may seem a little long-winded for a “Quick Start Guide”. The concept is
important, however, and a firm grasp of OSG memory management is important for
any OSG developer.
The sections that follow describe several classes derived from Referenced, and the
code snippets make extensive use of ref_ptr<> variables. As you read this chapter, keep
in mind that OSG uses ref_ptr<> internally for any long-term pointer storage, as in the
calls to osg::Group::addChild() in the previous examples.
2.2 Geodes и Geometry
В предыдущем разделе была представлена концепция управления памятью в OSG. Если вам
не знаком подсчет ссылок памяти, то лучше посмотреть на реальные OSG примеры, для
лучшего понимания. В этом разделе представлена простая OSG программа, которая
использует технику управления памятью описанную ранее, и знакомит вас с построением
графа сцены с помощью OSG классов содержащих геометрию. На первый взгляд код может
показаться громоздким, поскольку вы еще не знакомы со многими классами. Подробное
объяснение геометрических классов дается в комментариях в исходном коде.
Построение Графа Сцены
Листинг 2-1 широко использует шаблонный класс ref_ptr<>, описанный в предыдущем
разделе. Все выделения памяти в Листинге 2-1 с подсчетом ссылок. Даже функция
createSceneGraph() возвращает ref_ptr<>, созданного графа сцены. (Проще говоря, код в
Листинге 2-1 мог бы быть написан полностью с использованием обычных C++ указателей, а
возвращенный указатель сохранялся бы в ref_ptr<>. Тем не менее, является хорошей
практикой использование ref_ptr<> в вашем приложении, поскольку это автоматизирует
освобождение памяти даже в случае исключений или преждевременного выхода из функции.
В примерах кода этой книги используется ref_ptr<> для поддержки этой хорошей практики.)
Листинг 2-1
Построение простого графа сцены
Это отрывок из кода примера Simple идущего вместе с книгой. Функция
createSceneGraph() определяет геометрию для единственного прямоугольного
примитива. Прямоугольник имеет разный цвет вершин, но общую нормаль для
всего примитива.
#include <osg/Geode>
#include <osg/Geometry>
osg::ref_ptr<osg::Node> createSceneGraph()
{
// Создать объект для хранения в нем геометрии.
osg::ref_ptr<osg::Geometry> geom = new osg::Geometry;
// Создать массив для хранения четырех вершин.
osg::ref_ptr<osg::Vec3Array> v = new osg::Vec3Array;
geom->setVertexArray( v.get() );
v->push_back( osg::Vec3( -1.f, 0.f, -1.f ) );
v->push_back( osg::Vec3( 1.f, 0.f, -1.f ) );
v->push_back( osg::Vec3( 1.f, 0.f, 1.f ) );
v->push_back( osg::Vec3( -1.f, 0.f, 1.f ) );
// Создать массив их четырех цветов
osg::ref_ptr<osg::Vec4Array> c = new osg::Vec4Array;
geom->setColorArray( c.get() );
geom->setColorBinding( osg::Geometry::BIND_PER_VERTEX );
c->push_back( osg::Vec4( 1.f, 0.f, 0.f, 1.f ) );
c->push_back( osg::Vec4( 0.f, 1.f, 0.f, 1.f ) );
c->push_back( osg::Vec4( 0.f, 0.f, 1.f, 1.f ) );
c->push_back( osg::Vec4( 1.f, 1.f, 1.f, 1.f ) );
// Создать массив содержащий одну нормаль.
osg::ref_ptr<osg::Vec3Array> n = new osg::Vec3Array;
geom->setNormalArray( n.get() );
geom->setNormalBinding( osg::Geometry::BIND_OVERALL );
n->push_back( osg::Vec3( 0.f, -1.f, 0.f ) );
// Получить прямоугольник из четырех вершин из ранее
// подготовленных данных.
geom->addPrimitiveSet(
new osg::DrawArrays( osg::PrimitiveSet::QUADS, 0, 4 ) );
Введение в OpenSceneGraph
// Добавить Geometry (Drawable) в Geode и вернуть Geode.
osg::ref_ptr<osg::Geode> geode = new osg::Geode;
geode->addDrawable( geom.get() );
return geode.get();
}
Код в Листинге 2-1 создает граф сцены с одним узлом. На рисунке 2-3 показан этот
предельно простой граф сцены. Этот один узел графа сцены имеет образовательную цель
для новичков. Реальные графы сцен гораздо сложнее.
Обратите внимание, что код в Листинге 2-1 определяет четыре вершины в плоскости y=0.
Подобно OpenGL, OSG не налагает ограничений на систему координат, используемую
приложением. Тем не менее, по умолчанию
Рисунок 2-3
Граф сцены Листинга 2-1
В Листинге 2-1 создается граф сцены состоящий из одного Geode.
библиотека osgViewer использует мировую систему координат, которая ориентированна
положительный x вправо, положительный z вверх, и положительный y в экран. Это работает
в большинстве приложений, в которых земная поверхность находится в xy плоскости. Глава
3, Использование OpenSceneGraph в Вашем Приложении, описывает как изменить
систему мировых координат на отличную от по умолчанию. Код в Листинге 2-1 использует
ориентацию по умолчанию для отображения четырехугольника, обращенного к
наблюдателю.
В дополнение к созданному графу сцены, как показано в Листинге 2-1, вы так же захотите его
отобразить или анимировать. Примеры в этой главе используют приложение osgviewer для
отображения графа сцены, поскольку написание кода для отображения будет представлено
только в Глава 3, Использование OpenSceneGraph в Вашем Приложении. Для
отображения графа сцены в приложении osgviewer, вам необходимо записать его(граф
сцены) на диск. В Листинге 2-2 показан код, который вызывает функцию из Листинга 2-1, и
записывает граф сцены на диск в .osg файл. После того как граф сцены записан на диск, вы
можете использовать osgviewer для просмотра того, как выглядит результат.
Листинг 2-2
Запись графа сцены на диск
В этом листинге показана точка входа main() примера программы Simple. Из main()
вызывается createSceneGraph() из Листинга 2-1, где создается граф сцены, затем
этот граф сцены записывается на диск в файл с именем “Simple.osg”.
#include <osg/ref_ptr>
#include <osgDB/Registry>
#include <osgDB/WriteFile>
#include <osg/Notify>
#include <iostream>
using std::endl;
Построение Графа Сцены
osg::ref_ptr<osg::Node> createSceneGraph();
int main( int, char** )
{
osg::ref_ptr<osg::Node> root = createSceneGraph();
if (!root.valid())
osg::notify(osg::FATAL) << "Failed in createSceneGraph()." << endl;
bool result = osgDB::writeNodeFile(
*(root.get()), "Simple.osg" );
if ( !result )
osg::notify(osg::FATAL) << "Failed in osgDB::writeNodeFile()." << endl;
}
После вызова функции из Листинга 2-1 создающего граф сцены, код из Листинга 2-2
записывает его на диск в виде файла с именем “Simple.osg”. Формат файла .osg это
принадлежащий OSG текстовой ASCII формат файла. Поскольку это ASCII файл, файлы
.osg медленно загружаются да к тому же еще и велики, поэтому это формат редко
используется в промышленном коде. Тем не менее, очень полезно для отладки в процессе
разработки и быстрых демонстраций.
Код в Листинге 2-1 и 2-2 из примера Simple, идущего вместе c книгой. Если вы этого еще не
сделали, скачайте исходный код примера с Web сайта этой книги, откомпилируйте и
запустите его. После запуска примера вы найдете выходной файл Simple.osg в вашей рабочей
директории. Для того чтобы посмотреть, как выглядит граф сцены, используйте osgviewer:
osgviewer Simple.osg
osgviewer должен дать результат похожий представленному на Картинке 2-4. В Главе 1
описывается osgviewer и как им пользоваться. Например, вы можете вращать отображаемую
геометрию с помощью левой кнопки мыши и приближать или удалять с помощью правой.
Код из Листинга 2-1 широко использует классы геометрии OSG. Далее дается на более
высоком уровне объяснение как пользоваться этими классами.
Введение в OpenSceneGraph
Рисунок 2-4
Простой пример графа сцены отображаемый в osgviewer’е
На картинке показан квадрат созданный в Листинге 2-1, а после записанный в
файл .osg в коде Листинга 2-2 и отображенный с помощью osgviewer’a.
2.2.1 Обзор Классов Геометрии
Код в Листинге 2-1 может сбивать с толку, но в сущности в нем выполняется только три
операции.
1. Он создает массив вершин, нормалей и данные о цвете
2. Он создает объект osg::Geometry и добавляет в него массивы. Он так же добавляет
объект osg::DrawArrays для определения как рисовать данные.
3. Он создает узел графа сцены osg::Geode и добавляет объект Geometry в него.
В этом разделе рассматривается каждый из этих шагов подробно.
Классы Vector и Array
В OSG определен богатый набор классов для хранения векторных данных, таких как
вершины, нормали, цвета и текстурные координаты. osg::Vec3 это массив трехкомпонентных
чисел с плавающей запятой; используйте его для определения данных векторов и нормалей.
Используйте osg::Vec4 для определения данных цвета и osg::Vec2 для 2D текстурных
координат. В дополнение к простому хранению эти классы обеспечивают полный набор
Построение Графа Сцены
методов для расчета длины, векторного и скалярного произведения, сложения векторов и
векторно-матричного умножения.
В OSG определены шаблонные классы массивов для хранения объектов. Наиболее часто
используемые шаблонные классы массивов предназначены для хранения векторных данных.
В связи с тем фактом, что они часто используются, OSG предоставляет определение типов
для массивов векторных данных - osg::Vec2Array, osg::Vec3Array, и osg::Vec4Array.
В Листинге 2-1 создаются отдельные трехкомпонентные вектора для каждой xyz вершины с
помощью Vec3, затем каждый Vec3 сохраняется в конец Vec3Array. Код использует
Vec3Array похожим образом для хранения xyz данных нормалей. В Листинге 2-1
используется Vec4 и Vec4Array для данных о цвете, поскольку цвет определяется четырьмя
компонентами (красный, зеленый, синий и альфа). Позже в этой главе будет представлен код
примера, который использует Vec2 и Vec2Array для хранения двухэлементных текстурных
координат.
Типы массивов наследуются от std::vector, поэтому они поддерживают метод push_back()
для добавления новых элементов, как показано в Листинге 2-1. Как дочерние классы от
std::vector, классы массивов так же поддерживают методы resize() и operator[]().
osg::ref_ptr<osg::Vec3Array> v = new osg::Vec3Array;
geom->setVertexArray( v.get() );
v->resize( 4 );
(*v)[ 0 ] = osg::Vec3( -1.f, 0.f, -1.f );
(*v)[ 1 ] = osg::Vec3( 1.f, 0.f, -1.f );
(*v)[ 2 ] = osg::Vec3( 1.f, 0.f, 1.f );
(*v)[ 3 ] = osg::Vec3( -1.f, 0.f, 1.f );
Рисуемые
OSG определяет класс osg::Drawable, для хранения данных отображения.
Drawable это виртуальный базовый класс, который не создается
непосредственно. OSG предоставляет три производных класса от Drawable.
 osg::DrawPixels—DrawPixels это надстройка вокруг команды
glDrawPixels().
 osg::ShapeDrawable—ShapeDrawable предоставляет доступ к
нескольким предопределенным геометрическим формам, таким как
цилиндр и сфера.
 osg::Geometry—Geometry это гибкий класс для хранения и отображения
геометрии общего назначения. Пример кода использует Geometry,
который является наиболее часто используемым производным классом.
Если вы уже знакомы с массивами вершин в OpenGL, класс Geometry вам будет
легко использовать. Geometry предоставляет интерфейс, который позволяет
вашему приложению определять массивы данных вершин, как их
интерпретировать и отображать. Аналогично с определением данных массива
Введение в OpenSceneGraph
вершин в OpenGL (с помощью glVertexPointer() и glNormalPointer()) и отображением
массива вершин (с помощью glDrawArrays() и
glDrawElements()). Код из Листинга 2-1 использует следующие методы Geometry:

setVertexArray(), setColorArray() и setNormalArray()—Эти методы аналогичны
glVertexPointer(), glColorPointer() и glNormalPointer() в OpenGL. Ваше
приложение использует эти методы для определения массива вершин, цвета, и данных
нормалей. Каждый из методов setVertexArray() и setNormalArray() принимает
указатель на Vec3Array как параметр, и setColorArray() принимает указатель на
Vec4Array.

setColorBinding() and setNormalBinding()—Эти методы говорят Geometry как
применять данные о цвете и нормалях. Они принимают перечисляемый тип,
определенный в классе Geometry, как параметр. В Листинге 2-1 назначение цвета как
osg::Geometry::BIND_PER_VERTEX означает, что цвет у каждой вершины будет
индивидуальным. Тем не менее, нормали определены как
osg::Geometry::BIND_OVERALL что означает применить одну нормаль на всю
Geometry.

addPrimitiveSet()—Этот метод говорит как Geometry отображать свои данные. Он
принимает указатель на osg::PrimitiveSet как параметр. PrimitiveSet это виртуальный
базовый класс который вы не можете создать непосредственно. Ваш код должен
добавлять множество PrimitiveSet объектов к одной и той же Geometry.
Метод addPrimitiveSet() позволяет вашему приложению определять как OSG должен
рисовать геометрические данные хранимые в Geometry объекте. В Листинге 2-1 определен
osg::DrawArrays объект. DrawArrays наследуется от PrimitiveSet. Думайте об этом как о
надстройке над командой glDrawArrays() для рисования массива вершин. Другие дочерние
классы от PrimitiveSet (DrawElementsUByte, DrawElementsUShort и DrawElementsUInt)
имитируют OpenGL команду glDrawElements(). OSG так же предоставляет класс
DrawArrayLengths, которому нет аналога в OpenGL. Функционально он схож с множеством
вызовов glDrawArrays() с различными диапазонами индексов и количеством элементов.
Наиболее часто используемый конструктор класса DrawArrays имеет следующие
определение:
osg::DrawArrays::DrawArrays(
GLenum mode, GLint first, GLsizei count );
mode это один из десяти типов примитивов OpenGL, таких как GL_POINTS, GL_LINES или
GL_TRIANGLE_STRIP. PrimitiveSet это базовый класс в котором определен эквивалент
перечислению OpenGL, например такой osg::PrimitiveSet::POINTS, и ваш код может
использовать их так же.
first является индексом на первый элемент в массиве данных вершин которые OSG должно
использовать для отображения, и count это общее количество элементов которое OSG должно
использовать. Например, если данные вершин содержат 6 вершин и вы хотите отобразить
полоску треугольников (triangle strip) из этих вершин, то вы можете добавить следующий
DrawArrays набор примитивов к вашей Geometry:
geom->addPrimitiveSet( new osg::DrawArrays(
osg::PrimitiveSet::TRIANGLE_STRIP, 0, 6 );
Построение Графа Сцены
После добавления данных о вершинах, цвете, нормалях и DrawArrays наборе примитивов в
Geometry объект, код в Листинге 2-1 выполняет одну последнюю операцию с Geometry. Он
добавляет ее к узлу графа сцены. В следующем разделе описывается эта операция.
Как OSG Рисует
В то время как классы, производные от PrimitiveSet, обеспечивают
функциональность схожую с возможностями OpenGL для массивов вершин, не
стоит полагаться что PrimitiveSet всегда использует массивы вершин во
внутренней реализации. В зависимости от настроек отображения, OSG может
использовать массивы вершин (с или без буферизации объектов), дисплейные
списки или даже glBegin()/glEnd() для отображения геометрии.
Объекты, наследуемые от Drawable (такие как Geometry) используют дисплейные
списки по умолчанию. Вы можете изменять это поведение вызвав
osg::Drawable::setUseDisplayList( false ).
OSG перейдет на использование glBegin()/glEnd() в случае если вы определите
атрибут BIND_PER_PRIMITIVE, что вызовет установку атрибутов для каждого
примитива (например для каждого треугольника в режиме GL_TRIANGLES.)
Geodes
Класс osg::Geode в OSG является листовым узлом, который хранит геометрию для
отображения. В Листинге 2-1 создается простейший граф сцены, какой только возможно—
граф сцены, состоящий из единственного листового узла. В конце Листинга 2-1 функция
createSceneGraph() возвращает адрес этого Geode в виде ref_ptr<> на osg::Node. Это
допустимо в C++ коде, поскольку Geode является производным классом от Node. (По
определению в OSG все узлы графа сцены наследуются от Node.)
osg::Geode не имеет потомков, поскольку он является листовым узлом, но он может
содержать геометрию. Имя Geode это сокращение от “узел геометрии”—узел который
содержит геометрию. Любая геометрия, которую ваше приложение отображает, должно быть
присоединено к Geode. Geode предоставляет метод addDrawable() который позволяет
вашему приложению присоединять геометрию.
Geode::addDrawable() принимает указатель на Drawable в качестве параметра. Как было
написано в предыдущем разделе, Drawable это виртуальный базовый класс от которого
наследуется множество дочерних классов, например такой как Geometry. Посмотрите в
Листинге 2-1 пример того, как добавлять объект Geometry в Geode с помощью
addDrawable(). В коде эта операция выполняется в конце функции createSceneGraph() прямо
перед возвращением Geode.
2.3 Узел Group
Узлом групп в OSG является osg::Group, который позволяет вашему приложению добавлять
в себя любое количество дочерних узлов, которые в свою очередь так же могут быть узлами
Group со своими потомками, как показано на Рисунке 2-5. Group является базовым классом
Введение в OpenSceneGraph
для многих полезных узлов, включая osg::Transform, osg::LOD и osg::Switch, которые
описываются далее в этом разделе.
Group наследуется от Referenced. В обычном случае код ссылок дан в родители Group,
таким образом, если корневой узел графа сцены удален, это вызовет цепное удаление,
гарантирующие отсутствие утечек памяти.
На самом деле Group является сердцем OSG, поскольку он позволяет вашему приложению
организовывать данные в виду графа сцены. Сила объектов Group в их интерфейсе
управления дочерними узлами. В Group так же есть интерфейс управления родителями,
который он наследует от базового класса, osg::Node. В этом разделе дается обзор как
дочернего, так и родительского интерфейсов.
Далее описываются дочерние и родительские интерфейсы. Этот раздел описывает три часто
используемых класса наследуемых от Group—это узлы Transform, LOD и Switch.
2.3.1 Дочерний Интерфейс
Класс Group определяет интерфейс для потомков и все узлы, которые наследуются от
Group, наследуют этот интерфейс. Большинство OSG узлов, которые вы используете,
наследуются от Group (Geode является исключением), поэтому в общем случае вы можете
полагать, что большинство узлов поддерживают дочерний интерфейс.
Рисунок 2-5
Узел Group
Показанные зеленым, узлы Group могут иметь множество потомков, которые в
свою очередь так же могут быть узлами Group со своими потомками.
Group хранит указатели на свои дочерние узлы в std::vector< ref_ptr<Node> >—в массиве
ref_ptr<> переменных указывающих на Node. Вы можете получить доступ к потомку по
индексу, поскольку Group используется как массив. Group использует ref_ptr<>, что
Построение Графа Сцены
позволяет работать системе управления памятью OSG. Следующий отрывок кода показывает
часть описания дочернего интерфейса объекта Group. Все классы в пространстве имен osg.
class Group : public Node {
public:
...
// Добавит дочерний узел.
bool addChild( Node* child );
// Убрать дочерний узел. Если этот узел не является дочерним,
// ничего не делать и вернуть false.
bool removeChild( Node* child );
// Заменить дочерний узел на новый.
bool replaceChild( Node* origChild, Node* newChild );
// Вернуть количество детей.
unsigned int getNumChildren() const;
// Вернуть true если определенный узел является дочерним.
bool containsNode( const Node* node ) const;
...
};
Очень простой граф сцены может состоять из родительского узла Group с двумя потомками
Geode. Вы можете построить подобный граф сцены с помощью следующего кода.
osg::ref_ptr<osg::Group> group = new osg::Group;
osg::ref_ptr<osg::Geode> geode0 = new osg::Geode;
group->addChild( Geode0.get() );
osg::ref_ptr<osg::Geode> geode1 = new osg::Geode;
group->addChild( Geode1.get() );
Замете что Group использует ref_ptr<>, чтобы указывать на свои дочерние узлы. В этом
примере group принимает ссылки памяти на geode0 и geode1. Эта память остается занятой
после того как geode0
и geode1выйдут из области видимости, и освободится после удаления group.
2.3.2 Родительский Интерфейс
Group наследует интерфейс для управления родителем от Node. Geode имеет такой же
интерфейс, поскольку он так же наследуется от Node. OSG позволяет узлам иметь множество
родителей. Следующий отрывок кода показывает часть описания родительского интерфейса
объекта Node. Все классы в пространстве имен osg.
class Node : public Object {
public:
...
Введение в OpenSceneGraph
typedef std::vector<Group*> ParentList;
// Вернуть список всех родителей.
const ParentList& getParents() const;
// Вернуть указатель на родителя по заданному индексу.
Group* getParent( unsigned int I );
// Вернуть количество родителей
unsigned int getNumParents() const;
...
};
Node наследуется от osg::Object. Object это виртуальный базовый класс который ваше
приложение не может создавать непосредственно. Object предоставляет интерфейс для
хранения и извлечения строки с именем, определяет режим хранения данных как статический
или динамически модифицируемый, и так же наследуется от Referenced для поддержки
управления памятью OSG. В Главе 3.2 Динамическая Модификация, обсуждаются имена и
интерфейс динамических данных более детально.
Множество Родителей
Когда вы
добавляете один и
тот же узел как
дочерний к
нескольким узлам,
дочерний узел
становится
обладателем
нескольких
родителей, как
показано на
диаграмме. Вы
можете захотеть
сделать так, что бы
отобразить
дочерний узел
множество раз без
необходимости создания нескольких копий. В Главе 2.4.3 Пример Кода Настройки
Состояния показан пример отображения одного и тот же Geode с различными
трансформациями и состояниями отображения.
Когда узел имеет множество родителей, OSG обходит его множество раз. Каждый
из родителей хранит свой собственный ref_ptr<> на своего потомка, таким
образом, дочерний узел не будет удален до тех пор, пока все родители не
перестанут на него ссылаться.
Обратите внимание что osg::Node::ParentList это std::vector обычных C++ указателей.
Поскольку Node содержит адреса своих родителей, Node не нужно управление памятью
Построение Графа Сцены
OSG для ссылок на родителей. При удалении родителя, родитель удаляет себя из всех
списков дочерних узлов.
В обычном случае, когда узел имеет одного родителя (getNumParents() возвращает один),
вызов getParent( 0 ) получает указатель на родителя.
Вы часто используете дочерний и родительские интерфейсы при построении и управлении
графом сцены. Тем не менее классы, наследуемые от Group, предоставляют дополнительную
функциональность требуемую многим приложениям. В следующих разделах описываются
три из них.
2.3.3 Узлы Transform
OSG поддерживает трансформацию геометрических данных с помощью osg::Transform
семейства классов. Transform наследуется от Group, и может иметь множество потомков.
Это виртуальный базовый класс, который ваше приложение не может создавать
непосредственно. Вместо этого используйте osg::MatrixTransform или
osg::PositionAttitudeTransform, которые наследуются от Transform. Оба этих класса
обеспечивают различный интерфейс для трансформаций. В зависимости от требований
вашего приложения вы можете воспользоваться одним из них или двумя сразу.
Transform влияет на стек OpenGL модельных матриц. Иерархически расположенные узлы
Transform, создают последовательность трансформаций в манере, схожей с командами
OpenGL манипулирования матрицами (такими как glRotatef() или glScalef()) помещая
матрицы на вершину текущего стека матриц.
Узел Transform позволяет вам задавать его начало координат. По умолчанию установлен
режим относительного начала координат (osg::Transform::RELATIVE_RF), что дает в
результате последовательные трансформации, описанные ранее. Тем не менее, так же как и в
OpenGL вы вызывали glLoadMatrixf(), OSG позволяет вам задавать абсолютное начало
координат.
osg::ref_ptr<osg::MatrixTransform> mt = new osg::MatrixTransform;
mt->setReferenceFrame( osg::Transform::ABSOLUTE_RF );
Узел MatrixTransform
MatrixTransform внутри использует объект osg::Matrix (описание в сноске). Для создания
MatrixTransform узла, который выполняет трансформацию, создайте Matrix
трансформацию и назначьте Matrix узлу MatrixTransform.
osg::ref_ptr<osg::MatrixTransform> mt = new osg::MatrixTransform;
osg::Matrix m;
m.setTranslate( x, y, z );
mt->setMatrix( m );
Matrix не наследуется от Referenced. Вы можете создать локальную переменную типа
Matrix в стеке. Метод MatrixTransform::setMatrix() копирует Matrix параметр в
переменную члена узла MatrixTransform.
Введение в OpenSceneGraph
Matrix предоставляет интерфейс для общих преобразований таких как, перемещение,
вращение и масштабирование. Вы так же можете задать матрицу явно:
osg::ref_ptr<osg::MatrixTransform> mt = new osg::MatrixTransform;
osg::Matrix m;
//Задать все 16 значений Matrix:
m.set( 1.f, 0.f, 0.f, 0.f,
0.f, 1.f, 0.f, 0.f,
0.f, 0.f, 1.f, 0.f,
10.f, 0.f, 0.f, 1.f ); // переместить по x=10
mt->setMatrix( m );
Пример State, показанный в Главе 2.4.3. Пример Кода Установки Состояния, использует
множество MatrixTransform узлов для отображения одного и того же Geode для
различного расположения, и каждое со своими уникальными настройками состояния.
Узел PositionAttitudeTransform
Используйте узел PositionAttitudeTransform для задания трансформаций с помощью
позиции заданной через Vec3 и кватернионом. OSG предоставляет класс osg::Quat для
хранения данных о ориентации в виде кватерниона. Quat не наследуется от Referenced и
следовательно не ведет подсчет ссылок.
Matrices и osg::Matrix
Класс osg::Matrix хранит и позволяет оперировать матрицей 4x4, состоящей из 16
чисел с плавающей запятой. Matrix не наследуется от Referenced и не ведет
подсчет ссылок.
Matrix предоставляет интерфейс, который в чем то схож с нотацией
определенной в OpenGL, используемой в большинстве руководств по OpenGL.
Интерфейс доступа представляет собой двумерный строко-ориентированный C++
массив.
osg::Matrix m;
m( 0, 1 ) = 0.f; // Установить второй элемент (строка 0, столбец 1)
m( 1, 2 ) = 0.f; // Установить седьмой элемент (строка 1, столбец 2)
Матрицы в OpenGL представляют собой одномерные массивы, в документации по
OpenGL обычно ссылаются как на матрицу хранимую по столбцам:
┌
┐
│m0 m4
m8
m12
│
│m1 m5
m9
m13
│
│m2 m6
m10
m14
│
│m3 m7
m11
m15
│
└
┘
GLfloat m[ 16 ];
m[1] = 0.f; // Установить второй элемент
m[6] = 0.f; // Установить седьмой элемент
Построение Графа Сцены
Не смотря на эти очевидные различия, расположение матриц OSG и OpenGL в
памяти совпадает—OSG не тратит время на транспонирование, прежде чем
передать ее в OpenGL. Тем не менее, как разработчик вы должны помнить о
транспонированном представлении OSG, прежде чем обращаться к ее отдельным
элементам.
Matrix обладает широким набором операций для векторно-матричного умножения
и объединения матриц. Для преобразования Vec3 v посредством вращения R
вокруг положения T, используйте следующий код:
osg::Matrix T;
T.makeTranslate( x, y, z );
osg::Matrix R;
R.makeRotate( angle, axis );
Vec3 vPrime = v * R * T;
Стиль прямого умножения операторов класса Matrix противоположен тому, как
это описывается в документации по OpenGL:
v' = TRv
Такая запись в OpenGL дает такой же результат, поскольку
в OpenGL запись обратного умножения с матрицами
ориентированными по столбцам эквивалентна прямому
умножению OSG операторов матриц ориентированных по
строкам.
Для воздействия на весь Geode с помощью данного
преобразования, создайте узел MatrixTransform
содержащий T, добавьте к нему дочерний узел
MatrixTransform содержащий R, и добавьте Geode как
дочерний к узлу MatrixTransform содержащий вращение,
как показано на диаграмме. Это эквивалентно следующей
последовательности OpenGL команд:
glMatrixMode( GL_MODELVIEW );
glTranslatef( ... ); // Translation T
glRotatef( ... ); // Rotation R
...
glVertex3f( ... );
glVertex3f( ... );
...
В общем OSG работает с интерфейсом ориентированным по строкам, в то время
как в OpenGL документирована запись по столбцам, OSG и OpenGL внутри
производят эквивалентные операции и их матрицы 100% совместимы.
Quat предоставляет богатый интерфейс настройки. Следующий код демонстрирует
несколько методов создания и настройки кватернионов.
Введение в OpenSceneGraph
// Создать кватернион, поворачивающий оси на угол theta радиан.
float theta( M_PI * .5f );
osg::Vec3 axis( .707f, .707f, 0.f );
osg::Quat q0( theta, axis );
// Создать кватернион, использующий рыск/крен/тангаж
osg::Vec3 yawAxis( 0.f, 0.f, 1.f );
osg::Vec3 pitchAxis( 1.f, 0.f, 0.f );
osg::Vec3 rollAxis( 0.f, 1.f, 0.f );
// (Код в этом примере предполагает что переменные
// объявлены и определены как flot ранее.)
osg::Quat q1( yawRad, yawAxis, pitchRad, pitchAxis, rollRad, rollAxis );
// Объединение двух кватернионов
q0 *= q1;
// Настройка PositionAttitudeTransform с использованием
// setPosition() and setAttitude() методов.
// (x, y, z, theta, и axis объявлены и определены
// ранее.)
osg::Vec3 pos( x, y, z );
osg::Quat att( theta, axis );
osg::ref_ptr<osg::PositionAttitudeTransform> pat = new
osg::PositionAttitudeTransform;
pat->setPosition( pos );
pat->setAttitude( att );
Вы можете добавлять в PositionAttitudeTransform столько дочерних узлов, сколько
необходимо вашему приложению, поскольку PositionAttitudeTransform наследует
дочерний интерфейс от Group. Подобно MatrixTransform, узел
PositionAttitudeTransform трансформирует дочернюю геометрию по заданной позиции и
отношению.
2.3.4 Узел LOD
Используйте узел osg::LOD для отображения объектов с различными степенями
детализации. LOD наследуется от Group и следовательно наследует дочерний интерфейс.
LOD так же позволяет вам задавать диапазон для каждого потомка. Диапазон состоит из
значений минимума и максимума. Эти значения представляют собой расстояния по
умолчанию, и LOD отображает потомка в случае если расстояние до наблюдателя попадает в
диапазон потомка. LOD потомки могут храниться в любом порядке и нет необходимости в
сортировке по расстоянию степени детализации.
На Рисунке 2-6 показан узел LOD с тремя потомками. Первый потомок это узел Group со
своими потомками. Когда расстояние до точки наблюдения попадет в диапазон первого
потомка, OSG совершает обход его и его потомков. Узел LOD действует аналогично по
отношению ко второму и третьему потомкам. OSG может ничего не отобразить, какойнибудь или все потомки LOD, основываясь на их диапазонах.
osg::ref_ptr<osg::Geode> geode;
...
Построение Графа Сцены
osg::ref_ptr<osg::LOD> lod = new osg::LOD;
// Отобразить geode в случае когда 0.f <= distance < 1000.f
lod->addChild( geode.get(), 0.f, 1000.f );
LOD отобразит множество потомков одновременно случае наложения их диапазонов.
osg::ref_ptr<osg::Geode> geode0, geode1;
// Инициализация Geode.
...
osg::ref_ptr<osg::LOD> lod = new osg::LOD;
Рисунок 2-6
Узел LOD
На этом рисунке изображен узел LOD с тремя потомками. Каждый потомок имеет
диапазон расстояний. Узел LOD позволяет отображать своих потомков в случае
если расстояние до точки наблюдения попадает в диапазон потомков.
// Отобразить geode0 когда 0.f <= distance < 1050.f
lod->addChild( geode0.get(), 0.f, 1050.f );
// Отобразить geode1 когда 950.f <= distance < 2000.f
lod->addChild( geode1.get(), 950.f, 2000.f );
// Результат: отобразится geode0 и geode1 когда 950.f <= distance < 1050.f
По умолчанию узел LOD вычисляет расстояние от глаза до центра ограничивающего
объема. Если это не приемлемо в вашей ситуации, вы можете определить новый центр.
Следующий код настраивает определенный пользователем центр узла LOD.
osg::ref_ptr<osg::LOD> lod = new osg::LOD;
// Использовать определенный пользователем центр для расчетов дальности
lod->setCenterMode( osg::LOD::USER_DEFINED_CENTER );
// Задать определенный пользователем центр x=10 y=100
Lod->setCenter( osg::Vec3( 10.f, 100.f, 0.f ) );
Введение в OpenSceneGraph
Что бы восстановить расчет дальности на поведение по умолчанию использующее центр
ограничивающей сферы, вызовите osg::LOD::setCenterMode(
osg::LOD::USE_BOUNDING_SPHERE_CENTER ).
Значения минимума и максимума по умолчанию являются расстоянием. Тем не менее вы
можете настроить LOD так чтобы интерпретировать диапазон значений как размеры в
пикселях, и LOD отобразит потомка если его размер на экране в пикселях попадет в его
диапазон. Для настройки режима интерпретации диапазона узла LOD вызовите
osg::LOD::setRangeMode() и передайте osg::LOD::DISTANCE_FROM_EYE_POINT или
osg::LOD::PIXEL_SIZE_ON_SCREEN.
2.3.5 Узел Switch
Используйте узел osg::Switch для выбора или исключения отображения определенных
дочерних узлов. Типичное использование Switch подразумевает реализацию вашей
собственной схемы балансирования нагрузки для отображения определенных дочерних узлов
в зависимости от нагрузки отображения или переключение между экранами, или уровнями
игры.
Подобно LOD, Switch наследует дочерний интерфейс от Group. Каждый дочерний узел
Switch имеет ассоциированное с ним логическое значение типа bool. Switch отобразит
дочерний узел, если ассоциированное с ним логическое значение установлено в true, и
пропустит его в случае false. В следующем коде создается узел Switch и в него добавляются
два Group потомка, один видимый другой нет.
osg::ref_ptr<osg::Group> group0, group1;
...
// Создать родительский узел Switch и добавить к нему
// два потомка Group:
osg::ref_ptr<osg::Switch> switch = new osg::Switch;
// Отобразить первый потомок:
switch->addChild( group0.get(), true );
// Не отображать второй потомок:
switch->addChild( group1.get(), false );
Если в вашем коде добавляются потомки без явного определения отображения, Switch
назначит по умолчанию true.
// Добавить потомка. По умолчанию он видим
switch->addChild( group0.get() );
Вы можете изменить значение назначаемое по умолчанию новым потомкам вызвав
Switch::setNewChildDefaultValue().
// Изменить значение по умолчанию для новых потомков:
switch->setNewChildDefaultValue( false );
// Эти новые потомки будут выключены по умолчанию:
switch->addChild( group0.get() );
switch->addChild( group1.get() );
Построение Графа Сцены
После добавления потомка в Switch, вы можете изменять его значение. Используйте
Switch::setChildValue() и передавайте потомка вместе с его новым значением.
// Добавит потомка, изначально он включен:
switch->addChild( group0.get(), true );
// Выключить group0:
switch->setChildValue( group0.get(), false );
Отрывок кода выше очень надуман. Для того чтобы ощутить весь потенциал включения и
выключения потомков Switch во время выполнения, вам необходимо использовать обратный
вызов этапа обновления (osg::NodeCallback) или NodeVisitor (osg::NodeVisitor), для
управления значением узла графа сцены между отображением кадров. В этой книге
обсуждается обратный вызов этапа обновления и класс NodeVisitor в Главе 3,
Использование OpenSceneGraph в Вашем Приложении.
2.4 Состояние Отображения
Код примера Simple из Раздела 2.2 Geodes и Geometry создает граф сцены для проверки его
с помощью приложения osgviewer. Если вы вращали квадрат в osgviewer’е, то обращали
внимание что OSG освещает его источником света, идущим из точки наблюдения. Osgviewer
включает освещение при помощи настройки состояния отображения OSG.
Освещение одно из многих возможностей состояния отображения поддерживаемых OSG.
OSG поддерживает большинство функций состояния отображения фиксированного
конвейера (такие как альфа функция, смешивание, плоскости отсечения, маски цвета,
удаления лицевых/не лицевых граней, туман, состояние глубины и шаблона, состояние
растеризации точек и линий, и несколько других). Состояние отображения OSG так же
позволяет приложению определять вершинные и фрагментные шейдеры.
Ваше приложение устанавливает состояние отображения посредством osg::StateSet. Вы
можете присоединять StateSet к любому узлу в графе сцены, вы так же можете присоединить
его к Drawable. Большинство разработчиков OpenGL приложений знают о минимизации
изменений состояния отображения и что нужно по возможности избегать лишних установок
состояния; объект StateSet управляет этой оптимизацией автоматически.
Объект StateSet так же управляет стеком атрибутов OpenGL во время прохода OSG по графу
сцены. Это позволяет вашему приложению устанавливать различные состояния в различных
дочерних узлах. OSG эффективно сохраняет и восстанавливает состояние отображения при
проходе по графу сцены.
Вы должны минимизировать количество StateSet объектов присоединяемых к графу сцены.
Меньше StateSet объектов потребляют меньше памяти и уменьшают количество работы,
которое должна выполнить OSG во время прохода по графу сцены. Для облегчения
использования StateSet, StateSet наследуется от Referenced. Это значит, что Node или
Drawable объекты могут использовать один и тот же StateSet без необходимости
дополнительного кода управления освобождением памяти.
OSG организовывает состояние отображения в виде двух групп, атрибуты и режимы.
Атрибуты это переменные состояния, которые контролируют возможности отображения.
Например, цвет тумана и функция смешивания являются атрибутами состояния OSG.
Введение в OpenSceneGraph
Режимы один в один совпадают с возможностями управления состоянием в OpenGL
посредством glEnable() и glDisable(). Ваше приложение устанавливает режимы на
включение или выключение функциональности, например наложение текстур или
освещение. Кратко, режимы это возможности отображения, а атрибуты это переменные,
которые контролируют эти возможности.
Для установки значений состояния, вашему приложению необходимо сделать следующее:
 Получить StateSet состояние, которое вы хотите установить, для Node или Drawable.
 Обратиться к этому StateSet для установки режимов состояния и атрибутов.
Для того чтобы получить StateSet из Node или Drawable, вызовете следующий метод:
osg::StateSet* state = obj->getOrCreateStateSet();
В отрывке кода, представленном высшее, obj может быть как Node так и Drawable—
getOrCreateStateSet() определена в обоих классах. Этот метод возвращает указатель на
StateSet узла obj. Если obj не имеет еще StateSet, присоединенного к нему, этот метод
создает новый и присоединяет.
StateSet наследуется от Referenced. Владеющий Node или Drawable использует внутри
ref_ptr<> для того чтобы ссылаться на StateSet, поэтому в вашем приложении безопасно
использовать обычные C++ указатели в случае отсутствия необходимости долговременного
владения ссылкой на StateSet. Код высшее демонстрирует типичное использование, в
котором state это локальная переменная, получаемая через вызов функции, и приложению
нет необходимости в дублирующей ссылке на StateSet.
Переменная state в отрывке кода, представленного выше, является указателем на StateSet узла
obj. После того как ваше приложение получило указатель на StateSet, вы можете
устанавливать атрибуты и режимы. Следующие разделы книги рассматривают это более
детально и представляют простой пример.
2.4.1 Атрибуты and Режимы
В OSG определены различные классы для каждого атрибута состояния которое может
установить ваше приложение. Все классы атрибутов состояния наследуются от
osg::StateAttribute, который является виртуальным базовым классом, который ваше
приложение не может создать непосредственно.
Классов, наследуемых от StateAttribute, около дюжины. В этой книге упоминаются
несколько классов атрибутов, и описываются атрибуты освещения и наложения текстур более
детально. Полное описание всех классов атрибутов не входит в задачи этой книги. Для
самостоятельного рассмотрения, изучите заголовочные файлы в папке include/osg и обратите
внимание на классы, которые наследуются от StateAttribute.
OSG делит атрибуты и режимы на две группы, относящиеся к наложению текстур, и не
относящиеся. В этом разделе обсуждается настройка состояния не относящаяся к наложению
текстур. Настройка состояния относящиеся к наложению текстур представлена в разделе
2.4.4 Наложение Текстур. OSG предоставляет различный интерфейс для доступа к
атрибутам наложения текстур, поскольку им требуются доступ к текстурным модулям для
мультитекстурирования.
Построение Графа Сцены
Установка Атрибута
Для установки атрибута, создайте класс, отвечающий за атрибут, который вы хотите
модифицировать. Задайте значение этого класса, затем передайте его в StateSet, используя
osg::StateSet::setAttribute(). Следующий отрывок кода демонстрирует, как задать атрибут
удаления лицевых/не лицевых граней.
// Получить StateSet от geom.
osg::StateSet* state = geom->getOrCreateStateSet();
// Создать и добавить CullFace атрибут.
osg::CullFace* cf = new osg::CullFace(
osg::CullFace::BACK );
state->setAttribute( cf );
В отрывке кода, представленном выше, geom это объект типа Geometry (хотя он мог бы быть
любым другим объектом, унаследованным от Drawable или от Node). После получения
указателя на StateSet узла geom, в коде создается новый osg::CullFace объект, который затем
присоединяется к state.
CullFace это атрибут, и следовательно он унаследован от StateAttribute. Его конструктор
принимает один параметр—перечисление, которое определяет удаление лицевых или не
лицевых граней, FRONT, BACK, или FRONT_AND_BACK. Это перечисление определено
в заголовочном файле CullFace, и эквивалентно перечислениям GL_FRONT, GL_BACK, и
GL_FRONT_AND_BACK в OpenGL.
Если вы знакомы с OpenGL, думайте о коде выше как о вызове glCullFace( GL_BACK ). Тем
не менее, помните, что OSG является графом сцены. Когда ваше приложение присоединяет
атрибут CullFace к StateSet, вы указываете желаемое состояние и не заботитесь о командах
OpenGL. При выполнении этапа обхода узлов для отображения, OSG отслеживает
изменения состояния и только в случае необходимости вызывает команду glCullFace().
Подобно большинству объектов в OSG, StateAttribute наследуется от Referenced. После
создания любого объекта, унаследованного от StateAttribute и присоединения к StateSet,
StateSet ведет подсчет ссылок на атрибут. Вам не надо беспокоиться об освобождении
памяти позже. В обычном сценарии использования, показанном выше, вы ссылаетесь на
временный StateAttribute посредством обычного C++ указателя. После присоединения
StateAttribute к StateSet, StateSet управляет памятью с помощью ref_ptr<>.
Установка Режима
Для разрешения или запрещения режима, вызовите osg::StateSet::setMode(). Например, в
следующем отрывке кода включается туман:
// Получить StateSet.
osg::StateSet* state = geom->getOrCreateStateSet();
// Разрешить туман в этом StateSet.
state->setMode( GL_FOG, osg::StateAttribute::ON );
Введение в OpenSceneGraph
Первый параметр в setMode() это один из допустимых GLenum для glEnable()
или glDisable(). Второй параметр может быть osg::StateAttribute::ON или
osg::StateAttribute::OFF. (Вообще то это битовая маска, подробнее в разделе 2.4.2
Наследование Состояния.)
Установка Атрибута и Режима
OSG предоставляет удобный интерфейс для установки атрибута и режима с помощью вызова
одной функции. Во многих случаях существует очевидная связь между атрибутами и
режимами. Например, CullFace атрибут соответствует GL_CULL_FACE режиму. Для
присоединения атрибута к StateSet и одновременного включения соответствующего режима,
используйте метод osg::StateSet::setAttributeAndModes(). В следующем отрывке кода
присоединяется функция смешивания и включается режим смешивания:
// Создать атрибут BlendFunc.
osg::BlendFunc* bf = new osg::BlendFunc();
// Attach the BlendFunc attribute and enable blending.
// Присоединить атрибут BlendFunc и разрешить смешивание.
state->setAttributeAndModes( bf );
setAttributeAndModes() имеет второй параметр, который включает или выключает режим,
соответствующий атрибуту первого параметра. Второй параметр по умолчанию ON. Это
позволяет вашему приложению определять атрибут и автоматически включать
соответствующий режим с помощью вызова одной функции.
2.4.2 Наследование Состояния
Когда вы включаете состояние в узле, это состояние применяется к текущему узлу и к его
потомкам. Тем не менее, если в дочернем узле определено то же состояние, но с другим
значением, дочернее значение переопределит родительское состояние. Другими словами, в
поведении по умолчанию, дочерние узлы наследуют состояние своих родителей до тех пор,
пока потомки его не изменят. На Рисунке 2-7 показана эта концепция.
Наследование, в поведении по умолчанию, полезно во многих случаях. Тем не менее, в
некоторых случаях отображения, требуется другое поведение. Представьте себе граф сцены с
узлами, в которых определен режим закраски полигонов. Для отображения графа сцены в
режиме проволочного каркаса, ваше приложение должно переопределить режим закраски
полигонов, где бы он не был определен.
Рисунок 2-7
Построение Графа Сцены
Наследование состояния
В этом графе сцены, корневой узел разрешает освещение. Его первый потомок
выключает освещение, что является переопределением состояния освещения
своего предка. OSG отображает первого потомка с выключенными освещением.
Второй потомок не изменяет состояние. В результате OSG отображает второго
потомка с настройками состояния предка, т.е. освещение включено.
OSG позволяет вам изменять поведение наследования состояний индивидуально для каждого
атрибута и режима, в любой точке графа сцены. Доступны следующие перечисления:

osg::StateAttribute::OVERRIDE—Если вы установили атрибут или режим с
OVERRIDE, все потомки наследуют этот атрибут или режим вне зависимости от того
будут они менять его состояние или нет.

osg::StateAttribute::PROTECTED—Это исключение OVERRIDE’а. Вы можете
принудительно задать, чтобы атрибут или режим имел иммунитет на
переопределение с помощью установки PROTECTED.

osg::StateAttribute::INHERIT—этот режим вынуждает дочерние состояние
наследоваться от предка. В результате, состояние потомка убирается и вместо него
используется состояние родителя.
Вы можете задавать эти значения с побитовым ИЛИ во втором параметре методов
setAttribute(), setMode(), и setAttributeAndModes(). Следующий отрывок кода вынуждает
отображать граф сцены в режиме проволочного каркаса:
// Получить StateSet узла root.
osg::StateSet* state = root->getOrCreateStateSet();
// Создать атрибут PolygonMode
osg::PolygonMode* pm = new osg::PolygonMode(
osg::PolygonMode::FRONT_AND_BACK, osg::PolygonMode::LINE );
// Принудительно включить отображение
// в режиме проволочного каркаса.
state->setAttributeAndModes( pm,
osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE );
Используйте PROTECTED для того чтобы гарантировать что состояние предка никогда не
переопределится состоянием потомка. Например, вы можете создать сцену, содержащую
освещение, которая испускает свет из геометрии источника света. Если в узле потомке будет
запрещено освещение, геометрия источника света будет отображаться некорректно. В этом
случае используйте PROTECTED чтобы состояние геометрии источника света установило
GL_LIGHTING и гарантированно оставалось включенным.
2.4.3 Пример Кода Установки Состояния
В Разделе 2.3.2 Родительский Интерфейс описывается добавление одного и того же узла к
нескольким родителям. В разделе Узел MatrixTransform описывается узел трансформации
Введение в OpenSceneGraph
геометрии. В разделе 2.4 Состояние Отображения описывается состояние OSG. В
следующем примере кода собраны вместе три эти концепции.
Этот раздел представляет простой пример того, как изменять состояние отображения OSG. В
коде создается Geode с Drawable, который содержит два четырехугольника, но родителями
ему являются четыре узла MatrixTransform, каждый со своим StateSet. На Рисунке 2-8
показан этот граф сцены, а в Листинге 2-3 код, создающий его. Этот код взят из примера
State, идущего вместе с книгой.
Листинг 2-3
Модификации Состояния
В этом отрывке из примера State добавляется несколько Drawable объектов к
одному Geode. В коде устанавливаются различные состояния для каждого
Drawable, и запрещается освещение всей геометрии с помощью StateSet объекта
Geode.
#include <osg/Geode>
#include <osg/Group>
#include <osg/MatrixTransform>
#include <osg/Geode>
#include <osg/Geometry>
#include <osg/StateSet>
#include <osg/StateAttribute>
#include <osg/ShadeModel>
#include <osg/CullFace>
#include <osg/PolygonMode>
#include <osg/LineWidth>
...
osg::ref_ptr<osg::Node>
createSceneGraph()
{
// Создать узел root типа Group.
osg::ref_ptr<osg::Group> root = new osg::Group;
{
// Выключить освещение в узле root через StateSet.
// Сделать его PROTECTED для предотвращения
// включения освещения в osgviewer’е.
osg::StateSet* state = root->getOrCreateStateSet();
state->setMode( GL_LIGHTING,
osg::StateAttribute::OFF |
osg::StateAttribute::PROTECTED );
}
// Создать листовой узел Geode и присоединить к нему Drawable.
osg::ref_ptr<osg::Geode> geode = new osg::Geode;
geode->addDrawable( createDrawable().get() );
osg::Matrix m;
{
// Верхний-левый: Отобразить drawable с состоянием
// по умолчанию
osg::ref_ptr<osg::MatrixTransform> mt =
Построение Графа Сцены
new osg::MatrixTransform;
m.makeTranslate( -2.f, 0.f, 2.f );
mt->setMatrix( m );
root->addChild( mt.get() );
mt->addChild( geode.get() );
}
{
// Верхний правый: Задать модель закраски FLAT.
osg::ref_ptr<osg::MatrixTransform> mt =
new osg::MatrixTransform;
m.makeTranslate( 2.f, 0.f, 2.f );
mt->setMatrix( m );
root->addChild( mt.get() );
mt->addChild( geode.get() );
osg::StateSet* state = mt->getOrCreateStateSet();
osg::ShadeModel* sm = new osg::ShadeModel();
sm->setMode( osg::ShadeModel::FLAT );
state->setAttribute( sm );
}
{
// Нижний-левый: Включить режим удаления
// не лицевых граней
osg::ref_ptr<osg::MatrixTransform> mt =
new osg::MatrixTransform;
m.makeTranslate( -2.f, 0.f, -2.f );
mt->setMatrix( m );
root->addChild( mt.get() );
mt->addChild( geode.get() );
osg::StateSet* state = mt->getOrCreateStateSet();
osg::CullFace* cf = new osg::CullFace(); // Default: BACK
state->setAttributeAndModes( cf );
}
{
// Нижний-правый: Задать режим LINE при отображении
// полигонов, через StateSet.
osg::ref_ptr<osg::MatrixTransform> mt =
new osg::MatrixTransform;
m.makeTranslate( 2.f, 0.f, -2.f );
mt->setMatrix( m );
root->addChild( mt.get() );
mt->addChild( geode.get() );
osg::StateSet* state = mt->getOrCreateStateSet();
osg::PolygonMode* pm = new osg::PolygonMode(
osg::PolygonMode::FRONT_AND_BACK,
osg::PolygonMode::LINE );
state->setAttributeAndModes( pm );
// Так же установить толщину линий в 3.
osg::LineWidth* lw = new osg::LineWidth( 3.f );
Введение в OpenSceneGraph
state->setAttribute( lw );
}
return root.get();
}
В примере кода State создается узел Group, именуемый root, для того что бы быть корневым
узлом графа сцены, и с помощью StateSet узла root выключается освещение. Здесь
используется флаг PROTECTED для предотвращения включения освещения приложением
osgviewer.
Код использует функцию с именем createDrawable() для создания объекта Geometry,
содержащего два четырехугольника с различными цветами вершин. В Листинге 2-3 не
показана createDrawable(). Скачайте пример кода, для того чтобы увидеть эту функцию. Как
вы могли уже догадаться, этот код схож с кодом из Листинга 2-1. Код присоединяет
возвращенный Drawable к новому Geode, с именем geode.
Для отображения geode в четырех различных положениях, код создает четыре узла
MatrixTransform, каждое с различным смещением, затем добавляет geode как дочерний к
каждому из MatrixTransform. Для различного отображения geode, каждый из
MatrixTransform имеет свой уникальный StateSet.
Рисунок 2-8
Граф Сцены для примера программы State
Пример State отображает один и тот же Geode четыре раза. В примере Geode
позиционируется с помощью четырех узлов MatrixTransform, каждый со своим
StateSet.

Первый MatrixTransform, который перемещает geode вверх и влево, использует не
модифицированный StateSet. geode наследует всё состояние от своего предка. В
данном случае используется состояние по умолчанию.

Второй MatrixTransform, который перемещает geode вверх и вправо, использует
атрибут ShadeModel, установленный во FLAT, переданный в StateSet с помощью
StateAttribute. Это приводит к тому, что четырехугольники geode отображаются
Построение Графа Сцены
равномерно окрашенными. Результирующий цвет получается из последней
посланной вершины.

Третий MatrixTransform, который перемещает geode вниз и влево, использует
атрибут CullFace, переданный в StateSet с помощью StateAttribute. По умолчанию
CullFace удаляет BACK грани, хотя вы можете изменить это, передав другой параметр
конструктору. Вызов setAttributeAndModes( cf ) присоединяет CullFace и
одновременно включает GL_CULL_FACE. (Два четырехугольника, возвращенных
createDrawable(), имеют противоположный порядок обхода вершин, поэтому один
всегда обращен не лицевыми гранями к точке наблюдения.)

Последний MatrixTransform перемещает geode вниз и вправо. Его StateSet
содержит два объекта PolygonMode и LineWidth. Код устанавливает LINE режим
отображения полигонов для FRONT_AND_BACK граней, и задает толщину линий
равной 3.0.
Как и в примере Simple, пример State записывает граф сцены в файл, State.osg. После
выполнения примера State, отобразите выходной файл в osgviewer’е с помощью следующей
команды:
osgviewer State.osg
На Рисунке 2-9 показан как выглядит файл, загруженный в osgviewer.
Введение в OpenSceneGraph
Рисунок 2-9
Граф сцены примера State отображаемый в osgviewer’е
Это отображение графа сцены, созданного в Листинге 2-3, демонстрирует
различные состояния атрибутов и режимов. Верхний левый: состояние по
умолчанию. Верхний правый: модель закраски установлена во FLAT. Нижний
левый: включён режим отсечения не лицевых граней. Нижний правый: режим
отображения полигонов установлен в LINE, а толщина линий в 3.0. Освещение
запрещено для всего графа сцены.
2.4.4 Наложение Текстур
OSG полностью поддерживает OpenGL наложение текстур. Для выполнения простого
наложения текстуры в вашем приложении, ваш код должен сделать следующие:

Определить текстурные координаты для вашей геометрии.

Создать объект атрибута текстур и сохранить данные изображения текстуры в нём.

Установить подходящие атрибуты текстур и режимы в StateSet.
В этом разделе представлена информация о каждом шаге. Пример кода, идущего вместе с
книгой, TextureMapping демонстрирует основы техник наложения текстуры. В целях
экономии места этот код не приводится здесь.
Мультитекстурирование
Изначально в OpenGL не было поддержки мультитекстурирования. После
добавления мультитекстурирования, OpenGL продолжает поддерживать
интерфейс без мультитекстурирования для обратной совместимости. Внутри
OpenGL интерпретирует не-мультитекстурный интерфейс как модификацию
текстурного модуля 0. В отличие от OpenGL, OSG не предоставляет немультитекстурного интерфейса. В результате ваше приложение должно
определять текстурный модуль для данных текстурных координат и состояние
текстуры. Что бы использовать одну текстуру, просто определите текстурный
модуль 0.
Текстурные Координаты
В Разделе 2.2 Geodes и Geometry объяснялся интерфейс объекта Geometry для задания
вершин, нормалей и данных о цвете. Geometry так же позволяет вашему приложению
определять один или более массивов данных текстурных координат. Когда вы определяете
текстурные координаты, вы так же должны указать соответствующий текстурный модуль.
OSG использует значение текстурного модуля для мультитекстурирования.
Отрывок кода, который следует далее, создает osg::Vec2Array, сохраняет в нем текстурные
координаты, и присоединяет их к Geometry для использования совместно с текстурным
модулем 0. Для того чтобы применить несколько текстур к одному Geometry, присоедините
Построение Графа Сцены
множество массивов текстурных координат к Geometry и назначьте различные текстурные
модули каждому из массивов.
// Создать объект Geometry.
osg::ref_ptr<osg::Geometry> geom = new osg::Geometry;
// Создать Vec2Array текстурных координат для текстурного модуля 0
// и присоединить их к geom.
osg::ref_ptr<osg::Vec2Array> tc = new osg::Vec2Array;
geom->setTexCoordArray( 0, tc.get() );
tc->push_back( osg::Vec2( 0.f, 0.f ) );
tc->push_back( osg::Vec2( 1.f, 0.f ) );
tc->push_back( osg::Vec2( 1.f, 1.f ) );
tc->push_back( osg::Vec2( 0.f, 1.f ) );
В первом параметре в osg::Geometry::setTexCoordArray() передается номер текстурного
модуля, а во втором параметре данные массива текстурных координат. В функции
osg::Geometry::setTexCoordBinding() нет необходимости. Текстурные координаты всегда
задаются повершинно.
Загрузка Изображений
Ваше приложение использует два класса для простого 2D наложения текстуры,
osg::Texture2D и osg::Image. Texture2D это StateAttribute, который управляет объектом
текстуры OpenGL, а Image управляет данными пикселей изображения. Для того чтобы
использовать файл, содержащий 2D изображение для наложения текстуры, воспользуйтесь
библиотекой osgDB для создания объекта Image и присоедините Image к Texture2D.
Следующий отрывок кода использует файл sun.tif как текстуру.
#include <osg/Texture2D>
#include <osg/Image>
...
osg::StateSet* state = node->getOrCreateStateSet();
// Загрузить изображение текстуры
osg::ref_ptr<osg::Image> image =
osgDB::readImageFile( “sun.tif" );
// Присоединить изображение к объекту Texture2D
osg::ref_ptr<osg::Texture2D> tex = new osg::Texture2D;
tex->setImage( image.get() );
Метод readImageFile() библиотеки osgDB, создает новый объект Image, считывает файл с
изображением, помещает его в Image, и возвращает созданный объект Image. В Разделе 2.5
File I/O описывается readImageFile() более подробно.
После настройки атрибута Texture2D, присоедините его к StateSet. В следующем разделе,
Текстурное Состояние, эти шаги описываются более подробно.
Приложения, в которых широко используется наложение текстур, требуют тщательного
контроля за управлением памятью. Объект Image наследуется от Referenced, и Texture2D
Введение в OpenSceneGraph
хранит ref_ptr<> на Image. В течении первого прохода отображения, OSG создает OpenGL
объект текстуры для хранения данных изображения, результат – две копии изображения
текстуры, одна в объекте Image, а вторая принадлежит OpenGL. В простом сценарии
отображения с одним контекстом вывода, вы можете уменьшить потребление памяти,
сконфигурировав Texture2D на освобождение ссылки на Image. Если на Image ссылается
только объект Texture2D, это приведет к тому, что OSG удалит Image и занимаемую им
память. Следующий код демонстрирует как настроить Texture2D на освобождение от ссылки
на объект Image:
// После создания объекта текстуры OpenGL, освободится
// внутренний ref_ptr<Image> (удаление Image).
tex->setUnRefImageDataAfterApply( true );
По умолчанию Texture2D не освобождает ссылку на свой Image. Это желательное
поведение в много контекстном сценарии отображения, в случае если объект текстуры не
разделен между контекстами.
Текстурное Состояние
Интерфейс для настройки текстурного состояния позволяет вашему приложению определять
состояние для каждого текстурного модуля. Тем не менее интерфейс текстурного состояния
очень схож с интерфейсом состояния не относящимся к наложению текстур. Что бы
присоединить текстурный атрибут к StateSet, используйте
osg::StateSet::setTextureAttribute(). Первый параметр в setTextureAttribute() это номер
текстурного модуля, это текстурный атрибут унаследованный от StateAttribute. Всего
определено шесть текстурных атрибутов, по одному на каждый из пяти типов текстур
(osg::Texture1D, osg::Texture2D, osg::Texture3D, osg::TextureCubeMap, и
osg::TextureRectangle) и атрибут для генерации текстурных координат (osg::TexGen).
Передав атрибут Texture2D переменной tex и StateSet переменной state, следующий код
присоединяет tex к state для текстурного модуля 0:
// Создать атрибут Texture2D.
Osg::ref_ptr<osg::Texture2D> tex = new osg::Texture2D;
// ...
// Назначить текстурный атрибут текстурному модулю 0.
state->setTextureAttribute( 0, tex.get() );
Подобным образом, для установки режима наложения текстуры, используйте
osg::StateSet::setTextureMode(). Этот метод аналогичен методу setMode(). С помощью
setTextureMode() вы можете установить следующие режимы: GL_TEXTURE_1D,
GL_TEXTURE_2D, GL_TEXTURE_3D, GL_TEXTURE_CUBE_MAP,
GL_TEXTURE_RECTANGLE, GL_TEXTURE_GEN_Q, GL_TEXTURE_GEN_R,
GL_TEXTURE_GEN_S, и GL_TEXTURE_GEN_T.
Подобно setTextureAttribute(), первый параметр в setTextureMode() это желаемый
текстурный модуль. Следующий отрывок кода выключает 2D наложение текстуры для
текстурного модуля 1.
state->setTextureMode( 1, GL_TEXTURE_2D,
Построение Графа Сцены
osg::StateAttribute::OFF );
Конечно, используйте osg::StateSet::setTextureAttributesAndModes() для присоединения
атрибута к StateSet и одновременной установки соответствующего режима. Если атрибутом
является объект TexGen, setTextureAttributesAndModes() установит режимы генерации
текстурных координат GL_TEXTURE_GEN_Q, GL_TEXTURE_GEN_R,
GL_TEXTURE_GEN_S, и GL_TEXTURE_GEN_T. Режим неявно присутствует и для
других текстурных атрибутов. Например, в следующем коде
setTextureAttributesAndModes() разрешает GL_TEXTURE_2D, поскольку атрибут,
переданный в качестве второго параметра, это объект Texture2D.
// Создать атрибут Texture2D.
osg::ref_ptr<osg::Texture2D> tex = new osg::Texture2D;
// ...
// Присоединить 2D текстурный атрибут и разрешить
// GL_TEXTURE_2D, для текстурного модуля 0.
state->setTextureAttributeAndModes( 0, tex );
setTextureAttributeAndModes() имеет третий параметр, который по умолчанию равен ON
для разрешения режима наложения текстуры. В setAttributeAndModes() вы можете изменять
поведение наследования текстурных атрибутов с помощью битовой операции ИЛИ задав
OVERRIDE, PROTECTED, или INHERIT в последнем параметре. Вы так же можете задать
флаг наследования в третьем параметре в setTextureMode() и setTextureAttribute().
2.4.5 Освещение
OSG поддерживает OpenGL освещение в полном объеме, включая свойства материалов,
свойства источников света и режимы освещения. Подобно OpenGL, источники света не
видны—OSG не отображает ни лампочку ни какое либо другое физическое представление.
Так же источники света создают эффект затенения но не создают тени—используйте
osgShadow NodeKit для этого.
Для использования освещения в вашем приложении, ваш код должен сделать следующее:

Определить нормали в вашей геометрии.

Включить освещение и установить другие состояния освещения.

Определить свойства источника света и присоединить его в ваш граф сцены.

Определить свойства поверхности материала
В этом разделе предоставляется информация о каждом из этих шагов.
Если ваше приложение использует библиотеку osgViewer, описанную в Главе 3,
Использование OpenSceneGraph в Вашем Приложении, учтите что osgViewer включает
освещение и один источник света. Вы можете переопределить это в своем приложении,
Введение в OpenSceneGraph
настроив режим GL_LIGHTING, или изменив параметры GL_LIGHT0. За более подробной
информацией обратитесь в раздел Состояние Освещения.
Нормали
Правильное освещение требует, что бы длина векторов нормалей была соразмерна с
данными вашей геометрии. В Разделе 2.2 Geodes и Geometry описывается как задать массив
нормалей и назначить его объекту Geometry.
Как и в большинстве 3D API, ваши нормали должны иметь единичную длину, для получения
приемлемого результата. Помните, что масштабирование может нарушить длину ваших
нормалей. Если массив нормалей объекта Geometry содержит нормали единичной длины,
но результат освещения получается слишком ярким или слишком темным, знайте – виновато
масштабирование. Более эффективным решением является разрешение
перемасштабирования нормалей в StateSet.
osg::StateSet* state = geode->getOrCreateStateSet();
state->setMode( GL_RESCALE_NORMAL, osg::StateAttribute::ON );
Как и в OpenGL, разрешение этой возможности только восстанавливает ваши нормали к
единичной длине, в случае если имело место одинаковое масштабирование по всем осям. В
случае не одинакового масштабирования в вашем графе сцены, вы можете разрешить
нормализацию для приведения их единичным длинам.
osg::StateSet* state = geode->getOrCreateStateSet();
state->setMode( GL_NORMALIZE, osg::StateAttribute::ON );
Нормализации обычно более ресурсоемкая операция, чем перемасштабирование; по
возможности избегайте его.
Состояние Освещения
Что бы получить эффект освещения в OSG, вы должны разрешить освещение и хотя бы
один источник света. Приложение osgviewer делает это по умолчанию путем установки
соответствующих режимов в StateSet корневого узла. Вы так же можете сделать это в своем
приложении. Следующий отрывок кода разрешает освещение и два источника света
(GL_LIGHT0 и GL_LIGHT1) в StateSet переменной root.
osg::StateSet* state = root->getOrCreateStateSet();
state->setMode( GL_LIGHTING, osg::StateAttribute::ON );
state->setMode( GL_LIGHT0, osg::StateAttribute::ON );
state->setMode( GL_LIGHT1, osg::StateAttribute::ON );
В следующем разделе описывается, как контролировать свойства отдельных источников
света, а именно положение и цвет, и как управлять возможностями свойств материалов
OpenGL (и задавать цвет поверхности материала).
OSG так же предоставляет osg::LightModel StateAttribute для контроля за OpenGL
возможностями фонового цвета (ambient color), положением наблюдения, двух стороннего
освещения, и отделения зеркальной составляющей цвета.
Построение Графа Сцены
Источники Света
Что бы добавить источник света в вашу сцену, создайте объект osg::Light и определите
параметры источника света. Добавьте Light к osg::LightSource узлу, и добавьте узел
LightSource в ваш граф сцены. LightSource это просто листовой узел, содержащий
определение одного Light. Источник света, определенный в Light, влияет на всю сцену.
OSG поддерживает одновременно до восьми источников света, от GL_LIGHT0 до
GL_LIGHT7, и возможно даже больше, это зависит от лежащей в основе реализации
OpenGL. Включение множества источников света может значительно снизить
производительность в приложении содержащем большое количество вершин.
Внимание
Большинство начинающих разработчиков ошибочно полагают, что дочерние узлы
LightSource автоматически будут освещены. Это не так! OSG освещает ваш граф
сцены основываясь на текущем состоянии, а не на основании каких либо
иерархических взаимоотношений с узлом LightSource. GL_LIGHTING и хотя бы
один источник света (GL_LIGHT0 например) должен быть включён, для того чтобы
OSG осветил ваш граф сцены.
Думайте о LightSource как о листовом узле, который содержим один Light. Тем не
менее вы можете присоединять потомков к узлу LightSource, поскольку
LightSource наследуется от Group. Обычно в этом месте приложения
присоединяют геометрию представляющую физическое воплощение источника
света.
OSG позволяет вам использовать больше источников света чем поддерживает лежащая в
основе реализации OpenGL, но это находится за пределами материала представленного в
этой книге. Источник света включается с помощью setMode(), как описывалось ранее. Для
назначения соответствия объекта Light источнику света OpenGL, укажите номер света.
Например, для соответствия GL_LIGHT2 объекту Light, укажите число два:
// Создать объект Light для контроля за параметрами GL_LIGHT2.
osg::ref_ptr<osg::Light> light = new osg::Light;
light->setLightNum( 2 );
Номер света по умолчанию равен нулю.
Класс Light обладает функциональностью, схожей с командой OpenGL glLight(). Методы
Light позволяют вашему приложению задавать вклад света в
окружающий, дифузный, и зеркальный составляющий цветов. Вы можете создать точечный,
направленный, или свет прожектора, вы так же можете определить фактор ослабление для
уменьшения освещения с расстоянием. В следующем коде создается объект Light и
устанавливаются некоторые часто используемые параметры.
// Создать белый источник света типа прожектор
osg::ref_ptr<osg::Light> light = new osg::Light;
light->setAmbient( osg::Vec4( .1f, .1f, .1f, 1.f ));
Введение в OpenSceneGraph
light->setDiffuse( osg::Vec4( .8f, .8f, .8f, 1.f ));
light->setSpecular( osg::Vec4( .8f, .8f, .8f, 1.f ));
light->setPosition( osg::Vec3( 0.f, 0.f, 0.f ));
light->setDirection( osg::Vec3( 1.f, 0.f, 0.f ));
light->setSpotCutoff(25.f );
Для того чтобы добавить Light в вашу сцену, создайте узел LightSource, добавьте Light в
LightSource, присоедините LightSource к вашему графу сцены. От места в графе сцены,
куда вы добавили узел LightSource, будет зависеть результирующее положение Light в
пространстве. OSG преобразовывает положение Light с помощью текущего преобразования
узла LightSource. Разработчики OSG приложений обычно присоединяют LightSource как
дочерний узел к MatrixTransform для контроля за положением Light, как показано в
следующем коде:
Состояние Позиционирования
Когда OpenGL приложение обрабатывает команду glLight() для установки
положения освещения, OpenGL трансформирует позицию с помощью текущей
матрицы вида. В OSG это понятие известно как состояние позиционирования. Во
время прохода отсечения, OSG добавляет экземпляры состояний
позиционирования в контейнер состояний позиционирования, для того чтобы
гарантировать что они будут корректно преобразованы во время прохода
отрисовки.
// Создаем Light и настраиваем его свойства.
osg::ref_ptr<osg::Light> light = new osg::Light;
...
// Создаем MatrixTransform для позиционирования Light.
osg::ref_ptr<osg::MatrixTransform> mt = new osg::MatrixTransform;
osg::Matrix m;
m.makeTranslate( osg::Vec3( -3.f, 2.f, 5.f ) );
mt->setMatrix( m );
// Добавляем Light к LightSource. Добавляем LightSource
// и MatrixTransform в граф сцены.
osg::ref_ptr<osg::LightSource> ls = new osg::LightSource;
parent->addChild( mt.get() );
mt->addChild( ls.get() );
ls->setLight( light.get() );
По умолчанию OSG преобразует положение Light текущим преобразованием для
LightSource. Вы можете запретить это, задав начало координат LightSource, точно так же
как вы задавали начало координат для узла Transform (описанного в разделе 2.3.3 Узлы
Transform). Следующий код заставляет OSG игнорировать преобразования LightSource, и
обрабатывать положение Light в абсолютных величинах.
osg::ref_ptr<osg::LightSource> ls = new osg::LightSource;
ls->setReferenceFrame( osg::LightSource::ABSOLUTE_RF );
Построение Графа Сцены
Свойства Материала
osg::Material StateAttribute обладает функциональностью доступной через OpenGL
команды glMaterial() и glColorMaterial(). Для задания свойств материала в вашем
приложении, создайте объект Material, установите цвет и другие параметры, и присоедините
его к StateSet вашего графа сцены.
Material позволяет вам задать фоновый, диффузный, зеркальный и исходящий
(эмиссионный) цвет материала и то на сколько блестящим он будет выглядеть.
В Material определены перечисления FRONT, BACK, и FRONT_AND_BACK, таким
образом ваше приложение может задавать свойства материала как для лицевой так и
обратной стороны. Например, следующий код устанавливает диффузную, зеркальную и
показатель зеркального отражения параметры для лицевой стороны примитивов:
osg::StateSet* state = node->getOrCreateStateSet();
osg::ref_ptr<osg::Material> mat = new osg::Material;
mat->setDiffuse( osg::Material::FRONT,
osg::Vec4( .2f, .9f, .9f, 1.f ) );
mat->setSpecular( osg::Material::FRONT,
osg::Vec4( 1.f, 1.f, 1.f, 1.f ) );
mat->setShininess( osg::Material::FRONT, 96.f );
state->setAttribute( mat.get() );
Подобно OpenGL, показатель зеркального отражения должен быть в диапазоне от 1.0 до
128.0, кроме случаев реализации других значений в виде расширения.
Установка свойств материала напрямую может приводить к снижению производительности в
некоторых реализациях OpenGL, при обработке. Возможность цвета материала позволяет
вашему приложению изменять определенные свойства материала с помощью изменения
основного цвета. Это более эффективно, чем менять свойства материала напрямую,
облегчает связь между освещенной и не освещенной сценами, и удовлетворяет требования к
материалам во многих случаях.
Для разрешения возможности использования цвета материала, вызовите
Material::setColorMode(). В Material определены следующие перечисления AMBIENT,
DIFFUSE, SPECULAR, EMISSION, AMBIENT_AND_DIFFUSE, и OFF. По умолчанию
режим цвета материала OFF, и цвет материала выключен. Когда ваше приложение
устанавливает режим цвета материала в какое либо другое значение, OSG разрешает этот
режим для определения свойств материала и последующих изменений основного цвета как
свойств материала. Следующий отрывок кода разрешает режим цвета материала, таким
образом, что для лицевой стороны фоновый и диффузный цвета материала изменяют
соответствующим образом основной цвет:
osg::StateSet* state = node->getOrCreateStateSet();
osg::ref_ptr<osg::Material> mat = new osg::Material;
mat->setColorMode( osg::Material::AMBIENT_AND_DIFFUSE );
state->setAttribute( mat.get() );
Учтите, что Material автоматически разрешает или запрещает GL_COLOR_MATERIAL на
основании значения режима цвета материала. Ваше приложение не должно вызывать
setAttributeAndModes() для включения или выключения этой возможности.
Введение в OpenSceneGraph
Пример Освещения
Исходный код примера Lighting, идущий к этой книге, создает два источника света и
отображает геометрию, используя семь различных свойств материала. В целях экономии
места исходный код здесь не приводится. Пример записывает граф сцены в файл с именем
Lighting.osg. Граф сцены отображается с помощью следующей команды:
osgviewer Lighting.osg
На Рисунке 2-10 показан граф сцены отображенный osgviewer’ом.
Рисунок 2-10
Граф сцены примера Example отображенный в osgviewer.
В этом примере отображается шесть таблеток и плоскость сзади, каждая со
своими настройками материала. Два источника света освещают сцену.
2.5 Ввод/Вывод Файлов
В предыдущем разделе описывались техники программирования для создания графов сцены
с геометрией и состоянием, поскольку большинство приложений создают часть геометрии
программно. Тем не менее, приложения обычно загружают и отображают большие, сложные
Построение Графа Сцены
модели из файлов. Приложениям требуется функция для загрузки моделей из файла и
возврат их в виде подготовленного графа сцены.
Библиотека osgDB предоставляет интерфейс, который позволяет вашему приложению
читать и записывать 2D изображения и 3D модели файлов. osgDB управляется системой
подключаемых модулей OSG, предназначенной для поддержки различных типов файлов. В
разделе 1.6.3 Компоненты представлена концепция подключаемых модулей, а на Рисунке 19 показано как подключаемые модули вписываются в общую архитектуру OSG.
Примеры этого раздела используют osgDB для Ввода/Вывода файлов. Все примеры
используют подключаемый модуль .osg для записи графа сцены в .osg файл. Пример Lighting
использует подключаемый модуль .osg для загрузки дочернего графа из файла с именем
lozenge.osg, пример TextureMapping использует подключаемый модуль .png для загрузки
изображения. Тем не менее ранее не описывалась функциональность. В этом разделе
подключаемые модули описываются более подробно, таким образом что бы вы могли их
эффективно использовать в вашем приложении. Тут описывается интерфейс для чтения и
записи файлов, как OSG ищет файлы, и как OSG выбирает подключаемые модули для
загрузки этих файлов.
2.5.1 Интерфейс
osgDB предоставляет интерфейс для ввода/вывода файлов который полностью скрывает от
приложения лежащую в основе систему подключаемых модулей. Интерфейс osgDB
определен в двух заголовочных файлах:
#include <osgDB/ReadFile>
#include <osgDB/WriteFile>
Для того чтобы ваше приложение смогло использовать osgDB для ввода/вывода файлов,
включите эти заголовочные файлы в ваш исходный код.
В них определены несколько функций в пространстве имен osgDB для выполнения
ввода/вывода файлов.
To use the osgDB for file I/O in your application, include these header files in your
source code. They define several functions in the osgDB namespace for performing file I/O.
Чтение Файлов
Используйте функции osgDB::readNodeFile() и osgDB::readImageFile() для чтения 3D
моделей и 2D файлов изображений.
osg::Node* osgDB::readNodeFile( const std::string& filename );
osg::Image* osgDB::readImageFile( const std::string& filename );
Используйте readNodeFile() для чтения файлов содержащих 3D модели. OSG определяет
тип файла по его расширению и использует подходящий подключаемый модуль для
преобразования файла в граф сцены. readNodeFile() возвращает вашему приложению
указатель на корневой узел графа сцены. Схожим образом, readImageFile() загружает файлы
с 2D изображением и возвращает указатель на объект Image.
Введение в OpenSceneGraph
Параметр filename может содержать как абсолютный так и относительный путь. Если вы
определили абсолютный путь, OSG ищет файл в указанном месте.
Если filename содержит относительный путь (или содержит только имя файла), OSG ищет
файл, используя osgDB список путей файлов данных. Пользователи могут определять этот
список директорий, используя OSG_FILE_PATH переменную окружения, как описано в
разделе 1.3.3. Переменные Окружения.
Для добавления новых директорий данных в список путей файлов данных, используйте
функцию osgDB::Registry::getDataFilePathList(). osgDB::Registry это синглтон(паттерн
одиночка) —для вызова этой функции, получите доступ к экземпляру синглтона. Эта
функция возвращает ссылку на osgDB::FilePathList, которая просто является
std::deque<std::string>. Например для добавления директории находящейся в строковой
переменной newpath, используйте следующую строчку кода:
osgDB::Registry::instance()->getDataFilePathList().push_back
( newpath );
Если OSG по каким либо причинам не может загрузить ваш файл, обе эти функции вернут
NULL указатели. Для того чтобы убедиться, почему файл не загружается, установите в
переменной окружения OSG_NOTIFY_LEVEL более высокий уровень контроля (такой как
WARN), попробуйте загрузить файл еще раз, и проверьте предупреждения и сообщения об
ошибках отображаемые в консоли вашего приложения.
Запись Файлов
Используйте функции osgDB::writeNodeFile() и osgDB::writeImageFile() для записи
данных в файл 3D модели и 2D изображения.
bool osgDB::writeNodeFile( const osg::Node& node,
const std::string& filename );
bool osgDB::writeImageFile( const osg::Image& image,
const std::string& filename );
Если по каким либо причинам OSG не может записать файлы, эти функции возвращают
false. Снова, установите OSG_NOTIFY_LEVEL в WARN для просмотра сообщений о том
почему операция не смогла быть выполнена. В случае успешного выполнения операции
записи, эти функции возвращают true.
Если параметр filename содержит абсолютный путь, writeNodeFile() и
writeImageFile() пытаются файл в конкретное место. Если filename содержит
относительный путь (или не содержит путь вообще), эти функции пытаются записать файл
относительно текущей директории.
OSG перезаписывает существующий файл, с тем же именем, без предупреждений. Для
предотвращения этого поведения, ваше приложение должно проверить существует ли уже
такой файл и принят соответствующие меры.
Построение Графа Сцены
2.5.2 Рассмотрение Регистрации и
Подключаемых Модулей
Подключаемые модули OSG это группа сторонних библиотек которые реализуют интерфейс
описанный в osgDB заголовочном файле ReaderWriter. Для того что бы OSG смог найти
подключаемые модули, их директории должны быть перечислены в переменной окружения
PATH в Windows или в переменной окружения LD_LIBRARY_PATH в Linux. Конечные
пользователи могут сами определять дополнительные директории для поиска в переменной
окружения OSG_LIBRARY_PATH.
Внимание
Подключаемые модули могут не поддерживать одновременно операции чтения и
записи. Например, в OSG v2.0 3D Studio Max подключаемый модуль поддерживал
только чтение .3ds файлов, но не поддерживал запись в .3ds. Фактически
большинство подключаемых модулей поддерживают импорт файлов, но не
поддерживают экспорт. За более свежей информацией о поддерживаемых типах
файлов, обращайтесь на OSG Wiki Web site [OSGWiki].
OSG распознает библиотеки подключаемых модулей только если они соответствуют
следующему соглашению именования.



Apple—osgdb_<name>
Linux—osgdb_<name>.so
Windows—osgdb_<name>.dll
<name> обычно является расширением файла. Например, подключаемый модуль для чтения
изображений из GIF файлов имеет имя osgdb_gif.so в системах Linux.
Для разработчиков не всегда практично именовать свои подключаемые модули в
соответствии с расширением файла, поскольку некоторые подключаемые модули
поддерживают множество расширений файлов. Например, подключаемый модуль который
считывает файлы с RGB изображением может загружать файлы с расширениями .sgi, .int,
.inta. .bw, .rgba, и .rgb. Конструктор osgDB::Registry содержит специальный код для
поддержки такого рода подключаемых модулей. Registry содержит таблицу соответствий в
которой определено множество расширений поддерживаемых подключаемым модулем.
OSG не ищет все подключаемые модули и загружает что бы выяснить, какие типы файлов
они поддерживают. Это вызывало бы большую задержку при запуске приложения. OSG
реализует паттерн проектирования Цепочка Ответственности [Gamma95] что бы загружать
как можно меньше подключаемых модулей. Когда ваше приложение пытается произвести
чтение или запись файла используя osgDB, OSG выполняет следующие шаги для поиска
подходящего подключаемого модуля.
1. OSG просматривает свой список зарегистрированных подключаемых модулей для
поиска модуля поддерживающего этот файл. Изначально список зарегистрированных
подключаемых модулей содержит только те модули, которые были зарегистрированы
в конструкторе Registry. Если поиск подключаемого модуля поддерживающего файл
Введение в OpenSceneGraph
и операции ввода/вывода выполнился успешно, OSG возвращает загруженные
данные.
2. Если подключаемый модуль не зарегистрирован для данного типа файла или
операции ввода/вывода не поддерживаются, OSG создает соответствующее имя
подключаемого модуля, используя правила описанные ранее, и пытается загрузить эту
библиотеку. В случае успешной загрузки, OSG добавляет этот подключаемый модуль
в свой список зарегистрированных модулей.
3. OSG повторяет шаг 1. Если операция ввода/вывода опять закончилась неудачей, OSG
возвращает отказ.
В общем подключаемые модули просто работают, и вам вряд ли понадобится знать как OSG
поддерживает ввод/вывод файлов изнутри. Тем не менее, когда операция ввода/вывода
терпит неудачу, эта информация может помочь вам отследить проблемное место в коде.
2.6 NodeKits и osgText
OSG предоставляет богатые возможности. Тем не менее разработчики обычно нуждаются в
наследовании своих специализированных узлов от классов корневых узлов OSG. Более того,
наследуемая функциональность обычно не принадлежит ядру OSG и более предпочтительно
сделать их доступными как дополнительные поддерживаемые библиотеки. NodeKit это
библиотека которая расширяет существующую функциональность ядра OSG
специализированными Node, StateAttribute, или Drawable объектами, и добавляет
поддержку этих новых классов в формат файла .osg вместе с OSG wrapper.
В разделе 1.6.3 Компоненты представлена концепция NodeKits и предоставлен
высокоуровневый обзор доступных NodeKits находящихся в дистрибутиве OSG. В этом
разделе представлен пример использования популярного NodeKit, osgText, для вывода
отображаемого из текстуры текста в вашем графе сцены.
2.6.1 Компонент osgText
Библиотека osgText определена в пространстве имен osgText. В этом пространстве имен
находится небольшое количество полезных классов для загрузки шрифтов и отображения
строк текста.
Ключевым компонентом библиотеки osgText является класс osgText::Text. Text наследуется
от Drawable, и ваше приложение должно добавить экземпляр Text в Geode используя
addDrawable() (так же как вы добавляли экземпляр Geometry). Text выводит строку
символов произвольной длины. Обычно ваше приложение создает объект Text для каждой
строки экрана.
Другой ключевой компонент osgText это класс osgText::Font. osgText предоставляет
удобную функцию создания Font из файла. Font использует подключаемую библиотеку
FreeType для загрузки файлов шрифтов. Когда ваше приложение назначает Font объекту
Text, Font создает текстуру, содержащую только элементы символов необходимые для
отображения строки текста. Во время вывода, Text рисуется как отображаемые из текстуры
четырехугольники, где для каждого символа текста используются текстурные координаты,
которые соответствуют элементу символа в текстуре.
В osgText так же определен класс String для кодировок текста состоящих из нескольких байт.
Построение Графа Сцены
2.6.2 Использование osgText
Объекты Text и Font определены в двух заголовочных файлах. Следующий код
демонстрирует как подключить их к вашему приложению:
#include <osgText/Font>
#include <osgText/Text>
Для того чтобы использовать osgText в вашем приложении, обычно от вас требуется
выполнения трех шагов:
1. Для отображения нескольких строк текста используйте один шрифт, создайте один
объект Font к которому потом могут получить доступ все объекты Text.
2. Для каждой отображаемой строки текста, создайте объект Text. Определите
параметры выравнивания, ориентацию, положение и размер. Назначьте объект Font,
созданный вами на шаге 1 новому объекту Text.
3. Добавьте ваш объект Text к Geode используя addDrawable(). Вы можете добавить
множество объектов Text к одному Geode или создайте множество объектов Geode, в
зависимости от требований вашего приложения. Добавьте объект Geode как
дочерний узел в ваш граф сцены.
Следующий код демонстрирует, как создать объект Font, используя Courier New TrueType
файл со шрифтом, cour.ttf:
osg::ref_ptr<osgText::Font> font =
osgText::readFontFile( "fonts/cour.ttf" );
osgText::readFontFile() это вспомогательная функция которая использует FreeType OSG
подключаемый модуль для загрузки файла со шрифтом. Она использует osgDB для поиска
файла, таким образом поиск осуществляется в директориях определенных в
OSG_FILE_PATH, как описано в разделе 2.5 Ввод/Вывод Файлов. Тем не менее
readFontFile() так же ищет и в ряде других мест в зависимости от платформы. Если
readFontFile() не удается обнаружить запрашиваемый файл или файл не является шрифтом,
readFontFile() возвращает NULL.
Используйте следующий код для создания объекта Text, назначьте ему шрифт, и задайте
текст для отображения.
osg::ref_ptr<osgText::Text> text = new osgText::Text;
text->setFont( font.get() );
text->setText( “Display this message.” );
Хотя в данном примере Text::setText() принимает std::string в качестве параметра, на самом
деле она принимает osgText::String для поддержки кодировок состоящих из нескольких
байт. У osgText::String имеется несколько не явных конструкторов, которые принимают
std::string или строку символов в качестве параметра. В коде выше, вызов setText() со
строкой символов вызывает преобразование во время выполнения в osgText::String.
Введение в OpenSceneGraph
Если все попытки загрузить шрифт с помощью readFontFile() потерпят неудачу и ваше
приложение не сможет обнаружить подходящий шрифт во время выполнения, не вызывайте
Text::setFont(). В этом случае Text использует шрифт по умолчанию, который всегда
доступен.
Text имеет несколько методов, которые контролируют его размер, внешний вид,
ориентацию и положение. В следующем разделе описывается как контролировать многие из
этих параметров.
Положение
Text подобно Geometry, преобразовывает координаты своего объекта во время проходов
отсечения и отрисовки. По умолчанию положение это начало координат объекта. Вы можете
изменить это значение с помощью метода Text::setPosition(), который принимает Vec3 в
качестве параметра.
// Отобразить текст в (10., 0., 1.).
text->setPosition( osg::Vec3( 10.f, 0.f, 1.f ) );
Одно положение не определяет, где появится текст в вашем финальном изображении. Text
использует преобразованную позицию, совместно с значениями ориентации и
выравнивания, для определения где отображать текст. Ориентация и выравнивание
обсуждается далее.
Ориентация
Ориентация определяет в каком направлении отображать плоскости с текстом в 3D
пространстве. Установите ориентацию с помощью метода Text::setAxisAlignment(), и
передайте один из Text::AxisAlignment перечислений в качестве параметра. Для создания
текста, который всегда смотрит на наблюдателя, используйте Text::SCREEN.
text->setAxisAlignment( osgText::Text::SCREEN );
Кроме того, вы можете сделать так, чтобы текст лежал в определенной плоскости. По
умолчанию ориентация Text::XY_PLANE, что вынуждает помещать текст в xy плоскости
лицом в сторону положительного z.
text->setAxisAlignment( osgText::Text::XY_PLANE );
Таблица 2-1 содержит список значений Text::AxisAlignment перечислений и как они
влияют на ориентацию текста.
Выравнивание
Выравнивание аналогично выравниванию текста в программах обработки текста или
выравниванию ячеек в программах электронных таблиц. Оно определяет горизонтальное и
вертикальное выравнивание отображаемого текста относительно заданной позиции
(заданной setPosition()).Text определяет набор перечислений называемых
Построение Графа Сцены
Text::AlignmentType. В каждом имени перечисления закодирована сначала горизонтальное,
затем вертикальное выравнивание. По умолчанию Text::LEFT_BASE_LINE, что значит
горизонтальное выравнивание по левому краю текста, и вертикальное выравнивание по
линии основания шрифта, в заданной позиции. На Рисунке 2-11 показано как различные
типы выравнивания влияют на положение текста относительно заданной позиции.
Text::AxisAlignment
Перечисление
Влияние на Ориентацию
Text::XY_PLANE
(По умолчанию.) Располагает текст в плоскости xy лицом
в сторону положительного z.
Text::XZ_PLANE
Располагает текст в плоскости xz лицом в сторону
положительного y.
Text::YZ_PLANE
Располагает текст в плоскости yz лицом в сторону
положительного x.
Text::REVERSED_XY_PLANE
Располагает текст в плоскости xy лицом в сторону
отрицательного z.
Text::REVERSED_XZ_PLANE
Располагает текст в плоскости xz лицом в сторону
отрицательного y.
Text::REVERSED_YZ_PLANE
Располагает текст в плоскости yz лицом в сторону
отрицательного x.
Text::SCREEN
Отображает текст всегда лицом к экрану.
Таблица 2-1
Перечисления AxisAlignment Ориентации Текста
Для изменения выравнивания текста, вызовите Text::setAlignment(). Следующий код
определяет выравнивание по центру по горизонтали, и выравнивание по верхнему краю по
вертикали:
text->setAlignment( osgText::Text::CENTER_TOP );
Размер
По умолчанию высота символов составляет 32 единицы. Ширина символа переменна и
зависит от шрифта. Text отображает символы с правильным соотношением сторон согласно
информации хранимой в объекте Font.
Для изменения высоты символа, вызовите Text::setCharacterSize(). Следующий код
уменьшает высоту символа до одной единицы:
text->setCharacterSize( 1.0f );
Введение в OpenSceneGraph
По умолчанию, Text интерпретирует параметр setCharacterSize() как значение в единицах
координат объекта. Text так же позволяет вам задавать высоту символов в координатах
экрана, а не объекта. Используйте метод Text::setCharacterSizeMode() для задания
координат экрана.
text->setCharacterSizeMode( osgText::Text::SCREEN_COORDS );
После изменения режима высоты символов на координаты экрана, Text масштабирует
геометрию текста так чтобы сохранить постоянный размер на экране не смотря на эффект
перспективы. Учтите что OSG масштабирует текст во время прохода отсечения основываясь
на информации о перспективе предыдущего кадра, что вызывает задержку в один кадр. Эта
задержка обычно не заметна в приложениях с высоким значением частоты кадров.
Рисунок 2-11
Типы выравнивания текста
На этом рисунке показан эффект от применения 15 различных AlignmentType
перечислений. В каждом из примеров, позиция текста (задается с помощью
setPosition()) показана темно зеленной точкой.
С лева на право с верху вниз: RIGHT_BOTTOM, CENTER_BOTTOM,
LEFT_BOTTOM, RIGHT_BOTTOM_BASE_LINE, CENTER_BOTTOM_BASE_LINE,
LEFT_BOTTOM_BASE_LINE, RIGHT_BASE_LINE, CENTER_BASE_LINE,
LEFT_BASE_LINE, RIGHT_CENTER, CENTER_CENTER, LEFT_CENTER,
RIGHT_TOP,
CENTER_TOP, and LEFT_TOP.
Разрешение
Приложениям часто требуется изменять разрешение элемента символа, который берется из
карты текстуры символов, для предотвращения нечеткости символов. По умолчанию osgText
NodeKit выделяет 32×32 текселя на элемент символа. Для изменения этого значения
используйте метод Text::setFontResolution(). Следующий код увеличивает разрешение,
таким образом osgText выделяет 128×128 текселей на символ:
text->setFontResolution( 128, 128 );
Построение Графа Сцены
Когда несколько объектов Text, с различным разрешением, разделяют один объект Font и
одни и те же символы присутствуют в двух строках, карта текстуры символов будет содержать
несколько копий нужных символов с разными разрешениями.
Учтите, что увеличение разрешения символов так же увеличивает аппаратные требования,
такие как память для текстур графического адаптера. Вы должны использовать наименьшее
разрешение при котором получается приемлемый результат для заданного вами высоты
символов.
Цвет
Text отображает свою строку символов белым по умолчанию. Вы можете изменить это с
помощью метода Text::setColor(). Для того чтобы задать цвет, передайте значение цвета как
osg::Vec4 rgba в качестве параметра в setColor(). Следующий код устанавливает для Text
синий цвет для отображения строки символов.
// Установить цвет текста синим.
text->setColor( osg::Vec4( 0.f, 0.f, 1.f, 1.f ) );
Классы Text и Font библиотеки osgText позволяют вам контролировать несколько
дополнительных параметров, которые не освещены в этой книге. Посмотрите заголовочные
файлы include/osgText/Text и include/osgText/Font для дополнительной информации.
2.6.3 Код Примера Text
Исходный код примера Text, идущий к этой книге, демонстрирует размещение текста
относительно геометрии. В коде создается простой граф сцены состоящий из одного Geode.
Geode содержит четыре Drawable объекта, закрашенный по методу Гуро(Gouraud)
четырехугольник в плоскости xz и три объекта Text. Два объекта Text ориентированы
относительно экрана и позиционированы в верхнем-левом и верхнем-правом углах
четырехугольника, в то время как третий объект Text лежит в xz плоскости как раз под
четырехугольником.
Подобно другим примерам этого раздела, пример Text просто создает граф сцены и
записывает его в .osg файл. Для отображения этого графа сцены воспользуйтесь следующей
командой:
osgviewer Text.osg
Рисунок 2-12 показывает граф сцены отображенный в osgviewer’е.
2.6.4 The .osg File Format
Все NodeKits представлены в виде двух библиотек. В первой реализована основная
функциональность NodeKits используя новые классы, наследуемые от Node, Drawable, или
StateAttribute. Вторая библиотека представляет собой .osg посредник, который позволяет
этим новым классам быть загруженными или сохраненными в формат файла .osg.
Введение в OpenSceneGraph
Рисунок 2-12
Граф сцены примера Text отображаемый в osgviewer’е.
Пример Text создает три объекта Text с двумя различными разрешениями
шрифта и стилями ориентации (SCREEN и XZ_PLANE).
Формат файла .osg это просто ASCII текст, который не предназначенный быть эффективным
механизмом для хранения и загрузки. Тем не менее это прекрасное отладочное средство. Все
примеры этой главы записывают свои графы сцены в .osg файлы. Для процесса отображения,
в этом шаге нет необходимости—реальные приложения создают (или загружают) свои
графы сцен и отображают их. Примеры используют .osg для демонстрации пользы техники
отладки графа сцены.
В процессе разработки приложения вы должны записывать дочерние графы или весь граф
сцены в .osg файлы в случаях возникновения непредвиденного поведения при отображении.
Часто можно определить, что ошибки отображения происходят в корневом узле,
просматривая .osg файлы в текстовом редакторе. Если вы обнаружили что то, что выглядит
не вполне правильным, просто вручную скорректируйте файл для проверки ваших
подозрений.
Многие OSG разработчики пишут в список электронных писем osg-пользователей, когда они
сталкиваются с проблемами в отображении. Файл .osg содержащий небольшой дочерний
граф, который воспроизводит проблему, прикрепляется к письму, таким образом это
многократно увеличивает шансы что другой читатель сможет корректно выявить проблему.
Построение Графа Сцены
Листинг 2-4 показывает содержимое .osg файла, сгенерированного примером Text. По
содержимому видно как библиотека osg хранит свои Geode и Geometry классы, так же как
данные вершин и цвета. Так же показано как посредник .osg библиотеки osgText хранит
объекты Text и их параметры.
Предполагается что формат удобочитаем. Дочерние узлы или сохраненные данные смещены
на два пробела относительно родительского объекта, и вложенные уровни заключены в
изогнутые скобки.
Листинг 2-4
.osg файл графа сцены примера Text
The Text example creates a scene graph consisting of a single Geode that contains
four Drawable objects, a Geometry, and three Text objects.
Пример Text создает граф сцены, состоящий из одного Geode который состоит из
четырех Drawable объектов, а именно Geometry, и трех объектов Text.
Geode {
DataVariance UNSPECIFIED
nodeMask 0xffffffff
cullingActive TRUE
num_drawables 4
Geometry {
DataVariance UNSPECIFIED
useDisplayList TRUE
useVertexBufferObjects FALSE
PrimitiveSets 1
{
DrawArrays QUADS 0 4
}
VertexArray Vec3Array 4
{
-1 0 -1
1 0 -1
1 0 1
-1 0 1
}
NormalBinding OVERALL
NormalArray Vec3Array 1
{
0 -1 0
}
ColorBinding PER_VERTEX
ColorArray Vec4Array 4
{
1 0 0 1
0 1 0 1
0 0 1 1
1 1 1 1
}
}
osgText::Text {
DataVariance UNSPECIFIED
Введение в OpenSceneGraph
StateSet {
UniqueID StateSet_0
DataVariance UNSPECIFIED
rendering_hint TRANSPARENT_BIN
renderBinMode USE
binNumber 10
binName DepthSortedBin
}
supportsDisplayList FALSE
useDisplayList FALSE
useVertexBufferObjects FALSE
font C:\OSGDev\OpenSceneGraph-Data\fonts\arial.ttf
fontResolution 32 32
characterSize 0.15 1
characterSizeMode OBJECT_COORDS
alignment LEFT_BASE_LINE
autoRotateToScreen TRUE
layout LEFT_TO_RIGHT
position 1 0 1
color 1 1 1 1
drawMode 1
text "Top-right"
}
osgText::Text {
DataVariance UNSPECIFIED
Use StateSet_0
supportsDisplayList FALSE
useDisplayList FALSE
useVertexBufferObjects FALSE
font C:\OSGDev\OpenSceneGraph-Data\fonts\arial.ttf
fontResolution 32 32
characterSize 0.15 1
characterSizeMode OBJECT_COORDS
alignment LEFT_BASE_LINE
autoRotateToScreen TRUE
layout LEFT_TO_RIGHT
position -1 0 1
color 1 1 1 1
drawMode 1
text "Top-left"
}
osgText::Text {
DataVariance UNSPECIFIED
Use StateSet_0
supportsDisplayList FALSE
useDisplayList FALSE
useVertexBufferObjects FALSE
font C:\OSGDev\OpenSceneGraph-Data\fonts\arial.ttf
fontResolution 128 128
characterSize 0.4 1
characterSizeMode OBJECT_COORDS
alignment CENTER_TOP
rotation 0.707107 0 0 0.707107
Построение Графа Сцены
layout LEFT_TO_RIGHT
position 0 0 -1.04
color 1 1 1 1
drawMode 1
text "Hello OSG World"
}
}
Geode занимает верхний уровень в Листинге 2-4. В этом файле показаны несколько
параметров, таких как NodeMask и CullingActive, которые не описаны в этой книге, далее
следует параметр num_drawables, который установлен в четыре. Четыре Drawable объекта
занимают один уровень.
Первый Drawable это объект Geometry, который отображается в виде четырехугольника. Он
содержит все параметры и данные которые определены в примере Text, а так же некоторые
дополнительные параметры которые требуются Geometry.
После объекта Geometry следуют три объекта Text. У двух первых параметр
autoRotateToScreen установлен в TRUE, что всегда приводит к тому, что они обращены
лицевой стороной к экрану. Третий объект Text содержит параметр rotation за которым
следуют четыре Quat значения, которые вынуждают располагать текст в плоскости xz.
Объект Text содержит другие знакомые параметры, такие как цвет (белый), позиция, и имя
файла со шрифтом.
В качестве эксперимента отредактируйте rgba значение цвета одного из объектов Text,
сохраните файл и отобразите его с помощью osgviewer’а. Следующая строчка кода
устанавливает параметр color в пурпурный для примера:
color 0.6 0 1 1
На первый взгляд это может показаться тривиальным изменением, но если вы отлаживаете
проблему освещения и столкнулись с тем что диффузная составляющая цвета освещения
слишком темная, редактирование файла .osg на более яркий цвет, это наиболее быстрый и
простой способ проверить ваши подозрения.
Первый объект Text содержит StateSet. Параметры StateSet не описаны в этой книге, но
они явно указывают на то, что OSG должен отображать объект Text последним и
сортировать вывод от дальних к ближним для правильного отображения прозрачности и
смешивания. (Внутри, Text включает режим смешивания при своем отображении.) Другие
два объекта Text не содержат StateSet, поскольку библиотека osgText разделяет StateSet
объекты между объектами Text для экономии памяти. Если вы обратитесь к двум другим
объектам Text, вы увидите что они содержат следующую строку:
Use StateSet_0
Когда OSG загружает .osg файл, параметр Use указывает на разделение данных. В данном
случае это говорит OSG, что оставшиеся два объекта Text разделяют StateSet именуемый
StateSet_0 от первого объекта.
Как OSG разработчик, вы должны разобраться с форматом файла .osg. Выделите время на
изучение каждого из .osg файлов создаваемых в данной главе. Вам могут быть не понятны
Введение в OpenSceneGraph
некоторые параметры, но структура должна быть ясна и как она согласуется с кодом,
создающим граф сцены.
Построение Графа Сцены
Download