Войти
IrrlichtСтатьи

Перевод Example 011 Per-Pixel Lighting

Автор:

/** Пример 011 Попиксельное Освещение

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

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

(при переводе использовались доп.материалы отсюда
http://ru.wikipedia.org/wiki/Parallax_mapping
Современная терминология 3D графики
http://www.ixbt.com/video2/terms2k5.shtml#nm)
*/
#include <irrlicht.h>
#include "driverChoice.h"

using namespace irr;

#ifdef _MSC_VER
#pragma comment(lib, "Irrlicht.lib")
#endif

/*
Для этого примера нам необходим элемент 
event receiver(получатель событий), чтобы дать возможность 
пользователю переключаться между 3 возможными видами 
материалов. Кроме того, event receiver создаст небольшое GUI 
окно, которое покажет какой материал используется в данный 
момент. Если вы ничего не планируете специально делать с  
этим классом(event receiver), возможно вам захочется 
пробросить этот кусок кода.
*/
class MyEventReceiver : public IEventReceiver
{
public:

  MyEventReceiver(scene::ISceneNode* room,
    gui::IGUIEnvironment* env, video::IVideoDriver* driver)
  {
    // хранить указатель на нод комнаты, 
    //чтобы мы могли сменить ей режим рисования карт
    Room = room;
    Driver = driver;

    // установим шрифт получше
    gui::IGUISkin* skin = env->getSkin();
    gui::IGUIFont* font = env->getFont("../../media/fonthaettenschweiler.bmp");
    if (font)
      skin->setFont(font);

    // добавить окно и листбокс
    gui::IGUIWindow* window = env->addWindow(
      core::rect<s32>(460,375,630,470), false, L"Use 'E' + 'R' to change");

    ListBox = env->addListBox(
      core::rect<s32>(2,22,165,88), window);

    ListBox->addItem(L"Diffuse");
    ListBox->addItem(L"Bump mapping");
    ListBox->addItem(L"Parallax mapping");
    ListBox->setSelected(1);

    // Создать текст для отображения возникших в процессе проблем
    ProblemText = env->addStaticText(
      L"Your hardware or this renderer is not able to use the "\
      L"needed shaders for this material. Using fall back materials.",
      core::rect<s32>(150,20,470,80));

    ProblemText->setOverrideColor(video::SColor(100,255,255,255));

    // установить стартовый материал (предпочтительнее 
    //parallax mapping, если технология доступна)
    video::IMaterialRenderer* renderer =
      Driver->getMaterialRenderer(video::EMT_PARALLAX_MAP_SOLID);
    if (renderer && renderer->getRenderCapability() == 0)
      ListBox->setSelected(2);

    // Установить материал, который выбран в окне списка
    setMaterial();
  }

  bool OnEvent(const SEvent& event)
  {
    // Проверить, не нажимает ли пользователь клавишу 'E' или 'R'
    if (event.EventType == irr::EET_KEY_INPUT_EVENT &&
      !event.KeyInput.PressedDown && Room && ListBox)
    {
      // изменение выбранного элемента в списке

      int sel = ListBox->getSelected();
      if (event.KeyInput.Key == irr::KEY_KEY_R)
        ++sel;
      else
      if (event.KeyInput.Key == irr::KEY_KEY_E)
        --sel;
      else
        return false;

      if (sel > 2) sel = 0;
      if (sel < 0) sel = 2;
      ListBox->setSelected(sel);

      // установить материал, который выбран в списке
      setMaterial();
    }

    return false;
  }

private:

