[ Новые сообщения · Участники · Правила форума · Поиск · RSS ]
  • Страница 1 из 1
  • 1
Форум » Игровые движки » IrrLicht Engine » Туториал №10: Шейдеры
Туториал №10: Шейдеры
MerGC_TeamДата: Четверг, 03.04.2014, 16:57 | Сообщение # 1
Веселый админ
Группа: Администраторы
Сообщений: 32
Статус: Оффлайн
Если вы уже создали игру или создаете, то использование шейдеров позволит повысить качественно ее графику. Этот туториал покажет как использовать шейдеры с D3D8, D3D9, и OpenGL в движке и как создавать в нём новые типы материалов. Так же вы увидите как отключить генерацию mipmaps при загрузке текстуры, и как использовать текстовые узлы сцены. (mipmap(MIP maps) в компьютерной графике - разновидность фильтрации текстур. Оптимизированные коллекции изображений, предназначенные для сопровождения основной текстуры для повышения скорости рендеринга и сглаживания артефактов.

Этот туториал не объясняет как работают шейдеры. По этой теме я бы рекомендовал читать документацию по D3D или OpenGL, ищите туториалы, или читайте книги об этом.

Начнем как обычно, подключим заголовочный файл и объявим пространство имен 'irr' .

Код
#include < irrlicht.h >
#include < iostream >
#include "driverChoice.h"

using namespace irr;


Так как нам нужно использовать несколько интересных шейдеров в этом примере, то нам надо установить некоторые данные для них, чтобы они могли рассчитать симпатичные цвета. В этом примере, мы будем использовать простые вершинные шейдеры, которые вычисляют цвет вершины на основе положения камеры. Для этого, шейдерам необходимы следующие данные: Инвертированную(перевёрнутую) мировую матрицу для трансформации нормалей, clip-матрицу(clipping отсечение) для трансформации позиции, позицию камеры и мировую позиция объекта для расчёта угла света, и цвета для света. Для того, чтобы передать шейдерам все эти данные в каждом кадре, мы должны получить из класса IShaderConstantSetCallBack интерфейс и переопределить его единственный метод, по имени OnSetConstants(). Этот метод вызывается каждый раз, когда устанавливается материал.
Метод setVertexShaderConstant() интерфейса IMaterialRendererServices используется для установки данных, в которых нуждаются шейдеры. Если пользователь решил использовать высокоуровневый шейдерный язык как нпример HLSL вместо Ассемблера в этом примере, вы можете установить имя переменной в качестве параметра вместо индекса регистра.

Код
IrrlichtDevice* device = 0;
bool UseHighLevelShaders = false;

class MyShaderCallBack : public video::IShaderConstantSetCallBack
{
public:

         virtual void OnSetConstants(video::IMaterialRendererServices* services, s32 userData)
         {
                 video::IVideoDriver* driver = services->getVideoDriver();

                 // установить inverted(перевёрнутую) мировую матрицу
                 // Если мы используем высокоуровневые шейдеры (пользователь может выбрать это
                 // при старте программы), мы должны установить коснтанты как имя.

                 core::matrix4 invWorld = driver->getTransform(video::ETS_WORLD);
                 invWorld.makeInverse();

                 if (UseHighLevelShaders)
                         services->setVertexShaderConstant("mInvWorld", invWorld.pointer(), 16);
                 else
                         services->setVertexShaderConstant(invWorld.pointer(), 0, 4);

                 // Установить матрицу отсечения

                 core::matrix4 worldViewProj;
                 worldViewProj = driver->getTransform(video::ETS_PROJECTION);
                 worldViewProj *= driver->getTransform(video::ETS_VIEW);
                 worldViewProj *= driver->getTransform(video::ETS_WORLD);

                 if (UseHighLevelShaders)
                         services->setVertexShaderConstant("mWorldViewProj", worldViewProj.pointer(), 16);
                 else
                         services->setVertexShaderConstant(worldViewProj.pointer(), 4, 4);

                 // Установить позицию камеры

                 core::vector3df pos = device->getSceneManager()->getActiveCamera()->getAbsolutePosition();

                 if (UseHighLevelShaders)
                         services->setVertexShaderConstant("mLightPos", reinterpret_cast< f32* >(&pos), 3);
                 else
                         services->setVertexShaderConstant(reinterpret_cast< f32* >(&pos), 8, 1);

                 // установить цвет света

                 video::SColorf col(0.0f,1.0f,1.0f,0.0f);

                 if (UseHighLevelShaders)
                         services->setVertexShaderConstant("mLightColor", reinterpret_cast< f32* >(&col), 4);
                 else
                         services->setVertexShaderConstant(reinterpret_cast< f32* >(&col), 9, 1);

                 // Установить transposed(транспонированную) мировую матрицу

                 core::matrix4 world = driver->getTransform(video::ETS_WORLD);
                 world = world.getTransposed();

                 if (UseHighLevelShaders)
                         services->setVertexShaderConstant("mTransWorld", world.pointer(), 16);
                 else
                         services->setVertexShaderConstant(world.pointer(), 10, 4);
         }
};


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

Код
int main()
{
          
         // спрашиваем пользователя про драйвер (OpenGL, DirectX...)
         video::E_DRIVER_TYPE driverType=driverChoiceConsole();
         if (driverType==video::EDT_COUNT) return 1; // драйвер не выбран, выходим
          
         // спросить пользователя должны ли мы использовать высокоуровневые шейдеры для этого примера
         // учтите что ваш видеодрайвер  OpenGL может не поддерживать их, поэтому если что жмите 'n'
         if (driverType == video::EDT_DIRECT3D9 || driverType == video::EDT_OPENGL)
         {
                 char i;
                 printf("Please press 'y' if you want to use high level shaders");
                 std::cin >> i;
                 if (i == 'y') UseHighLevelShaders = true;
         }

         // создаем движок

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

         if (device == 0)
                 return 1; // драйвер не выбран, выходим.

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


Теперь более интересная часть. Если мы хотим использовать Direct3D, мы должны загрузить вершины и программировать пиксельные шейдеры. Если используем OpenGL, мы должны использовать ARB фрагменты и программируемые вершины. Я дописал соответствующие программы в файлах d3d8.ps, d3d8.vs, d3d9.ps, d3d9.vs, opengl.ps и opengl.vs. Теперь нам нужны только включить правильные имена файлов. Это делается в следующие куске кода. Обратите внимание, что нет необходимости писать шейдеры в текстовые файлы, как в этом примере. Вы даже можете записать шейдеры непосредственно в качестве строк в исходный cpp файл и использовать с addShaderMaterial() вместо addShaderMaterialFromFiles().

Код
io::path vsFileName; // Имя файлов для вершинных шейдеров
         io::path psFileName; // Имя файлов для пиксельных шейдеров

         switch(driverType)
         {
         case video::EDT_DIRECT3D8:
                 psFileName = "../../media/d3d8.psh";
                 vsFileName = "../../media/d3d8.vsh";
                 break;
         case video::EDT_DIRECT3D9:
                 if (UseHighLevelShaders)
                 {
                         psFileName = "../../media/d3d9.hlsl";
                         vsFileName = psFileName; // оба шейдера в одном файле
                 }
                 else
                 {
                         psFileName = "../../media/d3d9.psh";
                         vsFileName = "../../media/d3d9.vsh";
                 }
                 break;

         case video::EDT_OPENGL:
                 if (UseHighLevelShaders)
                 {
                         psFileName = "../../media/opengl.frag";
                         vsFileName = "../../media/opengl.vert";
                 }
                 else
                 {
                         psFileName = "../../media/opengl.psh";
                         vsFileName = "../../media/opengl.vsh";
                 }
                 break;
         }


В добавок, мы проверяем способны ли аппаратные средства и выбранный режим визуализации выполнить заданные шейдеры. Если нет, мы просто установим строку имени файла в 0. Это не обязательно, но полезно в этом случае: Например, если аппаратные средства способны выполнять вершинные шейдеры, но не пиксельные шейдеры, мы создаём новый материал, который использует только вершинные шейдеры, но не пиксельные шейдеры. В противном случае, если мы просто передадим движку для создания этот материал и движок увидит, что аппаратные средства не могут полностью выполнить запрос, он не будет создавать новый материал. Таким образом, при любом раскаладе в этом примере вы всё равно увидите работу вершинных шейдеров, даже без пиксельных шейдеров.

Код
if (!driver->queryFeature(video::EVDF_PIXEL_SHADER_1_1) && !driver->queryFeature(video::EVDF_ARB_FRAGMENT_PROGRAM_1))
         {
                 device->getLogger()->log("WARNING: Pixel shaders disabled because of missing driver/hardware support.");
                 psFileName = "";
         }

         if (!driver->queryFeature(video::EVDF_VERTEX_SHADER_1_1) && !driver->queryFeature(video::EVDF_ARB_VERTEX_PROGRAM_1))
         {
                 device->getLogger()->log("WARNING: Vertex shaders disabled because of missing driver/hardware support.");
                 vsFileName = "";
         }


Теперь создадим новые материалы. Как вы, быть может, знаете из предыдущих примеров, тип материала в движке Иррлихт устанавливается простым изменением значения MaterialType в структуре SMaterial. И это значение это просто небольшое 32-битное значение, например такое video::EMT_SOLID. Таким образом, нам лишь необходимо, чтобы движок создал новое значение для нас, которое мы можем задать структуре. Для этого, мы получим указатель на IGPUProgrammingServices и вызовем addShaderMaterialFromFiles(), которая возвращает новое 32-битное значение. Вот и всё.

Параметры этого метода следующие: Первый, имя файла содержащего код вершинных и пиксельных шейдеров. Если вы используете addShaderMaterial() вместо этого метода, вам не нужно имя файла, вы можете записать код шейдеров непосредственно в строку. Следующий параметр является указателем на класс IShaderConstantSetCallBack который мы описали в самом начале данного руководства. Если вы не желаете устанавливать константы, установите их в 0. Последний параметр передаёт движку материал, который следует использовать как базовый материал.

Чтобы продемонстрировать это, мы создаём два различных базовых материала, один с EMT_SOLID и другой с EMT_TRANSPARENT_ADD_COLOR.

Код
// создание материалов

         video::IGPUProgrammingServices* gpu = driver->getGPUProgrammingServices();
         s32 newMaterialType1 = 0;
         s32 newMaterialType2 = 0;

         if (gpu)
         {
                 MyShaderCallBack* mc = new MyShaderCallBack();

                 // создание шейдеров в зависимости от того, хочет пользователь использовать
                 // высокоуровневые или низкоуровневые шейдеры:

                 if (UseHighLevelShaders)
                 {
                         // создать материал с высокоуровневыми шейдерами(hlsl или glsl)

                         newMaterialType1 = gpu->addHighLevelShaderMaterialFromFiles(
                    vsFileName, "vertexMain", video::EVST_VS_1_1,
                    psFileName, "pixelMain", video::EPST_PS_1_1,
                    mc, video::EMT_SOLID);

                         newMaterialType2 = gpu->addHighLevelShaderMaterialFromFiles(
                    vsFileName, "vertexMain", video::EVST_VS_1_1,
                    psFileName, "pixelMain", video::EPST_PS_1_1,
                    mc, video::EMT_TRANSPARENT_ADD_COLOR);
                 }
                 else
                 {
                         // создать материал с низкоуровневыми шейдерами(asm или arb_asm)

                         newMaterialType1 = gpu->addShaderMaterialFromFiles(vsFileName,
                    psFileName, mc, video::EMT_SOLID);

                         newMaterialType2 = gpu->addShaderMaterialFromFiles(vsFileName,
                    psFileName, mc, video::EMT_TRANSPARENT_ADD_COLOR);
                 }

                 mc->drop();
         }


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

Код
// создать тестовый узел сцены 1, с новым созданным материалом 1-го типа

         scene::ISceneNode* node = smgr->addCubeSceneNode(50);
         node->setPosition(core::vector3df(0,0,0));
         node->setMaterialTexture(0, driver->getTexture("../../media/wall.bmp"));
         node->setMaterialFlag(video::EMF_LIGHTING, false);
         node->setMaterialType((video::E_MATERIAL_TYPE)newMaterialType1);

         smgr->addTextSceneNode(gui->getBuiltInFont(), L"PS & VS & EMT_SOLID", video::SColor(255,255,255,255), node);

         scene::ISceneNodeAnimator* anim = smgr->createRotationAnimator(core::vector3df(0,0.3f,0));
         node->addAnimator(anim);
         anim->drop();


То же самое для второго куба, но со вторым материала.

Код
// создать тестовый нод сцены 2, с новым созданным материалом 2 типа

         node = smgr->addCubeSceneNode(50);
         node->setPosition(core::vector3df(0,-10,50));
         node->setMaterialTexture(0, driver->getTexture("../../media/wall.bmp"));
         node->setMaterialFlag(video::EMF_LIGHTING, false);
         node->setMaterialType((video::E_MATERIAL_TYPE)newMaterialType2);

         smgr->addTextSceneNode(gui->getBuiltInFont(), L"PS & VS & EMT_TRANSPARENT", video::SColor(255,255,255,255), node);

         anim = smgr->createRotationAnimator(core::vector3df(0,0.3f,0));
         node->addAnimator(anim);
         anim->drop();


Затем мы добавим 3 куб без шейдеров на нём, для сравнения результата.

Код
// добавить нод сцены без шейдеров

         node = smgr->addCubeSceneNode(50);
         node->setPosition(core::vector3df(0,50,25));
         node->setMaterialTexture(0, driver->getTexture("../../media/wall.bmp"));
         node->setMaterialFlag(video::EMF_LIGHTING, false);
         smgr->addTextSceneNode(gui->getBuiltInFont(), L"NO SHADER", video::SColor(255,255,255,255), node);


И наконец, мы добавим skybox(ящик-иммитатор неба) и контролируемую пользователем камеру на сцену. Для текстурирования skybox, мы запрещаем генерацию mipmap, поскольку мы не нуждаемся в использовании mipmaps для скайбокса.

Код
// Добавить симпатичный скайбокс

         driver->setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS, false);

         smgr->addSkyBoxSceneNode(
                 driver->getTexture("../../media/irrlicht2_up.jpg"),
                 driver->getTexture("../../media/irrlicht2_dn.jpg"),
                 driver->getTexture("../../media/irrlicht2_lf.jpg"),
                 driver->getTexture("../../media/irrlicht2_rt.jpg"),
                 driver->getTexture("../../media/irrlicht2_ft.jpg"),
                 driver->getTexture("../../media/irrlicht2_bk.jpg"));

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

         // добавить камеру и запретить курсор мыши

         scene::ICameraSceneNode* cam = smgr->addCameraSceneNodeFPS();
         cam->setPosition(core::vector3df(-100,50,100));
         cam->setTarget(core::vector3df(0,0,0));
         device->getCursorControl()->setVisible(false);


Теперь все нарисуем и... и всё.

Код
int lastFPS = -1;

         while(device->run())
                 if (device->isWindowActive())
         {
                 driver->beginScene(true, true, video::SColor(255,0,0,0));
                 smgr->drawAll();
                 driver->endScene();

                 int fps = driver->getFPS();

                 if (lastFPS != fps)
                 {
                         core::stringw str = L"Irrlicht Engine - Vertex and pixel shader example ["; // строка заголовка окна
                         str += driver->getName(); // к которому приплюсовываем имя драйвера
                         str += "] FPS:"; // и колличество кадров в секунду
                         str += fps;

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

         device->drop();

         return 0;
}


Скомпилируйте и запустите это, и я надеюсь, вы получите максимум удовольствия от вашего нового инструментария
создания маленьких шейдеров :).
 
Форум » Игровые движки » IrrLicht Engine » Туториал №10: Шейдеры
  • Страница 1 из 1
  • 1
Поиск: