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; }
Скомпилируйте и запустите это, и я надеюсь, вы получите максимум удовольствия от вашего нового инструментария создания маленьких шейдеров :).
|
|
| |