Главная
страница 1страница 2 ... страница 4страница 5
2 Построение Графа Сцены
Эта глава покажет вам как писать код, который строит OSG граф сцены. Тут раскрываются как внешние, так и внутренние детали построения графа сцены, освещается механизм OSG для загрузки всего графа сцены из файлов с 3D моделями.
Первый раздел посвящен управлению памятью. Графы сцены и их данные обычно потребляют большие объемы памяти, и в этом разделе обсуждается OSG система управления этой памятью, для предотвращения висячих указателей и утечек памяти.
Простейший граф сцены состоит из одного листового узла, содержащего некоторую геометрию и состояние. Раздел 2.2 Geodes и Geometry описывает как определить геометрию, нормали и цвет. Далее вы изучите как контролировать отображение геометрии, определяя атрибуты состояния OSG и режимы.
Тем не менее реальным приложениям требуется граф сцены сложнее чем один узел. В этой главе так же представлено OSG семейство узлов group, обладающих широкими возможностями, используемыми в большинстве библиотек графов сцен.
Большинству приложений необходимо считывать данные геометрии из файлов 3D моделей. В этой главе описывается простой OSG интерфейс загрузки файлов, который поддерживает множество популярных форматов файлов 3D моделей.
В конце этой главы показано как добавить текст в ваше приложение. В NodeKits сосредоточено большое количество продвинутой функциональности OSG. В этой главе подробно рассмотрен один NodeKit, osgText.
2.1 Управление Памятью
Прежде чем вы начнете строить граф сцены, вам необходимо понять, как 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

Ссылка на граф сцены

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




Рисунок 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<> содержит правильное значение.
#include

#include

...

osg::ref_ptr geode = new osg::Geode;



if (!geode.valid())

// ref_ptr<> не правильна. Сгенерировать исключение

// или отобразить ошибку.
Как и в любом другом шаблоне, при объявлении укажите тип переменной между угловыми скобками. Пример выше создает переменную ref_ptr<>, которая хранит адрес на osg::Geode. Учтите, что вы назначаете адрес так, как будто переменная была обычным C++ указателем.
В обычном сценарии использования, вы создаете узел и добавляете его как потомка к другому узлу графа сцены:
#include

#include

#include

...


