Sources.RU Magazine Поиск по журналу
 

TopList

Использование Irrlicht Engine

Автор: impik777

Irrlicht Engine - кроссплатформенный графический 3D движок с открытым исходным кодом. Полностью бесплатен как для коммерческого, так и для некоммерческого использования. Основной автор движка, Nikolaus Gebhardt, разрабатывает его с 2002 года. Движок сочетает в себе универсальность, переносимость и широкую функциональность. Официальный сайт проекта: http://irrlicht.sourceforge.net/
Иррлихт – это кроссплатформенная библиотека. Имеется поддержка большей части операционных систем семейства Windows, а также Linux, MacOS и Solaris. Для успешного рендеринга на всех названных платформах, Иррлихт поддерживает несколько видов графического API: DirectX 8.1, DirectX 9.0, OpenGL (до версии 2.0) и имеет два собственных софтварных рендера.

Краткий список возможностей

  1. Поддержка 8ми форматов текстур и около 2х десятков форматов мешей.
  2. Поддержка пиксельных и вершинных шейдеров до версии 3.0. Поддержка HLSL и GLSL.
  3. Particle systems и Billboards.
  4. Bump и Parallax mapping.
  5. Динамическое освещение и динамические тени.
  6. Light maps.
  7. Туман.
  8. Terrain и water surfaces.
  9. Развитая система GUI, включающая до 2х десятков элементов.
  10. Встроенный XML парсер.
  11. Работа с zip и pak архивами, а также многое другое.

Иррлихт был написан на С++, однако уже имеются порты на .Net и Java. На движке уже выпушено большое количество как коммерческих, так и некоммерческих игр, а также проектов другого типа.
Главное достоинство Иррлихт - простота его интерфейса, а также возможность работы с ним без знания тех разделов математики, которые обычно необходимы при программировании 3D графики. Для работы необходимо скачать SDK с официального сайта. Последняя версия на 25.12.07 - 1.4.
Имеет Иррлихт также и один недостаток: полигоны мешей не хранятся в вершинных буферах в видеопамяти, а каждый раз при отрисовке загружаются в неё по шине из оперативной памяти, поэтому движок не тянет сильно нагруженные сцены вне зависимости от «крутизны» видеокарты. Сгладить этот недостаток можно, например, определяя вручную, какие меши видны данной камере, а остальные делая «невидимыми». Такой способ хорошо подходит к играм с камерой «от третьего лица сверху» (например, рпг и стратегии).


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

/*  Начало работы с Irrlicht Engine 1.4
    Все подробности есть в справке в папке irrlicht-1.4\doc в SDK
    Подключается единственный необходимый хедер */
#include 
// irr:: - основное пространство имен, в котором лежит все (!)
using namespace irr;
/*  остальные пространства имен.
    в ядре лежит математика, контейнеры.
    в сцене - все, что связано с 3д сценой.
    в гуй - весь гуй
    в видео - работа с выводом графики
    в ио - файловый ввод-вывод, в том числе XML и работа с файловой системой */
using namespace core;
using namespace scene;
using namespace video;
using namespace io;
using namespace gui;
// поключаем библиотеку
#pragma comment(lib, "Irrlicht.lib")
/*  Очень удобно работать с Ирлихтом вместе с консольным окном,
    т.к. туда кидается большое число полезных логов. */
