Журнал LinuxFormat - перейти на главную

LXF84:Ogre

Материал из Linuxformat
(перенаправлено с «LXF84:Разработка 3D-игры»)
Перейти к: навигация, поиск
Разработка 3D-игры
БЛАГОДАРНОСТЬ

Видеокарта Nvidia GeForce 7800, используемая для разработки этого руководства, была любезно предоставлена MSI. Спасибо, ребята!

Содержание

Ogre: Освещаем новый дом

ЧАСТЬ 3: Как превратить травянистый холм в крепость в стиле Тюдоров и создать солнце и луну всего на четырех страницах? У Пола Хадсона есть ответ…

В этот урок мы включили много интересного: наша задача — взять пустой ландшафт из прошлого номера, построить дом на плато, добавить света и тени, и, самое интересное, позволить игроку гулять по дому. Легко? Ой, нет. А моя задача — как раз облегчить работу, поэтому приготовьтесь разогнать игру до скорости света!

Мы загружали ландшафт в игру Висельник Чед с помощью файла terrain.cfg, поставляемого вместе с медиа-пакетом Ogre. Всего одной строкой кода создается живописная местность, где можно побродить, но остаются две проблемы: на границах ландшафт обрывается в пустоту и нет места, где можно поставить дом. Поэтому нам надо сначала отредактировать файл ландшафта, пусть запляшет под нашу дудку.

Переместите файл terrain.cfg из каталога media в ваш главный каталог, где размещен код. Затем переместите файлы terrain_detail.jpg, terrain.png и terrain_texture.jpg из media/materials/textures в ваш главный каталог. Учтите, я сказал «переместите», а не «скопируйте» — не оставляйте оригиналы там, где они находятся, не то они будут читаться вместо ваших собственных файлов. Эти четыре файла определяют, как выглядит и ведет себя ландшафт.

Используемый нами менеджер сцены, ответственный за обработку неба и земли, создает ландшафт по двумерной карте высот (это полутоновой план ландшафта, на котором наивысшие точки показаны белым цветом, а самые низкие — черным). В файле terrain.cfg указана карта высот terrain.png. Действительный цвет определяется файлом terrain_texture.jpg, который «натягивается» на весь ландшафт. Наконец, файл terrain_detail.jpg обеспечивает детали, которые будут расположены поверх terrain_texture.jpg, чтобы текстура выглядела сложнее, чем она есть на самом деле.

Чего нам не хватает, так это места для нашей постройки. В моей версии terrain.png (она находится на диске) я подредактировал карту, чтоб выделить пространство для здания (см. «Создание карты высот в Gimp»). Вы также заметите, что я сменил цвет terrain_texture. jpg на цвет травы, ну устал я от этой пустыни! Теперь откройте в текстовом редакторе файл terrain.cfg и найдите строчку #VertexNormal=Yes. Знак # — это комментарий, означающий, что VertexNormal отключен. Удалите #, чтобы включить опцию — это вынудит Ogre генерировать нормали для нашего ландшафта, которые понадобятся нам позже (см. врезку «Зачем нужны нормали»). В файле resources.cfg проверьте, что в секции General указано FileSystem=., это заставит Ogre считывать файлы ландшафта из вашего текущего каталога. Вперед! Запустите нашу игру — вы увидите обширную площадку, здесь-то мы и поставим…

Зачем нужны нормали
(thumbnail)
Нормаль сообщает Ogre, где у треугольника верх, это позволяет правильно осветить треугольник.

3D-графика составляет рисунки из множества многоугольников, обычно треугольников, потому что они проще. Но как Ogre узнает, в каком направлении смотрит треугольник? В нашем понимании, у плоской фигуры две стороны, но используется-то всего одна, поэтому необходимо сообщить Ogre, где у треугольника верх. Благодаря этой информации Ogre сможет правильно освещать объекты. Здесь и пригодится нормаль: это вектор, перпендикулярный поверхности и направленный вверх.

