G3D EngineСтатьи

Перевод документации

Автор:

Документация по инновационному движку G3D engine


Прошу в этой теме выкладывать все что нашли по этому движку и все кто с ним работает планируется создание русского компьюнити но инфы мала прошу людей откликнутся или связатся по аське 550632194

G3D - Это инновационный Open Source (BSD лицензия) C++ 3D движок AAA класса. Он используется в коммерческих играх, научных статьях, в военных симуляторах, университетские курсы. G3D поддерживает аппаратное ускорение реального времени отрисовки, офф-лайн отрисовки как луч трассировки и общие цели вычислений на GPUs.

G3D предоставляет набор процедур и структур, так широко, что они необходимы в почти каждый графический редактор. Это упрощает использование без ограничения функциональности и производительности низкоуровневой библиотеки как OpenGL и сокеты. G3D является надежной, оптимизированной базой для создания 3D-приложения.

Основные характеристики включают в себя:
3DS, OBJ, IFS, MD2, MD3, BSP, PLY, PLY2, OFF, и пользовательских моделей
Изображения JPG, PNG, BMP, PPM, PCX, TGA, DDS и ICO
MP4, MPG, MOV, AVI, DV, QT, WMV, видео

Команда разработчиков охватывает индустрии графики. Она включает в себя профессиональных разработчиков игр, военных и подрядчиков, доктора наук, студентов и преподавателей.
Добавлено (21.06.2010, 12:30)
---------------------------------------------
Взято с WIKI

Автор Loki

На данной странице я попытаюсь систематизировать свои познания о G3D на русском языке. По возможности, я постараюсь писать развернуто. Т.е. не сухой перевод документации по всем классам, их методам и переменным, а как можно более подробно... с объяснениями и примерами.

Если не указано обратного, то все классы находятся в пространстве имен G3D, т.е. нет необходимости каждый раз указывать"using namespace G3D". Вся документация относится к движку версии 8.00 BETA 4 (на момент написания этих строк).

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

G3D имеет встроенный сборщик мусора, который для управления памятью, использует подсчет количества указателей на объект. Большинство классов G3D управляются сборщиком мусора. Для того чтобы создать объект, который будет автоматически управлять памятью, необходимо объявить класс с окончанием "Ref" в конце имени класса или же использовать собственный производный класс от родительского. Общий вид объявления выглядит так:
typedef G3D::ReferenceCountedPointer<Foo> FooRef;
, где Foo - имя класса.

Ниже показан пример создания указателя на объект, хранящий изображение:
Texture::Ref map_texture = G3D::Texture::fromFile("apple.jpg");

Как видите, память для таких объектов выделяется специальными методами вместо использования оператора new или стандартной функции malloc(), а указатель на объект хранится в Ref (G3D::Texture::Ref вместо G3D::Texture*). В примере выше метод G3D::Texture::fromFile создает объект типа Texture::Ref и возвращает указатель на него. Класс Texture::Ref это класс Texture, унаследованный от ReferenceCountedPointer. В дальнейшем вы можете использовать Ref как самый обычный указатель, доступ к членам осуществляется при помощи -> (стрелка).

Объекты, унаследованные от ReferenceCountedPointer автоматически управляют количеством указателей на них. Когда число указателей становится равным 0, память, выделенная для хранения объекта, автоматически очищается.

Запомните! Если класс имеет наследника Ref, вы никогда не должны использовать непосредственно родительский класс, используйте Ref. Ну и естественно, что вы не должны вручную очищать выделенную для них память при помощи free() или delete. G3D возьмет эту заботу на себя. В закреплении выше сказанного предлагаю разобрать следующий пример:

/*1*/  class Foo : public G3D::ReferenceCountedObject {
/*2*/  public:
/*3*/    int x;
/*4*/  };
/*5*/  
/*6*/  class Bar : public Foo {};  
/*7*/  typedef G3D::ReferenceCountedPointer<Foo> FooRef;
/*8*/  typedef G3D::WeakReferenceCountedPointer<Foo> WeakFooRef;
/*9*/  typedef G3D::ReferenceCountedPointer<Bar> BarRef;
/*10*/  
/*11*/ int main(int argc, char *argv[]) {
/*12*/    WeakFooRef x;
/*13*/    {
/*14*/       FooRef a = new Foo();   //количество ссылок на класс FooRef равно 1
/*15*/       x = a;                  //объект типа weak не увеличивает значение счетчика (тип данных не совпадает)
/*16*/       {
/*17*/          FooRef b = a;        //количество ссылок на класс FooRef теперь равно 2
/*18*/       }                       //количество ссылок на класс FooRef снова равно 1 (объект "b" уничтожился по достижению конца блока)
/*19*/    }                          //А теперь и объект "a" уничтожился, количество ссылок равно 0
/*20*/ 
/*21*/    return 0; 
/*22*/ }