int main()
{
    /*  Первый шаг - инициализация движка.
        Функция вернет 0, если не удалось.
        Существует два способа инициализации: простой и расширенный (больше параметров).
        Здесь рассматривается простой способ.
        Параметры функции:      
        1 - video::E_DRIVER_TYPE  deviceType    - АПИ для вывода графики.
        Поддерживаются такие: DirectX 9, DirectX 8, OpenGL и два софтварных рендера,
        (The Burning's Software Renderer - получше будет)
        2 - const core::dimension2d< s32 > &  windowSize  - размер создаваемого окна.
        3 - u32  bits  - "битность" цветов в полноэкранном режиме, если он выбран.
        4 - bool  fullscreen  - флаг включения полноэкранного режима
        5 - bool  stencilbuffer - флаг, который используется, если необходимо рисовать тени в 3д
        6 - bool  vsync - вертикальная синхронизация (для 3д)
        7 - IEventReceiver *  receiver  - указатель на объектный callback, 
        в который будут скидываться все сообщения ввода     */
    IrrlichtDevice *device = createDevice( video::EDT_SOFTWARE, dimension2d(640, 480), 16,
                                                false, false, false, 0);
    /*  С помощью этой функции устанавливается заголовок окна.
        Внимание: все строки, которые выводятся на экран, принимаются движком только в Юникоде  */
    device->setWindowCaption(L"Hello World! - Irrlicht Engine Demo");
    /*  Ниже - основные инструменты.
        Видео драйвер отвечает непосредственно за отрисовку на экран
        Менеджер сцены - за расположение и работу с объектами 3д сцены
        Гуй менеджер - за управление элементами гуй */
    IVideoDriver* driver = device->getVideoDriver();
    ISceneManager* smgr = device->getSceneManager();
    IGUIEnvironment* guienv = device->getGUIEnvironment();
    /*  Простой пример создание гуй-контрола. Такие функции возвращают
        указатель на контрол, с помощью которого им можно управлять
        Здесь создается Label с текстовой строкой, 
        в заданном с помощью прямоугольника месте.  */
    IGUIStaticText* text = guienv->addStaticText(L"Hello World! This is the Irrlicht Software renderer!",
        rect(10,10,260,22), true);
    /* а здесь задается цвет текста. По умолчанию используется стандартный шрифт, но его можно сменить см. IGUIFont   */
    text->setOverrideColor(SColor(128,255,0,0)); 
    /*  Теперь дело за 3д графикой.
        Меш - объект, который хранит информацию о 3д модели. Треугольники, координаты текстур и т.д.
        Нод - объект сцены. Существует большое количество типов нодов, данный нод управляет выводом конкретного меша на экран.
        Один меш может выводиться по-разному разными нодами.     */
    IAnimatedMesh* mesh = smgr->getMesh("../../media/sydney.md2");
    IAnimatedMeshSceneNode* node = smgr->addAnimatedMeshSceneNode( mesh );
    /*  Установка некоторых параметров нода
        1 - Реакция на динамическое освещение (отключена)
        2 - Установка проигрываемой анимации. Для мд2 формата есть свои константы, 
        а вообще она устанавливается по кадрам: 
            node->setFrameLoop(0,310);
            node->setLoopMode(true); - установка зацикленности анимации

            setAnimationEndCallback (IAnimationEndCallBack *callback) - эта функция задает объектный callback, который вызывается после окончания анимации в том случае, если она НЕ зациклена.
        3 - наложение текстуры на меш, первый параметр обычно всегда равен нулю     */
    if (node)
    {
        node->setMaterialFlag(EMF_LIGHTING, false);
        node->setMD2Animation ( scene::EMAT_STAND );
        node->setMaterialTexture( 0, driver->getTexture("../../media/sydney.bmp") );
    }
    /*  Создание простейшей камеры.
        Первый параметр - указание на "родительский" нод, обычно это делают чтобы привязать к нему камеру 
        Второй - позиция камеры
        Третий - точка, куда она смотрит. Эти два параметра могут меняться в процессе, т.к. функция возвращает указатель на камеру  */
    smgr->addCameraSceneNode(0, vector3df(0,30,-40), vector3df(0,5,0));
    //  Основной цикл приложения. Функция run() вернет false, если пользователь закроет окно
    while(device->run())
    {
        /*  Функция, которая подготавливает сцену для отрисовки, вызывается обязательно. Здесь устанавливаются следующие параметры
            1 - очистка заднего фона (третий параметр - цвет очистки)
            2 - чистка z-буфера. Необходимо для 3д графики      */
        driver->beginScene(true, true, SColor(255,100,101,140));
        //  Рисуем сцену и гуй
        smgr->drawAll();
        guienv->drawAll();
        //  конец отрисовки и вывод на экран
        driver->endScene();
    }
    
    // Все объекты иррлихт унаследованы от IReferenceCounted,
    // который считает ссылки и удаляет объект, если на него нет ссылок
    // grab() и drop() - "++" и "--" для ссылок 
    device->drop();
    return 0;
}

скриншот результат работы

На рисунке показан скриншот результат работы. Позади него консольное окно с логами, очень полезное при отладке.

В качестве более сложного примера использования библиотеки ниже рассматривается создание просмотрщика уровней Квейка.