При программировании на чистом OpenGL нам пришлось бы бесчисленное множество раз вызывать функцию glNormal(), которая определяет нормаль для последующих векторов. Но в Ogre достаточно отредактировать terrain. cfg и установить VertexNormal на Yes, чтобы Ogre сам вычислял нормали к ландшафту. Благодаря этим нормалям наш ландшафт будет правильно освещаться, потому как Ogre знает лицевую сторону каждого треугольника.

Создание карт высот в Gimp

Карты высот – это полутоновые текстуры, которые сообщают Ogre, как надо рисовать ландшафт, где белые точки – это максимальные высоты, а черные – минимальные. Ландшафт, с которым мы работали на последних двух уроках, был уже заготовлен в Ogre, и выглядел он не особо хорошо, поэтому мы использовали Gimp и подредактировали края таким образом, чтобы они плавно уходили в море. Используя Gimp, я обнаружил, что лучше всего менять высоту с помощью Кисти – вот как она настраивается: в окне Редактор кисти выберите круглую форму и наименьшее значение жесткости, затем измените радиус до нужного вам значения. Установите цвет кисти черным, а затем превратите ваш ландшафт в остров. Для создания площадки я посадил большую светло-серую кляксу в правом верхнем углу карты. Малое значение жесткости позволяет сделать плавный переход от серого к черному, соответствующий на ландшафте пологому холму, куда будет взбираться наш герой.

Img 84 68 1.jpg Img 84 68 2.jpg
Слева представлена исходная карта высот, справа её отредактированный вариант: по краям зачернено, чтоб получился остров, а светлое пятно – площадка для нашего дома.

Дом, который построил Пол

(thumbnail)
Тюдоровский домик днем – дымоход отбрасывает тень на крышу, а внизу слева тень от ландшафта.

В LXF83 я писал, что в игре Висельник Чед предусмотрено наличие зданий и возможность игроков входить в них, а там, возможно, вступать в борьбу. Теперь, когда мы разровняли место, пора строить домик в тюдоровском стиле. Чтобы создать сетку дома, нам требуется объект Entity, представляющий наш дом, и SceneNode (оболочка объекта, которая позволит позиционировать дом в пространстве). Мы ставим дом раз навсегда, двигаться ему не надо, поэтому просто объявим Entity в chad.h. Добавьте строку в chad.h сразу за определением m_OceanFlowNum:

Entity* HouseEntity

Теперь добавьте этот код в конец метода createScene() файла chad.cpp:

HouseEntity = m_SceneMgr->createEntity("house", "tudorhouse.mesh");
SceneNode* HouseNode = m_SceneMgr->getRootSceneNode()-> createChildSceneNode("HouseNode");
HouseNode->attachObject(HouseEntity);
HouseNode->translate(1100,137, 500);
HouseNode->scale(0.25,0.125,0.25);

Файл tudorhouse.mesh — один из стандартных сеточных объектов, входящих в Ogre, и он выглядит великолепно. Заметим также, что дом огромный, поэтому я использовал scale() и приспособил его размер к ландшафту. Вот и все, что требовалось для добавления дома в нашу игру, поэтому перезапустите make и полюбуйтесь результатом. Нет, определения столкновений пока нет — оставим его на потом!

Небесное вторжение

(thumbnail)
При вызове m_SceneMgr->setShadowDebugShadows(true) Ogre покажет, куда предметы отбрасывают тени. Дом отбрасывает тень и от солнца, и от луны.

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

Потребуется четыре переменных для работы со светом в chad.h; добавим их в конец класса CChadGame:

Light* m_SunLight;
Light* m_MoonLight;
double m_SunY;
double m_SunZ;

Да, мы собираемся создать два света: один для солнца, второй для луны. Для получения простого эффекта смены дня и ночи нам надо вращать два источника света по кругу, чтобы свет плавно перемещался по ландшафту. Также необходимо включить тени. У Ogre есть три типа теней и три типа света. Разница между тенями заключается в скорости: тень низкого качества прорисовывается быстрее всего. Первый тип — SHADOWTYPE_TEXTURE_MODULATIVE, использующий простую текстуру для создания теней в нужных местах. На практике тени получаются грубоватые — сгодятся только для тех, у кого слабая видеокарта, все остальные в восторг не придут. Следующий по качеству тип — SHADOWTYPE_STENCIL_MODULATIVE. Тени выглядят более привлекательно, но создаются в один проход, поэтому результат не идеален. Наконец, SHADOWTYPE_STENCIL_ADDITIVE. Тут тени выглядят превосходно, для каждого источника света построение осуществляется за отдельный проход, поэтому пересечение теней от двух источников будет выглядеть темнее, чем остальные части тени. Как вы понимаете, это довольно сложно, но заманчиво!

Каждый из трех типов источников характеризуется своим способом освещения. Точечный источник излучает свет как Солнце — одинаково во всех направлениях, из определенной точки. Источник направленного света излучает свет в одном направлении и не имеет начальной точки. С нашей позиции на Земле солнце выглядит направленным источником, потому что мы видим его свет, исходящий из одного направления, но не видим его в других направлениях. Наконец, существуют источники узконаправленного света, типа софитов: у них есть начальная точка, направление, их свет слабеет с расстоянием, ширину угла освещения вы назначаете сами.

Оба наших источника, солнце и луна, находятся очень далеко, поэтому они могут считаться источниками направленного света. Не будем пренебрегать деталями — выберем опцию SHADOWTYPE_STENCIL_ADDITIVE, чтобы тени хорошо смотрелись. В последующем коде немного приглушим рассеянное освещение, чтобы заметнее стали дневной и ночной источники света.

Вот код — поместите его в метод createScene():

m_SceneMgr->setAmbientLight( ColourValue(0.35, 0.35, 0.35) );
m_SceneMgr->setShadowTechnique(SHADOWTYPE_STENCIL_ADDITIVE);
m_SunLight = m_SceneMgr->createLight("Sun");
m_SunLight->setType(Light::LT_DIRECTIONAL);
m_SunLight->setDiffuseColour(1.0, 1.0, 1.0);
m_SunLight->setSpecularColour(1.0, 1.0, 1.0);
m_SunLight->setDirection(Vector3(0, sin(m_SunY), sin(m_SunZ)));
m_MoonLight = m_SceneMgr->createLight("Moon");
m_MoonLight->setType(Light::LT_DIRECTIONAL);
m_MoonLight->setDiffuseColour(0.3, 0.3, 0.5);
m_MoonLight->setSpecularColour(0.0, 0.0, 0.0);
m_MoonLight->setDirection(Vector3(0, -sin(m_SunY), sin(m_SunZ)));

Привести луну и солнце в движение можно, отредактировав метод frameStarted(), чтобы метод setDirection() вызывался для каждого источника. Необходимо также изменить переменные m_SunY и m_SunX, заставив их медленно увеличиваться. Вставьте этот код в начало метода frameStarted():

m_SunLight->setDirection(Vector3(0, sin(m_SunY), sin(m_SunZ)));
m_MoonLight->setDirection(Vector3(0, -sin(m_SunY), -sin(m_SunZ)));
m_SunY += 0.001;
m_SunZ += 0.001;

Теперь вы должны увидеть, что здание отбрасывает тень.

Похождения бравого солдата Квейка

(thumbnail)
Это не интерьер XVI века, но карта Quake погружает наш дом в такие же потёмки, как во времена Тюдоров.

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

BSP означает binary space partition [двоичное разбиение пространства] и позволяет очень быстро загружать и обрабатывать закрытые пространства. Карты BSP используется во многих играх, главным образом в тех, что используют движок Quake, включая Jedi Knight, Soldier of Fortune, Half-life. Проблема с BSP состоит в том, что он не умеет хорошо обрабатывать открытые пространства (включая наш ландшафт), поэтому текущий менеджер сцены тоже придется оставить.