  // Устанавливает материал сетке комнаты - один комплект из
  // списка.
  void setMaterial()
  {
    video::E_MATERIAL_TYPE type = video::EMT_SOLID;

    // изменяем установки материала
    switch(ListBox->getSelected())
    {
    case 0: type = video::EMT_SOLID;
      break;
    case 1: type = video::EMT_NORMAL_MAP_SOLID;
      break;
    case 2: type = video::EMT_PARALLAX_MAP_SOLID;
      break;
    }

    Room->setMaterialType(type);

    /*
    Нам нужно добавить предупреждение, если эти материалы не смогут
    отображаться 100% правильно. Это не проблема, они будут
    рендериться используя материалы с пониженными требованиями, но 
    по крайней мере пользователь должен знать, что материалы будут 
    выглядеть лучше на более мощном оборудование. Мы
    просто проверим, может ли материал визуализироваться с 
    достижением полного качества на текущем оборудовании.
    IMaterialRenderer::getRenderCapability() возвращает 0 в этом случае.
    */
    video::IMaterialRenderer* renderer = Driver->getMaterialRenderer(type);

    // отобразить текст с описанием проблемы, если таковая имеет место
    if (!renderer || renderer->getRenderCapability() != 0)
      ProblemText->setVisible(true);
    else
      ProblemText->setVisible(false);
  }

private:

  gui::IGUIStaticText* ProblemText;
  gui::IGUIListBox* ListBox;

  scene::ISceneNode* Room;
  video::IVideoDriver* Driver;
};