#include 
#include 
using namespace irr;
#pragma comment(lib, "Irrlicht.lib")
int main()
{
    // здесь пользователь может выбрать понравившиеся ему АПИ 
    video::E_DRIVER_TYPE driverType;
    printf("Please select the driver you want for this example:\n"\
        " (a) Direct3D 9.0c\n (b) Direct3D 8.1\n (c) OpenGL 1.5\n"\
        " (d) Software Renderer\n (e) Burning's Software Renderer\n"\
        " (f) NullDevice\n (otherKey) exit\n\n");
    char i;
    std::cin >> i;
    switch(i)
    {
        case 'a': driverType = video::EDT_DIRECT3D9;break;
        case 'b': driverType = video::EDT_DIRECT3D8;break;
        case 'c': driverType = video::EDT_OPENGL;   break;
        case 'd': driverType = video::EDT_SOFTWARE; break;
        case 'e': driverType = video::EDT_BURNINGSVIDEO;break;
        case 'f': driverType = video::EDT_NULL;     break;
        default: return 1;
    }
    // инициализируется движок
    IrrlichtDevice *device =
        createDevice(driverType, core::dimension2d(640, 480));
    // проверка, все ли в порядке
    if (device == 0)
        return 1; 
    video::IVideoDriver* driver = device->getVideoDriver();
    scene::ISceneManager* smgr = device->getSceneManager();
    /* Иррлихт позволяет работать с zip архивами, Данной строчкой он добавляется в "видимость" для ирлихтовского контроллера файловой системы. */
    device->getFileSystem()->addZipFileArchive("../../media/map-20kdm2.pk3");
    // Теперь файлы из архива доступны
    scene::IAnimatedMesh* mesh = smgr->getMesh("20kdm2.bsp");
    scene::ISceneNode* node = 0;
    /* Здесь создается ОктТри нод. Этот нод будет выводиться на экран с оптимизацией по восьмеричному дереву - это необходимо для больших объектов */
    if (mesh)
        node = smgr->addOctTreeSceneNode(mesh->getMesh(0), 0, -1, 128);
    // Изменяется позицию нода, также можно его вращать и масштабировать:
    // SetRotation и SetScale  
    if (node)
        node->setPosition(core::vector3df(-1300,-144,-1249));
    /* Этот вид камеры автоматически подключен к клавиатуре (стрелки) и мышке,
       с помощью которых ей можно управлять, что очень удобно для тестов   */
    smgr->addCameraSceneNodeFPS();
    /* А тут задаются параметры курсора мыши, 
       кроме определения его видимости, можно спрашивать и задавать его расположение */
    device->getCursorControl()->setVisible(false);
    /* В основной цикл, в отличие от прошлого примера, добавлен код отображения
       текущего АПИ и ФПС - количество отрендеренных кадров в секунду   */
    int lastFPS = -1;
    while(device->run())
    if (device->isWindowActive()) // проверка, активно ли окно
    {
        driver->beginScene(true, true, video::SColor(0,200,200,200));
        smgr->drawAll();
        driver->endScene();
        int fps = driver->getFPS();
        if (lastFPS != fps)
        {
            core::stringw str = L"Irrlicht Engine - Quake 3 Map example [";
            str += driver->getName();
            str += "] FPS:";
            str += fps;
            device->setWindowCaption(str.c_str());
            lastFPS = fps;
        }
    }
    device->drop();
    return 0;
}

скриншот результат работы

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