{

// Создать новый объект osg::Geode. Назначение переменной

// ref_ptr<> увеличивает счетчик ссылок до 1.

osg::ref_ptr 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.
// Создать новый объект osg::Geode. Счетчик ссылок не увеличивается.

osg::Geode* geode = new osg::Geode;


// Внутренний ref_ptr<> в Group увеличивает счетчик ссылок дочернего

// Geode до 1.

grp->addChild( geode );
Пользуйтесь с осторожностью обычными C++ указателями для хранения Referenced объектов. Для корректной работы системы управления памятью OSG, адрес Referenced объекта должен быть назначен ref_ptr<> переменной. В коде выше это назначение происходит в методе osg::Group::addChild(). Если Referenced объект никогда не будет назначен ref_ptr<> переменной, произойдет утечка памяти.
{

osg::Geode* geode = new osg::Geode;

} // Не делайте так! Утечка памяти!
Как говорилось ранее, вы не можете явно удалить объект, унаследованный от Referenced или создать его в стеке. Следующий код вызывает ошибку компиляции:
osg::Geode* geode1 = new osg::Geode;

delete geode1; // Ошибка компиляции: деструктор защищен.


{

osg::Geode geode2;

} // Ошибка компиляции: деструктор защищен.
Переменная типа ref_ptr<> может указывать только на объект, унаследованный от Referenced (или объекты которые поддерживают то же интерфейс что и Referenced).
// OK, поскольку Geode унаследован от Referenced:

osg::ref_ptr geode = new osg::Geode;


int i;

osg::ref_ptr rpi = &i; // НЕ окей! 'int' не унаследован

// от Referenced и не поддерживает Referenced интерфейс.
Как обсуждалось ранее в этой главе, возможности управления памятью OSG облегчают каскадное удаление всего дерева графа сцены. Когда единственный ref_ptr<> на корневой узел удален, счетчик ссылок корневого узла становится равным нулю, и тогда деструктор корневого узла удаляет себя и ref_ptr<> указатели на своих потомков. Следующий код не вызывает утечки памяти из за дочернего Geode:
{

// ‘top’ увеличивает счетчик Group до 1.

osg::ref_ptr top = new osg::Group;
// addChild() увеличивает счетчик Geode до 1.

top->addChild( new osg::Geode );

} // ref_ptr ‘top’ выходит из области видимости, удаляется как память Group

// так и Geode.


Будьте осторожны когда возвращаете адрес объекта из функции. Если вы сделаете это не правильно, хранящийся адрес в ref_ptr<> может выйти из области видимости прежде чем будет помещен в стек для возврата из функции.
// НЕ делайте так. Значение адреса возвращается через стек,

// но когда grp ref_ptr<> выходит из области видимости,

// счетчик ссылок достигает нуля и память освобождается.

// Вызывающая функция получит висящий указатель.

osg::Group* createGroup()

{

// Создать новый узел Group.



osg::ref_ptr grp = new osg::Group;
// Вернуть новый адрес Group.

return *grp;

}
Существует несколько решений проблемы возврата адреса на Referenced объект. Метод, применяемый в примерах кода к этой книге, возвращает ref_ptr<> хранящий адрес, как показано в нижеследующем коде.
osg::ref_ptr createGroup()

{

osg::ref_ptr grp = new osg::Group;


// Вернуть новый адрес Group. Здесь адрес Group

// сохраняется в ref_ptr<> и ref_ptr<> помещается в стек как

// возвращаемое значение.

return grp.get();

}
Подводя итог:


  • Назначение объекта, унаследованного от Referenced в переменную ref_ptr<>, автоматически вызовет Referenced::ref() что бы увеличить счетчик ссылок.




  • Если переменную ref_ptr<> сделать указывающей на что-то другое или если она удалена, это вызовет Referenced::unref() для уменьшения счетчика ссылок. Если счетчик достиг нуля, unref() освободит память, занимаемую объектом.




  • Когда создаете объект типа Referenced, всегда проверяйте что назначаете его ref_ptr<> для того чтобы управление памятью OSG работало корректно.

Это может показаться немного витиеватым для “Введения”. Тем не менее, эта концепция важна, и твердое понимание управления памятью OSG важно для любого OSG разработчика.


В разделах следующих далее описывается несколько классов унаследованных от Referenced, и отрывки кода широко используют переменные ref_ptr<>. Поскольку вы читаете эту главу, помните что OSG использует ref_ptr<> для внутреннего долговременного хранения указателей, как в вызовах osg::Group::addChild() предыдущих примеров.
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

#include

osg::ref_ptr createSceneGraph()

{

// Создать объект для хранения в нем геометрии.



osg::ref_ptr geom = new osg::Geometry;
// Создать массив для хранения четырех вершин.

osg::ref_ptr 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 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 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 ) );


// Добавить Geometry (Drawable) в Geode и вернуть Geode.

osg::ref_ptr 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

#include

#include

#include

#include
using std::endl;
osg::ref_ptr createSceneGraph();
int main( int, char** )

{

osg::ref_ptr 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. Далее дается на более высоком уровне объяснение как пользоваться этими классами.


следующая страница >>
Смотрите также:
Построение Графа Сцены
895.8kb.
5 стр.
Принципы создания интеллектуальной системы обработки визуальной информации С. П. Пронин
24.77kb.
1 стр.
Англия при Генрихе II наследие Генриха I
32.37kb.
1 стр.
Название выступления
71.03kb.
1 стр.
Либретто пластического спектакля (пластическая драма) 2004 г. (редакция 2010 г.) Саратов
620.38kb.
2 стр.
5 Построение, содержание и планирование тренировки Построение тренировочных занятий О
409.8kb.
4 стр.
Моделирование распределения средств на благотворительные
76.14kb.
1 стр.
Завершающий урок по комедии Н. В. Гоголя
87.86kb.
1 стр.
Александрова Е. В., Коростелёва Анастасия, Филимонова Ирина (10 класс «А») Отечества прекрасные сыны…
144.85kb.
1 стр.
Презентация «Творчество как песня»
151.47kb.
1 стр.
Советская Россия в 1920-1930-е годы
29.75kb.
1 стр.
Строевые упражнения Основные построения1 Построение колонну
27.85kb.
1 стр.