Добавление нового менеджера сцены вводит интересные осложнения в наш игровой движок. Во-первых, необходимо отредактировать файл resources.cfg, чтобы использовался файл карты Quake 3. Сотни карт доступны бесплатно через сеть, но на самом деле демо-версии Ogre уже поставляются с очень красивой картой Chiroptera (доступа для скачивания по адресу http://simland.planetquake.gamespy.com/pages/q3maps/chiroptera.htm), ею мы и воспользуемся.

Во-вторых, необходимо разделить методы createScene() и frameStarted() на CreateIndoorScene(), createOutdoorScene(), frameStartedInside(), frameStartedOutside(). Проще всего это сделать, переименовав createScene() и frameStarted(), например, как createOutdoorScene() и frameStartedOutside(), а затем создав заглушки для createIndoorScene() и frameStartedIndoor(). Также потребуется объявить новые методы в chad.h. Сделав это, добавьте строчку в chad.h в конце класса CChadGame:

int scenemanager;

Переменная будет показывать, снаружи мы или внутри. Теперь можно написать новый метод frameStarted() для автоматического вызова нужного «подметода», нечто вроде такого:

bool CChadGame::frameStarted(const FrameEvent& evt) {
switch (scenemanager) {
case ST_EXTERIOR_CLOSE:
return frameStartedOutside();
break;
case ST_INTERIOR:
return frameStartedInside();
break;
}
return true;
}

Переменную scenemanager необходимо переустанавливать, как только происходит смена типа сцены. Для открытого пространства необходимо добавить эту строку в начале метода createOutdoorScene():

scenemanager = ST_EXTERIOR_CLOSE;

Метод frameStartedInside() новый, но ничего особенного не делает - в данный момент он должен представлять урезанную версию метода frameStartedOutside() и обрабатывать простой ввод от клавиатуры. Вот пример подобного кода:

bool CChadGame::frameStartedInside() {
m_InputReader->capture();
if(m_InputReader->isKeyDown(KC_ESCAPE)) return false;
Vector3 translateVector = Vector3::ZERO;
float playerspeed = m_Player->getSpeed();
if (m_InputReader->isKeyDown(KC_W)) translateVector.z =-playerspeed * 5;
if (m_InputReader->isKeyDown(KC_S)) translateVector.z =+playerspeed * 5;
if (m_InputReader->isKeyDown(KC_A)) translateVector.x =-playerspeed * 5;
if (m_InputReader->isKeyDown(KC_D)) translateVector.x =+playerspeed * 5;
if (m_InputReader->isKeyDown(KC_F1)) createIndoorScene();
if (m_InputReader->isKeyDown(KC_F2)) createOutdoorScene();
m_Camera->moveRelative(translateVector);
return true;
}

Вы заметите, что переменная playerspeed умножена на пять — дело в том, что карта Quake огромна, и прогулка с прежней скоростью превратилась бы в вечность. Если такое решение вы находите примитивным, попробуйте увеличить ландшафт, удалив вызов scale() для дома, а затем выберите значение скорости, пригодное везде.

Теперь перейдем к трудной задаче: загрузке карты Quake. Это делается в методе createIndoorScene(), который загрузит нашу карту и создаст новую камеру и область просмотра. Прежде чем перейти к другому менеджеру сцены, необходимо удалить камеры и области просмотра, ассоциированные с текущим менеджером. Вот этот код:

void CChadGame::createIndoorScene() {
scenemanager = ST_INTERIOR;
if (m_SceneMgr != NULL) {
  m_Ogre->getAutoCreatedWindow()->removeAllViewports();
  m_SceneMgr->destroyAllCameras();
 }
m_SceneMgr = m_Ogre->createSceneManager("BspSceneManager");
ResourceGroupManager::getSingleton().linkWorldGeometryToResourceGroup(
ResourceGroupManager::getSingleton().getWorldResourceGroupName(), "maps/chiropteradm.bsp", m_SceneMgr);
ResourceGroupManager::getSingleton().initialiseAllResourceGroups();
ResourceGroupManager::getSingleton().clearResourceGroup(ResourceGroupManager::getSingleton().getWorldResourceGroupName());
ResourceGroupManager::getSingleton().loadResourceGroup(ResourceGroupManager::getSingleton().getWorldResourceGroupName(), false, true);
m_Camera = m_SceneMgr->createCamera("Player Cam");
m_Camera->setNearClipDistance(4);
m_Camera->setFarClipDistance(4000);
m_Viewport = m_Ogre->getAutoCreatedWindow()->addViewport(m_Camera);
m_Viewport->setBackgroundColour(ColourValue(0, 0, 0));
ViewPoint vp = m_SceneMgr->getSuggestedViewpoint(true);
m_Camera->setPosition(vp.position);
m_Camera->pitch(Degree(90));
m_Camera->rotate(vp.orientation);
m_Camera->setFixedYawAxis(true, Vector3::UNIT_Z);
}

Установка значений «ближней» и «дальней» дистанций обрезания необходима, потому что наша камера работает внутри замкнутой области, и нужно фокусироваться на вещах, расположенных близко к камере. Метод getSuggestedViewport() считывает произвольную начальную позицию игрока на карте и устанавливает камеру в эту точку. Нам необходимо установить оси камеры pitch и yaw потому, что Quake использует для плоскости пола координаты Х и Y, а Z — для высоты, а мы обычно используем для пола X и Z и Y — для высоты.

Мы еще не закончили: понадобится добавить две строки в начало метода createOutdoorScene(), чтобы Ogre обновлял все свои ресурсы при загрузке сцены в открытом пространстве. Без них закрытая сцена обновит ресурсы и выгрузит все ресурсы открытой сцены. Итак, добавляем две строки:

ResourceGroupManager::getSingleton().clearResourceGroup(ResourceGroupManager::getSingleton().getWorldResourceGroupName());
ResourceGroupManager::getSingleton().initialiseAllResourceGroups();

Наконец, проверим, что вызов createScene() в конструкторе CChadGame() (который находится в начале chad.cpp) заменен на createOutdoorScene(). Ну вот и все: запустите make, затем chad. Посмотрим, заметите ли вы разницу….

Сложный вопрос

Скорая помощь

Игра Висельник Чед написана на 64-битной машине, но с использованием 32-битной ОС, поэтому мы не гарантируем, что игра пойдет на 64-битной ОС. Однако если у вас 64-битные версии библиотек, например, zziplib, вам, возможно, повезет больше. Если у вас все-таки есть проблемы, попробуйте добыть последнюю версию DevIL и запустить ldconfig от имени root.

Не моя вина

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

ОК, разницы нет — по крайней мере, с точки зрения игрока. Это потому, что мы написали код обработки для закрытой сцены, но не написали кода, загружающего эту сцену. Вы вольны написать код, способный обнаруживать, что игрок входит в дверь дома, а мы напишем более простую (лишь бы работала) «клавишную реализацию переключения сцен», которую можно использовать в любом месте ландшафта.

У нас уже есть функции обработки клавиатуры frameStaretd*(), поэтому остается добавить в них поддержку двух дополнительных клавиш: F1 (для загрузки закрытой сцены) и F2 (для загрузки открытой сцены). Код, который загружает и выгружает ресурсы, камеры и области просмотра, расположен в методах create*Scene(), поэтому о них беспокоиться не надо. Все, что нам надо сделать, это вызвать требуемый метод create*Scene(), например, так:

if (m_InputReader->isKeyDown(KC_F1)) createIndoorScene();
if (m_InputReader->isKeyDown(KC_F2)) createOutdoorScene();

Поместите эти две строчки вместе с остальными вызовами isKeyDown(), перезапустите make, затем саму игру. На этот раз вы должны попасть на карту Quake и исследовать ваш новый интерьер, просто нажав клавишу F1 и пару секунд подождав, пока загрузятся необходимые ресурсы Ogre.

Мы рассмотрели в этом руководстве много нового. Труднее всего уместить в голове переход от одного менеджера сцены к нескольким; все остальное сравнительно просто! Я надеюсь, что вам понравилось, как легко Ogre создает освещение и тени и работает с уровнями BSP, хотя покамест некоторые недостатки игры могут и раздражать — отсутствие обнаружения столкновений, плохой ввод, небо, на котором не сказывается освещение, и так далее. Часть недостатков вы можете исправить сами, ведь это руководство написано для того, чтобы игру программировали вы, а не я. Но с остальными мы разберемся вместе. До следующего раза!

Персональные инструменты
купить
подписаться
Яндекс.Метрика