#include 
#include 
using namespace irr;
using namespace core;
using namespace scene;
using namespace video;
using namespace io;
using namespace gui;
#pragma comment(lib, "Irrlicht.lib")
IrrlichtDevice *device = 0;
s32 cnt = 0;
IGUIListBox* listbox = 0;
class MyEventReceiver : public IEventReceiver
{
public:
    virtual bool OnEvent(const SEvent& event)
    {
        // ресивер может также принимать сообщения от собственного гуи
        if (event.EventType == EET_GUI_EVENT)
        {
            // каждому элементу можно и нужно задать свое числовое ид
            // здесь мы получаем ид гуй, который среагировал на действия пользователя
            s32 id = event.GUIEvent.Caller->getID();
            IGUIEnvironment* env = device->getGUIEnvironment();
            // можно определить тип события, которое произошло с контролом
            switch(event.GUIEvent.EventType)
            {
            // например был прокручен скроллбар
            case EGET_SCROLL_BAR_CHANGED:
                if (id == 104)
                {
                    // выясняется позиция бегунка
                    s32 pos = ((IGUIScrollBar*)event.GUIEvent.Caller)->getPos();
                    // и изменяется альфа канал у всех элементов активного скина
                    // скин отвечает за многие параметры (вообще оформление)
                    // навешенных на него гуи. Можно создавать разные скины   
                    // и активизировать по мере надобности.
                    for (u32 i=0; igetSkin()->getColor((EGUI_DEFAULT_COLOR)i);
                        col.setAlpha(pos);
                        env->getSkin()->setColor((EGUI_DEFAULT_COLOR)i, col);
                    }
                }
                break;
            // клик по кнопке
            case EGET_BUTTON_CLICKED:
                if (id == 101)
                {
                   // таким способом можно заставить device->run() вернуть false
                    device->closeDevice();
                    return true;
                }

                if (id == 102)
                {
                    // Добавить строку в листбокс
                    listbox->addItem(L"Window created");
                    cnt += 30;
                    if (cnt > 200) 
                        cnt = 0;
                    // создается "дочернее" (не Windows) окошко
                    IGUIWindow* window = env->addWindow(
                        rect(100 + cnt, 100 + cnt, 300 + cnt, 200 + cnt), 
                        false, // модальность
                        L"Test window"); // заголовок
                    // Label
                    env->addStaticText(L"Please close me",  
                        rect(35,35,140,50),
                        true, // border
                        false, // wordwrap
                        window);

                    return true;
                }

                if (id == 103)
                {
                    listbox->addItem(L"File open");
                    // Можно пользоваться стандартными диалогами открытия файла
                    env->addFileOpenDialog(L"Please choose a file.");
                    return true;
                }

                break;
            default:
                break;
            }
        }

        return false;
    }
};
int main()
{
    video::E_DRIVER_TYPE driverType;
    printf("Please select the driver you want for this example:\n"\
        " (a) Direct3D 9.0c\n (b) Direct3D 8.1\n (c) OpenGL 1.5\n"\
        " (d) Software Renderer\n (e) Burning's Software Renderer\n"\
        " (f) NullDevice\n (otherKey) exit\n\n");
    char i;
    std::cin >> i;
    switch(i)
    {
        case 'a': driverType = video::EDT_DIRECT3D9;break;
        case 'b': driverType = video::EDT_DIRECT3D8;break;
        case 'c': driverType = video::EDT_OPENGL;   break;
        case 'd': driverType = video::EDT_SOFTWARE; break;
        case 'e': driverType = video::EDT_BURNINGSVIDEO;break;
        case 'f': driverType = video::EDT_NULL;     break;
        default: return 1;
    }   
    device = createDevice(driverType, core::dimension2d(640, 480));
    if (device == 0)
        return 1; 
    MyEventReceiver receiver;
    device->setEventReceiver(&receiver);
    device->setWindowCaption(L"Irrlicht Engine - User Interface Demo");
    video::IVideoDriver* driver = device->getVideoDriver();
    IGUIEnvironment* env = device->getGUIEnvironment();
    // а здесь загружается новый шрифт, он должен быть создан с помощью специального инструмента 
    // FontTool из SDK, и состоит как минимум из xml и одной - нескольких текстур
    IGUISkin* skin = env->getSkin();
    IGUIFont* font = env->getFont("../../media/fonthaettenschweiler.bmp");
    if (font)
        skin->setFont(font);

    skin->setFont(env->getBuiltInFont(), EGDF_TOOLTIP);
    // Здесь создаются кнопки. Обязательно нужно задавать для кнопок id, чтобы потом ловить их в ресивере
    // кнопки могут быть двух видов Pressed и не Pressed, 
    // не Pressed обладает обычным поведением, как у виндовской кнопки
    // Pressed напоминает checkbox, после нажатия она остается в таком состоянии до повторного клика.
    // Для кнопок можно устанавливать картинки (2 шт. - нажатое и отжатое состояние)
    env->addButton(rect(10,240,110,240 + 32), 0, 101, L"Quit", L"Exits Program");
    env->addButton(rect(10,280,110,280 + 32), 0, 102, L"New Window", L"Launches a new Window");
    env->addButton(rect(10,320,110,320 + 32), 0, 103, L"File Open", L"Opens a file");
    env->addStaticText(L"Transparent Control:", rect(150,20,350,40), true);
    // создание скроллбара и установка максимального значения
    IGUIScrollBar* scrollbar = env->addScrollBar(true, rect(150, 45, 350, 60), 0, 104);
    scrollbar->setMax(255);
    // управление скроллбаром
    scrollbar->setPos(env->getSkin()->getColor(EGDC_WINDOW).getAlpha());
    env->addStaticText(L"Logging ListBox:", rect(50,110,250,130), true);
    listbox = env->addListBox(rect(50, 140, 250, 210));
    // есть, конечно, и editbox
    env->addEditBox(L"Editable Text", rect(350, 80, 550, 100));
    env->addImage(driver->getTexture("../../media/irrlichtlogo2.png"),position2d(10,10));
    while(device->run() && driver)
    if (device->isWindowActive())
    {
        driver->beginScene(true, true, SColor(0,200,200,200));
        env->drawAll();
        driver->endScene();
    }
    device->drop();
    return 0;
}

скриншот результат работы

Авторские права: весь код, использованный в данной статье, взят из Irrlicht SDK и принадлежит его создателям. Автор только изменил комментарии в коде.



 Design by Шишкин Алексей (Лёха)  ©2004-2008 by sources.ru