/*
Теперь начинается настоящее веселье. Мы создаём главное устройство 
Иррлихта и начинаем устанавливать сцену.
*/
int main()
{
  // позволим пользователю выбрать тип драйвера

  video::E_DRIVER_TYPE driverType = video::EDT_DIRECT3D9;

  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 0;
  }

  // создаём основное устройство иррлихта

  IrrlichtDevice* device = createDevice(driverType,
      core::dimension2d<u32>(640, 480));

  if (device == 0)
    return 1; // не смогло создать выбранный драйвер.


  /*
  Прежде чем начать делать интересные материалы, мы сделаем 
  самые простые и необходимые вещи:
  Сохраним указатели на самые важные части движка (видео драйвер,
  менеджер сцены, gui среда) чтобы обезопасить нас от слишком 
  большого количества типов, добавим  окно логотипа иррлихта и 
  управляемую пользователем камеру стиля шутеров(shooter style,FPS  
  от первого лица).
  Кроме того, мы укажем движку, что он должен хранить все 
  текстуры в 32 битной форме. Это необходимо потому, что 
  для параллаксного маппинга(parallax mapping) необходимы 
  именно 32 битные текстуры.
  */

  video::IVideoDriver* driver = device->getVideoDriver();
  scene::ISceneManager* smgr = device->getSceneManager();
  gui::IGUIEnvironment* env = device->getGUIEnvironment();

  driver->setTextureCreationFlag(video::ETCF_ALWAYS_32_BIT, true);

  // добавить логотип иррлихта
  env->addImage(driver->getTexture("../../media/irrlichtlogo2.png"),
    core::position2d<s32>(10,10));

  // добавить камеру
  scene::ICameraSceneNode* camera = smgr->addCameraSceneNodeFPS();
  camera->setPosition(core::vector3df(-200,200,-200));

  // запретить курсор мыши
  device->getCursorControl()->setVisible(false);


  /*
  Так как мы хотим, чтобы сцена смотрелась немного затемнённо, 
  то мы напустим тумана. Это делается с помощью IVideoDriver::setFog(). 
  Тут вы можете установить различные настройки тумана. В данном 
  примере мы используем пиксельный туман потому, что он хорошо 
  работает с материалами используемыми в данном примере.
  Обратите внимание, что вам придется установить флаг материала:
  EMF_FOG_ENABLE
  в 'true'(истину) в каждом узле(node) сцены, который должен быть 
  затронут туманом.
  */
  driver->setFog(video::SColor(0,138,125,81), 
  video::EFT_FOG_LINEAR, 250, 1000, .003f, true, false);

  /*
  Для того, чтобы показать нечто интересное, мы загружаем сетку из .3ds
  фйла, это комната, которую я моделировал в anim8or. Эта же комната есть в
  примере specialFX. Может быть вы помните из этого туториала, что я
  не лучший моделер из всех, и поэтому я совершенно перепутал текстуры
  в этой модели, но мы можем просто отремонтировать их посредством
  метода IMeshManipulator::makePlanarTextureMapping().
  */

  scene::IAnimatedMesh* roomMesh = smgr->getMesh(
    "../../media/room.3ds");
  scene::ISceneNode* room = 0;

  if (roomMesh)
  {
    smgr->getMeshManipulator()->makePlanarTextureMapping(
        roomMesh->getMesh(0), 0.003f);

    /*
    Тепеперь первая восхитительная вещь: Если мы 
    успешно загрузим сетку, то мы должны назначить 
    ей крутую текстуру. Ведь мы хотим, чтобы эта комната 
    отображалась с очень крутым материалом, значит должны сделать
    немногго больше, чем установить обыкновенные текстуры. 
    Вместо одной только загрузки цветовой карты(color map) 
    как обычно, мы так же загрузим карту высот, которая является 
    простой grayscale(Серая шкала, оттенки серого) текстурой. 
    Из этой карты высот, мы создадим карту нормалей(normal map) 
    которую мы установим как вторичную текстуру комнаты.
    Если у вас уже есть normal map, вы можете прямо установитьт её,
    но я просто не нашел хороший normal map для этой текстуры.
    Normal map текстура генерируется:
    makeNormalMapTexture() методом VideoDriver(ВидеоДрайвера). 
    Вторичный параметр определяет высоту heightmap(карты высот). 
    Если вы установите его в большее значенипе, карта будет 
    смотреться более скалистой(выпуклой).

    (о grayscale текстурах:
    http://ru.wikipedia.org/wiki/Grayscale)
    */

    video::ITexture* normalMap =
      driver->getTexture("../../media/rockwall_height.bmp");

    if (normalMap)
      driver->makeNormalMapTexture(normalMap, 9.0f);

    /*
    Но только настройка цветов и normal map это ещё не всё.
    Материалу, который мы хотим использовать, необходима 
    дополнительная информация о вершинах как касательных 
    и бинормалях(binormals). Мы слишком ленивы для того, чтобы 
    вычислять эту информацию, поэтому мы позволим иррлихту 
    сделать это за нас. Вот почему мы используем функцию:
    IMeshManipulator::createMeshWithTangents().
    Она создает копию сетки с касательными и бинормалями от 
    другой сетки. После того как мы это сделали, мы просто 
    создаем   стандартную сетку сцены, копируем пеересозданное 
    в узел(node) этой сетки, устанавливаем цвет и normal map
    и некоторые другие параметры материала. Обратите внимение, 
    что мы установили EMF_FOG_ENABLE в 'true' чтобы разрешить 
    туман для этой комнаты.
    */

    scene::IMesh* tangentMesh = smgr->getMeshManipulator()->
        createMeshWithTangents(roomMesh->getMesh(0));

    room = smgr->addMeshSceneNode(tangentMesh);
    room->setMaterialTexture(0,
        driver->getTexture("../../media/rockwall.jpg"));
    room->setMaterialTexture(1, normalMap);

    room->getMaterial(0).SpecularColor.set(0,0,0,0);

    room->setMaterialFlag(video::EMF_FOG_ENABLE, true);
    room->setMaterialType(video::EMT_PARALLAX_MAP_SOLID);
    // утстановить высоту для parallax эффекта
    room->getMaterial(0).MaterialTypeParam = 0.035f;

    // освободить сетку потому, что создали её для того, чтобы создать... вызов.
    tangentMesh->drop();
  }

  /*
  После того как мы создали комнату затенённую 
  попиксельным светом(per pixel lighting), мы добавляем
  сферу с теми же материалами, но ещё и добавляем ей 
  эффект прозрачности.
  В добавку, поскольку сфера выглядит как наша планета,
  мы добавим ей вращение. Процедура аналогична 
  проделанным прежде. Разница в том, что мы загружаем 
  сетки из файла .x, который уже содержит цветовую карту, 
  и таким образом, нам не нужно загружать её вручную. Но 
  сфера несколько меньше, чем нам необходимо, поэтому 
  мы растянем её на фактор 50.
  */

  // добавить сферу Земли

  scene::IAnimatedMesh* earthMesh = smgr->getMesh("../../media/earth.x");
  if (earthMesh)
  {
    //Чтобы выполнить различные задачи с  манипулятором сетки его надо создать
    scene::IMeshManipulator *manipulator = smgr->getMeshManipulator();

    // создать копию сетки с информацией о касательных из оригинальнго earth.x сетки
    scene::IMesh* tangentSphereMesh =
      manipulator->createMeshWithTangents(earthMesh->getMesh(0));

    // установить альфа-цвет(прозрачность) всех вершин в 200
    manipulator->setVertexColorAlpha(tangentSphereMesh, 200);

    // растянуть сетку на фактор 50
    core::matrix4 m;
    m.setScale ( core::vector3df(50,50,50) );
    manipulator->transformMesh( tangentSphereMesh, m );

    scene::ISceneNode *sphere = smgr->addMeshSceneNode(tangentSphereMesh);

    sphere->setPosition(core::vector3df(-70,130,45));

    // загрузить heightmap, создать normal map из heightmap и установить это
    video::ITexture* earthNormalMap = driver->getTexture("../../media/earthbump.jpg");
    if (earthNormalMap)
    {
      driver->makeNormalMapTexture(earthNormalMap, 20.0f);
      sphere->setMaterialTexture(1, earthNormalMap);
      sphere->setMaterialType(video::EMT_NORMAL_MAP_TRANSPARENT_VERTEX_ALPHA);
    }

    // установить настройки материала, разрешить туман
    sphere->setMaterialFlag(video::EMF_FOG_ENABLE, true);

    // добавить аниматор поворота
    scene::ISceneNodeAnimator* anim =
      smgr->createRotationAnimator(core::vector3df(0,0.1f,0));
    sphere->addAnimator(anim);
    anim->drop();

    // освободить сетку поскольку мы создали это для того, чтобы созлдать... вызов.
    tangentSphereMesh->drop();
  }

  /*
  Попиксельно освещённые материалы выглядят здорово только тогда, 
  когда они освещены moving lights(движущимися огнями, источниками света). 
  Поэтому мы и добавим такой эффект. И ещё потому, что истичник света сам 
  по себе скучноват, мы добавим к нему billboards(картинка, всегда поворачивающийся 
  "лицом" к камере) и целую систему частиц одному из них.
  Мы начнём с установки красного света и после этого присоединим bllboard.
  */

  // добавить свечение 1 (почти красный)
  scene::ILightSceneNode* light1 =
    smgr->addLightSceneNode(0, core::vector3df(0,0,0),
    video::SColorf(0.5f, 1.0f, 0.5f, 0.0f), 800.0f);

  light1->setDebugDataVisible ( scene::EDS_BBOX );


  // добавить аниматор кругового полёта свету 1
  scene::ISceneNodeAnimator* anim =
    smgr->createFlyCircleAnimator (core::vector3df(50,300,0),190.0f, -0.003f);
  light1->addAnimator(anim);
  anim->drop();

   //присоединить billboard свету
  scene::ISceneNode* bill =
    smgr->addBillboardSceneNode(light1, core::dimension2d<f32>(60, 60));

  bill->setMaterialFlag(video::EMF_LIGHTING, false);
  bill->setMaterialFlag(video::EMF_ZWRITE_ENABLE, false);
  bill->setMaterialType(video::EMT_TRANSPARENT_ADD_COLOR);
  bill->setMaterialTexture(0, driver->getTexture("../../media/particlered.bmp"));

  /*
  Теперь то же самое, со вторым источником света. 
  Разница в том, что мы должны добавить к нему 
  ещё и систему частиц. И потому, что источник света 
  движется цастицы системы тоже должны двигаться 
  следом. Если вы хотите узнать больше о том как работают 
  системы частиц в иррлихте, смотрите пример specialFx. Может быть,
   вы уже заметили, что мы добавили только лишь 2 источника света, 
  это имеет простую причину:
  Старая версия данного примера, он был записан в ps1.1 и vs1.1, 
  где не может быть больше источников света. Вы могли бы добавить 
  3 источник света к сцене, но он не может быть использован для 
  затенения стен. Но, конечно, эта ситуация изменится в будущих 
  версиях Irrlicht, где будет существовать более поздние версии 
  пиксельных/вершинных шейдеров.
  */

  // добавить источник света 2 (серый)
  scene::ISceneNode* light2 =
    smgr->addLightSceneNode(0, core::vector3df(0,0,0),
    video::SColorf(1.0f, 0.2f, 0.2f, 0.0f), 800.0f);

  // добавить аниматор полёта по кругу источника света 2
  anim = smgr->createFlyCircleAnimator(core::vector3df(0,150,0), 200.0f,
      0.001f, core::vector3df(0.2f, 0.9f, 0.f));
  light2->addAnimator(anim);
  anim->drop();

  // присоединить billboard к источнику света
  bill = smgr->addBillboardSceneNode(light2, core::dimension2d<f32>(120, 120));
  bill->setMaterialFlag(video::EMF_LIGHTING, false);
  bill->setMaterialFlag(video::EMF_ZWRITE_ENABLE, false);
  bill->setMaterialType(video::EMT_TRANSPARENT_ADD_COLOR);
  bill->setMaterialTexture(0, driver->getTexture("../../media/particlewhite.bmp"));

  // добавить систему частиц 2 источнику света
  scene::IParticleSystemSceneNode* ps =
    smgr->addParticleSystemSceneNode(false, light2);

  // создать и установить эмиттер
  scene::IParticleEmitter* em = ps->createBoxEmitter(
    core::aabbox3d<f32>(-3,0,-3,3,1,3),
    core::vector3df(0.0f,0.03f,0.0f),
    80,100,
    video::SColor(0,255,255,255), video::SColor(0,255,255,255),
    400,1100);
  em->setMinStartSize(core::dimension2d<f32>(30.0f, 40.0f));
  em->setMaxStartSize(core::dimension2d<f32>(30.0f, 40.0f));

  ps->setEmitter(em);
  em->drop();

  // создать и установить аффектор
  scene::IParticleAffector* paf = ps->createFadeOutParticleAffector();
  ps->addAffector(paf);
  paf->drop();

  // отрегулируем немного настройки материалов
  ps->setMaterialFlag(video::EMF_LIGHTING, false);
  ps->setMaterialFlag(video::EMF_ZWRITE_ENABLE, false);
  ps->setMaterialTexture(0, driver->getTexture("../../media/fireball.bmp"));
  ps->setMaterialType(video::EMT_TRANSPARENT_VERTEX_ALPHA);


  MyEventReceiver receiver(room, env, driver);
  device->setEventReceiver(&receiver);

  /*
  Финал, рисум всё, что есть.
  */

  int lastFPS = -1;

  while(device->run())
  if (device->isWindowActive())
  {
    driver->beginScene(true, true, 0);

    smgr->drawAll();
    env->drawAll();

    driver->endScene();

    int fps = driver->getFPS();

    if (lastFPS != fps)
    {
      core::stringw str = L"Per pixel lighting example - Irrlicht Engine [";
      str += driver->getName();
      str += "] FPS:";
      str += fps;

      device->setWindowCaption(str.c_str());
      lastFPS = fps;
    }
  }

  device->drop();

  return 0;
}

12 августа 2010