Пример довольно примитивный, но позволяет понять логику работы. Разберем подробно что в нем происходит.

Создается класс Foo, наследующий открытые методы класс ReferenceCountedObject. Теперь, если создать объект типа ReferenceCountedPointer<Foo> (в ReferenceCountedPointer<> можно включать только те классы, которые наследуются от ReferenceCountedObject), то такой объект перейдет под управление сборщика мусора, т.е. станет "garbage collected". Согласитесь, немного неудобно каждый раз писать такой длинный идентификатор, именно поэтому 7-9 строки определяют пользовательское имя (псевдоним) для этого определения.

Вы обратили внимание, что в этом примере ни разу не вызывается оператор delete для освобождения динамически выделенной памяти? Если бы класс Foo не наследовал ReferenceCountedObject, то в строке 19 память бы автоматически не очищалась. Т.е. уничтожиться то объект - уничтожился бы, но выделенная для его хранения память так и осталась бы занятой. А представь что будет если активно создаются и уничтожаются тысячи объектов. Свободная оперативка быстро закончится. Но поскольку Foo наследует ReferenceCountedObject, нам не нужно беспокоится о том, что мы забыли освободить память, когда объект стал более не нужен. В строке 19 это происходит автоматически. Contents [hide]
1 класс GApp
2 класс RenderDevice
3 класс Film
4 класс Material
5 класс ArticulatedModel
6 класс ArticulatedModel::Preproccess
7 класс BumpMap
8 класс SuperShader
9 класс _BSPMap::Map

класс GApp

Этот класс является каркасом (анг. framework), который управляет всеми событиями, происходящими в G3D и связывает все компоненты воедино. Его основные задачи:
Рендеринг кадров
Реакция на нажатие клавиш
Логика (предусмотрен специальный метод)
Физика (метод, вызывающий методы из физического движка)

У данного класса очень много методов, дружественных функций и параметров. Опишу основные из них

GApp::GApp(const Settings &options = Settings(), OSWindow *window = NULL)

Конструктор. По традиции в C++, конструктор используется для инициализации объекта. Так и здесь GApp() подготавливает приложение к запуску. Конструктор создает объект RenderDevice, UserInput, GConsole, FirstPersonManipulator и т.д. Обо всех этих классах я расскажу чуть позже, сейчас лишь стоит вкратце упомянуть что за что отвечает.

Итак, RenderDevice - это класс, через который происходит непосредственное взаимодействие с графическим чипсетом. Ну и соответственно с функциями OpenGL.

UserInput - класс, через методы которого обрабатывается пользовательский ввод.

GConsole - консоль для ввода команд. Тут стоит отметить, что поскольку объект типа GConsole создается в конструкторе, ему присваивается идентификатор console, то в принципе нет необходимости создавать свой собственный экземпляр... разве что вы захотите переопределить ее методы с помощью виртуальных функций.

FirstPersonManipulator - класс, отвечающий за работу мыши, джойстика, клавиатуры и т.д. В конструкторе создается объект этого класса с именем defaultController. В дальнейшем этот объект используется для управления положением камеры и направлением ее взгляда.

Это далеко не полный список объектов, создаваемых конструктором, но минимальный набор, необходимый что создать простое приложение.

Если посмотреть заголовочный файл GApp.h (G3D/GLG3D.lib/include/GLG3D/GApp.h), то вы увидите, что внутри класса GApp создаются еще два класса:

- GApp::Settings;

- GApp::DebugShape.

А внутри GApp::Settings создается класс GApp::Settings::FilmSettings.

Класс Settings служит для настройки приложения во время инициализации (разрешение экрана в пикселях, использовать ли полноэкранный режим, создавать ли файл с лицензией и т.д.). Так же внутри этого класса объявляется класс FilmSettings, а затем создается объект этого класса с именем film. Через методы этого класса осуществляется работа с буффером кадров (FrameBuffer).

После того как все параметры приложения заданы, можно передавать их в конструктор.

Пример:

//Объявляем класс App, который наследует класс GApp
class App : public GApp {
private:
    ...
    void message(const std::string& msg) const;
    void renderSplash();
    ...
public:
    //Переопределяем родительские методы на свои собственные
    App(const GApp::Settings& settings = GApp::Settings());
    virtual void onUserInput(UserInput* ui);
    virtual void onSimulation(RealTime rdt, SimTime sdt, SimTime idt);
    virtual void onPose(Array<Surface::Ref>& posed3D, Array<Surface2D::Ref>& posed2D);
    virtual void onGraphics2D(RenderDevice* rd, Array<Surface2D::Ref>& posed2D);
    virtual void onGraphics3D(RenderDevice* rd, Array<Surface::Ref>& posed3D);
};

int main(int argc, char* argv[])
{
    GApp::Settings settings;                  //Здесь будут определяться параметры, которые затем будут переданы
                                              //в конструктор App() для инициализации

    settings.film.enabled = true;             //Используем FrameBuffer
    settings.window.caption = "LOKI";         //Заголовок окна
    settings.window.width = 800;              //Разрешение по горизонтали
    settings.window.height = 600;             //Разрешение по вертикали
    settings.window.fullScreen = false;       //Не используем полноэкранный режим
    settings.window.framed = false;           //Отключаем заголовок окна
    settings.window.asynchronous = false;     //Используем синхронный режим обработки кадров
    settings.useDeveloperTools = true;
    settings.writeLicenseFile = false;        //Не создавать файл лицензии
    settings.dataDir = "/home/vit/g3d/data/"; //Здесь лежат файлы с текстурами, моделями и т.д.

    App application(settings);                //Передаем settings в конструктор
    return application.run();                 //Запускаем бесконечный цикл
}

Последние две строки можно заменить одной: "return App(settings).run();"

Тут может возникнуть резонный вопрос: для чего создавать App и делать его потомком GApp, почему бы просто не использовать базовый GApp? Ответ станет понятен из небольшого объяснения далее.

GApp не содержит конкретных реализаций методов обработки кадров, рендеринга моделей, теней, света, обработки нажатия клавиш и т.д. Он лишь предоставляет Вам интерфейс (API) для ваших собственных реализаций этих методов. Т.е. если бы это все в нем было бы заранее "прошито", то вы могли бы использовать это только в той форме, в которой оно было скомпилировано в бинарник и никак иначе. Выход был бы только один: написать свой собственный GApp. Вместо этого Морган решил использовать наследование: создал базовый класс GApp, который содержит только логику, но не конкретную реализацию. Вам же лишь остается унаследовать от GApp ваш собственный класс и переопределить его виртуальные методы, описав в них уже конкретную реализацию ваших функций. Таким образом, создание объекта класса, наследующего GApp, является практически обязательным. Кроме случаев, когда вы пишете полностью свой фреймворк, естественно.

void GApp::onPose(Array< Surface::Ref > &posed3D, Array< Surface2D::Ref > &posed2D)

У каждой модели в G3D есть параметр типа G3D::Surface. Surface это некое абстрактное понятие, которое описывает состояние модели на текущий момент (геометрия, положение, материал и т.д.). Сам Морган называет это snapshot (снимок), что весьма точно отражает смысл. Именно этот самый "снимок" и рендерится в кадре. ArticulatedModel::pose() заставляет объект скинуть свой "снимок" в заданный контейнер, откуда он позже может быть отрендерен в GApp::onGraphics3D() или в своей собственной функции. Как видно из определения функции, аргумент передается по ссылке, т.е. в примере ниже снэпшот скидывается в posed3D.

Пример:

//Создаем указатель на модель и загружаем ее из файла
ArticulatedModel::Ref model = ArticulatedModel::fromFile("data/3ds/superman.3ds");

void App::onPose(Array<Surface::Ref>& posed3D)
{
//Скидываем "снимок" модели в posed3D
    model->pose(posed3D);
}

void App::onGraphics3D(RenderDevice *rd, Array<Surface::Ref>& posed3D)
{
    ...
//Рендерим posed3D в кадр
    Surface::sortAndRender(rd, defaultCamera, posed3D, lighting,...);
    ...
}

Однако помимо скидывания своего "снимка" в методе pose(), можно еще и скидывать свои координаты в пространстве. Метод ArticulatedModel::pose(Surface::Ref &surface3D) является перегруженной функцией. Помимо указанной есть еще одна его форма: ArticulatedModel::pose(Surface::Ref &surface3D, CoordinateFrame cframe).

CoordinateFrame это ничто иное как матрица размерностью 4x4 (четвертая строка в ней всегда равна [0 0 0 1], поэтому она не хранится). cframe в данной функции хранит положение объекта в пространстве. Таким образом получив это значение в pose() мы можем перемещать объект. Давайте попробуем "посадить" камеру в кабину космического корабля.

Пример:

//Создаем указатель на модель и загружаем ее из файла
ArticulatedModel::Ref model = ArticulatedModel::fromFile("data/3ds/spaceship.3ds");

//Здесь будет сохраняться положение корабля
CoordinateFrame cframe;

void App::onPose(Array<Surface::Ref>& posed3D)
{
//Получаем положение камеры и сохраняем его в cframe (передается по ссылке)
    defaultCamera.getCoordinateFrame(cframe);

//Скидываем "снимок" модели в posed3D, а положение в пространстве в cframe
    model->pose(posed3D, cframe);
}

void App::onGraphics3D(RenderDevice *rd, Array<Surface::Ref>& posed3D)
{
    ...
//Рендерим posed3D в кадр
    Surface::sortAndRender(rd, defaultCamera, posed3D, lighting,...);
    ...
}

void GApp::onGraphics(RenderDevice *rd, Array< Surface::Ref > &surface, Array< Surface2D::Ref > &surface2D) 

Данный метод реализует покадровый рендеринг изображения. Метод вызывается автоматически. Если вы когда-нибудь работали с "голым" OpenGL и библиотекой glut, то этот метод отдаленно можно сравнить с вызовом glutMainLoop().
класс RenderDevice

Абстрактный интерфейс вывода. Т.е. через методы этого класса осуществляется взаимодействие с функциями OpenGL.

void setColorClearValue (const Color4 &c)

Метод, который устанавливает каким цветом будет отчищаться кадр. Данный метод это обычный вызов в OpenGL glClearColor();

void clear(bool clearColor, bool clearDepth, bool clearStencil);

Метод, который очищает кадр, обрабатываемый рендером. Перегруженный метод, имеет две формы:
void clear();
void clear(bool clearColor, bool clearDepth, bool clearStencil);

Первый это краткая запись второго с установленными в значение истина тремя аргументами. Метод практически всегда является обязательным - без него текущий кадр будет накладываться на предыдущий.

Данный метод это обычный вызов в OpenGL glClear();

swapBuffers()

Принудительно поменять местами задний (где происходит рендеринг) и передний буфер (то, что мы в данный момент видим).

push2D() и pop2D()

Первый метод перевод рендер в систему 2D-координат, второй возвращает в обычное состояние. Обязательно используются в паре. По историческим причинам так сложилось, что двухмерной системе координат точки (0, 0) это верхний левый угол экрана или изображения. Абсцисса (ось X) увеличивается вправо, ордината (ось Y) вниз. Режим 2D-координат это идеальное место чтобы нарисовать прицел, главное меню или любое другое двухмерное изображение.

Попробуем изобразить заставку в момент начальной загрузки игры.

void App::renderSplash() {
 Texture::Ref splash;
 Texture::Settings settings;
 settings.wrapMode = WrapMode::CLAMP;
 splash = Texture::fromFile("data/splash.jpg", ImageFormat::AUTO(), Texture::DIM_2D, settings);

 /*Очищаем экран от предыдущих кадров*/
 renderDevice->clear();

 /*Переводим устройство вывода в режим 2D-координат*/
 renderDevice->push2D();

 renderDevice->setTexture(0, splash);
 renderDevice->setBlendFunc(RenderDevice::BLEND_SRC_ALPHA,
   RenderDevice::BLEND_ONE_MINUS_SRC_ALPHA);
   Draw::rect2D(
     Rect2D::xywh(renderDevice->width()/2 - 400, 0, 800, 1200), 
       renderDevice, Color4(1,1,1,0.7f)
   );

 debugFont->draw2D(renderDevice, "Starting game...", renderDevice->viewport().center(), 24, 
    Color3::white(), Color4::clear(), GFont::XALIGN_CENTER, GFont::YALIGN_BOTTOM);

 /*Возвращаем устройство вывода в стандартный режим*/
 renderDevice->pop2D();

 /*Меняем местами передний и задний буфер*/
 renderDevice->swapBuffers();

 /*Показываем изображение в течении 3 секунд*/
 System::sleep(3);
}

Необходимость принудительно менять местами буферы в предпоследней строке продиктована тем, что функция рисования заставки вызывается вне контекста блоков beginFrame() и endFrame() (о них ниже) и поэтому мы должны самостоятельно озаботиться тем, что бы изображение появилось на экране. Вы же, надеюсь, помните что в заднем буфере происходит рендеринг, а в переднем выводится изображение.

beginOpenGL() и endOpenGL()

Методы вызываются без аргументов и используются обязательно в паре. Первый метод переводит рендер в режим "голого" OpenGL, при котором становится возможно оперировать функциями и типами данных OpenGL напрямую (устанавливает значение переменной RenderDevice::m_inRawOpenGL в истину). Второй метод возвращает рендер в стандартный режим (переводит RenderDevice::m_inRawOpenGL в ложь).

Давайте попробуем построить куб с помощью стандартых функций OpenGL, используя RenderDevice.

rd->beginOpenGL();
 glBegin ( GL_QUADS );
  glVertex3f ( 1.0f, 1.0f, 2.0f );
  glVertex3f ( 2.0f, 1.0f, 2.0f );
  glVertex3f ( 2.0f, 2.0f, 2.0f );
  glVertex3f ( 1.0f, 2.0f, 2.0f );

  glVertex3f ( 2.0f, 1.0f, 1.0f );
  glVertex3f ( 1.0f, 1.0f, 1.0f );
  glVertex3f ( 1.0f, 2.0f, 1.0f );
  glVertex3f ( 2.0f, 2.0f, 1.0f );

  glVertex3f ( 1.0f, 1.0f, 1.0f );
  glVertex3f ( 1.0f, 1.0f, 2.0f );
  glVertex3f ( 1.0f, 2.0f, 2.0f );
  glVertex3f ( 1.0f, 2.0f, 1.0f );

  glVertex3f ( 2.0f, 1.0f, 2.0f );
  glVertex3f ( 2.0f, 1.0f, 1.0f );
  glVertex3f ( 2.0f, 2.0f, 1.0f );
  glVertex3f ( 2.0f, 2.0f, 2.0f );

  glVertex3f ( 1.0f, 2.0f, 2.0f );
  glVertex3f ( 2.0f, 2.0f, 2.0f );
  glVertex3f ( 2.0f, 2.0f, 1.0f );
  glVertex3f ( 1.0f, 2.0f, 1.0f );

  glVertex3f ( 2.0f, 1.0f, 2.0f );
  glVertex3f ( 1.0f, 1.0f, 2.0f );
  glVertex3f ( 1.0f, 1.0f, 1.0f );
  glVertex3f ( 2.0f, 1.0f, 1.0f );
 glEnd ();
rd->endOpenGL();
 класс Film 
 класс Material 
 класс ArticulatedModel 
 класс ArticulatedModel::Preproccess 

Класс, определяющий опции, которые будут применены к модели после загрузки.

Пример, в котором загружается модель сферы и, используя класс ArticulatedModel::Preprocces, преобразуется в стеклянный шар:

//путь к модели сферы
std::string filename = System::findDataFile("sphere.ifs");

//Создаем материал "стекло"
Material::Specification glass;

//Описываем материал "стекло"
glass.setLambertian(Color3::zero());
glass.setTransmissive(Color3::white() * 0.9f); //стекло будет бесцветное
glass.setSpecular(Color3::white() * 0.05f);
glass.setGlossyExponentShininess(200);
glass.setEta(1.5f, 1.0f);
glass.setRefractionHint(RefractionQuality::DYNAMIC_FLAT);

//Создаем объект, содержащий описание материала для модели
ArticulatedModel::Preprocess p;

//Теперь объект "p" содержит описание матариала. Т.е. любая модель, использующая "p", станет стекляной
p.materialOverride = Material::create(glass);

//делаем модель стекляной (второй аргумент в функции "fromFile()")
ArticulatedModel::Ref model = ArticulatedModel::fromFile(filename, p);

класс BumpMap
класс SuperShader
класс _BSPMap::Map

#g3d

8 июля 2010 (Обновление: 12 мая 2011)

Комментарии [5]